Even more documentation, RTMP Connector compiles again, but still extremely buggy. Will create RTMP debugging tool soon. Something wrong with RTMP Connector reading FLV::Tags... needs more investigation.
This commit is contained in:
		
							parent
							
								
									b592442d6b
								
							
						
					
					
						commit
						6db6829545
					
				
					 29 changed files with 1451 additions and 1437 deletions
				
			
		|  | @ -1,3 +1,8 @@ | ||||||
|  | /// \file ABST_Parser/main.cpp
 | ||||||
|  | /// Debugging tool for ABST boxes.
 | ||||||
|  | /// Expects ABST data through stdin, outputs human-readable information to stderr.
 | ||||||
|  | /// \todo Erik, update, delete or properly document this file.
 | ||||||
|  | 
 | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <string> | #include <string> | ||||||
|  |  | ||||||
|  | @ -1,3 +1,7 @@ | ||||||
|  | /// \file AMF_Tester/main.cpp
 | ||||||
|  | /// Debugging tool for AMF data.
 | ||||||
|  | /// Expects AMF data through stdin, outputs human-readable information to stderr.
 | ||||||
|  | 
 | ||||||
| #define DEBUG 10 //maximum debugging level
 | #define DEBUG 10 //maximum debugging level
 | ||||||
| #include <cstdlib> | #include <cstdlib> | ||||||
| #include <iostream> | #include <iostream> | ||||||
|  | @ -5,6 +9,8 @@ | ||||||
| #include <string> | #include <string> | ||||||
| #include "../util/amf.h" | #include "../util/amf.h" | ||||||
| 
 | 
 | ||||||
|  | /// Debugging tool for AMF data.
 | ||||||
|  | /// Expects AMF data through stdin, outputs human-readable information to stderr.
 | ||||||
| int main() { | int main() { | ||||||
|   std::string temp; |   std::string temp; | ||||||
|   while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp
 |   while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp
 | ||||||
|  |  | ||||||
|  | @ -1,3 +1,8 @@ | ||||||
|  | /// \file Admin/main.cpp
 | ||||||
|  | /// Attempted administration tool for DDVTECH Clients.
 | ||||||
|  | /// Never finished - perhaps now obsolete...?
 | ||||||
|  | /// \todo This could serve as a basis for a new, more robust, control method for gearbox / the API.
 | ||||||
|  | 
 | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <fstream> | #include <fstream> | ||||||
| #include <sstream> | #include <sstream> | ||||||
|  |  | ||||||
							
								
								
									
										464
									
								
								Buffer/main.cpp
									
										
									
									
									
								
							
							
						
						
									
										464
									
								
								Buffer/main.cpp
									
										
									
									
									
								
							|  | @ -1,3 +1,6 @@ | ||||||
|  | /// \file Buffer/main.cpp
 | ||||||
|  | /// Contains the main code for the Buffer.
 | ||||||
|  | 
 | ||||||
| #include <fcntl.h> | #include <fcntl.h> | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <string> | #include <string> | ||||||
|  | @ -12,264 +15,267 @@ | ||||||
| 
 | 
 | ||||||
| #include <sys/epoll.h> | #include <sys/epoll.h> | ||||||
| 
 | 
 | ||||||
|  | /// Holds all code unique to the Buffer.
 | ||||||
| namespace Buffer{ | namespace Buffer{ | ||||||
| 
 | 
 | ||||||
| void termination_handler (int signum){ |   ///A simple signal handler that ignores all signals.
 | ||||||
|   switch (signum){ |   void termination_handler (int signum){ | ||||||
|     case SIGPIPE: return; break; |     switch (signum){ | ||||||
|     default: return; break; |       case SIGPIPE: return; break; | ||||||
|  |       default: return; break; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| ///holds FLV::Tag objects and their numbers
 |   ///holds FLV::Tag objects and their numbers
 | ||||||
| struct buffer{ |   struct buffer{ | ||||||
|   int number; |     int number; | ||||||
|   FLV::Tag FLV; |     FLV::Tag FLV; | ||||||
| };//buffer
 |   };//buffer
 | ||||||
| 
 | 
 | ||||||
| /// Holds connected users.
 |   /// Holds connected users.
 | ||||||
| /// Keeps track of what buffer users are using and the connection status.
 |   /// Keeps track of what buffer users are using and the connection status.
 | ||||||
| class user{ |   class user{ | ||||||
|   public: |     public: | ||||||
|     int MyBuffer; ///< Index of currently used buffer.
 |       int MyBuffer; ///< Index of currently used buffer.
 | ||||||
|     int MyBuffer_num; ///< Number of currently used buffer.
 |       int MyBuffer_num; ///< Number of currently used buffer.
 | ||||||
|     int MyBuffer_len; ///< Length in bytes of currently used buffer.
 |       int MyBuffer_len; ///< Length in bytes of currently used buffer.
 | ||||||
|     int MyNum; ///< User ID of this user.
 |       int MyNum; ///< User ID of this user.
 | ||||||
|     int currsend; ///< Current amount of bytes sent.
 |       int currsend; ///< Current amount of bytes sent.
 | ||||||
|     bool gotproperaudio; ///< Whether the user received proper audio yet.
 |       bool gotproperaudio; ///< Whether the user received proper audio yet.
 | ||||||
|     void * lastpointer; ///< Pointer to data part of current buffer.
 |       void * lastpointer; ///< Pointer to data part of current buffer.
 | ||||||
|     static int UserCount; ///< Global user counter.
 |       static int UserCount; ///< Global user counter.
 | ||||||
|     DDV::Socket S; ///< Connection to user
 |       DDV::Socket S; ///< Connection to user
 | ||||||
|     /// Creates a new user from a newly connected socket.
 |       /// Creates a new user from a newly connected socket.
 | ||||||
|     /// Also prints "User connected" text to stdout.
 |       /// Also prints "User connected" text to stdout.
 | ||||||
|     user(DDV::Socket fd){ |       user(DDV::Socket fd){ | ||||||
|       S = fd; |         S = fd; | ||||||
|       MyNum = UserCount++; |         MyNum = UserCount++; | ||||||
|       gotproperaudio = false; |         gotproperaudio = false; | ||||||
|       std::cout << "User " << MyNum << " connected" << std::endl; |         std::cout << "User " << MyNum << " connected" << std::endl; | ||||||
|     }//constructor
 |       }//constructor
 | ||||||
|     /// Disconnects the current user. Doesn't do anything if already disconnected.
 |       /// Disconnects the current user. Doesn't do anything if already disconnected.
 | ||||||
|     /// Prints "Disconnected user" to stdout if disconnect took place.
 |       /// Prints "Disconnected user" to stdout if disconnect took place.
 | ||||||
|     void Disconnect(std::string reason) { |       void Disconnect(std::string reason) { | ||||||
|       if (S.connected()) { |         if (S.connected()) { | ||||||
|         S.close(); |           S.close(); | ||||||
|         std::cout << "Disconnected user " << MyNum << ": " << reason << std::endl; |           std::cout << "Disconnected user " << MyNum << ": " << reason << std::endl; | ||||||
|       } |         } | ||||||
|     }//Disconnect
 |       }//Disconnect
 | ||||||
|     /// Tries to send the current buffer, returns true if success, false otherwise.
 |       /// 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.
 |       /// Has a side effect of dropping the connection if send will never complete.
 | ||||||
|     bool doSend(){ |       bool doSend(){ | ||||||
|       int r = S.iwrite((char*)lastpointer+currsend, MyBuffer_len-currsend); |         int r = S.iwrite((char*)lastpointer+currsend, MyBuffer_len-currsend); | ||||||
|       if (r <= 0){ |         if (r <= 0){ | ||||||
|         if ((r < 0) && (errno == EWOULDBLOCK)){return false;} |           if ((r < 0) && (errno == EWOULDBLOCK)){return false;} | ||||||
|         Disconnect("Connection closed"); |           Disconnect("Connection closed"); | ||||||
|         return false; |           return false; | ||||||
|       } |         } | ||||||
|       currsend += r; |         currsend += r; | ||||||
|       return (currsend == MyBuffer_len); |         return (currsend == MyBuffer_len); | ||||||
|     }//doSend
 |       }//doSend
 | ||||||
|     /// Try to send data to this user. Disconnects if any problems occur.
 |       /// Try to send data to this user. Disconnects if any problems occur.
 | ||||||
|     /// \param ringbuf Array of buffers (FLV:Tag with ID attached)
 |       /// \param ringbuf Array of buffers (FLV:Tag with ID attached)
 | ||||||
|     /// \param buffers Count of elements in ringbuf
 |       /// \param buffers Count of elements in ringbuf
 | ||||||
|     void Send(buffer ** ringbuf, int buffers){ |       void Send(buffer ** ringbuf, int buffers){ | ||||||
|       //TODO: Bij MP3: gotproperaudio - if false, stuur alleen als eerste byte is 0xFF en set op true
 |         /// \todo For MP3: gotproperaudio - if false, only send if first byte is 0xFF and set to true
 | ||||||
|       if (!S.connected()){return;}//cancel if not connected
 |         if (!S.connected()){return;}//cancel if not connected
 | ||||||
|    |    | ||||||
|       //still waiting for next buffer? check it
 |         //still waiting for next buffer? check it
 | ||||||
|       if (MyBuffer_num < 0){ |  | ||||||
|         MyBuffer_num = ringbuf[MyBuffer]->number; |  | ||||||
|         if (MyBuffer_num < 0){ |         if (MyBuffer_num < 0){ | ||||||
|           return; //still waiting? don't crash - wait longer.
 |           MyBuffer_num = ringbuf[MyBuffer]->number; | ||||||
|         }else{ |           if (MyBuffer_num < 0){ | ||||||
|           MyBuffer_len = ringbuf[MyBuffer]->FLV.len; |             return; //still waiting? don't crash - wait longer.
 | ||||||
|           lastpointer = ringbuf[MyBuffer]->FLV.data; |           }else{ | ||||||
|  |             MyBuffer_len = ringbuf[MyBuffer]->FLV.len; | ||||||
|  |             lastpointer = ringbuf[MyBuffer]->FLV.data; | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } |  | ||||||
|    |    | ||||||
|       //do check for buffer resizes
 |         //do check for buffer resizes
 | ||||||
|       if (lastpointer != ringbuf[MyBuffer]->FLV.data){ |         if (lastpointer != ringbuf[MyBuffer]->FLV.data){ | ||||||
|         Disconnect("Buffer resize at wrong time... had to disconnect"); |           Disconnect("Buffer resize at wrong time... had to disconnect"); | ||||||
|         return; |           return; | ||||||
|       } |         } | ||||||
|    |    | ||||||
|       //try to complete a send
 |         //try to complete a send
 | ||||||
|       if (doSend()){ |         if (doSend()){ | ||||||
|         //switch to next buffer
 |           //switch to next buffer
 | ||||||
|         if ((ringbuf[MyBuffer]->number != MyBuffer_num)){ |           if ((ringbuf[MyBuffer]->number != MyBuffer_num)){ | ||||||
|           //if corrupt data, warn and find keyframe
 |             //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; |             std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl; | ||||||
|           int nocrashcount = 0; |             int nocrashcount = 0; | ||||||
|           do{ |             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++; |             MyBuffer++; | ||||||
|             nocrashcount++; |  | ||||||
|             MyBuffer %= buffers; |             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_num = -1; | ||||||
|           MyBuffer++; |           lastpointer = 0; | ||||||
|           MyBuffer %= buffers; |           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); | ||||||
|  | 
 | ||||||
|  |     //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<user> users; | ||||||
|  |     std::vector<user>::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; | ||||||
|  |           } | ||||||
|  |           gotVideoInfo = false; | ||||||
|  |           gotAudioInfo = false; | ||||||
|         } |         } | ||||||
|         MyBuffer_num = -1; |         //store video init data, if available
 | ||||||
|         lastpointer = 0; |         if (!gotVideoInfo && ringbuf[current_buffer]->FLV.isKeyframe){ | ||||||
|         currsend = 0; |           if ((ringbuf[current_buffer]->FLV.data[11] & 0x0f) == 7){//avc packet
 | ||||||
|       }//completed a send
 |             if (ringbuf[current_buffer]->FLV.data[12] == 0){ | ||||||
|     }//send
 |               ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero
 | ||||||
| }; |               video_init = ringbuf[current_buffer]->FLV; | ||||||
| int user::UserCount = 0; |               gotVideoInfo = true; | ||||||
| 
 |               std::cout << "Received video configuration!" << std::endl; | ||||||
| /// Starts a loop, waiting for connections to send video data to.
 |             } | ||||||
| int Start(int argc, char ** argv) { |           }else{gotVideoInfo = true;}//non-avc = no config...
 | ||||||
|   //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]; |  | ||||||
| 
 |  | ||||||
|   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<user> users; |  | ||||||
|   std::vector<user>::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; |  | ||||||
|         } |         } | ||||||
|         gotVideoInfo = false; |         //store audio init data, if available
 | ||||||
|         gotAudioInfo = false; |         if (!gotAudioInfo && (packtype == 0x08)){ | ||||||
|       } |           if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 10){//aac packet
 | ||||||
|       //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
 |             ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero
 | ||||||
|             video_init = ringbuf[current_buffer]->FLV; |             audio_init = ringbuf[current_buffer]->FLV; | ||||||
|             gotVideoInfo = true; |             gotAudioInfo = true; | ||||||
|             std::cout << "Received video configuration!" << std::endl; |             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
 |       //check for new connections, accept them if there are any
 | ||||||
|     incoming = SS.accept(true); |       incoming = SS.accept(true); | ||||||
|     if (incoming.connected()){ |       if (incoming.connected()){ | ||||||
|       users.push_back(incoming); |         users.push_back(incoming); | ||||||
|       //send the FLV header
 |         //send the FLV header
 | ||||||
|       users.back().currsend = 0; |         users.back().currsend = 0; | ||||||
|       users.back().MyBuffer = lastproper; |         users.back().MyBuffer = lastproper; | ||||||
|       users.back().MyBuffer_num = -1; |         users.back().MyBuffer_num = -1; | ||||||
|       //TODO: Do this more nicely?
 |         /// \todo Do this more nicely?
 | ||||||
|       if (!incoming.write(FLV::Header, 13)){ |         if (!incoming.write(FLV::Header, 13)){ | ||||||
|         users.back().Disconnect("failed to receive the header!"); |           users.back().Disconnect("failed to receive the header!"); | ||||||
|       }else{ |  | ||||||
|         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; |  | ||||||
|         }else{ |         }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
 |       //send all connections what they need, if and when they need it
 | ||||||
|   if (FLV::Parse_Error){ |       if (users.size() > 0){ | ||||||
|     std::cout << "FLV parse error" << std::endl; |         for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ | ||||||
|   }else{ |           if (!(*usersIt).S.connected()){ | ||||||
|     std::cout << "Reached EOF of input" << std::endl; |             users.erase(usersIt); break; | ||||||
|   } |           }else{ | ||||||
|   SS.close(); |             (*usersIt).Send(ringbuf, buffers); | ||||||
|   while (users.size() > 0){ |           } | ||||||
|     for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ |         } | ||||||
|       (*usersIt).Disconnect("Shutting down..."); |       } | ||||||
|       if (!(*usersIt).S.connected()){users.erase(usersIt);break;} |     }//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
 | };//Buffer namespace
 | ||||||
| 
 | 
 | ||||||
|  | /// Entry point for Buffer, simply calls Buffer::Start().
 | ||||||
| int main(int argc, char ** argv){ | int main(int argc, char ** argv){ | ||||||
|   Buffer::Start(argc, argv); |   Buffer::Start(argc, argv); | ||||||
| }//main
 | }//main
 | ||||||
|  |  | ||||||
|  | @ -1,9 +1,12 @@ | ||||||
|  | /// \file Connector_HTTP/main.cpp
 | ||||||
|  | /// Contains the main code for the HTTP Connector
 | ||||||
|  | 
 | ||||||
| /// Sets the global debugging level.
 | /// Sets the global debugging level.
 | ||||||
| /// debugging level 0 = nothing
 | // debugging level 0 = nothing
 | ||||||
| /// debugging level 1 = critical errors
 | // debugging level 1 = critical errors
 | ||||||
| /// debugging level 2 = errors
 | // debugging level 2 = errors
 | ||||||
| /// debugging level 3 = status information
 | // debugging level 3 = status information
 | ||||||
| /// debugging level 4 = extremely verbose status information
 | // debugging level 4 = extremely verbose status information
 | ||||||
| #define DEBUG 4 | #define DEBUG 4 | ||||||
| 
 | 
 | ||||||
| #include <iostream> | #include <iostream> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,12 @@ | ||||||
|  | /// \file Connector_RAW/main.cpp
 | ||||||
|  | /// Contains the main code for the RAW connector.
 | ||||||
|  | 
 | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include "../util/ddv_socket.h" | #include "../util/ddv_socket.h" | ||||||
| 
 | 
 | ||||||
|  | /// Contains the main code for the RAW connector.
 | ||||||
|  | /// Expects a single commandline argument telling it which stream to connect to,
 | ||||||
|  | /// then outputs the raw stream to stdout.
 | ||||||
| int main(int argc, char  ** argv) { | int main(int argc, char  ** argv) { | ||||||
|   if (argc < 2){ |   if (argc < 2){ | ||||||
|     std::cout << "Usage: " << argv[0] << " stream_name" << std::endl; |     std::cout << "Usage: " << argv[0] << " stream_name" << std::endl; | ||||||
|  | @ -8,11 +14,14 @@ int main(int argc, char  ** argv) { | ||||||
|   } |   } | ||||||
|   std::string input = "/tmp/shared_socket_"; |   std::string input = "/tmp/shared_socket_"; | ||||||
|   input += argv[1]; |   input += argv[1]; | ||||||
|  |   //connect to the proper stream
 | ||||||
|   DDV::Socket S(input); |   DDV::Socket S(input); | ||||||
|   if (!S.connected()){ |   if (!S.connected()){ | ||||||
|     std::cout << "Could not open stream " << argv[1] << std::endl; |     std::cout << "Could not open stream " << argv[1] << std::endl; | ||||||
|     return 1; |     return 1; | ||||||
|   } |   } | ||||||
|  |   //transport ~50kb at a time
 | ||||||
|  |   //this is a nice tradeoff between CPU usage and speed
 | ||||||
|   char buffer[50000]; |   char buffer[50000]; | ||||||
|   while(std::cout.good() && S.read(buffer,50000)){std::cout.write(buffer,50000);} |   while(std::cout.good() && S.read(buffer,50000)){std::cout.write(buffer,50000);} | ||||||
|   S.close(); |   S.close(); | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp ../util/amf.cpp | SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp | ||||||
| OBJ = $(SRC:.cpp=.o) | OBJ = $(SRC:.cpp=.o) | ||||||
| OUT = DDV_Conn_RTMP | OUT = DDV_Conn_RTMP | ||||||
| INCLUDES = | INCLUDES = | ||||||
|  | @ -13,7 +13,7 @@ LIBS = -lssl -lcrypto | ||||||
| default: $(OUT) | default: $(OUT) | ||||||
| .cpp.o: | .cpp.o: | ||||||
| 	$(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ | 	$(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ | ||||||
| $(OUT): $(OBJ) chunkstream.cpp parsechunks.cpp handshake.cpp crypto.cpp | $(OUT): $(OBJ) | ||||||
| 	$(CC) -o $(OUT) $(OBJ) $(STATIC) $(LIBS) | 	$(CC) -o $(OUT) $(OBJ) $(STATIC) $(LIBS) | ||||||
| clean: | clean: | ||||||
| 	rm -rf $(OBJ) $(OUT) Makefile.bak *~ | 	rm -rf $(OBJ) $(OUT) Makefile.bak *~ | ||||||
|  |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -1,501 +0,0 @@ | ||||||
| #include <map> |  | ||||||
| #include <string.h> |  | ||||||
| #include <stdlib.h> |  | ||||||
| #include <sys/time.h> |  | ||||||
| #include <arpa/inet.h> |  | ||||||
| 
 |  | ||||||
| unsigned int getNowMS(){ |  | ||||||
|   timeval t; |  | ||||||
|   gettimeofday(&t, 0); |  | ||||||
|   return t.tv_sec + t.tv_usec/1000; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| unsigned int chunk_rec_max = 128; |  | ||||||
| unsigned int chunk_snd_max = 128; |  | ||||||
| unsigned int rec_window_size = 0xFA00; |  | ||||||
| unsigned int snd_window_size = 1024*500; |  | ||||||
| unsigned int rec_window_at = 0; |  | ||||||
| unsigned int snd_window_at = 0; |  | ||||||
| unsigned int rec_cnt = 0; |  | ||||||
| unsigned int snd_cnt = 0; |  | ||||||
| 
 |  | ||||||
| unsigned int firsttime; |  | ||||||
| 
 |  | ||||||
| struct chunkinfo { |  | ||||||
|   unsigned int cs_id; |  | ||||||
|   unsigned int timestamp; |  | ||||||
|   unsigned int len; |  | ||||||
|   unsigned int real_len; |  | ||||||
|   unsigned int len_left; |  | ||||||
|   unsigned char msg_type_id; |  | ||||||
|   unsigned int msg_stream_id; |  | ||||||
| };//chunkinfo
 |  | ||||||
| 
 |  | ||||||
| struct chunkpack { |  | ||||||
|   unsigned char chunktype; |  | ||||||
|   unsigned int cs_id; |  | ||||||
|   unsigned int timestamp; |  | ||||||
|   unsigned int len; |  | ||||||
|   unsigned int real_len; |  | ||||||
|   unsigned int len_left; |  | ||||||
|   unsigned char msg_type_id; |  | ||||||
|   unsigned int msg_stream_id; |  | ||||||
|   unsigned char * data; |  | ||||||
| };//chunkpack
 |  | ||||||
| 
 |  | ||||||
| //clean a chunk so that it may be re-used without memory leaks
 |  | ||||||
| void scrubChunk(struct chunkpack c){ |  | ||||||
|   if (c.data){free(c.data);} |  | ||||||
|   c.data = 0; |  | ||||||
|   c.real_len = 0; |  | ||||||
| }//scrubChunk
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| //ugly global, but who cares...
 |  | ||||||
| std::map<unsigned int, chunkinfo> prevmap; |  | ||||||
| //return previous packet of this cs_id
 |  | ||||||
| chunkinfo GetPrev(unsigned int cs_id){ |  | ||||||
|   return prevmap[cs_id]; |  | ||||||
| }//GetPrev
 |  | ||||||
| //store packet information of last packet of this cs_id
 |  | ||||||
| void PutPrev(chunkpack prev){ |  | ||||||
|   prevmap[prev.cs_id].timestamp = prev.timestamp; |  | ||||||
|   prevmap[prev.cs_id].len = prev.len; |  | ||||||
|   prevmap[prev.cs_id].real_len = prev.real_len; |  | ||||||
|   prevmap[prev.cs_id].len_left = prev.len_left; |  | ||||||
|   prevmap[prev.cs_id].msg_type_id = prev.msg_type_id; |  | ||||||
|   prevmap[prev.cs_id].msg_stream_id = prev.msg_stream_id; |  | ||||||
| }//PutPrev
 |  | ||||||
| 
 |  | ||||||
| //ugly global, but who cares...
 |  | ||||||
| std::map<unsigned int, chunkinfo> sndprevmap; |  | ||||||
| //return previous packet of this cs_id
 |  | ||||||
| chunkinfo GetSndPrev(unsigned int cs_id){ |  | ||||||
|   return sndprevmap[cs_id]; |  | ||||||
| }//GetPrev
 |  | ||||||
| //store packet information of last packet of this cs_id
 |  | ||||||
| void PutSndPrev(chunkpack prev){ |  | ||||||
|   sndprevmap[prev.cs_id].cs_id = prev.cs_id; |  | ||||||
|   sndprevmap[prev.cs_id].timestamp = prev.timestamp; |  | ||||||
|   sndprevmap[prev.cs_id].len = prev.len; |  | ||||||
|   sndprevmap[prev.cs_id].real_len = prev.real_len; |  | ||||||
|   sndprevmap[prev.cs_id].len_left = prev.len_left; |  | ||||||
|   sndprevmap[prev.cs_id].msg_type_id = prev.msg_type_id; |  | ||||||
|   sndprevmap[prev.cs_id].msg_stream_id = prev.msg_stream_id; |  | ||||||
| }//PutPrev
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| //sends the chunk over the network
 |  | ||||||
| void SendChunk(chunkpack ch){ |  | ||||||
|   unsigned char tmp; |  | ||||||
|   unsigned int tmpi; |  | ||||||
|   unsigned char chtype = 0x00; |  | ||||||
|   chunkinfo prev = GetSndPrev(ch.cs_id); |  | ||||||
|   ch.timestamp -= firsttime; |  | ||||||
|   if (prev.cs_id == ch.cs_id){ |  | ||||||
|     if (ch.msg_stream_id == prev.msg_stream_id){ |  | ||||||
|       chtype = 0x40;//do not send msg_stream_id
 |  | ||||||
|       if (ch.len == prev.len){ |  | ||||||
|         if (ch.msg_type_id == prev.msg_type_id){ |  | ||||||
|           chtype = 0x80;//do not send len and msg_type_id
 |  | ||||||
|           if (ch.timestamp == prev.timestamp){ |  | ||||||
|             chtype = 0xC0;//do not send timestamp
 |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   if (ch.cs_id <= 63){ |  | ||||||
|     tmp = chtype | ch.cs_id; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|     snd_cnt+=1; |  | ||||||
|   }else{ |  | ||||||
|     if (ch.cs_id <= 255+64){ |  | ||||||
|       tmp = chtype | 0; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       tmp = ch.cs_id - 64; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       snd_cnt+=2; |  | ||||||
|     }else{ |  | ||||||
|       tmp = chtype | 1; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       tmpi = ch.cs_id - 64; |  | ||||||
|       tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       snd_cnt+=3; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   unsigned int ntime = 0; |  | ||||||
|   if (chtype != 0xC0){ |  | ||||||
|     //timestamp or timestamp diff
 |  | ||||||
|     if (chtype == 0x00){ |  | ||||||
|       tmpi = ch.timestamp; |  | ||||||
|       if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;} |  | ||||||
|       tmp = tmpi / (256*256); DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       snd_cnt+=3; |  | ||||||
|     }else{ |  | ||||||
|       tmpi = ch.timestamp - prev.timestamp; |  | ||||||
|       if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;} |  | ||||||
|       tmp = tmpi / (256*256); DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       snd_cnt+=3; |  | ||||||
|     } |  | ||||||
|     if (chtype != 0x80){ |  | ||||||
|       //len
 |  | ||||||
|       tmpi = ch.len; |  | ||||||
|       tmp = tmpi / (256*256); DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       snd_cnt+=3; |  | ||||||
|       //msg type id
 |  | ||||||
|       tmp = ch.msg_type_id; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|       snd_cnt+=1; |  | ||||||
|       if (chtype != 0x40){ |  | ||||||
|         //msg stream id
 |  | ||||||
|         tmp = ch.msg_stream_id % 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|         tmp = ch.msg_stream_id / 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|         tmp = ch.msg_stream_id / (256*256); DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|         tmp = ch.msg_stream_id / (256*256*256); DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|         snd_cnt+=4; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   //support for 0x00ffffff timestamps
 |  | ||||||
|   if (ntime){ |  | ||||||
|     tmp = ntime / (256*256*256); DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|     tmp = ntime / (256*256); DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|     tmp = ntime / 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|     tmp = ntime % 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|     snd_cnt+=4; |  | ||||||
|   } |  | ||||||
|   ch.len_left = 0; |  | ||||||
|   while (ch.len_left < ch.len){ |  | ||||||
|     tmpi = ch.len - ch.len_left; |  | ||||||
|     if (tmpi > chunk_snd_max){tmpi = chunk_snd_max;} |  | ||||||
|     DDV_write((ch.data + ch.len_left), 1, tmpi, CONN_fd); |  | ||||||
|     snd_cnt+=tmpi; |  | ||||||
|     ch.len_left += tmpi; |  | ||||||
|     if (ch.len_left < ch.len){ |  | ||||||
|       if (ch.cs_id <= 63){ |  | ||||||
|         tmp = 0xC0 + ch.cs_id; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|         snd_cnt+=1; |  | ||||||
|       }else{ |  | ||||||
|         if (ch.cs_id <= 255+64){ |  | ||||||
|           tmp = 0xC0; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|           tmp = ch.cs_id - 64; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|           snd_cnt+=2; |  | ||||||
|         }else{ |  | ||||||
|           tmp = 0xC1; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|           tmpi = ch.cs_id - 64; |  | ||||||
|           tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|           tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd); |  | ||||||
|           snd_cnt+=4; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   PutSndPrev(ch); |  | ||||||
| }//SendChunk
 |  | ||||||
| 
 |  | ||||||
| //sends a chunk
 |  | ||||||
| void SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data){ |  | ||||||
|   chunkpack ch; |  | ||||||
|   ch.cs_id = cs_id; |  | ||||||
|   ch.timestamp = getNowMS(); |  | ||||||
|   ch.len = data.size(); |  | ||||||
|   ch.real_len = data.size(); |  | ||||||
|   ch.len_left = 0; |  | ||||||
|   ch.msg_type_id = msg_type_id; |  | ||||||
|   ch.msg_stream_id = msg_stream_id; |  | ||||||
|   ch.data = (unsigned char*)malloc(data.size()); |  | ||||||
|   memcpy(ch.data, data.c_str(), data.size()); |  | ||||||
|   SendChunk(ch); |  | ||||||
|   free(ch.data); |  | ||||||
| }//SendChunk
 |  | ||||||
| 
 |  | ||||||
| //sends a media chunk
 |  | ||||||
| void SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts){ |  | ||||||
|   chunkpack ch; |  | ||||||
|   ch.cs_id = msg_type_id; |  | ||||||
|   ch.timestamp = ts; |  | ||||||
|   ch.len = len; |  | ||||||
|   ch.real_len = len; |  | ||||||
|   ch.len_left = 0; |  | ||||||
|   ch.msg_type_id = msg_type_id; |  | ||||||
|   ch.msg_stream_id = 1; |  | ||||||
|   ch.data = (unsigned char*)malloc(len); |  | ||||||
|   memcpy(ch.data, data, len); |  | ||||||
|   SendChunk(ch); |  | ||||||
|   free(ch.data); |  | ||||||
| }//SendMedia
 |  | ||||||
| 
 |  | ||||||
| //sends a control message
 |  | ||||||
| void SendCTL(unsigned char type, unsigned int data){ |  | ||||||
|   chunkpack ch; |  | ||||||
|   ch.cs_id = 2; |  | ||||||
|   ch.timestamp = getNowMS(); |  | ||||||
|   ch.len = 4; |  | ||||||
|   ch.real_len = 4; |  | ||||||
|   ch.len_left = 0; |  | ||||||
|   ch.msg_type_id = type; |  | ||||||
|   ch.msg_stream_id = 0; |  | ||||||
|   ch.data = (unsigned char*)malloc(4); |  | ||||||
|   data = htonl(data); |  | ||||||
|   memcpy(ch.data, &data, 4); |  | ||||||
|   SendChunk(ch); |  | ||||||
|   free(ch.data); |  | ||||||
| }//SendCTL
 |  | ||||||
| 
 |  | ||||||
| //sends a control message
 |  | ||||||
| void SendCTL(unsigned char type, unsigned int data, unsigned char data2){ |  | ||||||
|   chunkpack ch; |  | ||||||
|   ch.cs_id = 2; |  | ||||||
|   ch.timestamp = getNowMS(); |  | ||||||
|   ch.len = 5; |  | ||||||
|   ch.real_len = 5; |  | ||||||
|   ch.len_left = 0; |  | ||||||
|   ch.msg_type_id = type; |  | ||||||
|   ch.msg_stream_id = 0; |  | ||||||
|   ch.data = (unsigned char*)malloc(5); |  | ||||||
|   data = htonl(data); |  | ||||||
|   memcpy(ch.data, &data, 4); |  | ||||||
|   ch.data[4] = data2; |  | ||||||
|   SendChunk(ch); |  | ||||||
|   free(ch.data); |  | ||||||
| }//SendCTL
 |  | ||||||
| 
 |  | ||||||
| //sends a usr control message
 |  | ||||||
| void SendUSR(unsigned char type, unsigned int data){ |  | ||||||
|   chunkpack ch; |  | ||||||
|   ch.cs_id = 2; |  | ||||||
|   ch.timestamp = getNowMS(); |  | ||||||
|   ch.len = 6; |  | ||||||
|   ch.real_len = 6; |  | ||||||
|   ch.len_left = 0; |  | ||||||
|   ch.msg_type_id = 4; |  | ||||||
|   ch.msg_stream_id = 0; |  | ||||||
|   ch.data = (unsigned char*)malloc(6); |  | ||||||
|   data = htonl(data); |  | ||||||
|   memcpy(ch.data+2, &data, 4); |  | ||||||
|   ch.data[0] = 0; |  | ||||||
|   ch.data[1] = type; |  | ||||||
|   SendChunk(ch); |  | ||||||
|   free(ch.data); |  | ||||||
| }//SendUSR
 |  | ||||||
| 
 |  | ||||||
| //sends a usr control message
 |  | ||||||
| void SendUSR(unsigned char type, unsigned int data, unsigned int data2){ |  | ||||||
|   chunkpack ch; |  | ||||||
|   ch.cs_id = 2; |  | ||||||
|   ch.timestamp = getNowMS(); |  | ||||||
|   ch.len = 10; |  | ||||||
|   ch.real_len = 10; |  | ||||||
|   ch.len_left = 0; |  | ||||||
|   ch.msg_type_id = 4; |  | ||||||
|   ch.msg_stream_id = 0; |  | ||||||
|   ch.data = (unsigned char*)malloc(10); |  | ||||||
|   data = htonl(data); |  | ||||||
|   data2 = htonl(data2); |  | ||||||
|   memcpy(ch.data+2, &data, 4); |  | ||||||
|   memcpy(ch.data+6, &data2, 4); |  | ||||||
|   ch.data[0] = 0; |  | ||||||
|   ch.data[1] = type; |  | ||||||
|   SendChunk(ch); |  | ||||||
|   free(ch.data); |  | ||||||
| }//SendUSR
 |  | ||||||
| 
 |  | ||||||
| //get a chunk from standard input
 |  | ||||||
| struct chunkpack getChunk(){ |  | ||||||
|   gettimeofday(&lastrec, 0); |  | ||||||
|   struct chunkpack ret; |  | ||||||
|   unsigned char temp; |  | ||||||
|   DDV_read(&(ret.chunktype), 1, 1, CONN_fd); |  | ||||||
|   rec_cnt++; |  | ||||||
|   //read the chunkstream ID properly
 |  | ||||||
|   switch (ret.chunktype & 0x3F){ |  | ||||||
|     case 0: |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       rec_cnt++; |  | ||||||
|       ret.cs_id = temp + 64; |  | ||||||
|       break; |  | ||||||
|     case 1: |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.cs_id = temp + 64; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.cs_id += temp * 256; |  | ||||||
|       rec_cnt+=2; |  | ||||||
|       break; |  | ||||||
|     default: |  | ||||||
|       ret.cs_id = ret.chunktype & 0x3F; |  | ||||||
|       break; |  | ||||||
|   } |  | ||||||
|   chunkinfo prev = GetPrev(ret.cs_id); |  | ||||||
|   //process the rest of the header, for each chunk type
 |  | ||||||
|   switch (ret.chunktype & 0xC0){ |  | ||||||
|     case 0x00: |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.timestamp = temp*256*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.timestamp += temp*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.timestamp += temp; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.len = temp*256*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.len += temp*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.len += temp; |  | ||||||
|       ret.len_left = 0; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.msg_type_id = temp; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.msg_stream_id = temp; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.msg_stream_id += temp*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.msg_stream_id += temp*256*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.msg_stream_id += temp*256*256*256; |  | ||||||
|       rec_cnt+=11; |  | ||||||
|       break; |  | ||||||
|     case 0x40: |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.timestamp = temp*256*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.timestamp += temp*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.timestamp += temp; |  | ||||||
|       ret.timestamp += prev.timestamp; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.len = temp*256*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.len += temp*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.len += temp; |  | ||||||
|       ret.len_left = 0; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.msg_type_id = temp; |  | ||||||
|       ret.msg_stream_id = prev.msg_stream_id; |  | ||||||
|       rec_cnt+=7; |  | ||||||
|       break; |  | ||||||
|     case 0x80: |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.timestamp = temp*256*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.timestamp += temp*256; |  | ||||||
|       DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|       ret.timestamp += temp; |  | ||||||
|       ret.timestamp += prev.timestamp; |  | ||||||
|       ret.len = prev.len; |  | ||||||
|       ret.len_left = prev.len_left; |  | ||||||
|       ret.msg_type_id = prev.msg_type_id; |  | ||||||
|       ret.msg_stream_id = prev.msg_stream_id; |  | ||||||
|       rec_cnt+=3; |  | ||||||
|       break; |  | ||||||
|     case 0xC0: |  | ||||||
|       ret.timestamp = prev.timestamp; |  | ||||||
|       ret.len = prev.len; |  | ||||||
|       ret.len_left = prev.len_left; |  | ||||||
|       ret.msg_type_id = prev.msg_type_id; |  | ||||||
|       ret.msg_stream_id = prev.msg_stream_id; |  | ||||||
|       break; |  | ||||||
|   } |  | ||||||
|   //calculate chunk length, real length, and length left till complete
 |  | ||||||
|   if (ret.len_left > 0){ |  | ||||||
|     ret.real_len = ret.len_left; |  | ||||||
|     ret.len_left -= ret.real_len; |  | ||||||
|   }else{ |  | ||||||
|     ret.real_len = ret.len; |  | ||||||
|   } |  | ||||||
|   if (ret.real_len > chunk_rec_max){ |  | ||||||
|     ret.len_left += ret.real_len - chunk_rec_max; |  | ||||||
|     ret.real_len = chunk_rec_max; |  | ||||||
|   } |  | ||||||
|   //read extended timestamp, if neccesary
 |  | ||||||
|   if (ret.timestamp == 0x00ffffff){ |  | ||||||
|     DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|     ret.timestamp = temp*256*256*256; |  | ||||||
|     DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|     ret.timestamp += temp*256*256; |  | ||||||
|     DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|     ret.timestamp += temp*256; |  | ||||||
|     DDV_read(&temp, 1, 1, CONN_fd); |  | ||||||
|     ret.timestamp += temp; |  | ||||||
|     rec_cnt+=4; |  | ||||||
|   } |  | ||||||
|   //read data if length > 0, and allocate it
 |  | ||||||
|   if (ret.real_len > 0){ |  | ||||||
|     ret.data = (unsigned char*)malloc(ret.real_len); |  | ||||||
|     DDV_read(ret.data, 1, ret.real_len, CONN_fd); |  | ||||||
|     rec_cnt+=ret.real_len; |  | ||||||
|   }else{ |  | ||||||
|     ret.data = 0; |  | ||||||
|   } |  | ||||||
|   PutPrev(ret); |  | ||||||
|   return ret; |  | ||||||
| }//getChunk
 |  | ||||||
| 
 |  | ||||||
| //adds newchunk to global list of unfinished chunks, re-assembling them complete
 |  | ||||||
| //returns pointer to chunk when a chunk is finished, 0 otherwise
 |  | ||||||
| //removes pointed to chunk from internal list if returned, without cleanup
 |  | ||||||
| // (cleanup performed in getWholeChunk function)
 |  | ||||||
| chunkpack * AddChunkPart(chunkpack newchunk){ |  | ||||||
|   chunkpack * p; |  | ||||||
|   unsigned char * tmpdata = 0; |  | ||||||
|   static std::map<unsigned int, chunkpack *> ch_lst; |  | ||||||
|   std::map<unsigned int, chunkpack *>::iterator it; |  | ||||||
|   it = ch_lst.find(newchunk.cs_id); |  | ||||||
|   if (it == ch_lst.end()){ |  | ||||||
|     p = (chunkpack*)malloc(sizeof(chunkpack)); |  | ||||||
|     *p = newchunk; |  | ||||||
|     p->data = (unsigned char*)malloc(p->real_len); |  | ||||||
|     memcpy(p->data, newchunk.data, p->real_len); |  | ||||||
|     if (p->len_left == 0){return p;} |  | ||||||
|     ch_lst[newchunk.cs_id] = p; |  | ||||||
|   }else{ |  | ||||||
|     p = it->second; |  | ||||||
|     tmpdata = (unsigned char*)realloc(p->data, p->real_len + newchunk.real_len); |  | ||||||
|     if (tmpdata == 0){ |  | ||||||
|       #if DEBUG >= 1 |  | ||||||
|       fprintf(stderr, "Error allocating memory!\n"); |  | ||||||
|       #endif |  | ||||||
|       return 0; |  | ||||||
|     } |  | ||||||
|     p->data = tmpdata; |  | ||||||
|     memcpy(p->data+p->real_len, newchunk.data, newchunk.real_len); |  | ||||||
|     p->real_len += newchunk.real_len; |  | ||||||
|     p->len_left -= newchunk.real_len; |  | ||||||
|     if (p->len_left <= 0){ |  | ||||||
|       ch_lst.erase(it); |  | ||||||
|       return p; |  | ||||||
|     }else{ |  | ||||||
|       ch_lst[newchunk.cs_id] = p;//pointer may have changed
 |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   return 0; |  | ||||||
| }//AddChunkPart
 |  | ||||||
| 
 |  | ||||||
| //grabs chunks until a whole one comes in, then returns that
 |  | ||||||
| chunkpack getWholeChunk(){ |  | ||||||
|   static chunkpack gwc_next, gwc_complete; |  | ||||||
|   static bool clean = false; |  | ||||||
|   int counter = 0; |  | ||||||
|   if (!clean){gwc_complete.data = 0; clean = true;}//prevent brain damage
 |  | ||||||
|   chunkpack * ret = 0; |  | ||||||
|   scrubChunk(gwc_complete); |  | ||||||
|   while (counter < 1000){ |  | ||||||
|     gwc_next = getChunk(); |  | ||||||
|     ret = AddChunkPart(gwc_next); |  | ||||||
|     scrubChunk(gwc_next); |  | ||||||
|     if (ret){ |  | ||||||
|       gwc_complete = *ret; |  | ||||||
|       free(ret);//cleanup returned chunk
 |  | ||||||
|       return gwc_complete; |  | ||||||
|     } |  | ||||||
|     if (socketError || socketBlocking){break;} |  | ||||||
|     counter++; |  | ||||||
|   } |  | ||||||
|   gwc_complete.msg_type_id = 0; |  | ||||||
|   return gwc_complete; |  | ||||||
| }//getWholeChunk
 |  | ||||||
|  | @ -1,45 +0,0 @@ | ||||||
| #ifndef _CRYPTO_H |  | ||||||
| #define	_CRYPTO_H |  | ||||||
| #define DLLEXP |  | ||||||
| 
 |  | ||||||
| #include <openssl/bn.h> |  | ||||||
| #include <openssl/dh.h> |  | ||||||
| #include <openssl/rc4.h> |  | ||||||
| #include <openssl/ssl.h> |  | ||||||
| #include <openssl/rand.h> |  | ||||||
| #include <openssl/err.h> |  | ||||||
| #include <openssl/bio.h> |  | ||||||
| #include <openssl/hmac.h> |  | ||||||
| 
 |  | ||||||
| class DLLEXP DHWrapper { |  | ||||||
| private: |  | ||||||
|     int32_t _bitsCount; |  | ||||||
|     DH *_pDH; |  | ||||||
|     uint8_t *_pSharedKey; |  | ||||||
|     int32_t _sharedKeyLength; |  | ||||||
|     BIGNUM *_peerPublickey; |  | ||||||
| public: |  | ||||||
|     DHWrapper(int32_t bitsCount); |  | ||||||
|     virtual ~DHWrapper(); |  | ||||||
| 
 |  | ||||||
|     bool Initialize(); |  | ||||||
|     bool CopyPublicKey(uint8_t *pDst, int32_t dstLength); |  | ||||||
|     bool CopyPrivateKey(uint8_t *pDst, int32_t dstLength); |  | ||||||
|     bool CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length); |  | ||||||
|     bool CopySharedKey(uint8_t *pDst, int32_t dstLength); |  | ||||||
| private: |  | ||||||
|     void Cleanup(); |  | ||||||
|     bool CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| DLLEXP void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, |  | ||||||
|         RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut); |  | ||||||
| DLLEXP std::string md5(std::string source, bool textResult); |  | ||||||
| DLLEXP std::string b64(std::string source); |  | ||||||
| DLLEXP std::string b64(uint8_t *pBuffer, uint32_t length); |  | ||||||
| DLLEXP std::string unb64(std::string source); |  | ||||||
| DLLEXP std::string unb64(uint8_t *pBuffer, uint32_t length); |  | ||||||
| 
 |  | ||||||
| #endif /* _CRYPTO_H */ |  | ||||||
| 
 |  | ||||||
|  | @ -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 |  | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
|  | /// \file Connector_RTMP/main.cpp
 | ||||||
|  | /// Contains the main code for the RTMP Connector
 | ||||||
|  | 
 | ||||||
| //debugging level 0 = nothing
 | //debugging level 0 = nothing
 | ||||||
| //debugging level 1 = critical errors
 | //debugging level 1 = critical errors
 | ||||||
| //debugging level 2 = errors
 | //debugging level 2 = errors
 | ||||||
| //debugging level 3 = status information
 | //debugging level 3 = status information
 | ||||||
| //debugging level 4 = extremely verbose status information
 | //debugging level 4 = extremely verbose status information
 | ||||||
| //debugging level 5 = save all streams to FLV files
 |  | ||||||
| #define DEBUG 4 | #define DEBUG 4 | ||||||
| 
 | 
 | ||||||
| #include <iostream> | #include <iostream> | ||||||
|  | @ -18,9 +20,8 @@ | ||||||
| #include <getopt.h> | #include <getopt.h> | ||||||
| #include "../util/ddv_socket.h" | #include "../util/ddv_socket.h" | ||||||
| #include "../util/flv_tag.h" | #include "../util/flv_tag.h" | ||||||
| 
 | #include "../util/amf.h" | ||||||
| #include "parsechunks.cpp" //chunkstream parsing
 | #include "../util/rtmpchunks.h" | ||||||
| #include "handshake.cpp" //handshaking
 |  | ||||||
| 
 | 
 | ||||||
| /// Holds all functions and data unique to the RTMP Connector
 | /// Holds all functions and data unique to the RTMP Connector
 | ||||||
| namespace Connector_RTMP{ | namespace Connector_RTMP{ | ||||||
|  | @ -29,144 +30,392 @@ namespace Connector_RTMP{ | ||||||
|   bool ready4data = false; ///< Set to true when streaming starts.
 |   bool ready4data = false; ///< Set to true when streaming starts.
 | ||||||
|   bool inited = false; ///< Set to true when ready to connect to Buffer.
 |   bool inited = false; ///< Set to true when ready to connect to Buffer.
 | ||||||
|   bool stopparsing = false; ///< Set to true when all parsing needs to be cancelled.
 |   bool stopparsing = false; ///< Set to true when all parsing needs to be cancelled.
 | ||||||
|   timeval lastrec; ///< Timestamp of last received data.
 |  | ||||||
|    |    | ||||||
|   DDV::Socket Socket; ///< Socket connected to user
 |   DDV::Socket Socket; ///< Socket connected to user
 | ||||||
|  |   std::string streamname = "/tmp/shared_socket"; ///< Stream that will be opened
 | ||||||
|  |   void parseChunk(); | ||||||
|  |   int Connector_RTMP(DDV::Socket conn); | ||||||
|  | };//Connector_RTMP namespace;
 | ||||||
| 
 | 
 | ||||||
|   /// Main Connector_RTMP function
 |  | ||||||
|   int Connector_RTMP(DDV::Socket conn){ |  | ||||||
|     Socket = conn; |  | ||||||
|     unsigned int ts; |  | ||||||
|     unsigned int fts = 0; |  | ||||||
|     unsigned int ftst; |  | ||||||
|     DDV::Socket SS; |  | ||||||
|     FLV::Tag tag = 0; |  | ||||||
| 
 | 
 | ||||||
|     //first timestamp set
 | /// Main Connector_RTMP function
 | ||||||
|     firsttime = getNowMS(); | 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; | ||||||
| 
 | 
 | ||||||
|     if (doHandshake()){ |   //first timestamp set
 | ||||||
|       #if DEBUG >= 4 |   RTMPStream::firsttime = RTMPStream::getNowMS(); | ||||||
|       fprintf(stderr, "Handshake succcess!\n"); |  | ||||||
|       #endif |  | ||||||
|     }else{ |  | ||||||
|       #if DEBUG >= 1 |  | ||||||
|       fprintf(stderr, "Handshake fail!\n"); |  | ||||||
|       #endif |  | ||||||
|       return 0; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     int retval; |   while (RTMPStream::handshake_in.size() < 1537){ | ||||||
|     int poller = epoll_create(1); |     Socket.read(RTMPStream::handshake_in); | ||||||
|     int sspoller = epoll_create(1); |   } | ||||||
|     struct epoll_event ev; |   if (RTMPStream::doHandshake()){ | ||||||
|     ev.events = EPOLLIN; |     Socket.write(RTMPStream::handshake_out); | ||||||
|     ev.data.fd = CONN_fd; |     Socket.read((char*)RTMPStream::handshake_in.c_str(), 1536); | ||||||
|     epoll_ctl(poller, EPOLL_CTL_ADD, CONN_fd, &ev); |     RTMPStream::rec_cnt += 1536; | ||||||
|     struct epoll_event events[1]; |     #if DEBUG >= 4 | ||||||
|     #if DEBUG >= 5 |     fprintf(stderr, "Handshake succcess!\n"); | ||||||
|     //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; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         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); |  | ||||||
|     #endif |     #endif | ||||||
|  |   }else{ | ||||||
|     #if DEBUG >= 1 |     #if DEBUG >= 1 | ||||||
|     if (FLV::Parse_Error){fprintf(stderr, "FLV Parse Error\n");} |     fprintf(stderr, "Handshake fail!\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"); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     #endif |     #endif | ||||||
|     return 0; |     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
 | // Load main server setup file, default port 1935, handler is Connector_RTMP::Connector_RTMP
 | ||||||
| #define DEFAULT_PORT 1935 | #define DEFAULT_PORT 1935 | ||||||
|  |  | ||||||
|  | @ -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
 |  | ||||||
							
								
								
									
										2
									
								
								Doxyfile
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								Doxyfile
									
										
									
									
									
								
							|  | @ -43,7 +43,7 @@ SYMBOL_CACHE_SIZE      = 0 | ||||||
| # Build related configuration options | # Build related configuration options | ||||||
| #--------------------------------------------------------------------------- | #--------------------------------------------------------------------------- | ||||||
| EXTRACT_ALL            = YES | EXTRACT_ALL            = YES | ||||||
| EXTRACT_PRIVATE        = NO | EXTRACT_PRIVATE        = YES | ||||||
| EXTRACT_STATIC         = YES | EXTRACT_STATIC         = YES | ||||||
| EXTRACT_LOCAL_CLASSES  = YES | EXTRACT_LOCAL_CLASSES  = YES | ||||||
| EXTRACT_LOCAL_METHODS  = NO | EXTRACT_LOCAL_METHODS  = NO | ||||||
|  |  | ||||||
|  | @ -1,3 +1,11 @@ | ||||||
|  | /// \file HTTP_Box_Parser/main.cpp
 | ||||||
|  | /// Debugging tool for F4M HTTP streaming data.
 | ||||||
|  | /// Expects raw TCP data through stdin, outputs human-readable information to stderr.
 | ||||||
|  | /// This will attempt to read either HTTP requests or responses from stdin, and if the body is more than
 | ||||||
|  | /// 10,000 bytes long will attempt to parse the data as a MP4 box. (Other cases show a message about the fragment being too small)
 | ||||||
|  | /// Then it will take the payload of this box, print the first four bytes, and attempt to parse the whole payload as FLV data.
 | ||||||
|  | /// The parsed FLV data is then pretty-printed, containing information about the codec parameters and types of tags it encounters.
 | ||||||
|  | 
 | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <iostream> | #include <iostream> | ||||||
| #include <string> | #include <string> | ||||||
|  | @ -6,6 +14,12 @@ | ||||||
| #include "../util/MP4/box_includes.h" | #include "../util/MP4/box_includes.h" | ||||||
| #include "../util/flv_tag.h" | #include "../util/flv_tag.h" | ||||||
| 
 | 
 | ||||||
|  | /// Debugging tool for F4M HTTP streaming data.
 | ||||||
|  | /// Expects raw TCP data through stdin, outputs human-readable information to stderr.
 | ||||||
|  | /// This will attempt to read either HTTP requests or responses from stdin, and if the body is more than
 | ||||||
|  | /// 10,000 bytes long will attempt to parse the data as a MP4 box. (Other cases show a message about the fragment being too small)
 | ||||||
|  | /// Then it will take the payload of this box, print the first four bytes, and attempt to parse the whole payload as FLV data.
 | ||||||
|  | /// The parsed FLV data is then pretty-printed, containing information about the codec parameters and types of tags it encounters.
 | ||||||
| int main(){ | int main(){ | ||||||
|   HTTPReader H; |   HTTPReader H; | ||||||
|   FLV::Tag F; |   FLV::Tag F; | ||||||
|  | @ -28,4 +42,4 @@ int main(){ | ||||||
|       std::cout << "Skipped too small fragment of size " << H.body.size() << std::endl; |       std::cout << "Skipped too small fragment of size " << H.body.size() << std::endl; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | }//main
 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
										
									
									
									
								
							|  | @ -18,5 +18,5 @@ client-install: client-clean client | ||||||
| 	cd Connector_RAW; $(MAKE) install | 	cd Connector_RAW; $(MAKE) install | ||||||
| 	cd Buffer; $(MAKE) install | 	cd Buffer; $(MAKE) install | ||||||
| docs: | docs: | ||||||
| 	doxygen ./Doxyfile | 	doxygen ./Doxyfile > /dev/null | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | /// \file amf.cpp
 | ||||||
|  | /// Holds all code for the AMF namespace.
 | ||||||
|  | 
 | ||||||
| #include "amf.h" | #include "amf.h" | ||||||
| 
 | 
 | ||||||
| /// Returns the std::string Indice for the current object, if available.
 | /// Returns the std::string Indice for the current object, if available.
 | ||||||
|  |  | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | /// \file amf.h
 | ||||||
|  | /// Holds all headers for the AMF namespace.
 | ||||||
|  | 
 | ||||||
| #pragma once | #pragma once | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <iostream> | #include <iostream> | ||||||
|  |  | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | /// \file crypto.cpp
 | ||||||
|  | /// Holds all code needed for RTMP cryptography.
 | ||||||
|  | 
 | ||||||
| #define STR(x) (((std::string)(x)).c_str()) | #define STR(x) (((std::string)(x)).c_str()) | ||||||
| 
 | 
 | ||||||
| #include "crypto.h" | #include "crypto.h" | ||||||
							
								
								
									
										56
									
								
								util/crypto.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								util/crypto.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | ||||||
|  | /// \file crypto.h
 | ||||||
|  | /// Holds all headers needed for RTMP cryptography functions.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <string> | ||||||
|  | #include <openssl/bn.h> | ||||||
|  | #include <openssl/dh.h> | ||||||
|  | #include <openssl/rc4.h> | ||||||
|  | #include <openssl/ssl.h> | ||||||
|  | #include <openssl/rand.h> | ||||||
|  | #include <openssl/err.h> | ||||||
|  | #include <openssl/bio.h> | ||||||
|  | #include <openssl/hmac.h> | ||||||
|  | 
 | ||||||
|  | class DHWrapper { | ||||||
|  | private: | ||||||
|  |     int32_t _bitsCount; | ||||||
|  |     DH *_pDH; | ||||||
|  |     uint8_t *_pSharedKey; | ||||||
|  |     int32_t _sharedKeyLength; | ||||||
|  |     BIGNUM *_peerPublickey; | ||||||
|  | public: | ||||||
|  |     DHWrapper(int32_t bitsCount); | ||||||
|  |     virtual ~DHWrapper(); | ||||||
|  | 
 | ||||||
|  |     bool Initialize(); | ||||||
|  |     bool CopyPublicKey(uint8_t *pDst, int32_t dstLength); | ||||||
|  |     bool CopyPrivateKey(uint8_t *pDst, int32_t dstLength); | ||||||
|  |     bool CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length); | ||||||
|  |     bool CopySharedKey(uint8_t *pDst, int32_t dstLength); | ||||||
|  | private: | ||||||
|  |     void Cleanup(); | ||||||
|  |     bool CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut); | ||||||
|  | std::string md5(std::string source, bool textResult); | ||||||
|  | std::string b64(std::string source); | ||||||
|  | std::string b64(uint8_t *pBuffer, uint32_t length); | ||||||
|  | std::string unb64(std::string source); | ||||||
|  | std::string unb64(uint8_t *pBuffer, uint32_t length); | ||||||
|  | 
 | ||||||
|  | void HMACsha256(const void *pData, uint32_t dataLength, const void *pKey, uint32_t keyLength, void *pResult); | ||||||
|  | 
 | ||||||
|  | uint32_t GetDigestOffset0(uint8_t *pBuffer); | ||||||
|  | uint32_t GetDigestOffset1(uint8_t *pBuffer); | ||||||
|  | uint32_t GetDigestOffset(uint8_t *pBuffer, uint8_t scheme); | ||||||
|  | uint32_t GetDHOffset0(uint8_t *pBuffer); | ||||||
|  | uint32_t GetDHOffset1(uint8_t *pBuffer); | ||||||
|  | uint32_t GetDHOffset(uint8_t *pBuffer, uint8_t scheme); | ||||||
|  | 
 | ||||||
|  | extern uint8_t genuineFMSKey[]; | ||||||
|  | 
 | ||||||
|  | bool ValidateClientScheme(uint8_t * pBuffer, uint8_t scheme); | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | /// \file ddv_socket.cpp
 | ||||||
|  | /// Holds all code for the DDV namespace.
 | ||||||
|  | 
 | ||||||
| #include "ddv_socket.h" | #include "ddv_socket.h" | ||||||
| 
 | 
 | ||||||
| /// Create a new base socket. This is a basic constructor for converting any valid socket to a DDV::Socket.
 | /// Create a new base socket. This is a basic constructor for converting any valid socket to a DDV::Socket.
 | ||||||
|  | @ -207,6 +210,21 @@ int DDV::Socket::iread(void * buffer, int len){ | ||||||
|   return r; |   return r; | ||||||
| }//DDV::Socket::iread
 | }//DDV::Socket::iread
 | ||||||
| 
 | 
 | ||||||
|  | /// Read call that is compatible with std::string.
 | ||||||
|  | /// Data is read using iread (which is nonblocking if the DDV::Socket itself is),
 | ||||||
|  | /// then appended to end of buffer.
 | ||||||
|  | /// \param buffer std::string to append data to.
 | ||||||
|  | /// \return True if new data arrived, false otherwise.
 | ||||||
|  | bool DDV::Socket::read(std::string & buffer){ | ||||||
|  |   char cbuffer[5000]; | ||||||
|  |   int num = iread(cbuffer, 5000); | ||||||
|  |   if (num > 0){ | ||||||
|  |     buffer.append(cbuffer, num); | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  | }//read
 | ||||||
|  | 
 | ||||||
| /// Create a new base ServerSocket. The socket is never connected, and a placeholder for later connections.
 | /// Create a new base ServerSocket. The socket is never connected, and a placeholder for later connections.
 | ||||||
| DDV::ServerSocket::ServerSocket(){ | DDV::ServerSocket::ServerSocket(){ | ||||||
|   sock = -1; |   sock = -1; | ||||||
|  |  | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | /// \file ddv_socket.h
 | ||||||
|  | /// Holds all headers for the DDV namespace.
 | ||||||
|  | 
 | ||||||
| #pragma once | #pragma once | ||||||
| #include <string> | #include <string> | ||||||
| #include <sys/types.h> | #include <sys/types.h> | ||||||
|  | @ -33,6 +36,7 @@ namespace DDV{ | ||||||
|       bool write(const std::string data); ///< Write call that is compatible with std::string.
 |       bool write(const std::string data); ///< Write call that is compatible with std::string.
 | ||||||
|       int iwrite(void * buffer, int len); ///< Incremental write call.
 |       int iwrite(void * buffer, int len); ///< Incremental write call.
 | ||||||
|       int iread(void * buffer, int len); ///< Incremental read call.
 |       int iread(void * buffer, int len); ///< Incremental read call.
 | ||||||
|  |       bool read(std::string & buffer); ///< Read call that is compatible with std::string.
 | ||||||
|       void close(); ///< Close connection.
 |       void close(); ///< Close connection.
 | ||||||
|       int getSocket(); ///< Returns internal socket number.
 |       int getSocket(); ///< Returns internal socket number.
 | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | /// \file flv_tag.cpp
 | ||||||
|  | /// Holds all code for the FLV namespace.
 | ||||||
|  | 
 | ||||||
| #include "flv_tag.h" | #include "flv_tag.h" | ||||||
| #include <stdio.h> //for Tag::FileLoader
 | #include <stdio.h> //for Tag::FileLoader
 | ||||||
| #include <unistd.h> //for Tag::FileLoader
 | #include <unistd.h> //for Tag::FileLoader
 | ||||||
|  | @ -8,6 +11,7 @@ | ||||||
| 
 | 
 | ||||||
| char FLV::Header[13]; ///< Holds the last FLV header parsed.
 | char FLV::Header[13]; ///< Holds the last FLV header parsed.
 | ||||||
| bool FLV::Parse_Error = false; ///< This variable is set to true if a problem is encountered while parsing the FLV.
 | bool FLV::Parse_Error = false; ///< This variable is set to true if a problem is encountered while parsing the FLV.
 | ||||||
|  | std::string FLV::Error_Str = ""; | ||||||
| 
 | 
 | ||||||
| /// Checks a FLV Header for validness. Returns true if the header is valid, false
 | /// Checks a FLV Header for validness. Returns true if the header is valid, false
 | ||||||
| /// if the header is not. Not valid can mean:
 | /// if the header is not. Not valid can mean:
 | ||||||
|  | @ -219,7 +223,7 @@ bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){ | ||||||
|           if (FLV::check_header(data)){ |           if (FLV::check_header(data)){ | ||||||
|             sofar = 0; |             sofar = 0; | ||||||
|             memcpy(FLV::Header, data, 13); |             memcpy(FLV::Header, data, 13); | ||||||
|           }else{FLV::Parse_Error = true; return false;} |           }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} | ||||||
|         } |         } | ||||||
|       }else{ |       }else{ | ||||||
|         //if a tag header, calculate length and read tag body
 |         //if a tag header, calculate length and read tag body
 | ||||||
|  | @ -227,7 +231,7 @@ bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){ | ||||||
|         len += (data[2] << 8); |         len += (data[2] << 8); | ||||||
|         len += (data[1] << 16); |         len += (data[1] << 16); | ||||||
|         if (buf < len){data = (char*)realloc(data, len); buf = len;} |         if (buf < len){data = (char*)realloc(data, len); buf = len;} | ||||||
|         if (data[0] > 0x12){FLV::Parse_Error = true; return false;} |         if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;} | ||||||
|         done = false; |         done = false; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | @ -259,7 +263,7 @@ bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & s | ||||||
|   if (r < 0){ |   if (r < 0){ | ||||||
|     if (errno != EWOULDBLOCK){ |     if (errno != EWOULDBLOCK){ | ||||||
|       FLV::Parse_Error = true; |       FLV::Parse_Error = true; | ||||||
|       fprintf(stderr, "ReadUntil fail: %s. All Hell Broke Loose!\n", strerror(errno)); |       Error_Str = "Error reading from socket."; | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  | @ -267,7 +271,7 @@ bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & s | ||||||
|   if (sofar == count){return true;} |   if (sofar == count){return true;} | ||||||
|   if (sofar > count){ |   if (sofar > count){ | ||||||
|     FLV::Parse_Error = true; |     FLV::Parse_Error = true; | ||||||
|     fprintf(stderr, "ReadUntil fail: %s. Read too much. All Hell Broke Loose!\n", strerror(errno)); |     Error_Str = "Socket buffer overflow."; | ||||||
|   } |   } | ||||||
|   return false; |   return false; | ||||||
| }//Tag::SockReadUntil
 | }//Tag::SockReadUntil
 | ||||||
|  | @ -289,7 +293,7 @@ bool FLV::Tag::SockLoader(DDV::Socket sock){ | ||||||
|           if (FLV::check_header(data)){ |           if (FLV::check_header(data)){ | ||||||
|             sofar = 0; |             sofar = 0; | ||||||
|             memcpy(FLV::Header, data, 13); |             memcpy(FLV::Header, data, 13); | ||||||
|           }else{FLV::Parse_Error = true; return false;} |           }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} | ||||||
|         } |         } | ||||||
|       }else{ |       }else{ | ||||||
|         //if a tag header, calculate length and read tag body
 |         //if a tag header, calculate length and read tag body
 | ||||||
|  | @ -297,7 +301,7 @@ bool FLV::Tag::SockLoader(DDV::Socket sock){ | ||||||
|         len += (data[2] << 8); |         len += (data[2] << 8); | ||||||
|         len += (data[1] << 16); |         len += (data[1] << 16); | ||||||
|         if (buf < len){data = (char*)realloc(data, len); buf = len;} |         if (buf < len){data = (char*)realloc(data, len); buf = len;} | ||||||
|         if (data[0] > 0x12){FLV::Parse_Error = true; return false;} |         if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;} | ||||||
|         done = false; |         done = false; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | @ -335,7 +339,7 @@ bool FLV::Tag::FileReadUntil(char * buffer, unsigned int count, unsigned int & s | ||||||
|   if (sofar >= count){return true;} |   if (sofar >= count){return true;} | ||||||
|   int r = 0; |   int r = 0; | ||||||
|   r = fread(buffer + sofar,1,count-sofar,f); |   r = fread(buffer + sofar,1,count-sofar,f); | ||||||
|   if (r < 0){FLV::Parse_Error = true; return false;} |   if (r < 0){FLV::Parse_Error = true; Error_Str = "File reading error."; return false;} | ||||||
|   sofar += r; |   sofar += r; | ||||||
|   if (sofar >= count){return true;} |   if (sofar >= count){return true;} | ||||||
|   return false; |   return false; | ||||||
|  | @ -363,7 +367,7 @@ bool FLV::Tag::FileLoader(FILE * f){ | ||||||
|           if (FLV::check_header(data)){ |           if (FLV::check_header(data)){ | ||||||
|             sofar = 0; |             sofar = 0; | ||||||
|             memcpy(FLV::Header, data, 13); |             memcpy(FLV::Header, data, 13); | ||||||
|           }else{FLV::Parse_Error = true;} |           }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} | ||||||
|         } |         } | ||||||
|       }else{ |       }else{ | ||||||
|         //if a tag header, calculate length and read tag body
 |         //if a tag header, calculate length and read tag body
 | ||||||
|  | @ -371,7 +375,7 @@ bool FLV::Tag::FileLoader(FILE * f){ | ||||||
|         len += (data[2] << 8); |         len += (data[2] << 8); | ||||||
|         len += (data[1] << 16); |         len += (data[1] << 16); | ||||||
|         if (buf < len){data = (char*)realloc(data, len); buf = len;} |         if (buf < len){data = (char*)realloc(data, len); buf = len;} | ||||||
|         if (data[0] > 0x12){FLV::Parse_Error = true;} |         if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;} | ||||||
|         done = false; |         done = false; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | /// \file flv_tag.h
 | ||||||
|  | /// Holds all headers for the FLV namespace.
 | ||||||
|  | 
 | ||||||
| #pragma once | #pragma once | ||||||
| #include "ddv_socket.h" | #include "ddv_socket.h" | ||||||
| #include <string> | #include <string> | ||||||
|  | @ -7,6 +10,7 @@ namespace FLV { | ||||||
|   //variables
 |   //variables
 | ||||||
|   extern char Header[13]; ///< Holds the last FLV header parsed.
 |   extern char Header[13]; ///< Holds the last FLV header parsed.
 | ||||||
|   extern bool Parse_Error; ///< This variable is set to true if a problem is encountered while parsing the FLV.
 |   extern bool Parse_Error; ///< This variable is set to true if a problem is encountered while parsing the FLV.
 | ||||||
|  |   extern std::string Error_Str; ///< This variable is set if a problem is encountered while parsing the FLV.
 | ||||||
|    |    | ||||||
|   //functions
 |   //functions
 | ||||||
|   bool check_header(char * header); ///< Checks a FLV Header for validness.
 |   bool check_header(char * header); ///< Checks a FLV Header for validness.
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,14 @@ | ||||||
| #include "http_parser.h" | /// \file http_parser.cpp
 | ||||||
| #include "ddv_socket.h" | /// Holds all code for the HTTP namespace.
 | ||||||
| 
 | 
 | ||||||
| HTTPReader::HTTPReader(){Clean();} | #include "http_parser.h" | ||||||
| void HTTPReader::Clean(){ | 
 | ||||||
|  | /// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing.
 | ||||||
|  | /// All this constructor does is call HTTP::Parser::Clean().
 | ||||||
|  | HTTP::Parser::Parser(){Clean();} | ||||||
|  | 
 | ||||||
|  | /// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing usage.
 | ||||||
|  | void HTTP::Parser::Clean(){ | ||||||
|   seenHeaders = false; |   seenHeaders = false; | ||||||
|   seenReq = false; |   seenReq = false; | ||||||
|   method = "GET"; |   method = "GET"; | ||||||
|  | @ -11,11 +17,14 @@ void HTTPReader::Clean(){ | ||||||
|   body = ""; |   body = ""; | ||||||
|   length = 0; |   length = 0; | ||||||
|   HTTPbuffer = ""; |   HTTPbuffer = ""; | ||||||
|   headers.erase(headers.begin(), headers.end()); |   headers.clear(); | ||||||
|   vars.erase(vars.begin(), vars.end()); |   vars.clear(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool HTTPReader::CleanForNext(){ | /// Re-initializes the HTTP::Parser, leaving the internal data buffer alone, then tries to parse a new request or response.
 | ||||||
|  | /// First does the same as HTTP::Parser::Clean(), but does not clear the internal data buffer.
 | ||||||
|  | /// This function then calls the HTTP::Parser::parse() function, and returns that functions return value.
 | ||||||
|  | bool HTTP::Parser::CleanForNext(){ | ||||||
|   seenHeaders = false; |   seenHeaders = false; | ||||||
|   seenReq = false; |   seenReq = false; | ||||||
|   method = "GET"; |   method = "GET"; | ||||||
|  | @ -23,12 +32,19 @@ bool HTTPReader::CleanForNext(){ | ||||||
|   protocol = "HTTP/1.1"; |   protocol = "HTTP/1.1"; | ||||||
|   body = ""; |   body = ""; | ||||||
|   length = 0; |   length = 0; | ||||||
|   headers.erase(headers.begin(), headers.end()); |   headers.clear(); | ||||||
|   vars.erase(vars.begin(), vars.end()); |   vars.clear(); | ||||||
|   return parse(); |   return parse(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::string HTTPReader::BuildRequest(){ | /// Returns a string containing a valid HTTP 1.0 or 1.1 request, ready for sending.
 | ||||||
|  | /// The request is build from internal variables set before this call is made.
 | ||||||
|  | /// To be precise, method, url, protocol, headers and the internal data buffer are used,
 | ||||||
|  | /// where the internal data buffer is used as the body of the request.
 | ||||||
|  | /// This means you cannot mix receiving and sending, because the body would get corrupted.
 | ||||||
|  | /// \return A string containing a valid HTTP 1.0 or 1.1 request, ready for sending.
 | ||||||
|  | std::string HTTP::Parser::BuildRequest(){ | ||||||
|  |   /// \todo Include GET/POST variable parsing?
 | ||||||
|   std::map<std::string, std::string>::iterator it; |   std::map<std::string, std::string>::iterator it; | ||||||
|   std::string tmp = method+" "+url+" "+protocol+"\n"; |   std::string tmp = method+" "+url+" "+protocol+"\n"; | ||||||
|   for (it=headers.begin(); it != headers.end(); it++){ |   for (it=headers.begin(); it != headers.end(); it++){ | ||||||
|  | @ -39,7 +55,16 @@ std::string HTTPReader::BuildRequest(){ | ||||||
|   return tmp; |   return tmp; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::string HTTPReader::BuildResponse(std::string code, std::string message){ | /// Returns a string containing a valid HTTP 1.0 or 1.1 response, ready for sending.
 | ||||||
|  | /// The response is partly build from internal variables set before this call is made.
 | ||||||
|  | /// To be precise, protocol, headers and the internal data buffer are used,
 | ||||||
|  | /// where the internal data buffer is used as the body of the response.
 | ||||||
|  | /// This means you cannot mix receiving and sending, because the body would get corrupted.
 | ||||||
|  | /// \param code The HTTP response code. Usually you want 200.
 | ||||||
|  | /// \param message The HTTP response message. Usually you want "OK".
 | ||||||
|  | /// \return A string containing a valid HTTP 1.0 or 1.1 response, ready for sending.
 | ||||||
|  | std::string HTTP::Parser::BuildResponse(std::string code, std::string message){ | ||||||
|  |   /// \todo Include GET/POST variable parsing?
 | ||||||
|   std::map<std::string, std::string>::iterator it; |   std::map<std::string, std::string>::iterator it; | ||||||
|   std::string tmp = protocol+" "+code+" "+message+"\n"; |   std::string tmp = protocol+" "+code+" "+message+"\n"; | ||||||
|   for (it=headers.begin(); it != headers.end(); it++){ |   for (it=headers.begin(); it != headers.end(); it++){ | ||||||
|  | @ -50,47 +75,61 @@ std::string HTTPReader::BuildResponse(std::string code, std::string message){ | ||||||
|   return tmp; |   return tmp; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void HTTPReader::Trim(std::string & s){ | /// Trims any whitespace at the front or back of the string.
 | ||||||
|  | /// Used when getting/setting headers.
 | ||||||
|  | /// \param s The string to trim. The string itself will be changed, not returned.
 | ||||||
|  | void HTTP::Parser::Trim(std::string & s){ | ||||||
|   size_t startpos = s.find_first_not_of(" \t"); |   size_t startpos = s.find_first_not_of(" \t"); | ||||||
|   size_t endpos = s.find_last_not_of(" \t"); |   size_t endpos = s.find_last_not_of(" \t"); | ||||||
|   if ((std::string::npos == startpos) || (std::string::npos == endpos)){s = "";}else{s = s.substr(startpos, endpos-startpos+1);} |   if ((std::string::npos == startpos) || (std::string::npos == endpos)){s = "";}else{s = s.substr(startpos, endpos-startpos+1);} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void HTTPReader::SetBody(std::string s){ | /// Function that sets the body of a response or request, along with the correct Content-Length header.
 | ||||||
|  | /// \param s The string to set the body to.
 | ||||||
|  | void HTTP::Parser::SetBody(std::string s){ | ||||||
|   HTTPbuffer = s; |   HTTPbuffer = s; | ||||||
|   SetHeader("Content-Length", s.length()); |   SetHeader("Content-Length", s.length()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void HTTPReader::SetBody(char * buffer, int len){ | /// Function that sets the body of a response or request, along with the correct Content-Length header.
 | ||||||
|  | /// \param buffer The buffer data to set the body to.
 | ||||||
|  | /// \param len Length of the buffer data.
 | ||||||
|  | void HTTP::Parser::SetBody(char * buffer, int len){ | ||||||
|   HTTPbuffer = ""; |   HTTPbuffer = ""; | ||||||
|   HTTPbuffer.append(buffer, len); |   HTTPbuffer.append(buffer, len); | ||||||
|   SetHeader("Content-Length", len); |   SetHeader("Content-Length", len); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Returns header i, if set.
 | ||||||
|  | std::string HTTP::Parser::GetHeader(std::string i){return headers[i];} | ||||||
|  | /// Returns POST variable i, if set.
 | ||||||
|  | std::string HTTP::Parser::GetVar(std::string i){return vars[i];} | ||||||
| 
 | 
 | ||||||
| std::string HTTPReader::GetHeader(std::string i){return headers[i];} | /// Sets header i to string value v.
 | ||||||
| std::string HTTPReader::GetVar(std::string i){return vars[i];} | void HTTP::Parser::SetHeader(std::string i, std::string v){ | ||||||
| 
 |  | ||||||
| void HTTPReader::SetHeader(std::string i, std::string v){ |  | ||||||
|   Trim(i); |   Trim(i); | ||||||
|   Trim(v); |   Trim(v); | ||||||
|   headers[i] = v; |   headers[i] = v; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void HTTPReader::SetHeader(std::string i, int v){ | /// Sets header i to integer value v.
 | ||||||
|  | void HTTP::Parser::SetHeader(std::string i, int v){ | ||||||
|   Trim(i); |   Trim(i); | ||||||
|   char val[128]; |   char val[128]; | ||||||
|   sprintf(val, "%i", v); |   sprintf(val, "%i", v); | ||||||
|   headers[i] = val; |   headers[i] = val; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void HTTPReader::SetVar(std::string i, std::string v){ | /// Sets POST variable i to string value v.
 | ||||||
|  | void HTTP::Parser::SetVar(std::string i, std::string v){ | ||||||
|   Trim(i); |   Trim(i); | ||||||
|   Trim(v); |   Trim(v); | ||||||
|   vars[i] = v; |   vars[i] = v; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool HTTPReader::Read(DDV::Socket & sock){ | /// Attempt to read a whole HTTP request or response from DDV::Socket sock.
 | ||||||
|  | /// \return True of a whole request or response was read, false otherwise.
 | ||||||
|  | bool HTTP::Parser::Read(DDV::Socket & sock){ | ||||||
|   //returned true als hele http packet gelezen is
 |   //returned true als hele http packet gelezen is
 | ||||||
|   int r = 0; |   int r = 0; | ||||||
|   int b = 0; |   int b = 0; | ||||||
|  | @ -111,7 +150,9 @@ bool HTTPReader::Read(DDV::Socket & sock){ | ||||||
|   return false; |   return false; | ||||||
| }//HTTPReader::ReadSocket
 | }//HTTPReader::ReadSocket
 | ||||||
| 
 | 
 | ||||||
| bool HTTPReader::Read(FILE * F){ | /// Reads a full set of HTTP responses/requests from file F.
 | ||||||
|  | /// \return Always false. Use HTTP::Parser::CleanForNext() to parse the contents of the file.
 | ||||||
|  | bool HTTP::Parser::Read(FILE * F){ | ||||||
|   //returned true als hele http packet gelezen is
 |   //returned true als hele http packet gelezen is
 | ||||||
|   int b = 1; |   int b = 1; | ||||||
|   char buffer[500]; |   char buffer[500]; | ||||||
|  | @ -122,7 +163,11 @@ bool HTTPReader::Read(FILE * F){ | ||||||
|   return false; |   return false; | ||||||
| }//HTTPReader::ReadSocket
 | }//HTTPReader::ReadSocket
 | ||||||
| 
 | 
 | ||||||
| bool HTTPReader::parse(){ | /// Attempt to read a whole HTTP response or request from the internal data buffer.
 | ||||||
|  | /// If succesful, fills its own fields with the proper data and removes the response/request
 | ||||||
|  | /// from the internal data buffer.
 | ||||||
|  | /// \return True on success, false otherwise.
 | ||||||
|  | bool HTTP::Parser::parse(){ | ||||||
|   size_t f; |   size_t f; | ||||||
|   std::string tmpA, tmpB, tmpC; |   std::string tmpA, tmpB, tmpC; | ||||||
|   while (HTTPbuffer != ""){ |   while (HTTPbuffer != ""){ | ||||||
|  | @ -140,7 +185,7 @@ bool HTTPReader::parse(){ | ||||||
|         if (f != std::string::npos){url = tmpA.substr(0, f); tmpA.erase(0, f+1);} |         if (f != std::string::npos){url = tmpA.substr(0, f); tmpA.erase(0, f+1);} | ||||||
|         f = tmpA.find(' '); |         f = tmpA.find(' '); | ||||||
|         if (f != std::string::npos){protocol = tmpA.substr(0, f); tmpA.erase(0, f+1);} |         if (f != std::string::npos){protocol = tmpA.substr(0, f); tmpA.erase(0, f+1);} | ||||||
|         //TODO: GET variable parsing?
 |         /// \todo Include GET variable parsing?
 | ||||||
|       }else{ |       }else{ | ||||||
|         if (tmpA.size() == 0){ |         if (tmpA.size() == 0){ | ||||||
|           seenHeaders = true; |           seenHeaders = true; | ||||||
|  | @ -156,7 +201,7 @@ bool HTTPReader::parse(){ | ||||||
|     } |     } | ||||||
|     if (seenHeaders){ |     if (seenHeaders){ | ||||||
|       if (length > 0){ |       if (length > 0){ | ||||||
|         //TODO: POST variable parsing?
 |         /// \todo Include POST variable parsing?
 | ||||||
|         if (HTTPbuffer.length() >= length){ |         if (HTTPbuffer.length() >= length){ | ||||||
|           body = HTTPbuffer.substr(0, length); |           body = HTTPbuffer.substr(0, length); | ||||||
|           HTTPbuffer.erase(0, length); |           HTTPbuffer.erase(0, length); | ||||||
|  | @ -172,23 +217,40 @@ bool HTTPReader::parse(){ | ||||||
|   return false; //we should never get here...
 |   return false; //we should never get here...
 | ||||||
| }//HTTPReader::parse
 | }//HTTPReader::parse
 | ||||||
| 
 | 
 | ||||||
| void HTTPReader::SendResponse(DDV::Socket & conn, std::string code, std::string message){ | /// Sends data as response to conn.
 | ||||||
|  | /// The response is automatically first build using HTTP::Parser::BuildResponse().
 | ||||||
|  | /// \param conn The DDV::Socket to send the response over.
 | ||||||
|  | /// \param code The HTTP response code. Usually you want 200.
 | ||||||
|  | /// \param message The HTTP response message. Usually you want "OK".
 | ||||||
|  | void HTTP::Parser::SendResponse(DDV::Socket & conn, std::string code, std::string message){ | ||||||
|   std::string tmp = BuildResponse(code, message); |   std::string tmp = BuildResponse(code, message); | ||||||
|   conn.write(tmp); |   conn.write(tmp); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void HTTPReader::SendBodyPart(DDV::Socket & conn, char * buffer, int len){ | /// Sends data as HTTP/1.1 bodypart to conn.
 | ||||||
|  | /// HTTP/1.1 chunked encoding is automatically applied if needed.
 | ||||||
|  | /// \param conn The DDV::Socket to send the part over.
 | ||||||
|  | /// \param buffer The buffer to send.
 | ||||||
|  | /// \param len The length of the buffer.
 | ||||||
|  | void HTTP::Parser::SendBodyPart(DDV::Socket & conn, char * buffer, int len){ | ||||||
|   std::string tmp; |   std::string tmp; | ||||||
|   tmp.append(buffer, len); |   tmp.append(buffer, len); | ||||||
|   SendBodyPart(conn, tmp); |   SendBodyPart(conn, tmp); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void HTTPReader::SendBodyPart(DDV::Socket & conn, std::string bodypart){ | /// Sends data as HTTP/1.1 bodypart to conn.
 | ||||||
|   static char len[10]; | /// HTTP/1.1 chunked encoding is automatically applied if needed.
 | ||||||
|   int sizelen; | /// \param conn The DDV::Socket to send the part over.
 | ||||||
|   sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size()); | /// \param bodypart The data to send.
 | ||||||
|   conn.write(len, sizelen); | void HTTP::Parser::SendBodyPart(DDV::Socket & conn, std::string bodypart){ | ||||||
|   conn.write(bodypart); |   if (protocol == "HTTP/1.1"){ | ||||||
|   conn.write(len+sizelen-2, 2); |     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); | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | /// \file http_parser.h
 | ||||||
|  | /// Holds all headers for the HTTP namespace.
 | ||||||
|  | 
 | ||||||
| #pragma once | #pragma once | ||||||
| #include <map> | #include <map> | ||||||
| #include <string> | #include <string> | ||||||
|  | @ -5,37 +8,40 @@ | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| #include "ddv_socket.h" | #include "ddv_socket.h" | ||||||
| 
 | 
 | ||||||
| class HTTPReader{ | /// Holds all HTTP processing related code.
 | ||||||
|   public: | namespace HTTP{ | ||||||
|     HTTPReader(); |   /// Simple class for reading and writing HTTP 1.0 and 1.1.
 | ||||||
|     bool Read(DDV::Socket & sock); |   class Parser{ | ||||||
|     bool Read(FILE * F); |     public: | ||||||
|     std::string GetHeader(std::string i); |       Parser(); | ||||||
|     std::string GetVar(std::string i); |       bool Read(DDV::Socket & sock); | ||||||
|     void SetHeader(std::string i, std::string v); |       bool Read(FILE * F); | ||||||
|     void SetHeader(std::string i, int v); |       std::string GetHeader(std::string i); | ||||||
|     void SetVar(std::string i, std::string v); |       std::string GetVar(std::string i); | ||||||
|     void SetBody(std::string s); |       void SetHeader(std::string i, std::string v); | ||||||
|     void SetBody(char * buffer, int len); |       void SetHeader(std::string i, int v); | ||||||
|     std::string BuildRequest(); |       void SetVar(std::string i, std::string v); | ||||||
|     std::string BuildResponse(std::string code, std::string message); |       void SetBody(std::string s); | ||||||
|     void SendResponse(DDV::Socket & conn, std::string code, std::string message); |       void SetBody(char * buffer, int len); | ||||||
|     void SendBodyPart(DDV::Socket & conn, char * buffer, int len); |       std::string BuildRequest(); | ||||||
|     void SendBodyPart(DDV::Socket & conn, std::string bodypart); |       std::string BuildResponse(std::string code, std::string message); | ||||||
|     void Clean(); |       void SendResponse(DDV::Socket & conn, std::string code, std::string message); | ||||||
|     bool CleanForNext(); |       void SendBodyPart(DDV::Socket & conn, char * buffer, int len); | ||||||
|     std::string body; |       void SendBodyPart(DDV::Socket & conn, std::string bodypart); | ||||||
|     std::string method; |       void Clean(); | ||||||
|     std::string url; |       bool CleanForNext(); | ||||||
|     std::string protocol; |       std::string body; | ||||||
|     unsigned int length; |       std::string method; | ||||||
|   private: |       std::string url; | ||||||
|     bool seenHeaders; |       std::string protocol; | ||||||
|     bool seenReq; |       unsigned int length; | ||||||
|     bool parse(); |     private: | ||||||
|     std::string HTTPbuffer; |       bool seenHeaders; | ||||||
|     std::map<std::string, std::string> headers; |       bool seenReq; | ||||||
|     std::map<std::string, std::string> vars; |       bool parse(); | ||||||
|     void Trim(std::string & s); |       std::string HTTPbuffer; | ||||||
| };//HTTPReader
 |       std::map<std::string, std::string> headers; | ||||||
| 
 |       std::map<std::string, std::string> vars; | ||||||
|  |       void Trim(std::string & s); | ||||||
|  |   };//HTTP::Parser class
 | ||||||
|  | };//HTTP namespace
 | ||||||
|  |  | ||||||
							
								
								
									
										442
									
								
								util/rtmpchunks.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										442
									
								
								util/rtmpchunks.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,442 @@ | ||||||
|  | /// \file rtmpchunks.cpp
 | ||||||
|  | /// Holds all code for the RTMPStream namespace.
 | ||||||
|  | 
 | ||||||
|  | #include "rtmpchunks.h" | ||||||
|  | #include "crypto.h" | ||||||
|  | 
 | ||||||
|  | char versionstring[] = "WWW.DDVTECH.COM "; ///< String that is repeated in the RTMP handshake
 | ||||||
|  | std::string RTMPStream::handshake_in; ///< Input for the handshake.
 | ||||||
|  | std::string RTMPStream::handshake_out;///< Output for the handshake.
 | ||||||
|  | 
 | ||||||
|  | /// Gets the current system time in milliseconds.
 | ||||||
|  | unsigned int RTMPStream::getNowMS(){ | ||||||
|  |   timeval t; | ||||||
|  |   gettimeofday(&t, 0); | ||||||
|  |   return t.tv_sec + t.tv_usec/1000; | ||||||
|  | }//RTMPStream::getNowMS
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | unsigned int RTMPStream::chunk_rec_max = 128; | ||||||
|  | unsigned int RTMPStream::chunk_snd_max = 128; | ||||||
|  | unsigned int RTMPStream::rec_window_size = 0xFA00; | ||||||
|  | unsigned int RTMPStream::snd_window_size = 1024*500; | ||||||
|  | unsigned int RTMPStream::rec_window_at = 0; | ||||||
|  | unsigned int RTMPStream::snd_window_at = 0; | ||||||
|  | unsigned int RTMPStream::rec_cnt = 0; | ||||||
|  | unsigned int RTMPStream::snd_cnt = 0; | ||||||
|  | 
 | ||||||
|  | timeval RTMPStream::lastrec; | ||||||
|  | unsigned int RTMPStream::firsttime; | ||||||
|  | 
 | ||||||
|  | /// Holds the last sent chunk for every msg_id.
 | ||||||
|  | std::map<unsigned int, RTMPStream::Chunk> RTMPStream::Chunk::lastsend; | ||||||
|  | /// Holds the last received chunk for every msg_id.
 | ||||||
|  | std::map<unsigned int, RTMPStream::Chunk> RTMPStream::Chunk::lastrecv; | ||||||
|  | 
 | ||||||
|  | /// Packs up the chunk for sending over the network.
 | ||||||
|  | /// \warning Do not call if you are not actually sending the resulting data!
 | ||||||
|  | /// \returns A std::string ready to be sent.
 | ||||||
|  | std::string RTMPStream::Chunk::Pack(){ | ||||||
|  |   std::string output = ""; | ||||||
|  |   RTMPStream::Chunk prev = lastsend[cs_id]; | ||||||
|  |   unsigned int tmpi; | ||||||
|  |   unsigned char chtype = 0x00; | ||||||
|  |   timestamp -= firsttime; | ||||||
|  |   if (prev.cs_id == cs_id){ | ||||||
|  |     if (msg_stream_id == prev.msg_stream_id){ | ||||||
|  |       chtype = 0x40;//do not send msg_stream_id
 | ||||||
|  |       if (len == prev.len){ | ||||||
|  |         if (msg_type_id == prev.msg_type_id){ | ||||||
|  |           chtype = 0x80;//do not send len and msg_type_id
 | ||||||
|  |           if (timestamp == prev.timestamp){ | ||||||
|  |             chtype = 0xC0;//do not send timestamp
 | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (cs_id <= 63){ | ||||||
|  |     output += (unsigned char)(chtype | cs_id); | ||||||
|  |   }else{ | ||||||
|  |     if (cs_id <= 255+64){ | ||||||
|  |       output += (unsigned char)(chtype | 0); | ||||||
|  |       output += (unsigned char)(cs_id - 64); | ||||||
|  |     }else{ | ||||||
|  |       output += (unsigned char)(chtype | 1); | ||||||
|  |       output += (unsigned char)((cs_id - 64) % 256); | ||||||
|  |       output += (unsigned char)((cs_id - 64) / 256); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   unsigned int ntime = 0; | ||||||
|  |   if (chtype != 0xC0){ | ||||||
|  |     //timestamp or timestamp diff
 | ||||||
|  |     if (chtype == 0x00){ | ||||||
|  |       tmpi = timestamp; | ||||||
|  |     }else{ | ||||||
|  |       tmpi = timestamp - prev.timestamp; | ||||||
|  |     } | ||||||
|  |     if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;} | ||||||
|  |     output += (unsigned char)(tmpi / (256*256)); | ||||||
|  |     output += (unsigned char)(tmpi / 256); | ||||||
|  |     output += (unsigned char)(tmpi % 256); | ||||||
|  |     if (chtype != 0x80){ | ||||||
|  |       //len
 | ||||||
|  |       tmpi = len; | ||||||
|  |       output += (unsigned char)(tmpi / (256*256)); | ||||||
|  |       output += (unsigned char)(tmpi / 256); | ||||||
|  |       output += (unsigned char)(tmpi % 256); | ||||||
|  |       //msg type id
 | ||||||
|  |       output += (unsigned char)msg_type_id; | ||||||
|  |       if (chtype != 0x40){ | ||||||
|  |         //msg stream id
 | ||||||
|  |         output += (unsigned char)(msg_stream_id % 256); | ||||||
|  |         output += (unsigned char)(msg_stream_id / 256); | ||||||
|  |         output += (unsigned char)(msg_stream_id / (256*256)); | ||||||
|  |         output += (unsigned char)(msg_stream_id / (256*256*256)); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   //support for 0x00ffffff timestamps
 | ||||||
|  |   if (ntime){ | ||||||
|  |     output += (unsigned char)(ntime % 256); | ||||||
|  |     output += (unsigned char)(ntime / 256); | ||||||
|  |     output += (unsigned char)(ntime / (256*256)); | ||||||
|  |     output += (unsigned char)(ntime / (256*256*256)); | ||||||
|  |   } | ||||||
|  |   len_left = 0; | ||||||
|  |   while (len_left < len){ | ||||||
|  |     tmpi = len - len_left; | ||||||
|  |     if (tmpi > RTMPStream::chunk_snd_max){tmpi = RTMPStream::chunk_snd_max;} | ||||||
|  |     output.append(data, len_left, tmpi); | ||||||
|  |     len_left += tmpi; | ||||||
|  |     if (len_left < len){ | ||||||
|  |       if (cs_id <= 63){ | ||||||
|  |         output += (unsigned char)(0xC0 + cs_id); | ||||||
|  |       }else{ | ||||||
|  |         if (cs_id <= 255+64){ | ||||||
|  |           output += (unsigned char)(0xC0); | ||||||
|  |           output += (unsigned char)(cs_id - 64); | ||||||
|  |         }else{ | ||||||
|  |           output += (unsigned char)(0xC1); | ||||||
|  |           output += (unsigned char)((cs_id - 64) % 256); | ||||||
|  |           output += (unsigned char)((cs_id - 64) / 256); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   lastsend[cs_id] = *this; | ||||||
|  |   RTMPStream::snd_cnt += output.size(); | ||||||
|  |   return output; | ||||||
|  | }//SendChunk
 | ||||||
|  | 
 | ||||||
|  | /// Default contructor, creates an empty chunk with all values initialized to zero.
 | ||||||
|  | RTMPStream::Chunk::Chunk(){ | ||||||
|  |   cs_id = 0; | ||||||
|  |   timestamp = 0; | ||||||
|  |   len = 0; | ||||||
|  |   real_len = 0; | ||||||
|  |   len_left = 0; | ||||||
|  |   msg_type_id = 0; | ||||||
|  |   msg_stream_id = 0; | ||||||
|  |   data = ""; | ||||||
|  | }//constructor
 | ||||||
|  | 
 | ||||||
|  | /// Packs up a chunk with the given arguments as properties.
 | ||||||
|  | std::string RTMPStream::SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data){ | ||||||
|  |   RTMPStream::Chunk ch; | ||||||
|  |   ch.cs_id = cs_id; | ||||||
|  |   ch.timestamp = RTMPStream::getNowMS(); | ||||||
|  |   ch.len = data.size(); | ||||||
|  |   ch.real_len = data.size(); | ||||||
|  |   ch.len_left = 0; | ||||||
|  |   ch.msg_type_id = msg_type_id; | ||||||
|  |   ch.msg_stream_id = msg_stream_id; | ||||||
|  |   ch.data = data; | ||||||
|  |   return ch.Pack(); | ||||||
|  | }//constructor
 | ||||||
|  | 
 | ||||||
|  | /// Packs up a chunk with media contents.
 | ||||||
|  | /// \param msg_type_id Type number of the media, as per FLV standard.
 | ||||||
|  | /// \param data Contents of the media data.
 | ||||||
|  | /// \param len Length of the media data, in bytes.
 | ||||||
|  | /// \param ts Timestamp of the media data, relative to current system time.
 | ||||||
|  | std::string RTMPStream::SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts){ | ||||||
|  |   RTMPStream::Chunk ch; | ||||||
|  |   ch.cs_id = msg_type_id; | ||||||
|  |   ch.timestamp = ts; | ||||||
|  |   ch.len = len; | ||||||
|  |   ch.real_len = len; | ||||||
|  |   ch.len_left = 0; | ||||||
|  |   ch.msg_type_id = msg_type_id; | ||||||
|  |   ch.msg_stream_id = 1; | ||||||
|  |   ch.data.append((char*)data, (size_t)len); | ||||||
|  |   return ch.Pack(); | ||||||
|  | }//SendMedia
 | ||||||
|  | 
 | ||||||
|  | /// Packs up a chunk for a control message with 1 argument.
 | ||||||
|  | std::string RTMPStream::SendCTL(unsigned char type, unsigned int data){ | ||||||
|  |   RTMPStream::Chunk ch; | ||||||
|  |   ch.cs_id = 2; | ||||||
|  |   ch.timestamp = RTMPStream::getNowMS(); | ||||||
|  |   ch.len = 4; | ||||||
|  |   ch.real_len = 4; | ||||||
|  |   ch.len_left = 0; | ||||||
|  |   ch.msg_type_id = type; | ||||||
|  |   ch.msg_stream_id = 0; | ||||||
|  |   ch.data.resize(4); | ||||||
|  |   *(int*)((char*)ch.data.c_str()) = htonl(data); | ||||||
|  |   return ch.Pack(); | ||||||
|  | }//SendCTL
 | ||||||
|  | 
 | ||||||
|  | /// Packs up a chunk for a control message with 2 arguments.
 | ||||||
|  | std::string RTMPStream::SendCTL(unsigned char type, unsigned int data, unsigned char data2){ | ||||||
|  |   RTMPStream::Chunk ch; | ||||||
|  |   ch.cs_id = 2; | ||||||
|  |   ch.timestamp = RTMPStream::getNowMS(); | ||||||
|  |   ch.len = 5; | ||||||
|  |   ch.real_len = 5; | ||||||
|  |   ch.len_left = 0; | ||||||
|  |   ch.msg_type_id = type; | ||||||
|  |   ch.msg_stream_id = 0; | ||||||
|  |   ch.data.resize(5); | ||||||
|  |   *(int*)((char*)ch.data.c_str()) = htonl(data); | ||||||
|  |   ch.data[4] = data2; | ||||||
|  |   return ch.Pack(); | ||||||
|  | }//SendCTL
 | ||||||
|  | 
 | ||||||
|  | /// Packs up a chunk for a user control message with 1 argument.
 | ||||||
|  | std::string RTMPStream::SendUSR(unsigned char type, unsigned int data){ | ||||||
|  |   RTMPStream::Chunk ch; | ||||||
|  |   ch.cs_id = 2; | ||||||
|  |   ch.timestamp = RTMPStream::getNowMS(); | ||||||
|  |   ch.len = 6; | ||||||
|  |   ch.real_len = 6; | ||||||
|  |   ch.len_left = 0; | ||||||
|  |   ch.msg_type_id = 4; | ||||||
|  |   ch.msg_stream_id = 0; | ||||||
|  |   ch.data.resize(6); | ||||||
|  |   *(int*)((char*)ch.data.c_str()+2) = htonl(data); | ||||||
|  |   ch.data[0] = 0; | ||||||
|  |   ch.data[1] = type; | ||||||
|  |   return ch.Pack(); | ||||||
|  | }//SendUSR
 | ||||||
|  | 
 | ||||||
|  | /// Packs up a chunk for a user control message with 2 arguments.
 | ||||||
|  | std::string RTMPStream::SendUSR(unsigned char type, unsigned int data, unsigned int data2){ | ||||||
|  |   RTMPStream::Chunk ch; | ||||||
|  |   ch.cs_id = 2; | ||||||
|  |   ch.timestamp = RTMPStream::getNowMS(); | ||||||
|  |   ch.len = 10; | ||||||
|  |   ch.real_len = 10; | ||||||
|  |   ch.len_left = 0; | ||||||
|  |   ch.msg_type_id = 4; | ||||||
|  |   ch.msg_stream_id = 0; | ||||||
|  |   ch.data.resize(10); | ||||||
|  |   *(int*)((char*)ch.data.c_str()+2) = htonl(data); | ||||||
|  |   *(int*)((char*)ch.data.c_str()+6) = htonl(data2); | ||||||
|  |   ch.data[0] = 0; | ||||||
|  |   ch.data[1] = type; | ||||||
|  |   return ch.Pack(); | ||||||
|  | }//SendUSR
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /// Parses the argument string into the current chunk.
 | ||||||
|  | /// Tries to read a whole chunk, if successful it will remove
 | ||||||
|  | /// the corresponding data from the input string.
 | ||||||
|  | /// \param indata The input string to parse and update.
 | ||||||
|  | /// \warning This function will destroy the current data in this chunk!
 | ||||||
|  | /// \returns True if a whole chunk could be read, false otherwise.
 | ||||||
|  | bool RTMPStream::Chunk::Parse(std::string & indata){ | ||||||
|  |   gettimeofday(&RTMPStream::lastrec, 0); | ||||||
|  |   unsigned int i = 0; | ||||||
|  |   if (indata.size() < 3) return false;//need at least 3 bytes to continue
 | ||||||
|  | 
 | ||||||
|  |   unsigned char chunktype = indata[i++]; | ||||||
|  |   //read the chunkstream ID properly
 | ||||||
|  |   switch (chunktype & 0x3F){ | ||||||
|  |     case 0: | ||||||
|  |       cs_id = indata[i++] + 64; | ||||||
|  |       break; | ||||||
|  |     case 1: | ||||||
|  |       cs_id = indata[i++] + 64; | ||||||
|  |       cs_id += indata[i++] * 256; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       cs_id = chunktype & 0x3F; | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   RTMPStream::Chunk prev = lastrecv[cs_id]; | ||||||
|  | 
 | ||||||
|  |   //process the rest of the header, for each chunk type
 | ||||||
|  |   switch (chunktype & 0xC0){ | ||||||
|  |     case 0x00: | ||||||
|  |       if (indata.size() < i+11) return false; //can't read whole header
 | ||||||
|  |       timestamp = indata[i++]*256*256; | ||||||
|  |       timestamp += indata[i++]*256; | ||||||
|  |       timestamp += indata[i++]; | ||||||
|  |       len = indata[i++]*256*256; | ||||||
|  |       len += indata[i++]*256; | ||||||
|  |       len += indata[i++]; | ||||||
|  |       len_left = 0; | ||||||
|  |       msg_type_id = indata[i++]; | ||||||
|  |       msg_stream_id = indata[i++]; | ||||||
|  |       msg_stream_id += indata[i++]*256; | ||||||
|  |       msg_stream_id += indata[i++]*256*256; | ||||||
|  |       msg_stream_id += indata[i++]*256*256*256; | ||||||
|  |       break; | ||||||
|  |     case 0x40: | ||||||
|  |       if (indata.size() < i+7) return false; //can't read whole header
 | ||||||
|  |       timestamp = indata[i++]*256*256; | ||||||
|  |       timestamp += indata[i++]*256; | ||||||
|  |       timestamp += indata[i++]; | ||||||
|  |       timestamp += prev.timestamp; | ||||||
|  |       len = indata[i++]*256*256; | ||||||
|  |       len += indata[i++]*256; | ||||||
|  |       len += indata[i++]; | ||||||
|  |       len_left = 0; | ||||||
|  |       msg_type_id = indata[i++]; | ||||||
|  |       msg_stream_id = prev.msg_stream_id; | ||||||
|  |       break; | ||||||
|  |     case 0x80: | ||||||
|  |       if (indata.size() < i+3) return false; //can't read whole header
 | ||||||
|  |       timestamp = indata[i++]*256*256; | ||||||
|  |       timestamp += indata[i++]*256; | ||||||
|  |       timestamp += indata[i++]; | ||||||
|  |       timestamp += prev.timestamp; | ||||||
|  |       len = prev.len; | ||||||
|  |       len_left = prev.len_left; | ||||||
|  |       msg_type_id = prev.msg_type_id; | ||||||
|  |       msg_stream_id = prev.msg_stream_id; | ||||||
|  |       break; | ||||||
|  |     case 0xC0: | ||||||
|  |       timestamp = prev.timestamp; | ||||||
|  |       len = prev.len; | ||||||
|  |       len_left = prev.len_left; | ||||||
|  |       msg_type_id = prev.msg_type_id; | ||||||
|  |       msg_stream_id = prev.msg_stream_id; | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |   //calculate chunk length, real length, and length left till complete
 | ||||||
|  |   if (len_left > 0){ | ||||||
|  |     real_len = len_left; | ||||||
|  |     len_left -= real_len; | ||||||
|  |   }else{ | ||||||
|  |     real_len = len; | ||||||
|  |   } | ||||||
|  |   if (real_len > RTMPStream::chunk_rec_max){ | ||||||
|  |     len_left += real_len - RTMPStream::chunk_rec_max; | ||||||
|  |     real_len = RTMPStream::chunk_rec_max; | ||||||
|  |   } | ||||||
|  |   //read extended timestamp, if neccesary
 | ||||||
|  |   if (timestamp == 0x00ffffff){ | ||||||
|  |     if (indata.size() < i+4) return false; //can't read whole header
 | ||||||
|  |     timestamp = indata[i++]*256*256*256; | ||||||
|  |     timestamp += indata[i++]*256*256; | ||||||
|  |     timestamp += indata[i++]*256; | ||||||
|  |     timestamp += indata[i++]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   //read data if length > 0, and allocate it
 | ||||||
|  |   if (real_len > 0){ | ||||||
|  |     if (prev.len_left > 0){ | ||||||
|  |       data = prev.data; | ||||||
|  |     }else{ | ||||||
|  |       data = ""; | ||||||
|  |     } | ||||||
|  |     if (indata.size() < i+real_len) return false;//can't read all data (yet)
 | ||||||
|  |     data.append(indata, i, real_len); | ||||||
|  |     indata = indata.substr(i+real_len); | ||||||
|  |     lastrecv[cs_id] = *this; | ||||||
|  |     RTMPStream::rec_cnt += i+real_len; | ||||||
|  |     if (len_left == 0){ | ||||||
|  |       return true; | ||||||
|  |     }else{ | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |   }else{ | ||||||
|  |     data = ""; | ||||||
|  |     indata = indata.substr(i+real_len); | ||||||
|  |     lastrecv[cs_id] = *this; | ||||||
|  |     RTMPStream::rec_cnt += i+real_len; | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | }//Parse
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /// Does the handshake. Expects handshake_in to be filled, and fills handshake_out.
 | ||||||
|  | /// After calling this function, don't forget to read and ignore 1536 extra bytes,
 | ||||||
|  | /// this is the handshake response and not interesting for us because we don't do client
 | ||||||
|  | /// verification.
 | ||||||
|  | bool RTMPStream::doHandshake(){ | ||||||
|  |   char Version; | ||||||
|  |   //Read C0
 | ||||||
|  |   Version = RTMPStream::handshake_in[0]; | ||||||
|  |   uint8_t * Client = (uint8_t *)RTMPStream::handshake_in.c_str() + 1; | ||||||
|  |   RTMPStream::handshake_out.resize(3073); | ||||||
|  |   uint8_t * Server = (uint8_t *)RTMPStream::handshake_out.c_str() + 1; | ||||||
|  |   RTMPStream::rec_cnt += 1537; | ||||||
|  | 
 | ||||||
|  |   //Build S1 Packet
 | ||||||
|  |   *((uint32_t*)Server) = 0;//time zero
 | ||||||
|  |   *(((uint32_t*)(Server+4))) = htonl(0x01020304);//version 1 2 3 4
 | ||||||
|  |   for (int i = 8; i < 3072; ++i){Server[i] = versionstring[i%16];}//"random" data
 | ||||||
|  | 
 | ||||||
|  |   bool encrypted = (Version == 6); | ||||||
|  |   #if DEBUG >= 4 | ||||||
|  |   fprintf(stderr, "Handshake version is %hhi\n", Version); | ||||||
|  |   #endif | ||||||
|  |   uint8_t _validationScheme = 5; | ||||||
|  |   if (ValidateClientScheme(Client, 0)) _validationScheme = 0; | ||||||
|  |   if (ValidateClientScheme(Client, 1)) _validationScheme = 1; | ||||||
|  |      | ||||||
|  |   #if DEBUG >= 4 | ||||||
|  |   fprintf(stderr, "Handshake type is %hhi, encryption is %s\n", _validationScheme, encrypted?"on":"off"); | ||||||
|  |   #endif | ||||||
|  |      | ||||||
|  |   //FIRST 1536 bytes from server response
 | ||||||
|  |   //compute DH key position
 | ||||||
|  |   uint32_t serverDHOffset = GetDHOffset(Server, _validationScheme); | ||||||
|  |   uint32_t clientDHOffset = GetDHOffset(Client, _validationScheme); | ||||||
|  |      | ||||||
|  |   //generate DH key
 | ||||||
|  |   DHWrapper dhWrapper(1024); | ||||||
|  |   if (!dhWrapper.Initialize()) return false; | ||||||
|  |   if (!dhWrapper.CreateSharedKey(Client + clientDHOffset, 128)) return false; | ||||||
|  |   if (!dhWrapper.CopyPublicKey(Server + serverDHOffset, 128)) return false; | ||||||
|  |      | ||||||
|  |   if (encrypted) { | ||||||
|  |     uint8_t secretKey[128]; | ||||||
|  |     if (!dhWrapper.CopySharedKey(secretKey, sizeof (secretKey))) return false; | ||||||
|  |     RC4_KEY _pKeyIn; | ||||||
|  |     RC4_KEY _pKeyOut; | ||||||
|  |     InitRC4Encryption(secretKey, (uint8_t*) & Client[clientDHOffset], (uint8_t*) & Server[serverDHOffset], &_pKeyIn, &_pKeyOut); | ||||||
|  |     uint8_t data[1536]; | ||||||
|  |     RC4(&_pKeyIn, 1536, data, data); | ||||||
|  |     RC4(&_pKeyOut, 1536, data, data); | ||||||
|  |   } | ||||||
|  |   //generate the digest
 | ||||||
|  |   uint32_t serverDigestOffset = GetDigestOffset(Server, _validationScheme); | ||||||
|  |   uint8_t *pTempBuffer = new uint8_t[1536 - 32]; | ||||||
|  |   memcpy(pTempBuffer, Server, serverDigestOffset); | ||||||
|  |   memcpy(pTempBuffer + serverDigestOffset, Server + serverDigestOffset + 32, 1536 - serverDigestOffset - 32); | ||||||
|  |   uint8_t *pTempHash = new uint8_t[512]; | ||||||
|  |   HMACsha256(pTempBuffer, 1536 - 32, genuineFMSKey, 36, pTempHash); | ||||||
|  |   memcpy(Server + serverDigestOffset, pTempHash, 32); | ||||||
|  |   delete[] pTempBuffer; | ||||||
|  |   delete[] pTempHash; | ||||||
|  |      | ||||||
|  |   //SECOND 1536 bytes from server response
 | ||||||
|  |   uint32_t keyChallengeIndex = GetDigestOffset(Client, _validationScheme); | ||||||
|  |   pTempHash = new uint8_t[512]; | ||||||
|  |   HMACsha256(Client + keyChallengeIndex, 32, genuineFMSKey, 68, pTempHash); | ||||||
|  |   uint8_t *pLastHash = new uint8_t[512]; | ||||||
|  |   HMACsha256(Server + 1536, 1536 - 32, pTempHash, 32, pLastHash); | ||||||
|  |   memcpy(Server + 1536 * 2 - 32, pLastHash, 32); | ||||||
|  |   delete[] pTempHash; | ||||||
|  |   delete[] pLastHash; | ||||||
|  |   //DONE BUILDING THE RESPONSE ***//
 | ||||||
|  |   Server[-1] = Version; | ||||||
|  |   RTMPStream::snd_cnt += 3073; | ||||||
|  |   return true; | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								util/rtmpchunks.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								util/rtmpchunks.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | /// \file rtmpchunks.h
 | ||||||
|  | /// Holds all headers for the RTMPStream namespace.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | #include <map> | ||||||
|  | #include <string.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <sys/time.h> | ||||||
|  | #include <string> | ||||||
|  | #include <arpa/inet.h> | ||||||
|  | #define DEBUG 4 | ||||||
|  | 
 | ||||||
|  | /// Contains all functions and classes needed for RTMP connections.
 | ||||||
|  | namespace RTMPStream{ | ||||||
|  | 
 | ||||||
|  |   /// Gets the current system time in milliseconds.
 | ||||||
|  |   unsigned int getNowMS(); | ||||||
|  | 
 | ||||||
|  |   extern unsigned int chunk_rec_max; ///< Maximum size for a received chunk.
 | ||||||
|  |   extern unsigned int chunk_snd_max; ///< Maximum size for a sent chunk.
 | ||||||
|  |   extern unsigned int rec_window_size; ///< Window size for receiving.
 | ||||||
|  |   extern unsigned int snd_window_size; ///< Window size for sending.
 | ||||||
|  |   extern unsigned int rec_window_at; ///< Current position of the receiving window.
 | ||||||
|  |   extern unsigned int snd_window_at; ///< Current position of the sending window.
 | ||||||
|  |   extern unsigned int rec_cnt; ///< Counter for total data received, in bytes.
 | ||||||
|  |   extern unsigned int snd_cnt; ///< Counter for total data sent, in bytes.
 | ||||||
|  | 
 | ||||||
|  |   extern timeval lastrec; ///< Timestamp of last time data was received.
 | ||||||
|  |   extern unsigned int firsttime; ///< Timestamp of first time a chunk was sent.
 | ||||||
|  | 
 | ||||||
|  |   /// Holds a single RTMP chunk, either send or receive direction.
 | ||||||
|  |   class Chunk{ | ||||||
|  |     public: | ||||||
|  |       unsigned int cs_id; ///< ContentStream ID
 | ||||||
|  |       unsigned int timestamp; ///< Timestamp of this chunk.
 | ||||||
|  |       unsigned int len; ///< Length of the complete chunk.
 | ||||||
|  |       unsigned int real_len; ///< Length of this particular part of it.
 | ||||||
|  |       unsigned int len_left; ///< Length not yet received, out of complete chunk.
 | ||||||
|  |       unsigned char msg_type_id; ///< Message Type ID
 | ||||||
|  |       unsigned int msg_stream_id; ///< Message Stream ID
 | ||||||
|  |       std::string data; ///< Payload of chunk.
 | ||||||
|  | 
 | ||||||
|  |       Chunk(); | ||||||
|  |       bool Parse(std::string & data); | ||||||
|  |       std::string Pack(); | ||||||
|  | 
 | ||||||
|  |     private: | ||||||
|  |       static std::map<unsigned int, Chunk> lastsend; | ||||||
|  |       static std::map<unsigned int, Chunk> lastrecv; | ||||||
|  |   };//RTMPStream::Chunk
 | ||||||
|  | 
 | ||||||
|  |   std::string SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data); | ||||||
|  |   std::string SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts); | ||||||
|  |   std::string SendCTL(unsigned char type, unsigned int data); | ||||||
|  |   std::string SendCTL(unsigned char type, unsigned int data, unsigned char data2); | ||||||
|  |   std::string SendUSR(unsigned char type, unsigned int data); | ||||||
|  |   std::string SendUSR(unsigned char type, unsigned int data, unsigned int data2); | ||||||
|  | 
 | ||||||
|  |   /// This value should be set to the first 1537 bytes received.
 | ||||||
|  |   extern std::string handshake_in; | ||||||
|  |   /// This value is the handshake response that is to be sent out.
 | ||||||
|  |   extern std::string handshake_out; | ||||||
|  |   /// Does the handshake. Expects handshake_in to be filled, and fills handshake_out.
 | ||||||
|  |   bool doHandshake(); | ||||||
|  | };//RTMPStream namespace
 | ||||||
|  | @ -1,12 +1,34 @@ | ||||||
| #include <signal.h> | /// \file server_setup.cpp
 | ||||||
|  | /// Contains generic functions for setting up a DDVTECH Connector.
 | ||||||
|  | 
 | ||||||
|  | #ifndef MAINHANDLER | ||||||
|  |   /// Handler that is called for accepted incoming connections.
 | ||||||
|  |   #define MAINHANDLER NoHandler | ||||||
|  |   #error "No handler was set!" | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #ifndef DEFAULT_PORT | ||||||
|  |   /// Default port for this server.
 | ||||||
|  |   #define DEFAULT_PORT 0 | ||||||
|  |   #error "No default port was set!" | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #ifndef CONFIGSECT | ||||||
|  |   /// Configuration file section for this server.
 | ||||||
|  |   #define CONFIGSECT None | ||||||
|  |   #error "No configuration file section was set!" | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #include "ddv_socket.h" //DDVTech Socket wrapper
 | #include "ddv_socket.h" //DDVTech Socket wrapper
 | ||||||
| #include "flv_tag.h" //FLV parsing with DDVTech Socket wrapper
 | #include <signal.h> | ||||||
| #include <sys/types.h> | #include <sys/types.h> | ||||||
| #include <pwd.h> | #include <pwd.h> | ||||||
| #include <fstream> | #include <fstream> | ||||||
| #define defstr(x) #x //converts a define name to string
 | #define defstr(x) #x ///< converts a define name to string
 | ||||||
| #define defstrh(x) "[" defstr(x) "]" //converts define name to [string]
 | #define defstrh(x) "[" defstr(x) "]" ///< converts define name to [string]
 | ||||||
| DDV::ServerSocket server_socket(-1); | DDV::ServerSocket server_socket(-1); ///< Placeholder for the server socket
 | ||||||
| 
 | 
 | ||||||
| /// Basic signal handler. Disconnects the server_socket if it receives
 | /// Basic signal handler. Disconnects the server_socket if it receives
 | ||||||
| /// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE.
 | /// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE.
 | ||||||
|  | @ -26,10 +48,10 @@ void signal_handler (int signum){ | ||||||
| /// Generic main entry point and loop for DDV Connectors.
 | /// Generic main entry point and loop for DDV Connectors.
 | ||||||
| /// This sets up the proper termination handler, checks commandline options,
 | /// This sets up the proper termination handler, checks commandline options,
 | ||||||
| /// parses config files and opens a listening socket on the requested port.
 | /// parses config files and opens a listening socket on the requested port.
 | ||||||
| /// Any incoming connections will be accepted and start up the function MAINHANDLER,
 | /// Any incoming connections will be accepted and start up the function #MAINHANDLER,
 | ||||||
| /// which should be #defined before including server_setup.cpp.
 | /// which should be defined before including server_setup.cpp.
 | ||||||
| /// The default port is set by #define DEFAULT_PORT.
 | /// The default port is set by define #DEFAULT_PORT.
 | ||||||
| /// The configuration file section is set by #define CONFIGSECT.
 | /// The configuration file section is set by define #CONFIGSECT.
 | ||||||
| int main(int argc, char ** argv){ | int main(int argc, char ** argv){ | ||||||
|   DDV::Socket S;//placeholder for incoming connections
 |   DDV::Socket S;//placeholder for incoming connections
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Thulinma
						Thulinma