From cc78697ba2d4f0f31e45c6f020a495603c416ab0 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Wed, 6 Apr 2011 03:53:30 +0200 Subject: [PATCH 01/24] make docs now works (needs doxygen installed), added documentation to a LOT of stuff, refactored most utility functions, complete rewrites of DDV sockets and the FLV parsers, updated HTTP_Box_Parser and Connector_HTTP to be able to cope with these new changes - Connector_RTMP, Buffer and Connector_RAW are currently broken. Need to be fixed ASAP. --- .gitignore | 1 + Connector_HTTP/Makefile | 2 +- Connector_HTTP/main.cpp | 635 ++++++++++++++++++++------------------- Doxyfile | 296 ++++++++++++++++++ HTTP_Box_Parser/Makefile | 2 +- HTTP_Box_Parser/main.cpp | 12 +- Makefile | 3 + util/MP4/box.cpp | 1 + util/ddv_socket.cpp | 402 +++++++++++++++++-------- util/ddv_socket.h | 53 ++++ util/flv.cpp | 88 ------ util/flv_data.cpp | 87 ------ util/flv_pack.cpp | 123 -------- util/flv_sock.cpp | 106 ------- util/flv_tag.cpp | 391 ++++++++++++++++++++++++ util/flv_tag.h | 40 +++ util/http_parser.cpp | 66 +--- util/http_parser.h | 41 +++ util/server_setup.cpp | 39 +-- 19 files changed, 1470 insertions(+), 918 deletions(-) create mode 100644 Doxyfile create mode 100644 util/ddv_socket.h delete mode 100644 util/flv.cpp delete mode 100644 util/flv_data.cpp delete mode 100644 util/flv_pack.cpp delete mode 100644 util/flv_sock.cpp create mode 100644 util/flv_tag.cpp create mode 100644 util/flv_tag.h create mode 100644 util/http_parser.h diff --git a/.gitignore b/.gitignore index 4531cec1..755b1999 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ Connector_RTMP/Connector_RTMP Connector_RTSP/Connector_RTSP *~ bin/* +docs gearbox/plugins/001_putserversup.sh AMF_Tester/AMFtest AMF_Creator/AMFtest diff --git a/Connector_HTTP/Makefile b/Connector_HTTP/Makefile index 92cc8357..22ef3c3f 100644 --- a/Connector_HTTP/Makefile +++ b/Connector_HTTP/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp +SRC = main.cpp ../util/ddv_socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp OBJ = $(SRC:.cpp=.o) OUT = Connector_HTTP INCLUDES = diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index 032786d8..ea43a9ba 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -1,8 +1,9 @@ -//debugging level 0 = nothing -//debugging level 1 = critical errors -//debugging level 2 = errors -//debugging level 3 = status information -//debugging level 4 = extremely verbose status information +/// Sets the global debugging level. +/// debugging level 0 = nothing +/// debugging level 1 = critical errors +/// debugging level 2 = errors +/// debugging level 3 = status information +/// debugging level 4 = extremely verbose status information #define DEBUG 4 #include @@ -16,325 +17,341 @@ #include #include #include - -enum {HANDLER_NONE, HANDLER_PROGRESSIVE, HANDLER_FLASH, HANDLER_APPLE, HANDLER_MICRO}; - -#define DEFAULT_PORT 8080 -#include "../util/server_setup.cpp" -#include "../util/http_parser.cpp" +#include "../util/ddv_socket.h" +#include "../util/http_parser.h" +#include "../util/flv_tag.h" #include "../util/MP4/interface.cpp" #include "amf.cpp" -static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -std::string base64_encode(std::string const input) { - std::string ret; - unsigned int in_len = input.size(); - char quad[4], triple[3]; - unsigned int i, x, n = 3; - for (x = 0; x < in_len; x = x + 3){ - if ((in_len - x) / 3 == 0){n = (in_len - x) % 3;} - for (i=0; i < 3; i++){triple[i] = '0';} - for (i=0; i < n; i++){triple[i] = input[x + i];} - quad[0] = base64_chars[(triple[0] & 0xFC) >> 2]; // FC = 11111100 - quad[1] = base64_chars[((triple[0] & 0x03) << 4) | ((triple[1] & 0xF0) >> 4)]; // 03 = 11 - quad[2] = base64_chars[((triple[1] & 0x0F) << 2) | ((triple[2] & 0xC0) >> 6)]; // 0F = 1111, C0=11110 - quad[3] = base64_chars[triple[2] & 0x3F]; // 3F = 111111 - if (n < 3){quad[3] = '=';} - if (n < 2){quad[2] = '=';} - for(i=0; i < 4; i++){ret += quad[i];} - } - return ret; -}//base64_encode +/// Holds everything unique to HTTP Connector. +namespace Connector_HTTP{ -std::string GetMetaData( ) { - AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER); - amfreply.addContent(AMFType("onMetaData",(unsigned char)AMF0_STRING)); - amfreply.addContent(AMFType("",(unsigned char)AMF0_ECMA_ARRAY)); - amfreply.getContentP(1)->addContent(AMFType("trackinfo", (unsigned char)AMF0_STRICT_ARRAY)); - amfreply.getContentP(1)->getContentP(0)->addContent(AMFType("arrVal")); -// amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("timescale",(double)1000)); -// amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("length",(double)59641700)); - amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("language","eng")); - amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("sampledescription", (unsigned char)AMF0_STRICT_ARRAY)); - amfreply.getContentP(1)->getContentP(0)->getContentP(0)->getContentP(1)->addContent(AMFType("arrVal")); - amfreply.getContentP(1)->getContentP(0)->getContentP(0)->getContentP(1)->getContentP(0)->addContent(AMFType("sampletype","avc1")); - amfreply.getContentP(1)->getContentP(0)->addContent(AMFType("arrVal")); -// amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("timescale",(double)44100)); -// amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("length",(double)28630000)); - amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("language","eng")); - amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("sampledescription", (unsigned char)AMF0_STRICT_ARRAY)); - amfreply.getContentP(1)->getContentP(0)->getContentP(1)->getContentP(1)->addContent(AMFType("arrVal")); - amfreply.getContentP(1)->getContentP(0)->getContentP(1)->getContentP(1)->getContentP(0)->addContent(AMFType("sampletype","mp4a")); - amfreply.getContentP(1)->addContent(AMFType("audiochannels",(double)2)); - amfreply.getContentP(1)->addContent(AMFType("audiosamplerate",(double)44100)); - amfreply.getContentP(1)->addContent(AMFType("videoframerate",(double)25)); - amfreply.getContentP(1)->addContent(AMFType("aacaot",(double)2)); - amfreply.getContentP(1)->addContent(AMFType("avclevel",(double)12)); - amfreply.getContentP(1)->addContent(AMFType("avcprofile",(double)77)); - amfreply.getContentP(1)->addContent(AMFType("audiocodecid","mp4a")); - amfreply.getContentP(1)->addContent(AMFType("videocodecid","avc1")); - amfreply.getContentP(1)->addContent(AMFType("width",(double)1280)); - amfreply.getContentP(1)->addContent(AMFType("height",(double)720)); - amfreply.getContentP(1)->addContent(AMFType("frameWidth",(double)1280)); - amfreply.getContentP(1)->addContent(AMFType("frameHeight",(double)720)); - amfreply.getContentP(1)->addContent(AMFType("displayWidth",(double)1280)); - amfreply.getContentP(1)->addContent(AMFType("displayHeight",(double)720)); -// amfreply.getContentP(1)->addContent(AMFType("moovposition",(double)35506700)); -// amfreply.getContentP(1)->addContent(AMFType("duration",(double)596.458)); - return amfreply.Pack( ); -} + /// Defines the type of handler used to process this request. + enum {HANDLER_NONE, HANDLER_PROGRESSIVE, HANDLER_FLASH, HANDLER_APPLE, HANDLER_MICRO}; -std::string BuildManifest( std::string MetaData, std::string MovieId, int CurrentMediaTime ) { - Interface * temp = new Interface; - std::string Result="\n\n"; - Result += ""; - Result += MovieId; - Result += "\nvideo/mp4\n"; - Result += "live\n"; - Result += "streaming\n"; - Result += ""; - Result += base64_encode(temp->GenerateLiveBootstrap(1)); - Result += "\n"; - Result += "\n"; - Result += ""; - Result += base64_encode(GetMetaData()); - Result += "\n"; - Result += "\n"; - Result += "\n"; - delete temp; - return Result; -} + /// Needed for base64_encode function + static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -int mainHandler(int CONN_fd){ -// GetMetaData( ); return 0; - int handler = HANDLER_PROGRESSIVE; - bool ready4data = false;//set to true when streaming starts - bool inited = false; - bool progressive_has_sent_header = false; - int ss; - std::string streamname; - std::string FlashBuf; - std::string FlashMeta; - bool Flash_ManifestSent = false; - int Flash_RequestPending = 0; - unsigned int Flash_StartTime; - std::queue Flash_FragBuffer; - FLV_Pack * tag = 0; - FLV_Pack Audio_Init; - FLV_Pack Video_Init; - bool FlashFirstVideo = false; - bool FlashFirstAudio = false; - HTTPReader HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. + /// Used to base64 encode data. Input is the plaintext as std::string, output is the encoded data as std::string. + /// \param input Plaintext data to encode. + /// \returns Base64 encoded data. + std::string base64_encode(std::string const input) { + std::string ret; + unsigned int in_len = input.size(); + char quad[4], triple[3]; + unsigned int i, x, n = 3; + for (x = 0; x < in_len; x = x + 3){ + if ((in_len - x) / 3 == 0){n = (in_len - x) % 3;} + for (i=0; i < 3; i++){triple[i] = '0';} + for (i=0; i < n; i++){triple[i] = input[x + i];} + quad[0] = base64_chars[(triple[0] & 0xFC) >> 2]; // FC = 11111100 + quad[1] = base64_chars[((triple[0] & 0x03) << 4) | ((triple[1] & 0xF0) >> 4)]; // 03 = 11 + quad[2] = base64_chars[((triple[1] & 0x0F) << 2) | ((triple[2] & 0xC0) >> 6)]; // 0F = 1111, C0=11110 + quad[3] = base64_chars[triple[2] & 0x3F]; // 3F = 111111 + if (n < 3){quad[3] = '=';} + if (n < 2){quad[2] = '=';} + for(i=0; i < 4; i++){ret += quad[i];} + } + return ret; + }//base64_encode - int retval; - int poller = epoll_create(1); - int sspoller = epoll_create(1); - struct epoll_event ev; - ev.events = EPOLLIN; - ev.data.fd = CONN_fd; - epoll_ctl(poller, EPOLL_CTL_ADD, CONN_fd, &ev); - struct epoll_event events[1]; + /// Returns AMF-format metadata for Adobe HTTP Dynamic Streaming. + std::string GetMetaData( ) { + AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER); + amfreply.addContent(AMFType("onMetaData",(unsigned char)AMF0_STRING)); + amfreply.addContent(AMFType("",(unsigned char)AMF0_ECMA_ARRAY)); + amfreply.getContentP(1)->addContent(AMFType("trackinfo", (unsigned char)AMF0_STRICT_ARRAY)); + amfreply.getContentP(1)->getContentP(0)->addContent(AMFType("arrVal")); + //amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("timescale",(double)1000)); + //amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("length",(double)59641700)); + amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("language","eng")); + amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("sampledescription", (unsigned char)AMF0_STRICT_ARRAY)); + amfreply.getContentP(1)->getContentP(0)->getContentP(0)->getContentP(1)->addContent(AMFType("arrVal")); + amfreply.getContentP(1)->getContentP(0)->getContentP(0)->getContentP(1)->getContentP(0)->addContent(AMFType("sampletype","avc1")); + amfreply.getContentP(1)->getContentP(0)->addContent(AMFType("arrVal")); + //amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("timescale",(double)44100)); + //amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("length",(double)28630000)); + amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("language","eng")); + amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("sampledescription", (unsigned char)AMF0_STRICT_ARRAY)); + amfreply.getContentP(1)->getContentP(0)->getContentP(1)->getContentP(1)->addContent(AMFType("arrVal")); + amfreply.getContentP(1)->getContentP(0)->getContentP(1)->getContentP(1)->getContentP(0)->addContent(AMFType("sampletype","mp4a")); + amfreply.getContentP(1)->addContent(AMFType("audiochannels",(double)2)); + amfreply.getContentP(1)->addContent(AMFType("audiosamplerate",(double)44100)); + amfreply.getContentP(1)->addContent(AMFType("videoframerate",(double)25)); + amfreply.getContentP(1)->addContent(AMFType("aacaot",(double)2)); + amfreply.getContentP(1)->addContent(AMFType("avclevel",(double)12)); + amfreply.getContentP(1)->addContent(AMFType("avcprofile",(double)77)); + amfreply.getContentP(1)->addContent(AMFType("audiocodecid","mp4a")); + amfreply.getContentP(1)->addContent(AMFType("videocodecid","avc1")); + amfreply.getContentP(1)->addContent(AMFType("width",(double)1280)); + amfreply.getContentP(1)->addContent(AMFType("height",(double)720)); + amfreply.getContentP(1)->addContent(AMFType("frameWidth",(double)1280)); + amfreply.getContentP(1)->addContent(AMFType("frameHeight",(double)720)); + amfreply.getContentP(1)->addContent(AMFType("displayWidth",(double)1280)); + amfreply.getContentP(1)->addContent(AMFType("displayHeight",(double)720)); + //amfreply.getContentP(1)->addContent(AMFType("moovposition",(double)35506700)); + //amfreply.getContentP(1)->addContent(AMFType("duration",(double)596.458)); + return amfreply.Pack( ); + }//getMetaData - std::string Movie = ""; - std::string Quality = ""; - int Segment = -1; - int ReqFragment = -1; - int temp; - //int CurrentFragment = -1; later herbruiken? + /// Returns a F4M-format manifest file for Adobe HTTP Dynamic Streaming. + std::string BuildManifest( std::string MetaData, std::string MovieId, int CurrentMediaTime ) { + Interface * temp = new Interface; + std::string Result="\n\n"; + Result += ""; + Result += MovieId; + Result += "\nvideo/mp4\n"; + Result += "live\n"; + Result += "streaming\n"; + Result += ""; + Result += base64_encode(temp->GenerateLiveBootstrap(1)); + Result += "\n"; + Result += "\n"; + Result += ""; + Result += base64_encode(GetMetaData()); + Result += "\n"; + Result += "\n"; + Result += "\n"; + delete temp; + return Result; + }//BuildManifest - while (!socketError && !All_Hell_Broke_Loose){ - //only parse input if available or not yet init'ed - if (HTTP_R.ReadSocket(CONN_fd)){ - handler = HANDLER_PROGRESSIVE; - if ((HTTP_R.url.find("Seg") != std::string::npos) && (HTTP_R.url.find("Frag") != std::string::npos)){handler = HANDLER_FLASH;} - if (HTTP_R.url.find("f4m") != std::string::npos){handler = HANDLER_FLASH;} - if (HTTP_R.url == "/crossdomain.xml"){ - handler = HANDLER_NONE; - HTTP_S.Clean(); - HTTP_S.SetHeader("Content-Type", "text/xml"); - HTTP_S.SetBody(""); - HTTP_S.SendResponse(CONN_fd, "200", "OK");//geen SetBody = unknown length! Dat willen we hier. - #if DEBUG >= 3 - printf("Sending crossdomain.xml file\n"); - #endif - } - if(handler == HANDLER_FLASH){ - if (HTTP_R.url.find("f4m") == std::string::npos){ - Movie = HTTP_R.url.substr(1); - Movie = Movie.substr(0,Movie.find("/")); - Quality = HTTP_R.url.substr( HTTP_R.url.find("/",1)+1 ); - Quality = Quality.substr(0, Quality.find("Seg")); - temp = HTTP_R.url.find("Seg") + 3; - Segment = atoi( HTTP_R.url.substr(temp,HTTP_R.url.find("-",temp)-temp).c_str()); - temp = HTTP_R.url.find("Frag") + 4; - ReqFragment = atoi( HTTP_R.url.substr(temp).c_str() ); - #if DEBUG >= 4 - printf( "URL: %s\n", HTTP_R.url.c_str()); - printf( "Movie: %s, Quality: %s, Seg %d Frag %d\n", Movie.c_str(), Quality.c_str(), Segment, ReqFragment); + /// Main function for Connector_HTTP + int Connector_HTTP(DDV::Socket conn){ + int handler = HANDLER_PROGRESSIVE;///< The handler used for processing this request. + bool ready4data = false;///< Set to true when streaming is to begin. + bool inited = false; + bool progressive_has_sent_header = false; + DDV::Socket ss(-1); + std::string streamname; + std::string FlashBuf; + std::string FlashMeta; + bool Flash_ManifestSent = false; + int Flash_RequestPending = 0; + unsigned int Flash_StartTime; + std::queue Flash_FragBuffer; + FLV::Tag tag;///< Temporary tag buffer for incoming video data. + FLV::Tag Audio_Init;///< Audio initialization data, if available. + FLV::Tag Video_Init;///< Video initialization data, if available. + bool FlashFirstVideo = false; + bool FlashFirstAudio = false; + HTTPReader HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. + + int retval; + int poller = epoll_create(1); + int sspoller = epoll_create(1); + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = conn.getSocket(); + epoll_ctl(poller, EPOLL_CTL_ADD, conn.getSocket(), &ev); + struct epoll_event events[1]; + + std::string Movie = ""; + std::string Quality = ""; + int Segment = -1; + int ReqFragment = -1; + int temp; + //int CurrentFragment = -1; later herbruiken? + + while (conn.connected() && !FLV::Parse_Error){ + //only parse input if available or not yet init'ed + if (HTTP_R.Read(conn)){ + handler = HANDLER_PROGRESSIVE; + if ((HTTP_R.url.find("Seg") != std::string::npos) && (HTTP_R.url.find("Frag") != std::string::npos)){handler = HANDLER_FLASH;} + if (HTTP_R.url.find("f4m") != std::string::npos){handler = HANDLER_FLASH;} + if (HTTP_R.url == "/crossdomain.xml"){ + handler = HANDLER_NONE; + HTTP_S.Clean(); + HTTP_S.SetHeader("Content-Type", "text/xml"); + HTTP_S.SetBody(""); + HTTP_S.SendResponse(conn, "200", "OK");//geen SetBody = unknown length! Dat willen we hier. + #if DEBUG >= 3 + printf("Sending crossdomain.xml file\n"); #endif - Flash_RequestPending++; - }else{ - Movie = HTTP_R.url.substr(1); - Movie = Movie.substr(0,Movie.find("/")); } - streamname = "/tmp/shared_socket_"; - for (std::string::iterator i=Movie.end()-1; i>=Movie.begin(); --i){ - if (!isalpha(*i) && !isdigit(*i)){ - Movie.erase(i); + if(handler == HANDLER_FLASH){ + if (HTTP_R.url.find("f4m") == std::string::npos){ + Movie = HTTP_R.url.substr(1); + Movie = Movie.substr(0,Movie.find("/")); + Quality = HTTP_R.url.substr( HTTP_R.url.find("/",1)+1 ); + Quality = Quality.substr(0, Quality.find("Seg")); + temp = HTTP_R.url.find("Seg") + 3; + Segment = atoi( HTTP_R.url.substr(temp,HTTP_R.url.find("-",temp)-temp).c_str()); + temp = HTTP_R.url.find("Frag") + 4; + ReqFragment = atoi( HTTP_R.url.substr(temp).c_str() ); + #if DEBUG >= 4 + printf( "URL: %s\n", HTTP_R.url.c_str()); + printf( "Movie: %s, Quality: %s, Seg %d Frag %d\n", Movie.c_str(), Quality.c_str(), Segment, ReqFragment); + #endif + Flash_RequestPending++; }else{ - *i=tolower(*i); - }//strip nonalphanumeric - } - streamname += Movie; - ready4data = true; - }//FLASH handler - if (handler == HANDLER_PROGRESSIVE){ - //in het geval progressive nemen we aan dat de URL de streamname is, met .flv erachter - streamname = HTTP_R.url.substr(0, HTTP_R.url.size()-4);//strip de .flv - for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){ - if (!isalpha(*i) && !isdigit(*i)){streamname.erase(i);}else{*i=tolower(*i);}//strip nonalphanumeric - } - streamname = "/tmp/shared_socket_" + streamname;//dit is dan onze shared_socket - //normaal zouden we ook een position uitlezen uit de URL, maar bij LIVE streams is dat zinloos - printf("Streamname: %s\n", streamname.c_str()); - ready4data = true; - }//PROGRESSIVE handler - HTTP_R.CleanForNext(); //maak schoon na verwerken voor eventuele volgende requests... - } - if (ready4data){ - if (!inited){ - //we are ready, connect the socket! - ss = DDV_OpenUnix(streamname); - if (ss <= 0){ - #if DEBUG >= 1 - fprintf(stderr, "Could not connect to server!\n"); - #endif - socketError = 1; - break; - } - ev.events = EPOLLIN; - ev.data.fd = ss; - epoll_ctl(sspoller, EPOLL_CTL_ADD, ss, &ev); - #if DEBUG >= 3 - fprintf(stderr, "Everything connected, starting to send video data...\n"); - #endif - inited = true; - } - if ((Flash_RequestPending > 0) && !Flash_FragBuffer.empty()){ - HTTP_S.Clean(); - HTTP_S.SetHeader("Content-Type","video/mp4"); - HTTP_S.SetBody(Interface::mdatFold(Flash_FragBuffer.front())); - Flash_FragBuffer.pop(); - HTTP_S.SendResponse(CONN_fd, "200", "OK");//schrijf de HTTP response header - Flash_RequestPending--; - #if DEBUG >= 3 - fprintf(stderr, "Sending a video fragment. %i left in buffer, %i requested\n", (int)Flash_FragBuffer.size(), Flash_RequestPending); - #endif - } - retval = epoll_wait(sspoller, events, 1, 1); - switch (DDV_ready(ss)){ - case 0: - socketError = true; - #if DEBUG >= 1 - fprintf(stderr, "Source socket is disconnected.\n"); - #endif - break; - case -1: break;//not ready yet - default: - if (FLV_GetPacket(tag, ss)){//able to read a full packet?f - if (handler == HANDLER_FLASH){ - if (tag->tagTime() > 0){ - if (Flash_StartTime == 0){ - Flash_StartTime = tag->tagTime(); - } - tag->tagTime(tag->tagTime() - Flash_StartTime); - } - if (tag->data[0] != 0x12 ) { - if ((tag->isKeyframe) && (Video_Init.len == 0)){ - if (((tag->data[11] & 0x0f) == 7) && (tag->data[12] == 0)){ - tag->tagTime(0);//timestamp to zero - Video_Init = *tag; - } - } - if ((tag->data[0] == 0x08) && (Audio_Init.len == 0)){ - if (((tag->data[11] & 0xf0) >> 4) == 10){//aac packet - tag->tagTime(0);//timestamp to zero - Audio_Init = *tag; - } - } - if (tag->isKeyframe){ - if (FlashBuf != ""){ - Flash_FragBuffer.push(FlashBuf); - #if DEBUG >= 4 - fprintf(stderr, "Received a fragment. Now %i in buffer.\n", (int)Flash_FragBuffer.size()); - #endif - } - FlashBuf.clear(); - FlashFirstVideo = true; - FlashFirstAudio = true; - } - if (FlashFirstVideo && (tag->data[0] == 0x09) && (Video_Init.len > 0)){ - Video_Init.tagTime(tag->tagTime()); - FlashBuf.append(Video_Init.data, Video_Init.len); - FlashFirstVideo = false; - } - if (FlashFirstAudio && (tag->data[0] == 0x08) && (Audio_Init.len > 0)){ - Audio_Init.tagTime(tag->tagTime()); - FlashBuf.append(Audio_Init.data, Audio_Init.len); - FlashFirstAudio = false; - } - #if DEBUG >= 5 - fprintf(stderr, "Received a tag of type %2hhu and length %i\n", tag->data[0], tag->len); - #endif - FlashBuf.append(tag->data,tag->len); - } else { - FlashMeta = ""; - FlashMeta.append(tag->data+11,tag->len-15); - if( !Flash_ManifestSent ) { - HTTP_S.Clean(); - HTTP_S.SetHeader("Content-Type","text/xml"); - HTTP_S.SetHeader("Cache-Control","no-cache"); - HTTP_S.SetBody(BuildManifest(FlashMeta, Movie, tag->tagTime())); - HTTP_S.SendResponse(CONN_fd, "200", "OK"); - } - } - } - if (handler == HANDLER_PROGRESSIVE){ - if (!progressive_has_sent_header){ - HTTP_S.Clean();//troep opruimen die misschien aanwezig is... - HTTP_S.SetHeader("Content-Type", "video/x-flv");//FLV files hebben altijd dit content-type. - //HTTP_S.SetHeader("Transfer-Encoding", "chunked"); - HTTP_S.protocol = "HTTP/1.0"; - HTTP_S.SendResponse(CONN_fd, "200", "OK");//geen SetBody = unknown length! Dat willen we hier. - //HTTP_S.SendBodyPart(CONN_fd, FLVHeader, 13);//schrijf de FLV header - DDV_write(FLVHeader, 13, CONN_fd); - progressive_has_sent_header = true; - } - //HTTP_S.SendBodyPart(CONN_fd, tag->data, tag->len);//schrijf deze FLV tag onbewerkt weg - DDV_write(tag->data, tag->len, CONN_fd); - }//PROGRESSIVE handler + Movie = HTTP_R.url.substr(1); + Movie = Movie.substr(0,Movie.find("/")); } - break; + streamname = "/tmp/shared_socket_"; + for (std::string::iterator i=Movie.end()-1; i>=Movie.begin(); --i){ + if (!isalpha(*i) && !isdigit(*i)){ + Movie.erase(i); + }else{ + *i=tolower(*i); + }//strip nonalphanumeric + } + streamname += Movie; + ready4data = true; + }//FLASH handler + if (handler == HANDLER_PROGRESSIVE){ + //in het geval progressive nemen we aan dat de URL de streamname is, met .flv erachter + streamname = HTTP_R.url.substr(0, HTTP_R.url.size()-4);//strip de .flv + for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){ + if (!isalpha(*i) && !isdigit(*i)){streamname.erase(i);}else{*i=tolower(*i);}//strip nonalphanumeric + } + streamname = "/tmp/shared_socket_" + streamname;//dit is dan onze shared_socket + //normaal zouden we ook een position uitlezen uit de URL, maar bij LIVE streams is dat zinloos + printf("Streamname: %s\n", streamname.c_str()); + ready4data = true; + }//PROGRESSIVE handler + HTTP_R.CleanForNext(); //maak schoon na verwerken voor eventuele volgende requests... + } + 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 + conn.close(); + 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; + } + if ((Flash_RequestPending > 0) && !Flash_FragBuffer.empty()){ + HTTP_S.Clean(); + HTTP_S.SetHeader("Content-Type","video/mp4"); + HTTP_S.SetBody(Interface::mdatFold(Flash_FragBuffer.front())); + Flash_FragBuffer.pop(); + HTTP_S.SendResponse(conn, "200", "OK");//schrijf de HTTP response header + Flash_RequestPending--; + #if DEBUG >= 3 + fprintf(stderr, "Sending a video fragment. %i left in buffer, %i requested\n", (int)Flash_FragBuffer.size(), Flash_RequestPending); + #endif + } + retval = epoll_wait(sspoller, events, 1, 1); + switch (ss.ready()){ + case -1: + conn.close(); + #if DEBUG >= 1 + fprintf(stderr, "Source socket is disconnected.\n"); + #endif + break; + case 0: break;//not ready yet + default: + if (tag.SockLoader(ss)){//able to read a full packet?f + if (handler == HANDLER_FLASH){ + if (tag.tagTime() > 0){ + if (Flash_StartTime == 0){ + Flash_StartTime = tag.tagTime(); + } + tag.tagTime(tag.tagTime() - Flash_StartTime); + } + if (tag.data[0] != 0x12 ) { + if ((tag.isKeyframe) && (Video_Init.len == 0)){ + if (((tag.data[11] & 0x0f) == 7) && (tag.data[12] == 0)){ + tag.tagTime(0);//timestamp to zero + Video_Init = tag; + } + } + if ((tag.data[0] == 0x08) && (Audio_Init.len == 0)){ + if (((tag.data[11] & 0xf0) >> 4) == 10){//aac packet + tag.tagTime(0);//timestamp to zero + Audio_Init = tag; + } + } + if (tag.isKeyframe){ + if (FlashBuf != ""){ + Flash_FragBuffer.push(FlashBuf); + #if DEBUG >= 4 + fprintf(stderr, "Received a fragment. Now %i in buffer.\n", (int)Flash_FragBuffer.size()); + #endif + } + FlashBuf.clear(); + FlashFirstVideo = true; + FlashFirstAudio = true; + } + if (FlashFirstVideo && (tag.data[0] == 0x09) && (Video_Init.len > 0)){ + Video_Init.tagTime(tag.tagTime()); + FlashBuf.append(Video_Init.data, Video_Init.len); + FlashFirstVideo = false; + } + if (FlashFirstAudio && (tag.data[0] == 0x08) && (Audio_Init.len > 0)){ + Audio_Init.tagTime(tag.tagTime()); + FlashBuf.append(Audio_Init.data, Audio_Init.len); + FlashFirstAudio = false; + } + #if DEBUG >= 5 + fprintf(stderr, "Received a tag of type %2hhu and length %i\n", tag.data[0], tag.len); + #endif + FlashBuf.append(tag.data,tag.len); + } else { + FlashMeta = ""; + FlashMeta.append(tag.data+11,tag.len-15); + if( !Flash_ManifestSent ) { + HTTP_S.Clean(); + HTTP_S.SetHeader("Content-Type","text/xml"); + HTTP_S.SetHeader("Cache-Control","no-cache"); + HTTP_S.SetBody(BuildManifest(FlashMeta, Movie, tag.tagTime())); + HTTP_S.SendResponse(conn, "200", "OK"); + } + } + } + if (handler == HANDLER_PROGRESSIVE){ + if (!progressive_has_sent_header){ + HTTP_S.Clean();//troep opruimen die misschien aanwezig is... + HTTP_S.SetHeader("Content-Type", "video/x-flv");//FLV files hebben altijd dit content-type. + //HTTP_S.SetHeader("Transfer-Encoding", "chunked"); + HTTP_S.protocol = "HTTP/1.0"; + HTTP_S.SendResponse(conn, "200", "OK");//geen SetBody = unknown length! Dat willen we hier. + //HTTP_S.SendBodyPart(CONN_fd, FLVHeader, 13);//schrijf de FLV header + conn.write(FLV::Header, 13); + progressive_has_sent_header = true; + } + //HTTP_S.SendBodyPart(CONN_fd, tag->data, tag->len);//schrijf deze FLV tag onbewerkt weg + conn.write(tag.data, tag.len); + }//PROGRESSIVE handler + } + break; + } } } - } - close(CONN_fd); - if (inited) close(ss); - #if DEBUG >= 1 - if (All_Hell_Broke_Loose){fprintf(stderr, "All Hell Broke Loose\n");} - fprintf(stderr, "User %i disconnected.\n", CONN_fd); - if (inited){ - fprintf(stderr, "Status was: inited\n"); - }else{ - if (ready4data){ - fprintf(stderr, "Status was: ready4data\n"); + conn.close(); + if (inited) ss.close(); + #if DEBUG >= 1 + if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error\n");} + fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); + if (inited){ + fprintf(stderr, "Status was: inited\n"); }else{ - fprintf(stderr, "Status was: connected\n"); + if (ready4data){ + fprintf(stderr, "Status was: ready4data\n"); + }else{ + fprintf(stderr, "Status was: connected\n"); + } } - } - #endif - return 0; -} + #endif + return 0; + }//Connector_HTTP main function +};//Connector_HTTP namespace + +// Load main server setup file, default port 8080, handler is Connector_HTTP::Connector_HTTP +#define DEFAULT_PORT 8080 +#define MAINHANDLER Connector_HTTP::Connector_HTTP +#include "../util/server_setup.cpp" diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 00000000..dd8ba205 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,296 @@ +# Doxyfile 1.6.3 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = DDVTECH Streaming Server +PROJECT_NUMBER = 1 +OUTPUT_DIRECTORY = ./docs +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = YES +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 2 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +BUILTIN_STL_SUPPORT = YES +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +TYPEDEF_HIDES_STRUCT = NO +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +INPUT = . +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = */.git/* +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_TIMESTAMP = YES +HTML_ALIGN_MEMBERS = YES +HTML_DYNAMIC_SECTIONS = NO + +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project + +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO + +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = + +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +USE_INLINE_TREES = NO +TREEVIEW_WIDTH = 250 +FORMULA_FONTSIZE = 10 +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +GENERATE_LATEX = YES +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_FONTNAME = FreeSans +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES + diff --git a/HTTP_Box_Parser/Makefile b/HTTP_Box_Parser/Makefile index 6dd89381..97900497 100644 --- a/HTTP_Box_Parser/Makefile +++ b/HTTP_Box_Parser/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp +SRC = main.cpp ../util/flv_tag.cpp ../util/http_parser.cpp ../util/ddv_socket.cpp OBJ = $(SRC:.cpp=.o) OUT = Box_Parser INCLUDES = diff --git a/HTTP_Box_Parser/main.cpp b/HTTP_Box_Parser/main.cpp index 788d6eed..6cc55223 100644 --- a/HTTP_Box_Parser/main.cpp +++ b/HTTP_Box_Parser/main.cpp @@ -2,25 +2,25 @@ #include #include #include -#include "../util/http_parser.cpp" +#include "../util/http_parser.h" #include "../util/MP4/box_includes.h" -#include "../util/flv_data.cpp" +#include "../util/flv_tag.h" int main(){ HTTPReader H; - FLV_Pack * F = 0; + FLV::Tag F; unsigned int P = 0; char * Payload = 0; - while (H.ReadSocket(stdin) || H.CleanForNext()){ + while (H.Read(stdin) || H.CleanForNext()){ if (H.body.size() > 10000){ Box * TestBox = new Box((uint8_t*)H.body.c_str(), H.body.size()); Payload = (char*)TestBox->GetPayload(); printf("First bytes: %2hhu %2hhu %2hhu %2hhu\n", Payload[0], Payload[1], Payload[2], Payload[3]); P = 0; while (TestBox->GetPayloadSize() > P){ - if (FLV_GetPacket(F, Payload, TestBox->GetPayloadSize(), P)){ - std::cout << "Got a " << F->len << " bytes " << F->tagType() << " FLV tag of time " << F->tagTime() << "." << std::endl; + if (F.MemLoader(Payload, TestBox->GetPayloadSize(), P)){ + std::cout << "Got a " << F.len << " bytes " << F.tagType() << " FLV tag of time " << F.tagTime() << "." << std::endl; } } delete TestBox; diff --git a/Makefile b/Makefile index 50ff2432..71a53364 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ default: client-install +.PHONY: client client-clean clean client-install docs client: cd Connector_HTTP; $(MAKE) @@ -16,4 +17,6 @@ client-install: client-clean client cd Connector_HTTP; $(MAKE) install cd Connector_RAW; $(MAKE) install cd Buffer; $(MAKE) install +docs: + doxygen ./Doxyfile diff --git a/util/MP4/box.cpp b/util/MP4/box.cpp index 57ccbb93..9f790b7f 100644 --- a/util/MP4/box.cpp +++ b/util/MP4/box.cpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index b1bd03e0..2835a5cf 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -1,141 +1,112 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "ddv_socket.h" -bool socketError = false; -bool socketBlocking = false; +/// Create a new base socket. This is a basic constructor for converting any valid socket to a DDV::Socket. +/// \param sockNo Integer representing the socket to convert. +DDV::Socket::Socket(int sockNo){ + sock = sockNo; + Error = false; + Blocking = false; +}//DDV::Socket basic constructor -int DDV_OpenUnix(std::string adres, bool nonblock = false){ - int s = socket(PF_UNIX, SOCK_STREAM, 0); +/// Close connection. The internal socket is closed and then set to -1. +void DDV::Socket::close(){ + #if DEBUG >= 3 + fprintf(stderr, "Socket closed.\n"); + #endif + ::close(sock); + sock = -1; +}//DDV::Socket::close + +/// Returns internal socket number. +int DDV::Socket::getSocket(){return sock;} + +/// Create a new Unix Socket. This socket will (try to) connect to the given address right away. +/// \param address String containing the location of the Unix socket to connect to. +/// \param nonblock Whether the socket should be nonblocking. False by default. +DDV::Socket::Socket(std::string address, bool nonblock){ + sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0){ + #if DEBUG >= 1 + fprintf(stderr, "Could not create socket! Error: %s\n", strerror(errno)); + #endif + return; + } + Error = false; + Blocking = false; sockaddr_un addr; addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, adres.c_str(), adres.size()+1); - int r = connect(s, (sockaddr*)&addr, sizeof(addr)); + strncpy(addr.sun_path, address.c_str(), address.size()+1); + int r = connect(sock, (sockaddr*)&addr, sizeof(addr)); if (r == 0){ if (nonblock){ - int flags = fcntl(s, F_GETFL, 0); + int flags = fcntl(sock, F_GETFL, 0); flags |= O_NONBLOCK; - fcntl(s, F_SETFL, flags); - } - return s; - }else{ - close(s); - return 0; - } -} - -int DDV_Listen(int port){ - int s = socket(AF_INET, SOCK_STREAM, 0); - int on = 1; - setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port);//port 8888 - inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr);//listen on all interfaces - int ret = bind(s, (sockaddr*)&addr, sizeof(addr));//bind to all interfaces, chosen port - if (ret == 0){ - ret = listen(s, 100);//start listening, backlog of 100 allowed - if (ret == 0){ - return s; - }else{ - fprintf(stderr, "Listen failed! Error: %s\n", strerror(errno)); - close(s); - return 0; + fcntl(sock, F_SETFL, flags); } }else{ - fprintf(stderr, "Binding failed! Error: %s\n", strerror(errno)); - close(s); - return 0; + #if DEBUG >= 1 + fprintf(stderr, "Could not connect to %s! Error: %s\n", address.c_str(), strerror(errno)); + #endif + close(); } -} +}//DDV::Socket Unix Contructor -int DDV_UnixListen(std::string adres, bool nonblock = false){ - unlink(adres.c_str()); - int s = socket(AF_UNIX, SOCK_STREAM, 0); - if (nonblock){ - int flags = fcntl(s, F_GETFL, 0); - flags |= O_NONBLOCK; - fcntl(s, F_SETFL, flags); - } - sockaddr_un addr; - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, adres.c_str(), adres.size()+1); - int ret = bind(s, (sockaddr*)&addr, sizeof(addr)); - if (ret == 0){ - ret = listen(s, 100);//start listening, backlog of 100 allowed - if (ret == 0){ - return s; - }else{ - fprintf(stderr, "Listen failed! Error: %s\n", strerror(errno)); - close(s); - return 0; - } - }else{ - fprintf(stderr, "Binding failed! Error: %s\n", strerror(errno)); - close(s); - return 0; - } -} - -int DDV_Accept(int sock, bool nonblock = false){ - int r = accept(sock, 0, 0); - if ((r >= 0) && nonblock){ - int flags = fcntl(r, F_GETFL, 0); - flags |= O_NONBLOCK; - fcntl(r, F_SETFL, flags); - } - return r; -} - -bool DDV_write(const void * buffer, int todo, int sock){ - int sofar = 0; - socketBlocking = false; - while (sofar != todo){ - int r = send(sock, (char*)buffer + sofar, todo-sofar, 0); - if (r <= 0){ - switch (errno){ - case EWOULDBLOCK: socketBlocking = true; break; - default: - socketError = true; - fprintf(stderr, "Could not write! %s\n", strerror(errno)); - return false; - break; - } - } - sofar += r; - } - return true; -} - -signed int DDV_ready(int sock){ +/// Returns the ready-state for this socket. +/// \returns 1 if data is waiting to be read, -1 if not connected, 0 otherwise. +signed int DDV::Socket::ready(){ + if (sock < 0) return -1; char tmp; int preflags = fcntl(sock, F_GETFL, 0); int postflags = preflags | O_NONBLOCK; fcntl(sock, F_SETFL, postflags); int r = recv(sock, &tmp, 1, MSG_PEEK); fcntl(sock, F_SETFL, preflags); + if (r < 0){ + if (errno == EAGAIN || errno == EWOULDBLOCK){ + return 0; + }else{ + #if DEBUG >= 2 + fprintf(stderr, "Socket ready error! Error: %s\n", strerror(errno)); + #endif + close(); + return -1; + } + } + if (r == 0){ + close(); return -1; + } return r; } -bool DDV_read(void * buffer, int todo, int sock){ +/// Returns the connected-state for this socket. +/// Note that this function might be slightly behind the real situation. +/// The connection status is updated after every read/write attempt, when errors occur +/// and when the socket is closed manually. +/// \returns True if socket is connected, false otherwise. +bool DDV::Socket::connected(){ + return (sock >= 0); +} + +/// Writes data to socket. This function blocks if the socket is blocking and all data cannot be written right away. +/// If the socket is nonblocking and not all data can be written, this function sets internal variable Blocking to true +/// and returns false. +/// \param buffer Location of the buffer to write from. +/// \param len Amount of bytes to write. +/// \returns True if the whole write was succesfull, false otherwise. +bool DDV::Socket::write(const void * buffer, int len){ int sofar = 0; - socketBlocking = false; - while (sofar != todo){ - int r = recv(sock, (char*)buffer + sofar, todo-sofar, 0); + Blocking = false; + while (sofar != len){ + int r = send(sock, (char*)buffer + sofar, len-sofar, 0); if (r <= 0){ switch (errno){ - case EWOULDBLOCK: socketBlocking = true; break; + case EWOULDBLOCK: Blocking = true; break; default: - socketError = true; - fprintf(stderr, "Could not read! %s\n", strerror(errno)); + Error = true; + #if DEBUG >= 2 + fprintf(stderr, "Could not write data! Error: %s\n", strerror(errno)); + #endif + close(); return false; break; } @@ -143,41 +114,218 @@ bool DDV_read(void * buffer, int todo, int sock){ sofar += r; } return true; -} +}//DDv::Socket::write -bool DDV_read(void * buffer, int width, int count, int sock){return DDV_read(buffer, width*count, sock);} -bool DDV_write(void * buffer, int width, int count, int sock){return DDV_write(buffer, width*count, sock);} +/// Reads data from socket. This function blocks if the socket is blocking and all data cannot be read right away. +/// If the socket is nonblocking and not all data can be read, this function sets internal variable Blocking to true +/// and returns false. +/// \param buffer Location of the buffer to read to. +/// \param len Amount of bytes to read. +/// \returns True if the whole read was succesfull, false otherwise. +bool DDV::Socket::read(void * buffer, int len){ + int sofar = 0; + Blocking = false; + while (sofar != len){ + int r = recv(sock, (char*)buffer + sofar, len-sofar, 0); + if (r <= 0){ + switch (errno){ + case EWOULDBLOCK: Blocking = true; break; + default: + Error = true; + #if DEBUG >= 2 + fprintf(stderr, "Could not read data! Error: %s\n", strerror(errno)); + #endif + close(); + return false; + break; + } + } + sofar += r; + } + return true; +}//DDV::Socket::read +/// Read call that is compatible with file access syntax. This function simply calls the other read function. +bool DDV::Socket::read(void * buffer, int width, int count){return read(buffer, width*count);} +/// Write call that is compatible with file access syntax. This function simply calls the other write function. +bool DDV::Socket::write(void * buffer, int width, int count){return write(buffer, width*count);} +/// Write call that is compatible with std::string. This function simply calls the other write function. +bool DDV::Socket::write(const std::string data){return write(data.c_str(), data.size());} -int DDV_iwrite(void * buffer, int todo, int sock){ - int r = send(sock, buffer, todo, 0); +/// Incremental write call. This function tries to write len bytes to the socket from the buffer, +/// returning the amount of bytes it actually wrote. +/// \param buffer Location of the buffer to write from. +/// \param len Amount of bytes to write. +/// \returns The amount of bytes actually written. +int DDV::Socket::iwrite(void * buffer, int len){ + int r = send(sock, buffer, len, 0); if (r < 0){ switch (errno){ case EWOULDBLOCK: return 0; break; default: - socketError = true; - fprintf(stderr, "Could not write! %s\n", strerror(errno)); + Error = true; + #if DEBUG >= 2 + fprintf(stderr, "Could not iwrite data! Error: %s\n", strerror(errno)); + #endif + close(); return 0; break; } } return r; -} +}//DDV::Socket::iwrite -int DDV_iread(void * buffer, int todo, int sock){ - int r = recv(sock, buffer, todo, 0); +/// Incremental read call. This function tries to read len bytes to the buffer from the socket, +/// returning the amount of bytes it actually read. +/// \param buffer Location of the buffer to read to. +/// \param len Amount of bytes to read. +/// \returns The amount of bytes actually read. +int DDV::Socket::iread(void * buffer, int len){ + int r = recv(sock, buffer, len, 0); if (r < 0){ switch (errno){ - case EWOULDBLOCK: break; + case EWOULDBLOCK: return 0; break; default: - socketError = true; - fprintf(stderr, "Could not read! %s\n", strerror(errno)); + Error = true; + #if DEBUG >= 2 + fprintf(stderr, "Could not iread data! Error: %s\n", strerror(errno)); + #endif + close(); return 0; break; } } return r; +}//DDV::Socket::iread + +/// Create a new base ServerSocket. The socket is never connected, and a placeholder for later connections. +DDV::ServerSocket::ServerSocket(){ + sock = -1; +}//DDV::ServerSocket base Constructor + +/// Create a new TCP ServerSocket. The socket is immediately bound and set to listen. +/// A maximum of 100 connections will be accepted between accept() calls. +/// Any further connections coming in will be dropped. +/// \param port The TCP port to listen on +/// \param hostname (optional) The interface to bind to. The default is 0.0.0.0 (all interfaces). +/// \param nonblock (optional) Whether accept() calls will be nonblocking. Default is false (blocking). +DDV::ServerSocket::ServerSocket(int port, std::string hostname, bool nonblock){ + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0){ + #if DEBUG >= 1 + fprintf(stderr, "Could not create socket! Error: %s\n", strerror(errno)); + #endif + return; + } + int on = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (nonblock){ + int flags = fcntl(sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + } + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port);//set port + inet_pton(AF_INET, hostname.c_str(), &addr.sin_addr);//set interface, 0.0.0.0 (default) is all + int ret = bind(sock, (sockaddr*)&addr, sizeof(addr));//do the actual bind + if (ret == 0){ + ret = listen(sock, 100);//start listening, backlog of 100 allowed + if (ret == 0){ + return; + }else{ + #if DEBUG >= 1 + fprintf(stderr, "Listen failed! Error: %s\n", strerror(errno)); + #endif + close(); + return; + } + }else{ + #if DEBUG >= 1 + fprintf(stderr, "Binding failed! Error: %s\n", strerror(errno)); + #endif + close(); + return; + } +}//DDV::ServerSocket TCP Constructor + +/// Create a new Unix ServerSocket. The socket is immediately bound and set to listen. +/// A maximum of 100 connections will be accepted between accept() calls. +/// Any further connections coming in will be dropped. +/// The address used will first be unlinked - so it succeeds if the Unix socket already existed. Watch out for this behaviour - it will delete any file located at address! +/// \param address The location of the Unix socket to bind to. +/// \param nonblock (optional) Whether accept() calls will be nonblocking. Default is false (blocking). +DDV::ServerSocket::ServerSocket(std::string address, bool nonblock){ + unlink(address.c_str()); + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0){ + #if DEBUG >= 1 + fprintf(stderr, "Could not create socket! Error: %s\n", strerror(errno)); + #endif + return; + } + if (nonblock){ + int flags = fcntl(sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + } + sockaddr_un addr; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, address.c_str(), address.size()+1); + int ret = bind(sock, (sockaddr*)&addr, sizeof(addr)); + if (ret == 0){ + ret = listen(sock, 100);//start listening, backlog of 100 allowed + if (ret == 0){ + return; + }else{ + #if DEBUG >= 1 + fprintf(stderr, "Listen failed! Error: %s\n", strerror(errno)); + #endif + close(); + return; + } + }else{ + #if DEBUG >= 1 + fprintf(stderr, "Binding failed! Error: %s\n", strerror(errno)); + #endif + close(); + return; + } +}//DDV::ServerSocket Unix Constructor + +/// Accept any waiting connections. If the DDV::ServerSocket is blocking, this function will block until there is an incoming connection. +/// If the DDV::ServerSocket is nonblocking, it might return a DDV::Socket that is not connected, so check for this. +/// \param nonblock (optional) Whether the newly connected socket should be nonblocking. Default is false (blocking). +/// \returns A DDV::Socket, which may or may not be connected, depending on settings and circumstances. +DDV::Socket DDV::ServerSocket::accept(bool nonblock){ + if (sock < 0){return DDV::Socket(-1);} + int r = ::accept(sock, 0, 0); + if ((r >= 0) && nonblock){ + int flags = fcntl(r, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(r, F_SETFL, flags); + } + if (r < 0){ + if (errno != EWOULDBLOCK && errno != EAGAIN){close();} + } + return DDV::Socket(r); } +/// Close connection. The internal socket is closed and then set to -1. +void DDV::ServerSocket::close(){ + ::close(sock); + sock = -1; +}//DDV::ServerSocket::close +/// Returns the connected-state for this socket. +/// Note that this function might be slightly behind the real situation. +/// The connection status is updated after every accept attempt, when errors occur +/// and when the socket is closed manually. +/// \returns True if socket is connected, false otherwise. +bool DDV::ServerSocket::connected(){ + return (sock >= 0); +}//DDV::ServerSocket::connected + +/// Returns internal socket number. +int DDV::ServerSocket::getSocket(){return sock;} diff --git a/util/ddv_socket.h b/util/ddv_socket.h new file mode 100644 index 00000000..cc9421ae --- /dev/null +++ b/util/ddv_socket.h @@ -0,0 +1,53 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +///Holds DDV Socket tools. +namespace DDV{ + + /// This class is for easy communicating through sockets, either TCP or Unix. + class Socket{ + private: + int sock; ///< Internally saved socket number. + public: + Socket(int sockNo); ///< Create a new base socket. + Socket(std::string adres, bool nonblock = false); ///< Create a new Unix Socket. + bool Error; ///< Set to true if a socket error happened. + bool Blocking; ///< Set to true if a socket is currently or wants to be blocking. + signed int ready(); ///< Returns the ready-state for this socket. + bool connected(); ///< Returns the connected-state for this socket. + bool read(void * buffer, int len); ///< Reads data from socket. + bool read(void * buffer, int width, int count); ///< Read call that is compatible with file access syntax. + bool write(const void * buffer, int len); ///< Writes data to socket. + bool write(void * buffer, int width, int count); ///< Write call that is compatible with file access syntax. + bool write(const std::string data); ///< Write call that is compatible with std::string. + int iwrite(void * buffer, int len); ///< Incremental write call. + int iread(void * buffer, int len); ///< Incremental read call. + void close(); ///< Close connection. + int getSocket(); ///< Returns internal socket number. + }; + + /// This class is for easily setting up listening socket, either TCP or Unix. + class ServerSocket{ + private: + int sock; ///< Internally saved socket number. + public: + ServerSocket(); ///< Create a new base ServerSocket. + ServerSocket(int port, std::string hostname = "0.0.0.0", bool nonblock = false); ///< Create a new TCP ServerSocket. + ServerSocket(std::string adres, bool nonblock = false); ///< Create a new Unix ServerSocket. + Socket accept(bool nonblock = false); ///< Accept any waiting connections. + bool connected(); ///< Returns the connected-state for this socket. + void close(); ///< Close connection. + int getSocket(); ///< Returns internal socket number. + }; + +}; diff --git a/util/flv.cpp b/util/flv.cpp deleted file mode 100644 index 8780077d..00000000 --- a/util/flv.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include //for read() -#include -#include "flv_pack.cpp" - -char FLVHeader[13]; -bool All_Hell_Broke_Loose = false; - -//checks FLV Header for correctness -//returns true if everything is alright, false otherwise -bool FLV_Checkheader(char * header){ - if (header[0] != 'F') return false; - if (header[1] != 'L') return false; - if (header[2] != 'V') return false; - if (header[8] != 0x09) return false; - if (header[9] != 0) return false; - if (header[10] != 0) return false; - if (header[11] != 0) return false; - if (header[12] != 0) return false; - return true; -}//FLV_Checkheader - -//returns true if header is an FLV header -bool FLV_Isheader(char * header){ - if (header[0] != 'F') return false; - if (header[1] != 'L') return false; - if (header[2] != 'V') return false; - return true; -}//FLV_Isheader - -bool ReadUntil(char * buffer, unsigned int count, unsigned int & sofar){ - if (sofar >= count){return true;} - int r = 0; - r = fread(buffer + sofar,1,count-sofar,stdin); - if (r < 0){All_Hell_Broke_Loose = true; return false;} - sofar += r; - if (sofar >= count){return true;} - return false; -} - -//gets a packet, storing in given FLV_Pack pointer. -//will assign pointer if null -//resizes FLV_Pack data field bigger if data doesn't fit -// (does not auto-shrink for speed!) -bool FLV_GetPacket(FLV_Pack *& p){ - int preflags = fcntl(fileno(stdin), F_GETFL, 0); - int postflags = preflags | O_NONBLOCK; - fcntl(fileno(stdin), F_SETFL, postflags); - static bool done = true; - static unsigned int sofar = 0; - if (!p){p = (FLV_Pack*)calloc(1, sizeof(FLV_Pack));} - if (p->buf < 15){p->data = (char*)realloc(p->data, 15); p->buf = 15;} - - if (done){ - //read a header - if (ReadUntil(p->data, 11, sofar)){ - //if its a correct FLV header, throw away and read tag header - if (FLV_Isheader(p->data)){ - if (ReadUntil(p->data, 13, sofar)){ - if (FLV_Checkheader(p->data)){ - sofar = 0; - memcpy(FLVHeader, p->data, 13); - }else{All_Hell_Broke_Loose = true;} - } - }else{ - //if a tag header, calculate length and read tag body - p->len = p->data[3] + 15; - p->len += (p->data[2] << 8); - p->len += (p->data[1] << 16); - if (p->buf < p->len){p->data = (char*)realloc(p->data, p->len);p->buf = p->len;} - done = false; - } - } - }else{ - //read tag body - if (ReadUntil(p->data, p->len, sofar)){ - //calculate keyframeness, next time read header again, return true - p->isKeyframe = false; - if ((p->data[0] == 0x09) && (((p->data[11] & 0xf0) >> 4) == 1)){p->isKeyframe = true;} - done = true; - sofar = 0; - fcntl(fileno(stdin), F_SETFL, preflags); - return true; - } - } - fcntl(fileno(stdin), F_SETFL, preflags); - return false; -}//FLV_GetPacket - diff --git a/util/flv_data.cpp b/util/flv_data.cpp deleted file mode 100644 index fde4d2c5..00000000 --- a/util/flv_data.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include //for read() -#include -#include "flv_pack.cpp" - -char FLVHeader[13]; -bool All_Hell_Broke_Loose = false; - -//checks FLV Header for correctness -//returns true if everything is alright, false otherwise -bool FLV_Checkheader(char * header){ - if (header[0] != 'F') return false; - if (header[1] != 'L') return false; - if (header[2] != 'V') return false; - if (header[8] != 0x09) return false; - if (header[9] != 0) return false; - if (header[10] != 0) return false; - if (header[11] != 0) return false; - if (header[12] != 0) return false; - return true; -}//FLV_Checkheader - -//returns true if header is an FLV header -bool FLV_Isheader(char * header){ - if (header[0] != 'F') return false; - if (header[1] != 'L') return false; - if (header[2] != 'V') return false; - return true; -}//FLV_Isheader - -bool ReadUntil(char * buffer, unsigned int count, unsigned int & sofar, char * D, unsigned int S, unsigned int & P){ - if (sofar >= count){return true;} - int r = 0; - if (P+(count-sofar) > S){r = S-P;}else{r = count-sofar;} - memcpy(buffer+sofar, D+P, r); - P += r; - sofar += r; - if (sofar >= count){return true;} - return false; -} - -//gets a packet, storing in given FLV_Pack pointer. -//will assign pointer if null -//resizes FLV_Pack data field bigger if data doesn't fit -// (does not auto-shrink for speed!) -bool FLV_GetPacket(FLV_Pack *& p, char * D, unsigned int S, unsigned int & P){ - static bool done = true; - static unsigned int sofar = 0; - if (!p){p = (FLV_Pack*)calloc(1, sizeof(FLV_Pack));} - if (p->buf < 15){p->data = (char*)realloc(p->data, 15000000); p->buf = 15000000;} - - if (done){ - //read a header - if (ReadUntil(p->data, 11, sofar, D, S, P)){ - //if its a correct FLV header, throw away and read tag header - if (FLV_Isheader(p->data)){ - if (ReadUntil(p->data, 13, sofar, D, S, P)){ - if (FLV_Checkheader(p->data)){ - sofar = 0; - memcpy(FLVHeader, p->data, 13); - }else{All_Hell_Broke_Loose = true;} - } - }else{ - //if a tag header, calculate length and read tag body - p->len = p->data[3] + 15; - p->len += (p->data[2] << 8); - p->len += (p->data[1] << 16); - //if (p->buf < p->len){p->data = (char*)realloc(p->data, p->len);p->buf = p->len;} - if (p->data[0] > 0x12){ - printf("Invalid data: %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx\n", p->data[0], p->data[1], p->data[2], p->data[3], p->data[4], p->data[5], p->data[6]); - } - done = false; - } - } - }else{ - //read tag body - if (ReadUntil(p->data, p->len, sofar, D, S, P)){ - //calculate keyframeness, next time read header again, return true - p->isKeyframe = false; - if ((p->data[0] == 0x09) && (((p->data[11] & 0xf0) >> 4) == 1)){p->isKeyframe = true;} - done = true; - sofar = 0; - return true; - } - } - return false; -}//FLV_GetPacket - diff --git a/util/flv_pack.cpp b/util/flv_pack.cpp deleted file mode 100644 index a60bf6a5..00000000 --- a/util/flv_pack.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#pragma once - -class FLV_Pack { - public: - int len; - int buf; - bool isKeyframe; - char * data; - std::string tagType(){ - std::string R = ""; - switch (data[0]){ - case 0x09: - switch (data[11] & 0x0F){ - case 1: R += "JPEG"; break; - case 2: R += "H263"; break; - case 3: R += "ScreenVideo1"; break; - case 4: R += "VP6"; break; - case 5: R += "VP6Alpha"; break; - case 6: R += "ScreenVideo2"; break; - case 7: R += "AVC"; break; - default: R += "unknown"; break; - } - R += " video "; - switch (data[11] & 0xF0){ - case 0x10: R += "keyframe"; break; - case 0x20: R += "iframe"; break; - case 0x30: R += "disposableiframe"; break; - case 0x40: R += "generatedkeyframe"; break; - case 0x50: R += "videoinfo"; break; - } - if ((data[11] & 0x0F) == 7){ - switch (data[12]){ - case 0: R += " header"; break; - case 1: R += " NALU"; break; - case 2: R += " endofsequence"; break; - } - } - break; - case 0x08: - switch (data[11] & 0xF0){ - case 0x00: R += "linear PCM PE"; break; - case 0x10: R += "ADPCM"; break; - case 0x20: R += "MP3"; break; - case 0x30: R += "linear PCM LE"; break; - case 0x40: R += "Nelly16kHz"; break; - case 0x50: R += "Nelly8kHz"; break; - case 0x60: R += "Nelly"; break; - case 0x70: R += "G711A-law"; break; - case 0x80: R += "G711mu-law"; break; - case 0x90: R += "reserved"; break; - case 0xA0: R += "AAC"; break; - case 0xB0: R += "Speex"; break; - case 0xE0: R += "MP38kHz"; break; - case 0xF0: R += "DeviceSpecific"; break; - default: R += "unknown"; break; - } - switch (data[11] & 0x0C){ - case 0x0: R += " 5.5kHz"; break; - case 0x4: R += " 11kHz"; break; - case 0x8: R += " 22kHz"; break; - case 0xC: R += " 44kHz"; break; - } - switch (data[11] & 0x02){ - case 0: R += " 8bit"; break; - case 2: R += " 16bit"; break; - } - switch (data[11] & 0x01){ - case 0: R += " mono"; break; - case 1: R += " stereo"; break; - } - R += " audio"; - if ((data[12] == 0) && ((data[11] & 0xF0) == 0xA0)){ - R += " initdata"; - } - break; - case 0x12: - R += "(meta)data"; - break; - default: - R += "unknown"; - break; - } - return R; - };//tagtype - unsigned int tagTime(){ - return (data[4] << 16) + (data[5] << 8) + data[6] + (data[7] << 24); - }//tagTime getter - void tagTime(unsigned int T){ - data[4] = ((T >> 16) & 0xFF); - data[5] = ((T >> 8) & 0xFF); - data[6] = (T & 0xFF); - data[7] = ((T >> 24) & 0xFF); - }//tagTime setter - FLV_Pack(){ - len = 0; buf = 0; data = 0; isKeyframe = false; - }//empty constructor - FLV_Pack(const FLV_Pack& O){ - buf = O.len; - len = buf; - if (len > 0){ - data = (char*)malloc(len); - memcpy(data, O.data, len); - }else{ - data = 0; - } - isKeyframe = O.isKeyframe; - }//copy constructor - FLV_Pack & operator= (const FLV_Pack& O){ - if (this != &O){//no self-assignment - if (data != 0){free(data);} - buf = O.len; - len = buf; - if (len > 0){ - data = (char*)malloc(len); - memcpy(data, O.data, len); - }else{ - data = 0; - } - isKeyframe = O.isKeyframe; - } - return *this; - }//assignment operator -};//FLV_Pack diff --git a/util/flv_sock.cpp b/util/flv_sock.cpp deleted file mode 100644 index 8b74c9aa..00000000 --- a/util/flv_sock.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "flv_pack.cpp" - -char FLVHeader[13]; -bool All_Hell_Broke_Loose = false; - -//checks FLV Header for correctness -//returns true if everything is alright, false otherwise -bool FLV_Checkheader(char * header){ - if (header[0] != 'F') return false; - if (header[1] != 'L') return false; - if (header[2] != 'V') return false; - if (header[8] != 0x09) return false; - if (header[9] != 0) return false; - if (header[10] != 0) return false; - if (header[11] != 0) return false; - if (header[12] != 0) return false; - return true; -}//FLV_Checkheader - -//returns true if header is an FLV header -bool FLV_Isheader(char * header){ - if (header[0] != 'F') return false; - if (header[1] != 'L') return false; - if (header[2] != 'V') return false; - return true; -}//FLV_Isheader - -bool ReadUntil(char * buffer, unsigned int count, unsigned int & sofar, int sock){ - if (sofar == count){return true;} - int r = DDV_iread(buffer + sofar,count-sofar,sock); - if (r < 0){ - if (errno != EWOULDBLOCK){ - All_Hell_Broke_Loose = true; - fprintf(stderr, "ReadUntil fail: %s. All Hell Broke Loose!\n", strerror(errno)); - } - return false; - } - sofar += r; - if (sofar == count){return true;} - if (sofar > count){ - All_Hell_Broke_Loose = true; - fprintf(stderr, "ReadUntil fail: %s. Read too much. All Hell Broke Loose!\n", strerror(errno)); - } - return false; -} - -//gets a packet, storing in given FLV_Pack pointer. -//will assign pointer if null -//resizes FLV_Pack data field bigger if data doesn't fit -// (does not auto-shrink for speed!) -bool FLV_GetPacket(FLV_Pack *& p, int sock){ - static bool done = true; - static unsigned int sofar = 0; - if (!p){p = (FLV_Pack*)calloc(1, sizeof(FLV_Pack));} - if (p->buf < 15){p->data = (char*)realloc(p->data, 15); p->buf = 15;} - - if (done){ - //read a header - if (ReadUntil(p->data, 11, sofar, sock)){ - //if its a correct FLV header, throw away and read tag header - if (FLV_Isheader(p->data)){ - if (ReadUntil(p->data, 13, sofar, sock)){ - if (FLV_Checkheader(p->data)){ - sofar = 0; - memcpy(FLVHeader, p->data, 13); - //fwrite(p->data, 13, 1, stdout);//output raw stream - }else{ - All_Hell_Broke_Loose = true; - fprintf(stderr, "Invalid FLV header. All Hell Broke Loose!\n"); - } - } - }else{ - //if a tag header, calculate length and read tag body - p->len = p->data[3] + 15; - p->len += (p->data[2] << 8); - p->len += (p->data[1] << 16); - //fprintf(stderr, "Tag of len %i\n", p->len); - if (p->buf < p->len){p->data = (char*)realloc(p->data, p->len);p->buf = p->len;} - done = false; - } - } - }else{ - //read tag body - if (ReadUntil(p->data, p->len, sofar, sock)){ - //calculate keyframeness, next time read header again, return true - p->isKeyframe = false; - if ((p->data[0] == 0x09) && (((p->data[11] & 0xf0) >> 4) == 1)){p->isKeyframe = true;} - int testlen = p->data[p->len-1] + 4; - testlen += (p->data[p->len-2] << 8); - testlen += (p->data[p->len-3] << 16); - testlen += (p->data[p->len-4] << 24); - //fwrite(p->data, p->len, 1, stdout);//output raw stream - if (p->len != testlen){ - fprintf(stderr, "Len: %i, testlen: %i\n", p->len, testlen); - All_Hell_Broke_Loose = true; - fprintf(stderr, "ReadUntil fail: Wrong size tag? All Hell Broke Loose!\n"); - return false; - } - done = true; - sofar = 0; - return true; - } - } - return false; -}//FLV_GetPacket - diff --git a/util/flv_tag.cpp b/util/flv_tag.cpp new file mode 100644 index 00000000..70321b17 --- /dev/null +++ b/util/flv_tag.cpp @@ -0,0 +1,391 @@ +#include "flv_tag.h" +#include //for Tag::FileLoader +#include //for Tag::FileLoader +#include //for Tag::FileLoader +#include //malloc +#include //memcpy +#include "ddv_socket.h" //socket functions + +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. + +/// Checks a FLV Header for validness. Returns true if the header is valid, false +/// if the header is not. Not valid can mean: +/// - Not starting with the string "FLV". +/// - The DataOffset is not 9 bytes. +/// - The PreviousTagSize is not 0 bytes. +/// +/// Note that we see PreviousTagSize as part of the FLV header, not part of the tag header! +bool FLV::check_header(char * header){ + if (header[0] != 'F') return false; + if (header[1] != 'L') return false; + if (header[2] != 'V') return false; + if (header[5] != 0) return false; + if (header[6] != 0) return false; + if (header[7] != 0) return false; + if (header[8] != 0x09) return false; + if (header[9] != 0) return false; + if (header[10] != 0) return false; + if (header[11] != 0) return false; + if (header[12] != 0) return false; + return true; +}//FLV::check_header + +/// Checks the first 3 bytes for the string "FLV". Implementing a basic FLV header check, +/// returning true if it is, false if not. +bool FLV::is_header(char * header){ + if (header[0] != 'F') return false; + if (header[1] != 'L') return false; + if (header[2] != 'V') return false; + return true; +}//FLV::is_header + + +/// Returns a std::string describing the tag in detail. +/// The string includes information about whether the tag is +/// audio, video or metadata, what encoding is used, and the details +/// of the encoding itself. +std::string FLV::Tag::tagType(){ + std::string R = ""; + switch (data[0]){ + case 0x09: + switch (data[11] & 0x0F){ + case 1: R += "JPEG"; break; + case 2: R += "H263"; break; + case 3: R += "ScreenVideo1"; break; + case 4: R += "VP6"; break; + case 5: R += "VP6Alpha"; break; + case 6: R += "ScreenVideo2"; break; + case 7: R += "AVC"; break; + default: R += "unknown"; break; + } + R += " video "; + switch (data[11] & 0xF0){ + case 0x10: R += "keyframe"; break; + case 0x20: R += "iframe"; break; + case 0x30: R += "disposableiframe"; break; + case 0x40: R += "generatedkeyframe"; break; + case 0x50: R += "videoinfo"; break; + } + if ((data[11] & 0x0F) == 7){ + switch (data[12]){ + case 0: R += " header"; break; + case 1: R += " NALU"; break; + case 2: R += " endofsequence"; break; + } + } + break; + case 0x08: + switch (data[11] & 0xF0){ + case 0x00: R += "linear PCM PE"; break; + case 0x10: R += "ADPCM"; break; + case 0x20: R += "MP3"; break; + case 0x30: R += "linear PCM LE"; break; + case 0x40: R += "Nelly16kHz"; break; + case 0x50: R += "Nelly8kHz"; break; + case 0x60: R += "Nelly"; break; + case 0x70: R += "G711A-law"; break; + case 0x80: R += "G711mu-law"; break; + case 0x90: R += "reserved"; break; + case 0xA0: R += "AAC"; break; + case 0xB0: R += "Speex"; break; + case 0xE0: R += "MP38kHz"; break; + case 0xF0: R += "DeviceSpecific"; break; + default: R += "unknown"; break; + } + switch (data[11] & 0x0C){ + case 0x0: R += " 5.5kHz"; break; + case 0x4: R += " 11kHz"; break; + case 0x8: R += " 22kHz"; break; + case 0xC: R += " 44kHz"; break; + } + switch (data[11] & 0x02){ + case 0: R += " 8bit"; break; + case 2: R += " 16bit"; break; + } + switch (data[11] & 0x01){ + case 0: R += " mono"; break; + case 1: R += " stereo"; break; + } + R += " audio"; + if ((data[12] == 0) && ((data[11] & 0xF0) == 0xA0)){ + R += " initdata"; + } + break; + case 0x12: + R += "(meta)data"; + break; + default: + R += "unknown"; + break; + } + return R; +}//FLV::Tag::tagtype + +/// Returns the 32-bit timestamp of this tag. +unsigned int FLV::Tag::tagTime(){ + return (data[4] << 16) + (data[5] << 8) + data[6] + (data[7] << 24); +}//tagTime getter + +/// Sets the 32-bit timestamp of this tag. +void FLV::Tag::tagTime(unsigned int T){ + data[4] = ((T >> 16) & 0xFF); + data[5] = ((T >> 8) & 0xFF); + data[6] = (T & 0xFF); + data[7] = ((T >> 24) & 0xFF); +}//tagTime setter + +/// Constructor for a new, empty, tag. +/// The buffer length is initialized to 0, and later automatically +/// increased if neccesary. +FLV::Tag::Tag(){ + len = 0; buf = 0; data = 0; isKeyframe = false; +}//empty constructor + +/// Copy constructor, copies the contents of an existing tag. +/// The buffer length is initialized to the actual size of the tag +/// that is being copied, and later automaticallt increased if +/// neccesary. +FLV::Tag::Tag(const Tag& O){ + buf = O.len; + len = buf; + if (len > 0){ + data = (char*)malloc(len); + memcpy(data, O.data, len); + }else{ + data = 0; + } + isKeyframe = O.isKeyframe; +}//copy constructor + +/// Assignment operator - works exactly like the copy constructor. +/// This operator checks for self-assignment. +FLV::Tag & FLV::Tag::operator= (const FLV::Tag& O){ + if (this != &O){//no self-assignment + if (data != 0){free(data);} + buf = O.len; + len = buf; + if (len > 0){ + data = (char*)malloc(len); + memcpy(data, O.data, len); + }else{ + data = 0; + } + isKeyframe = O.isKeyframe; + } + return *this; +}//assignment operator + +/// Helper function for FLV::MemLoader. +/// This function will try to read count bytes from data buffer D into buffer. +/// This function should be called repeatedly until true. +/// P and sofar are not the same value, because D may not start with the current tag. +/// \param buffer The target buffer. +/// \param count Amount of bytes to read. +/// \param sofar Current amount read. +/// \param D The location of the data buffer. +/// \param S The size of the data buffer. +/// \param P The current position in the data buffer. Will be updated to reflect new position. +/// \return True if count bytes are read succesfully, false otherwise. +bool FLV::Tag::MemReadUntil(char * buffer, unsigned int count, unsigned int & sofar, char * D, unsigned int S, unsigned int & P){ + if (sofar >= count){return true;} + int r = 0; + if (P+(count-sofar) > S){r = S-P;}else{r = count-sofar;} + memcpy(buffer+sofar, D+P, r); + P += r; + sofar += r; + if (sofar >= count){return true;} + return false; +}//Tag::MemReadUntil + + +/// Try to load a tag from a data buffer in memory. +/// This is a stateful function - if fed incorrect data, it will most likely never return true again! +/// While this function returns false, the Tag might not contain valid data. +/// \param D The location of the data buffer. +/// \param S The size of the data buffer. +/// \param P The current position in the data buffer. Will be updated to reflect new position. +/// \return True if a whole tag is succesfully read, false otherwise. +bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){ + static bool done = true; + static unsigned int sofar = 0; + if (buf < 15){data = (char*)realloc(data, 15); buf = 15;} + if (done){ + //read a header + if (MemReadUntil(data, 11, sofar, D, S, P)){ + //if its a correct FLV header, throw away and read tag header + if (FLV::is_header(data)){ + if (MemReadUntil(data, 13, sofar, D, S, P)){ + if (FLV::check_header(data)){ + sofar = 0; + memcpy(FLV::Header, data, 13); + }else{FLV::Parse_Error = true; return false;} + } + }else{ + //if a tag header, calculate length and read tag body + len = data[3] + 15; + len += (data[2] << 8); + len += (data[1] << 16); + if (buf < len){data = (char*)realloc(data, len); buf = len;} + if (data[0] > 0x12){FLV::Parse_Error = true; return false;} + done = false; + } + } + }else{ + //read tag body + if (MemReadUntil(data, len, sofar, D, S, P)){ + //calculate keyframeness, next time read header again, return true + if ((data[0] == 0x09) && (((data[11] & 0xf0) >> 4) == 1)){isKeyframe = true;}else{isKeyframe = false;} + done = true; + sofar = 0; + return true; + } + } + return false; +}//Tag::MemLoader + + +/// Helper function for FLV::SockLoader. +/// This function will try to read count bytes from socket sock into buffer. +/// This function should be called repeatedly until true. +/// \param buffer The target buffer. +/// \param count Amount of bytes to read. +/// \param sofar Current amount read. +/// \param sock Socket to read from. +/// \return True if count bytes are read succesfully, false otherwise. +bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & sofar, DDV::Socket & sock){ + if (sofar == count){return true;} + int r = sock.read(buffer + sofar,count-sofar); + if (r < 0){ + if (errno != EWOULDBLOCK){ + FLV::Parse_Error = true; + fprintf(stderr, "ReadUntil fail: %s. All Hell Broke Loose!\n", strerror(errno)); + } + return false; + } + sofar += r; + if (sofar == count){return true;} + if (sofar > count){ + FLV::Parse_Error = true; + fprintf(stderr, "ReadUntil fail: %s. Read too much. All Hell Broke Loose!\n", strerror(errno)); + } + return false; +}//Tag::SockReadUntil + +/// Try to load a tag from a socket. +/// This is a stateful function - if fed incorrect data, it will most likely never return true again! +/// While this function returns false, the Tag might not contain valid data. +/// \param sock The socket to read from. +/// \return True if a whole tag is succesfully read, false otherwise. +bool FLV::Tag::SockLoader(DDV::Socket sock){ + static bool done = true; + static unsigned int sofar = 0; + if (buf < 15){data = (char*)realloc(data, 15); buf = 15;} + if (done){ + if (SockReadUntil(data, 11, sofar, sock)){ + //if its a correct FLV header, throw away and read tag header + if (FLV::is_header(data)){ + if (SockReadUntil(data, 13, sofar, sock)){ + if (FLV::check_header(data)){ + sofar = 0; + memcpy(FLV::Header, data, 13); + }else{FLV::Parse_Error = true; return false;} + } + }else{ + //if a tag header, calculate length and read tag body + len = data[3] + 15; + len += (data[2] << 8); + len += (data[1] << 16); + if (buf < len){data = (char*)realloc(data, len); buf = len;} + if (data[0] > 0x12){FLV::Parse_Error = true; return false;} + done = false; + } + } + }else{ + //read tag body + if (SockReadUntil(data, len, sofar, sock)){ + //calculate keyframeness, next time read header again, return true + if ((data[0] == 0x09) && (((data[11] & 0xf0) >> 4) == 1)){isKeyframe = true;}else{isKeyframe = false;} + done = true; + sofar = 0; + return true; + } + } + return false; +}//Tag::SockLoader + +/// Try to load a tag from a socket. +/// This is a stateful function - if fed incorrect data, it will most likely never return true again! +/// While this function returns false, the Tag might not contain valid data. +/// \param sock The socket to read from. +/// \return True if a whole tag is succesfully read, false otherwise. +bool FLV::Tag::SockLoader(int sock){ + return SockLoader(DDV::Socket(sock)); +}//Tag::SockLoader + +/// Helper function for FLV::FileLoader. +/// This function will try to read count bytes from file f into buffer. +/// This function should be called repeatedly until true. +/// \param buffer The target buffer. +/// \param count Amount of bytes to read. +/// \param sofar Current amount read. +/// \param f File to read from. +/// \return True if count bytes are read succesfully, false otherwise. +bool FLV::Tag::FileReadUntil(char * buffer, unsigned int count, unsigned int & sofar, FILE * f){ + if (sofar >= count){return true;} + int r = 0; + r = fread(buffer + sofar,1,count-sofar,f); + if (r < 0){FLV::Parse_Error = true; return false;} + sofar += r; + if (sofar >= count){return true;} + return false; +} + +/// Try to load a tag from a file. +/// This is a stateful function - if fed incorrect data, it will most likely never return true again! +/// While this function returns false, the Tag might not contain valid data. +/// \param f The file to read from. +/// \return True if a whole tag is succesfully read, false otherwise. +bool FLV::Tag::FileLoader(FILE * f){ + int preflags = fcntl(fileno(f), F_GETFL, 0); + int postflags = preflags | O_NONBLOCK; + fcntl(fileno(f), F_SETFL, postflags); + static bool done = true; + static unsigned int sofar = 0; + if (buf < 15){data = (char*)realloc(data, 15); buf = 15;} + + if (done){ + //read a header + if (FileReadUntil(data, 11, sofar, f)){ + //if its a correct FLV header, throw away and read tag header + if (FLV::is_header(data)){ + if (FileReadUntil(data, 13, sofar, f)){ + if (FLV::check_header(data)){ + sofar = 0; + memcpy(FLV::Header, data, 13); + }else{FLV::Parse_Error = true;} + } + }else{ + //if a tag header, calculate length and read tag body + len = data[3] + 15; + len += (data[2] << 8); + len += (data[1] << 16); + if (buf < len){data = (char*)realloc(data, len); buf = len;} + if (data[0] > 0x12){FLV::Parse_Error = true;} + done = false; + } + } + }else{ + //read tag body + if (FileReadUntil(data, len, sofar, f)){ + //calculate keyframeness, next time read header again, return true + if ((data[0] == 0x09) && (((data[11] & 0xf0) >> 4) == 1)){isKeyframe = true;}else{isKeyframe = false;} + done = true; + sofar = 0; + fcntl(fileno(f), F_SETFL, preflags); + return true; + } + } + fcntl(fileno(f), F_SETFL, preflags); + return false; +}//FLV_GetPacket diff --git a/util/flv_tag.h b/util/flv_tag.h new file mode 100644 index 00000000..d91cabb5 --- /dev/null +++ b/util/flv_tag.h @@ -0,0 +1,40 @@ +#pragma once +#include "ddv_socket.h" +#include + +/// This namespace holds all FLV-parsing related functionality. +namespace FLV { + //variables + extern char Header[13]; ///< Holds the last FLV header parsed. + extern bool Parse_Error; ///< This variable is set to true if a problem is encountered while parsing the FLV. + + //functions + bool check_header(char * header); ///< Checks a FLV Header for validness. + bool is_header(char * header); ///< Checks the first 3 bytes for the string "FLV". + + /// This class is used to hold, work with and get information about a single FLV tag. + class Tag { + public: + int len; ///< Actual length of tag. + bool isKeyframe; ///< True if current tag is a video keyframe. + char * data; ///< Pointer to tag buffer. + std::string tagType(); ///< Returns a std::string describing the tag in detail. + unsigned int tagTime(); ///< Returns the 32-bit timestamp of this tag. + void tagTime(unsigned int T); ///< Sets the 32-bit timestamp of this tag. + Tag(); ///< Constructor for a new, empty, tag. + Tag(const Tag& O); ///< Copy constructor, copies the contents of an existing tag. + Tag & operator= (const Tag& O); ///< Assignment operator - works exactly like the copy constructor. + //loader functions + bool MemLoader(char * D, unsigned int S, unsigned int & P); + bool SockLoader(int sock); + bool SockLoader(DDV::Socket sock); + bool FileLoader(FILE * f); + protected: + int buf; ///< Maximum length of buffer space. + //loader helper functions + bool MemReadUntil(char * buffer, unsigned int count, unsigned int & sofar, char * D, unsigned int S, unsigned int & P); + bool SockReadUntil(char * buffer, unsigned int count, unsigned int & sofar, DDV::Socket & sock); + bool FileReadUntil(char * buffer, unsigned int count, unsigned int & sofar, FILE * f); + };//Tag + +};//FLV namespace diff --git a/util/http_parser.cpp b/util/http_parser.cpp index 36557fbd..dd01e9b2 100644 --- a/util/http_parser.cpp +++ b/util/http_parser.cpp @@ -1,42 +1,5 @@ -#pragma once -#include "ddv_socket.cpp" -#include -#include -#include - -class HTTPReader{ - public: - HTTPReader(); - bool ReadSocket(int CONN_fd); - bool ReadSocket(FILE * F); - std::string GetHeader(std::string i); - std::string GetVar(std::string i); - void SetHeader(std::string i, std::string v); - void SetHeader(std::string i, int v); - void SetVar(std::string i, std::string v); - void SetBody(std::string s); - void SetBody(char * buffer, int len); - std::string BuildRequest(); - std::string BuildResponse(std::string code, std::string message); - void SendResponse(int conn, std::string code, std::string message); - void SendBodyPart(int conn, char * buffer, int len); - void SendBodyPart(int conn, std::string bodypart); - void Clean(); - bool CleanForNext(); - std::string body; - std::string method; - std::string url; - std::string protocol; - unsigned int length; - private: - bool seenHeaders; - bool seenReq; - bool parse(); - std::string HTTPbuffer; - std::map headers; - std::map vars; - void Trim(std::string & s); -};//HTTPReader +#include "http_parser.h" +#include "ddv_socket.h" HTTPReader::HTTPReader(){Clean();} void HTTPReader::Clean(){ @@ -127,29 +90,28 @@ void HTTPReader::SetVar(std::string i, std::string v){ vars[i] = v; } -bool HTTPReader::ReadSocket(int CONN_fd){ +bool HTTPReader::Read(DDV::Socket & sock){ //returned true als hele http packet gelezen is int r = 0; int b = 0; char buffer[500]; while (true){ - r = DDV_ready(CONN_fd); + r = sock.ready(); if (r < 1){ - if (r == 0){ - socketError = true; + if (r == -1){ #if DEBUG >= 1 fprintf(stderr, "User socket is disconnected.\n"); #endif } return parse(); } - b = DDV_iread(buffer, 500, CONN_fd); + b = sock.iread(buffer, 500); HTTPbuffer.append(buffer, b); } return false; }//HTTPReader::ReadSocket -bool HTTPReader::ReadSocket(FILE * F){ +bool HTTPReader::Read(FILE * F){ //returned true als hele http packet gelezen is int b = 1; char buffer[500]; @@ -210,23 +172,23 @@ bool HTTPReader::parse(){ return false; //we should never get here... }//HTTPReader::parse -void HTTPReader::SendResponse(int conn, std::string code, std::string message){ +void HTTPReader::SendResponse(DDV::Socket & conn, std::string code, std::string message){ std::string tmp = BuildResponse(code, message); - DDV_write(tmp.c_str(), tmp.size(), conn); + conn.write(tmp); } -void HTTPReader::SendBodyPart(int conn, char * buffer, int len){ +void HTTPReader::SendBodyPart(DDV::Socket & conn, char * buffer, int len){ std::string tmp; tmp.append(buffer, len); SendBodyPart(conn, tmp); } -void HTTPReader::SendBodyPart(int conn, std::string bodypart){ +void HTTPReader::SendBodyPart(DDV::Socket & conn, std::string bodypart){ static char len[10]; int sizelen; sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size()); - DDV_write(len, sizelen, conn); - DDV_write(bodypart.c_str(), bodypart.size(), conn); - DDV_write(len+sizelen-2, 2, conn); + conn.write(len, sizelen); + conn.write(bodypart); + conn.write(len+sizelen-2, 2); } diff --git a/util/http_parser.h b/util/http_parser.h new file mode 100644 index 00000000..2741f0c4 --- /dev/null +++ b/util/http_parser.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include +#include +#include "ddv_socket.h" + +class HTTPReader{ + public: + HTTPReader(); + bool Read(DDV::Socket & sock); + bool Read(FILE * F); + std::string GetHeader(std::string i); + std::string GetVar(std::string i); + void SetHeader(std::string i, std::string v); + void SetHeader(std::string i, int v); + void SetVar(std::string i, std::string v); + void SetBody(std::string s); + void SetBody(char * buffer, int len); + std::string BuildRequest(); + std::string BuildResponse(std::string code, std::string message); + void SendResponse(DDV::Socket & conn, std::string code, std::string message); + void SendBodyPart(DDV::Socket & conn, char * buffer, int len); + void SendBodyPart(DDV::Socket & conn, std::string bodypart); + void Clean(); + bool CleanForNext(); + std::string body; + std::string method; + std::string url; + std::string protocol; + unsigned int length; + private: + bool seenHeaders; + bool seenReq; + bool parse(); + std::string HTTPbuffer; + std::map headers; + std::map vars; + void Trim(std::string & s); +};//HTTPReader + diff --git a/util/server_setup.cpp b/util/server_setup.cpp index fbd93aa0..bd9e9390 100644 --- a/util/server_setup.cpp +++ b/util/server_setup.cpp @@ -1,23 +1,21 @@ -int mainHandler(int CONN_fd);//define this function in your own code! #include -#include "ddv_socket.cpp" //DDVTech Socket wrapper -#include "flv_sock.cpp" //FLV parsing with DDVTech Socket wrapper -int server_socket = 0; +#include "ddv_socket.h" //DDVTech Socket wrapper +#include "flv_tag.h" //FLV parsing with DDVTech Socket wrapper +DDV::ServerSocket server_socket(-1); void termination_handler (int signum){ - if (server_socket == 0) return; + if (!server_socket.connected()) return; switch (signum){ case SIGINT: break; case SIGHUP: break; case SIGTERM: break; default: return; break; } - close(server_socket); - server_socket = 0; + server_socket.close(); } int main(int argc, char ** argv){ - int CONN_fd = 0; + DDV::Socket CONN_fd(-1); //setup signal handler struct sigaction new_action; @@ -31,12 +29,14 @@ int main(int argc, char ** argv){ int listen_port = DEFAULT_PORT; bool daemon_mode = true; + std::string interface = "0.0.0.0"; int opt = 0; - static const char *optString = "np:h?"; + static const char *optString = "np:i:h?"; static const struct option longOpts[] = { {"help",0,0,'h'}, {"port",1,0,'p'}, + {"interface",1,0,'i'}, {"no-daemon",0,0,'n'} }; while ((opt = getopt_long(argc, argv, optString, longOpts, 0)) != -1){ @@ -44,6 +44,9 @@ int main(int argc, char ** argv){ case 'p': listen_port = atoi(optarg); break; + case 'i': + interface = optarg; + break; case 'n': daemon_mode = false; break; @@ -55,11 +58,11 @@ int main(int argc, char ** argv){ } } - server_socket = DDV_Listen(listen_port); + server_socket = DDV::ServerSocket(listen_port, interface); #if DEBUG >= 3 - fprintf(stderr, "Made a listening socket on port %i...\n", listen_port); + fprintf(stderr, "Made a listening socket on %s:%i...\n", interface.c_str(), listen_port); #endif - if (server_socket > 0){ + if (server_socket.connected()){ if (daemon_mode){ daemon(1, 0); #if DEBUG >= 3 @@ -73,22 +76,22 @@ int main(int argc, char ** argv){ return 1; } int status; - while (server_socket > 0){ + while (server_socket.connected()){ waitpid((pid_t)-1, &status, WNOHANG); - CONN_fd = DDV_Accept(server_socket); - if (CONN_fd > 0){ + CONN_fd = server_socket.accept(); + if (CONN_fd.connected()){ pid_t myid = fork(); if (myid == 0){ break; }else{ #if DEBUG >= 3 - fprintf(stderr, "Spawned new process %i for handling socket %i\n", (int)myid, CONN_fd); + fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, CONN_fd.getSocket()); #endif } } } - if (server_socket <= 0){ + if (!server_socket.connected()){ return 0; } - return mainHandler(CONN_fd); + return MAINHANDLER(CONN_fd); } From 2b22834fd859ab9f8b368a916338ac92c8b2cf94 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sat, 9 Apr 2011 03:24:29 +0200 Subject: [PATCH 02/24] Renamed all binaries with DDV_ prefix, updated Buffer for new systems, updated RAW connector, partly updated RTMP connector - ready for recode. All connectors now have host binding options. --- .gitignore | 9 +- Buffer/Makefile | 4 +- Buffer/main.cpp | 196 ++++++++++++++------------ Connector_HTTP/HTTP_Conn | 4 +- Connector_HTTP/Makefile | 2 +- Connector_RAW/Makefile | 4 +- Connector_RAW/main.cpp | 33 +++-- Connector_RTMP/Makefile | 4 +- Connector_RTMP/main.cpp | 293 +++++++++++++++++++-------------------- util/ddv_socket.cpp | 8 ++ util/ddv_socket.h | 1 + 11 files changed, 295 insertions(+), 263 deletions(-) diff --git a/.gitignore b/.gitignore index 755b1999..d81a8893 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ #ignore object files and nonsense like that *.[oa] Admin/main -Connector_HTTP/Connector_HTTP -Buffer/Buffer -Connector_RTMP/Connector_RTMP -Connector_RTSP/Connector_RTSP +Connector_HTTP/DDV_Conn_HTTP +Buffer/DDV_Buffer +Connector_RTMP/DDV_Conn_RTMP +Connector_RTSP/DDV_Conn_RTSP +Connector_RAW/DDV_Conn_RAW *~ bin/* docs diff --git a/Buffer/Makefile b/Buffer/Makefile index beae6c91..63cc2524 100644 --- a/Buffer/Makefile +++ b/Buffer/Makefile @@ -1,6 +1,6 @@ -SRC = main.cpp +SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp OBJ = $(SRC:.cpp=.o) -OUT = Buffer +OUT = DDV_Buffer INCLUDES = CCFLAGS = -Wall -Wextra -funsigned-char -g CC = $(CROSS)g++ diff --git a/Buffer/main.cpp b/Buffer/main.cpp index 344ddb9d..10eee8b1 100644 --- a/Buffer/main.cpp +++ b/Buffer/main.cpp @@ -7,11 +7,13 @@ #include #include #include -#include "../util/flv.cpp" //FLV format parser -#include "../util/ddv_socket.cpp" //DDV Socket lib +#include "../util/flv_tag.h" //FLV format parser +#include "../util/ddv_socket.h" //DDV Socket lib #include +namespace Buffer{ + void termination_handler (int signum){ switch (signum){ case SIGPIPE: return; break; @@ -19,81 +21,90 @@ void termination_handler (int signum){ } } - +///holds FLV::Tag objects and their numbers struct buffer{ int number; - bool iskeyframe; - FLV_Pack * FLV; - buffer(){ - number = -1; - iskeyframe = false; - FLV = 0; - }//constructor + FLV::Tag FLV; };//buffer +/// Holds connected users. +/// Keeps track of what buffer users are using and the connection status. class user{ public: - int MyBuffer; - int MyBuffer_num; - int MyBuffer_len; - int MyNum; - int currsend; - bool gotproperaudio; - void * lastpointer; - static int UserCount; - int s; - user(int fd){ - s = fd; + int MyBuffer; ///< Index of currently used buffer. + int MyBuffer_num; ///< Number of currently used buffer. + int MyBuffer_len; ///< Length in bytes of currently used buffer. + int MyNum; ///< User ID of this user. + int currsend; ///< Current amount of bytes sent. + bool gotproperaudio; ///< Whether the user received proper audio yet. + void * lastpointer; ///< Pointer to data part of current buffer. + static int UserCount; ///< Global user counter. + DDV::Socket S; ///< Connection to user + /// Creates a new user from a newly connected socket. + /// Also prints "User connected" text to stdout. + user(DDV::Socket fd){ + S = fd; MyNum = UserCount++; gotproperaudio = false; std::cout << "User " << MyNum << " connected" << std::endl; }//constructor + /// Disconnects the current user. Doesn't do anything if already disconnected. + /// Prints "Disconnected user" to stdout if disconnect took place. void Disconnect(std::string reason) { - if (s != -1) { - close(s); - s = -1; + if (S.connected()) { + S.close(); std::cout << "Disconnected user " << MyNum << ": " << reason << std::endl; } }//Disconnect - bool doSend(char * buffer, int todo){ - int r = send(s, buffer+currsend, todo-currsend, 0); + /// Tries to send the current buffer, returns true if success, false otherwise. + /// Has a side effect of dropping the connection if send will never complete. + bool doSend(){ + int r = S.iwrite((char*)lastpointer+currsend, MyBuffer_len-currsend); if (r <= 0){ if ((r < 0) && (errno == EWOULDBLOCK)){return false;} Disconnect("Connection closed"); return false; } currsend += r; - return (currsend == todo); - } + return (currsend == MyBuffer_len); + }//doSend + /// Try to send data to this user. Disconnects if any problems occur. + /// \param ringbuf Array of buffers (FLV:Tag with ID attached) + /// \param buffers Count of elements in ringbuf void Send(buffer ** ringbuf, int buffers){ //TODO: Bij MP3: gotproperaudio - if false, stuur alleen als eerste byte is 0xFF en set op true - //not connected? cancel - if (s < 0){return;} + if (!S.connected()){return;}//cancel if not connected + //still waiting for next buffer? check it if (MyBuffer_num < 0){ MyBuffer_num = ringbuf[MyBuffer]->number; - //still waiting? don't crash - wait longer. if (MyBuffer_num < 0){ - return; + return; //still waiting? don't crash - wait longer. }else{ - MyBuffer_len = ringbuf[MyBuffer]->FLV->len; - lastpointer = ringbuf[MyBuffer]->FLV->data; + MyBuffer_len = ringbuf[MyBuffer]->FLV.len; + lastpointer = ringbuf[MyBuffer]->FLV.data; } } - if (lastpointer != ringbuf[MyBuffer]->FLV->data){ + + //do check for buffer resizes + if (lastpointer != ringbuf[MyBuffer]->FLV.data){ Disconnect("Buffer resize at wrong time... had to disconnect"); return; } - if (doSend(ringbuf[MyBuffer]->FLV->data, MyBuffer_len)){ - //completed a send - switch to next buffer + + //try to complete a send + if (doSend()){ + //switch to next buffer if ((ringbuf[MyBuffer]->number != MyBuffer_num)){ + //if corrupt data, warn and find keyframe std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl; int nocrashcount = 0; do{ MyBuffer++; nocrashcount++; MyBuffer %= buffers; - }while(!ringbuf[MyBuffer]->FLV->isKeyframe && (nocrashcount < buffers)); + }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; @@ -110,25 +121,27 @@ class user{ }; int user::UserCount = 0; -int main( int argc, char * argv[] ) { +/// 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); - - if (argc < 2) { - std::cout << "usage: " << argv[0] << " buffers_count [streamname]" << std::endl; + + //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"; - if (argc > 2){ - shared_socket = argv[2]; - shared_socket = "/tmp/shared_socket_" + shared_socket; - } + std::string shared_socket = "/tmp/shared_socket_"; + shared_socket += argv[2]; - int metabuflen = 0; - char * metabuffer = 0; + DDV::ServerSocket SS(shared_socket, true); + FLV::Tag metadata; + FLV::Tag video_init; + FLV::Tag audio_init; int buffers = atoi(argv[1]); buffer ** ringbuf = (buffer**) calloc (buffers,sizeof(buffer*)); std::vector users; @@ -137,14 +150,15 @@ int main( int argc, char * argv[] ) { int current_buffer = 0; int lastproper = 0;//last properly finished buffer number unsigned int loopcount = 0; - int listener = DDV_UnixListen(shared_socket, true); - int incoming = 0; + DDV::Socket incoming; unsigned char packtype; bool gotVideoInfo = false; bool gotAudioInfo = false; - int infile = fileno(stdin); + 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; @@ -153,54 +167,46 @@ int main( int argc, char * argv[] ) { struct epoll_event events[1]; - while(!feof(stdin) && !All_Hell_Broke_Loose){ + while(!feof(stdin) && !FLV::Parse_Error){ //invalidate the current buffer ringbuf[current_buffer]->number = -1; - if ((epoll_wait(poller, events, 1, 10) > 0) && FLV_GetPacket(ringbuf[current_buffer]->FLV)){ + if ((epoll_wait(poller, events, 1, 10) > 0) && ringbuf[current_buffer]->FLV.FileLoader(stdin)){ loopcount++; - packtype = ringbuf[current_buffer]->FLV->data[0]; + packtype = ringbuf[current_buffer]->FLV.data[0]; //store metadata, if available if (packtype == 0x12){ - metabuflen = ringbuf[current_buffer]->FLV->len; - metabuffer = (char*)realloc(metabuffer, metabuflen); - memcpy(metabuffer, ringbuf[current_buffer]->FLV->data, metabuflen); + metadata = ringbuf[current_buffer]->FLV; std::cout << "Received metadata!" << std::endl; if (gotVideoInfo && gotAudioInfo){ - All_Hell_Broke_Loose = true; + FLV::Parse_Error = true; std::cout << "... after proper video and audio? Cancelling broadcast!" << std::endl; } gotVideoInfo = false; gotAudioInfo = false; } - 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->data[4] = 0;//timestamp to zero - ringbuf[current_buffer]->FLV->data[5] = 0;//timestamp to zero - ringbuf[current_buffer]->FLV->data[6] = 0;//timestamp to zero - metabuffer = (char*)realloc(metabuffer, metabuflen + ringbuf[current_buffer]->FLV->len); - memcpy(metabuffer+metabuflen, ringbuf[current_buffer]->FLV->data, ringbuf[current_buffer]->FLV->len); - metabuflen += ringbuf[current_buffer]->FLV->len; + //store video init data, if available + if (!gotVideoInfo && ringbuf[current_buffer]->FLV.isKeyframe){ + if ((ringbuf[current_buffer]->FLV.data[11] & 0x0f) == 7){//avc packet + if (ringbuf[current_buffer]->FLV.data[12] == 0){ + ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero + video_init = ringbuf[current_buffer]->FLV; gotVideoInfo = true; std::cout << "Received video configuration!" << std::endl; } }else{gotVideoInfo = true;}//non-avc = no config... } + //store audio init data, if available if (!gotAudioInfo && (packtype == 0x08)){ - if (((ringbuf[current_buffer]->FLV->data[11] & 0xf0) >> 4) == 10){//aac packet - ringbuf[current_buffer]->FLV->data[4] = 0;//timestamp to zero - ringbuf[current_buffer]->FLV->data[5] = 0;//timestamp to zero - ringbuf[current_buffer]->FLV->data[6] = 0;//timestamp to zero - metabuffer = (char*)realloc(metabuffer, metabuflen + ringbuf[current_buffer]->FLV->len); - memcpy(metabuffer+metabuflen, ringbuf[current_buffer]->FLV->data, ringbuf[current_buffer]->FLV->len); - metabuflen += ringbuf[current_buffer]->FLV->len; + 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 start point + //on keyframe set possible start point if (packtype == 0x09){ - if (((ringbuf[current_buffer]->FLV->data[11] & 0xf0) >> 4) == 1){ + if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 1){ lastproper = current_buffer; } } @@ -209,29 +215,35 @@ int main( int argc, char * argv[] ) { current_buffer++; current_buffer %= buffers; } - + //check for new connections, accept them if there are any - incoming = DDV_Accept(listener, true); - if (incoming >= 0){ + incoming = SS.accept(true); + if (incoming.connected()){ users.push_back(incoming); //send the FLV header users.back().currsend = 0; users.back().MyBuffer = lastproper; users.back().MyBuffer_num = -1; //TODO: Do this more nicely? - if (!DDV_write(FLVHeader, 13, incoming)){ + if (!incoming.write(FLV::Header, 13)){ users.back().Disconnect("failed to receive the header!"); }else{ - if (!DDV_write(metabuffer, metabuflen, incoming)){ + 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 == -1){ + if (!(*usersIt).S.connected()){ users.erase(usersIt); break; }else{ (*usersIt).Send(ringbuf, buffers); @@ -241,13 +253,23 @@ int main( int argc, char * argv[] ) { }//main loop // disconnect listener - std::cout << "Reached EOF of input" << std::endl; - close(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 == -1){users.erase(usersIt);break;} + if (!(*usersIt).S.connected()){users.erase(usersIt);break;} } } return 0; } + +};//Buffer namespace + +int main(int argc, char ** argv){ + Buffer::Start(argc, argv); +}//main diff --git a/Connector_HTTP/HTTP_Conn b/Connector_HTTP/HTTP_Conn index e06cbc29..9ad03e7f 100755 --- a/Connector_HTTP/HTTP_Conn +++ b/Connector_HTTP/HTTP_Conn @@ -3,8 +3,8 @@ # description: DDVTech HTTP Connector # processname: Connector_HTTP -prog="Connector_HTTP" -fullprog="/usr/bin/Connector_HTTP" +prog="DDV_Conn_HTTP" +fullprog="/usr/bin/DDV_Conn_HTTP" RETVAL=0 start() { diff --git a/Connector_HTTP/Makefile b/Connector_HTTP/Makefile index 22ef3c3f..56ba16e1 100644 --- a/Connector_HTTP/Makefile +++ b/Connector_HTTP/Makefile @@ -1,6 +1,6 @@ SRC = main.cpp ../util/ddv_socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp OBJ = $(SRC:.cpp=.o) -OUT = Connector_HTTP +OUT = DDV_Conn_HTTP INCLUDES = CCFLAGS = -Wall -Wextra -funsigned-char -g CC = $(CROSS)g++ diff --git a/Connector_RAW/Makefile b/Connector_RAW/Makefile index fc992f72..aaf541a1 100644 --- a/Connector_RAW/Makefile +++ b/Connector_RAW/Makefile @@ -1,6 +1,6 @@ -SRC = main.cpp ../sockets/sw_base.cpp ../sockets/sw_inet.cpp ../sockets/sw_unix.cpp +SRC = main.cpp ../util/ddv_socket.cpp OBJ = $(SRC:.cpp=.o) -OUT = Connector_RAW +OUT = DDV_Conn_RAW INCLUDES = CCFLAGS = -Wall -Wextra -funsigned-char -g CC = $(CROSS)g++ diff --git a/Connector_RAW/main.cpp b/Connector_RAW/main.cpp index 12bb6638..15f2b9b9 100644 --- a/Connector_RAW/main.cpp +++ b/Connector_RAW/main.cpp @@ -1,5 +1,5 @@ #include -#include "../sockets/SocketW.h" +#include "../util/ddv_socket.h" #include #include #include @@ -7,19 +7,24 @@ #include #include -int main() { - SWUnixSocket mySocket; - std::string input; - std::cin >> input; - input = "/tmp/shared_socket_"+input; - mySocket.connect(input); - char buffer[500000]; - int msg; - while(std::cout.good()) { - msg = mySocket.recv(&buffer[0],10000); - std::cout.write(buffer,msg); +int main(int argc, char ** argv) { + if (argc < 2){ + std::cout << "Usage: " << argv[0] << " stream_name" << std::endl; + return 1; } - // disconnect - mySocket.disconnect(); + std::string input; + input = "/tmp/shared_socket_"; + input += argv[1]; + DDV::Socket S(input); + if (!S.connected()){ + std::cout << "Could not open stream " << argv[1] << std::endl; + return 1; + } + char buffer[50000]; + int msg; + while(std::cout.good() && S.read(buffer,50000)){ + std::cout.write(buffer,50000); + } + S.close(); return 0; } diff --git a/Connector_RTMP/Makefile b/Connector_RTMP/Makefile index 71f3ffb0..f33e7118 100644 --- a/Connector_RTMP/Makefile +++ b/Connector_RTMP/Makefile @@ -1,6 +1,6 @@ -SRC = main.cpp +SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp OBJ = $(SRC:.cpp=.o) -OUT = Connector_RTMP +OUT = DDV_Conn_RTMP INCLUDES = STATIC = CCFLAGS = -Wall -Wextra -funsigned-char -g diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index e7e246c0..8a70bec0 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -16,164 +16,159 @@ #include #include #include +#include "../util/ddv_socket.h" +#include "../util/flv_tag.h" -//for connection to server -bool ready4data = false;//set to true when streaming starts -bool inited = false; -bool stopparsing = false; -timeval lastrec; - -#define DEFAULT_PORT 1935 -#include "../util/server_setup.cpp" - -int CONN_fd = 0; #include "parsechunks.cpp" //chunkstream parsing #include "handshake.cpp" //handshaking -int mainHandler(int connection){ - CONN_fd = connection; - unsigned int ts; - unsigned int fts = 0; - unsigned int ftst; - int ss; - FLV_Pack * tag = 0; +/// Holds all functions and data unique to the RTMP Connector +namespace Connector_RTMP{ - //first timestamp set - firsttime = getNowMS(); + //for connection to server + bool ready4data = false; ///< Set to true when streaming starts. + bool inited = false; ///< Set to true when ready to connect to Buffer. + bool stopparsing = false; ///< Set to true when all parsing needs to be cancelled. + timeval lastrec; ///< Timestamp of last received data. - if (doHandshake()){ - #if DEBUG >= 4 - fprintf(stderr, "Handshake succcess!\n"); + DDV::Socket Socket; ///< Socket connected to user + + /// Main Connector_RTMP function + int Connector_RTMP(DDV::Socket conn){ + Socket = conn; + unsigned int ts; + unsigned int fts = 0; + unsigned int ftst; + DDV::Socket SS; + FLV::Tag tag = 0; + + //first timestamp set + firsttime = getNowMS(); + + if (doHandshake()){ + #if DEBUG >= 4 + fprintf(stderr, "Handshake succcess!\n"); + #endif + }else{ + #if DEBUG >= 1 + fprintf(stderr, "Handshake fail!\n"); + #endif + return 0; + } + + int retval; + int poller = epoll_create(1); + int sspoller = epoll_create(1); + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = CONN_fd; + epoll_ctl(poller, EPOLL_CTL_ADD, CONN_fd, &ev); + struct epoll_event events[1]; + #if DEBUG >= 5 + //for writing whole stream to a file + FILE * tmpfile = 0; + char tmpstr[200]; + #endif + + while (Socket.connected() && !FLV::Parse_Error){ + //only parse input if available or not yet init'ed + //rightnow = getNowMS(); + retval = epoll_wait(poller, events, 1, 1); + if ((retval > 0) || !ready4data){// || (snd_cnt - snd_window_at >= snd_window_size) + switch (Socket.ready()){ + case -1: break; //disconnected + case 0: break; //not ready yet + default: parseChunk(); break; //new data is waiting + } + } + if (ready4data){ + if (!inited){ + //we are ready, connect the socket! + SS = DDV::Socket(streamname); + if (!SS.connected()){ + #if DEBUG >= 1 + fprintf(stderr, "Could not connect to server!\n"); + #endif + Socket.close();//disconnect user + break; + } + ev.events = EPOLLIN; + ev.data.fd = SS.getSocket(); + epoll_ctl(sspoller, EPOLL_CTL_ADD, SS.getSocket(), &ev); + #if DEBUG >= 3 + fprintf(stderr, "Everything connected, starting to send video data...\n"); + #endif + inited = true; + } + + 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 - }else{ #if DEBUG >= 1 - fprintf(stderr, "Handshake fail!\n"); + if (FLV::Parse_Error){fprintf(stderr, "FLV Parse Error\n");} + fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); + if (inited){ + fprintf(stderr, "Status was: inited\n"); + }else{ + if (ready4data){ + fprintf(stderr, "Status was: ready4data\n"); + }else{ + fprintf(stderr, "Status was: connected\n"); + } + } #endif return 0; - } + }//Connector_RTMP - int retval; - int poller = epoll_create(1); - int sspoller = epoll_create(1); - struct epoll_event ev; - ev.events = EPOLLIN; - ev.data.fd = CONN_fd; - epoll_ctl(poller, EPOLL_CTL_ADD, CONN_fd, &ev); - struct epoll_event events[1]; - #if DEBUG >= 5 - //for writing whole stream to a file - FILE * tmpfile = 0; - char tmpstr[200]; - #endif - - while (!socketError && !All_Hell_Broke_Loose){ - //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 (DDV_ready(CONN_fd)){ - case 0: - socketError = true; - #if DEBUG >= 1 - fprintf(stderr, "User socket is disconnected.\n"); - #endif - break; - case -1: break;//not ready yet - default: - parseChunk(); - break; - } - } - if (ready4data){ - if (!inited){ - //we are ready, connect the socket! - ss = DDV_OpenUnix(streamname); - if (ss <= 0){ - #if DEBUG >= 1 - fprintf(stderr, "Could not connect to server!\n"); - #endif - socketError = 1; - break; - } - ev.events = EPOLLIN; - ev.data.fd = ss; - epoll_ctl(sspoller, EPOLL_CTL_ADD, ss, &ev); - #if DEBUG >= 3 - fprintf(stderr, "Everything connected, starting to send video data...\n"); - #endif - inited = true; - } +};//Connector_RTMP namespace - retval = epoll_wait(sspoller, events, 1, 1); - switch (DDV_ready(ss)){ - case 0: - socketError = true; - #if DEBUG >= 1 - fprintf(stderr, "Source socket is disconnected.\n"); - #endif - break; - case -1: break;//not ready yet - default: - if (FLV_GetPacket(tag, ss)){//able to read a full packet? - ts = tag->data[7] * 256*256*256; - ts += tag->data[4] * 256*256; - ts += tag->data[5] * 256; - ts += tag->data[6]; - if (ts != 0){ - if (fts == 0){fts = ts;ftst = getNowMS();} - ts -= fts; - tag->data[7] = ts / (256*256*256); - tag->data[4] = ts / (256*256); - tag->data[5] = ts / 256; - tag->data[6] = ts % 256; - ts += ftst; - }else{ - ftst = getNowMS(); - tag->data[7] = ftst / (256*256*256); - tag->data[4] = ftst / (256*256); - tag->data[5] = ftst / 256; - tag->data[6] = ftst % 256; - } - 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) - } - } - close(CONN_fd); - #if DEBUG >= 5 - fclose(tmpfile); - #endif - if (inited) close(ss); - #if DEBUG >= 1 - if (All_Hell_Broke_Loose){fprintf(stderr, "All Hell Broke Loose\n");} - fprintf(stderr, "User %i disconnected.\n", CONN_fd); - 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; -}//mainHandler +// Load main server setup file, default port 1935, handler is Connector_RTMP::Connector_RTMP +#define DEFAULT_PORT 1935 +#define MAINHANDLER Connector_RTMP::Connector_RTMP +#include "../util/server_setup.cpp" diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index 2835a5cf..6c5027b7 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -8,6 +8,14 @@ DDV::Socket::Socket(int sockNo){ Blocking = false; }//DDV::Socket basic constructor +/// Create a new disconnected base socket. This is a basic constructor for placeholder purposes. +/// A socket created like this is always disconnected and should/could be overwritten at some point. +DDV::Socket::Socket(){ + sock = -1; + Error = false; + Blocking = false; +}//DDV::Socket basic constructor + /// Close connection. The internal socket is closed and then set to -1. void DDV::Socket::close(){ #if DEBUG >= 3 diff --git a/util/ddv_socket.h b/util/ddv_socket.h index cc9421ae..298b5954 100644 --- a/util/ddv_socket.h +++ b/util/ddv_socket.h @@ -19,6 +19,7 @@ namespace DDV{ private: int sock; ///< Internally saved socket number. public: + Socket(); ///< Create a new disconnected base socket. Socket(int sockNo); ///< Create a new base socket. Socket(std::string adres, bool nonblock = false); ///< Create a new Unix Socket. bool Error; ///< Set to true if a socket error happened. From 1b86b9a5efcfc462b36ad8602c666b228392575f Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 10 Apr 2011 20:02:04 +0200 Subject: [PATCH 03/24] RAW connector cleanup, added configfile support, added setuid support, added some more comments other places --- Connector_HTTP/main.cpp | 1 + Connector_RAW/main.cpp | 14 +--- Connector_RTMP/main.cpp | 1 + util/ddv_socket.cpp | 2 + util/server_setup.cpp | 142 ++++++++++++++++++++++++++++++---------- 5 files changed, 114 insertions(+), 46 deletions(-) diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index ea43a9ba..67f21841 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -354,4 +354,5 @@ namespace Connector_HTTP{ // Load main server setup file, default port 8080, handler is Connector_HTTP::Connector_HTTP #define DEFAULT_PORT 8080 #define MAINHANDLER Connector_HTTP::Connector_HTTP +#define CONFIGSECT HTTP #include "../util/server_setup.cpp" diff --git a/Connector_RAW/main.cpp b/Connector_RAW/main.cpp index 15f2b9b9..51898ec0 100644 --- a/Connector_RAW/main.cpp +++ b/Connector_RAW/main.cpp @@ -1,19 +1,12 @@ #include #include "../util/ddv_socket.h" -#include -#include -#include -#include -#include -#include int main(int argc, char ** argv) { if (argc < 2){ std::cout << "Usage: " << argv[0] << " stream_name" << std::endl; return 1; } - std::string input; - input = "/tmp/shared_socket_"; + std::string input = "/tmp/shared_socket_"; input += argv[1]; DDV::Socket S(input); if (!S.connected()){ @@ -21,10 +14,7 @@ int main(int argc, char ** argv) { return 1; } char buffer[50000]; - int msg; - 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(); return 0; } diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 8a70bec0..3cc33d29 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -171,4 +171,5 @@ namespace Connector_RTMP{ // Load main server setup file, default port 1935, handler is Connector_RTMP::Connector_RTMP #define DEFAULT_PORT 1935 #define MAINHANDLER Connector_RTMP::Connector_RTMP +#define CONFIGSECT RTMP #include "../util/server_setup.cpp" diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index 6c5027b7..76c2ee8a 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -309,6 +309,8 @@ DDV::ServerSocket::ServerSocket(std::string address, bool nonblock){ DDV::Socket DDV::ServerSocket::accept(bool nonblock){ if (sock < 0){return DDV::Socket(-1);} int r = ::accept(sock, 0, 0); + //set the socket to be nonblocking, if requested. + //we could do this through accept4 with a flag, but that call is non-standard... if ((r >= 0) && nonblock){ int flags = fcntl(r, F_GETFL, 0); flags |= O_NONBLOCK; diff --git a/util/server_setup.cpp b/util/server_setup.cpp index bd9e9390..daa9e6c6 100644 --- a/util/server_setup.cpp +++ b/util/server_setup.cpp @@ -1,9 +1,18 @@ #include #include "ddv_socket.h" //DDVTech Socket wrapper #include "flv_tag.h" //FLV parsing with DDVTech Socket wrapper +#include +#include +#include +#define defstr(x) #x //converts a define name to string +#define defstrh(x) "[" defstr(x) "]" //converts define name to [string] DDV::ServerSocket server_socket(-1); -void termination_handler (int signum){ +/// Basic signal handler. Disconnects the server_socket if it receives +/// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE. +/// Disconnecting the server_socket will terminate the main listening loop +/// and cleanly shut down the process. +void signal_handler (int signum){ if (!server_socket.connected()) return; switch (signum){ case SIGINT: break; @@ -12,57 +21,104 @@ void termination_handler (int signum){ default: return; break; } server_socket.close(); -} +}//signal_handler +/// Generic main entry point and loop for DDV Connectors. +/// This sets up the proper termination handler, checks commandline options, +/// parses config files and opens a listening socket on the requested port. +/// Any incoming connections will be accepted and start up the function MAINHANDLER, +/// which should be #defined before including server_setup.cpp. +/// The default port is set by #define DEFAULT_PORT. +/// The configuration file section is set by #define CONFIGSECT. int main(int argc, char ** argv){ - DDV::Socket CONN_fd(-1); - + DDV::Socket S;//placeholder for incoming connections + //setup signal handler struct sigaction new_action; - new_action.sa_handler = termination_handler; + new_action.sa_handler = signal_handler; sigemptyset (&new_action.sa_mask); new_action.sa_flags = 0; sigaction(SIGINT, &new_action, NULL); sigaction(SIGHUP, &new_action, NULL); sigaction(SIGTERM, &new_action, NULL); sigaction(SIGPIPE, &new_action, NULL); - + + //default values int listen_port = DEFAULT_PORT; bool daemon_mode = true; std::string interface = "0.0.0.0"; + std::string configfile = "/etc/ddvtech.conf"; + std::string username = "root"; + bool ignore_daemon = false; + bool ignore_interface = false; + bool ignore_port = false; + bool ignore_user = false; int opt = 0; - static const char *optString = "np:i:h?"; + static const char *optString = "ndp:i:u:c:h?"; static const struct option longOpts[] = { {"help",0,0,'h'}, {"port",1,0,'p'}, {"interface",1,0,'i'}, - {"no-daemon",0,0,'n'} + {"username",1,0,'u'}, + {"no-daemon",0,0,'n'}, + {"daemon",0,0,'d'}, + {"configfile",1,0,'c'} }; while ((opt = getopt_long(argc, argv, optString, longOpts, 0)) != -1){ switch (opt){ - case 'p': - listen_port = atoi(optarg); - break; - case 'i': - interface = optarg; - break; - case 'n': - daemon_mode = false; - break; + case 'p': listen_port = atoi(optarg); ignore_port = true; break; + case 'i': interface = optarg; ignore_interface = true; break; + case 'n': daemon_mode = false; ignore_daemon = true; break; + case 'd': daemon_mode = true; ignore_daemon = true; break; + case 'c': configfile = optarg; break; + case 'u': username = optarg; ignore_user = true; break; case 'h': case '?': - printf("Options: -h[elp], -?, -n[o-daemon], -p[ort] #\n"); + printf("Options: -h[elp], -?, -n[odaemon], -d[aemon], -p[ort] VAL, -i[nterface] VAL, -c[onfigfile] VAL, -u[sername] VAL\n"); + printf("Defaults:\n interface: 0.0.0.0\n port: %i\n daemon mode: true\n configfile: /etc/ddvtech.conf\n username: root\n", listen_port); + printf("Username root means no change to UID, no matter what the UID is.\n"); + printf("If the configfile exists, it is always loaded first. Commandline settings then overwrite the config file.\n"); + printf("\nThis process takes it directives from the %s section of the configfile.\n", defstrh(CONFIGSECT)); return 1; break; } - } - + }//commandline options parser + + std::ifstream conf(configfile.c_str(), std::ifstream::in); + std::string tmpstr; + bool acc_comm = false; + size_t foundeq; + if (conf.fail()){ + #if DEBUG >= 3 + fprintf(stderr, "Configuration file %s not found - using build-in defaults...\n", configfile.c_str()); + #endif + }else{ + while (conf.good()){ + getline(conf, tmpstr); + if (tmpstr[0] == '['){//new section? check if we care. + if (tmpstr == defstrh(CONFIGSECT)){acc_comm = true;}else{acc_comm = false;} + }else{ + if (!acc_comm){break;}//skip all lines in this section if we do not care about it + foundeq = tmpstr.find('='); + if (foundeq != std::string::npos){ + if ((tmpstr.substr(0, foundeq) == "port") && !ignore_port){listen_port = atoi(tmpstr.substr(foundeq+1).c_str());} + if ((tmpstr.substr(0, foundeq) == "interface") && !ignore_interface){interface = tmpstr.substr(foundeq+1);} + if ((tmpstr.substr(0, foundeq) == "username") && !ignore_user){username = tmpstr.substr(foundeq+1);} + if ((tmpstr.substr(0, foundeq) == "daemon") && !ignore_daemon){daemon_mode = true;} + if ((tmpstr.substr(0, foundeq) == "nodaemon") && !ignore_daemon){daemon_mode = false;} + }//found equals sign + }//section contents + }//configfile line loop + }//configuration + + //setup a new server socket, for the correct interface and port server_socket = DDV::ServerSocket(listen_port, interface); #if DEBUG >= 3 fprintf(stderr, "Made a listening socket on %s:%i...\n", interface.c_str(), listen_port); #endif if (server_socket.connected()){ + //if setup success, enter daemon mode if requested if (daemon_mode){ daemon(1, 0); #if DEBUG >= 3 @@ -75,23 +131,41 @@ int main(int argc, char ** argv){ #endif return 1; } - int status; - while (server_socket.connected()){ - waitpid((pid_t)-1, &status, WNOHANG); - CONN_fd = server_socket.accept(); - if (CONN_fd.connected()){ - pid_t myid = fork(); - if (myid == 0){ - break; + + if (username != "root"){ + struct passwd * user_info = getpwnam(username.c_str()); + if (!user_info){ + #if DEBUG >= 1 + fprintf(stderr, "Error: could not setuid %s: could not get PID\n", username.c_str()); + #endif + return 1; + }else{ + if (setuid(user_info->pw_uid) != 0){ + #if DEBUG >= 1 + fprintf(stderr, "Error: could not setuid %s: not allowed\n", username.c_str()); + #endif }else{ #if DEBUG >= 3 - fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, CONN_fd.getSocket()); + fprintf(stderr, "Changed user to %s\n", username.c_str()); #endif } } } - if (!server_socket.connected()){ - return 0; - } - return MAINHANDLER(CONN_fd); -} + + int status; + while (server_socket.connected()){ + while (waitpid((pid_t)-1, &status, WNOHANG) > 0){}//clean up all child processes + S = server_socket.accept(); + if (S.connected()){//check if the new connection is valid + pid_t myid = fork(); + if (myid == 0){//if new child, start MAINHANDLER + return MAINHANDLER(S); + }else{//otherwise, do nothing or output debugging text + #if DEBUG >= 3 + fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket()); + #endif + } + } + }//while connected + return 0; +}//main From fab932253dfc9b5c9031a223feb81596e905f559 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 10 Apr 2011 22:25:51 +0200 Subject: [PATCH 04/24] Documented and nicified AMF parsing. Updated all existing code for this change, except for RTMP connector (which gets a rewrite, like, today). --- AMF_Tester/Makefile | 4 +- AMF_Tester/amf.cpp | 1 - AMF_Tester/main.cpp | 14 +- Connector_HTTP/Makefile | 2 +- Connector_HTTP/amf.cpp | 1 - Connector_HTTP/main.cpp | 70 ++--- Connector_RTMP/Makefile | 4 +- Connector_RTMP/amf.cpp | 1 - util/amf.cpp | 580 ++++++++++++++++++++-------------------- util/amf.h | 68 +++++ 10 files changed, 404 insertions(+), 341 deletions(-) delete mode 120000 AMF_Tester/amf.cpp delete mode 120000 Connector_HTTP/amf.cpp delete mode 120000 Connector_RTMP/amf.cpp create mode 100644 util/amf.h diff --git a/AMF_Tester/Makefile b/AMF_Tester/Makefile index bae68b0d..6695539c 100644 --- a/AMF_Tester/Makefile +++ b/AMF_Tester/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp +SRC = main.cpp ../util/amf.cpp OBJ = $(SRC:.cpp=.o) OUT = AMFtest INCLUDES = @@ -12,7 +12,7 @@ LIBS = -lssl -lcrypto default: $(OUT) .cpp.o: $(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ -$(OUT): $(OBJ) amf.cpp +$(OUT): $(OBJ) $(CC) $(LIBS) -o $(OUT) $(OBJ) clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ diff --git a/AMF_Tester/amf.cpp b/AMF_Tester/amf.cpp deleted file mode 120000 index 20bd6c3e..00000000 --- a/AMF_Tester/amf.cpp +++ /dev/null @@ -1 +0,0 @@ -../util/amf.cpp \ No newline at end of file diff --git a/AMF_Tester/main.cpp b/AMF_Tester/main.cpp index 6b271c3e..5f6fe992 100644 --- a/AMF_Tester/main.cpp +++ b/AMF_Tester/main.cpp @@ -1,18 +1,16 @@ -#define DEBUG 10 //maximum debugging level evah +#define DEBUG 10 //maximum debugging level #include #include #include #include -#include "amf.cpp" +#include "../util/amf.h" int main() { std::string temp; - while( std::cin.good() ) { - temp += std::cin.get(); - } - static AMFType amfdata("empty", (unsigned char)AMF0_DDV_CONTAINER); - amfdata = parseAMF( (const unsigned char*)temp.c_str(), temp.length()-1 ); - amfdata.Print( ); + while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp + temp.erase(temp.size()-1, 1);//strip the invalid last character + AMF::Object amfdata = AMF::parse(temp);//parse temp into an AMF::Object + amfdata.Print();//pretty-print the object return 0; } diff --git a/Connector_HTTP/Makefile b/Connector_HTTP/Makefile index 56ba16e1..3ce05f27 100644 --- a/Connector_HTTP/Makefile +++ b/Connector_HTTP/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/ddv_socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp +SRC = main.cpp ../util/ddv_socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Conn_HTTP INCLUDES = diff --git a/Connector_HTTP/amf.cpp b/Connector_HTTP/amf.cpp deleted file mode 120000 index 20bd6c3e..00000000 --- a/Connector_HTTP/amf.cpp +++ /dev/null @@ -1 +0,0 @@ -../util/amf.cpp \ No newline at end of file diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index 67f21841..ee517cf2 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -21,7 +21,7 @@ #include "../util/http_parser.h" #include "../util/flv_tag.h" #include "../util/MP4/interface.cpp" -#include "amf.cpp" +#include "../util/amf.h" /// Holds everything unique to HTTP Connector. namespace Connector_HTTP{ @@ -57,40 +57,40 @@ namespace Connector_HTTP{ /// Returns AMF-format metadata for Adobe HTTP Dynamic Streaming. std::string GetMetaData( ) { - AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER); - amfreply.addContent(AMFType("onMetaData",(unsigned char)AMF0_STRING)); - amfreply.addContent(AMFType("",(unsigned char)AMF0_ECMA_ARRAY)); - amfreply.getContentP(1)->addContent(AMFType("trackinfo", (unsigned char)AMF0_STRICT_ARRAY)); - amfreply.getContentP(1)->getContentP(0)->addContent(AMFType("arrVal")); - //amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("timescale",(double)1000)); - //amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("length",(double)59641700)); - amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("language","eng")); - amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMFType("sampledescription", (unsigned char)AMF0_STRICT_ARRAY)); - amfreply.getContentP(1)->getContentP(0)->getContentP(0)->getContentP(1)->addContent(AMFType("arrVal")); - amfreply.getContentP(1)->getContentP(0)->getContentP(0)->getContentP(1)->getContentP(0)->addContent(AMFType("sampletype","avc1")); - amfreply.getContentP(1)->getContentP(0)->addContent(AMFType("arrVal")); - //amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("timescale",(double)44100)); - //amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("length",(double)28630000)); - amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("language","eng")); - amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMFType("sampledescription", (unsigned char)AMF0_STRICT_ARRAY)); - amfreply.getContentP(1)->getContentP(0)->getContentP(1)->getContentP(1)->addContent(AMFType("arrVal")); - amfreply.getContentP(1)->getContentP(0)->getContentP(1)->getContentP(1)->getContentP(0)->addContent(AMFType("sampletype","mp4a")); - amfreply.getContentP(1)->addContent(AMFType("audiochannels",(double)2)); - amfreply.getContentP(1)->addContent(AMFType("audiosamplerate",(double)44100)); - amfreply.getContentP(1)->addContent(AMFType("videoframerate",(double)25)); - amfreply.getContentP(1)->addContent(AMFType("aacaot",(double)2)); - amfreply.getContentP(1)->addContent(AMFType("avclevel",(double)12)); - amfreply.getContentP(1)->addContent(AMFType("avcprofile",(double)77)); - amfreply.getContentP(1)->addContent(AMFType("audiocodecid","mp4a")); - amfreply.getContentP(1)->addContent(AMFType("videocodecid","avc1")); - amfreply.getContentP(1)->addContent(AMFType("width",(double)1280)); - amfreply.getContentP(1)->addContent(AMFType("height",(double)720)); - amfreply.getContentP(1)->addContent(AMFType("frameWidth",(double)1280)); - amfreply.getContentP(1)->addContent(AMFType("frameHeight",(double)720)); - amfreply.getContentP(1)->addContent(AMFType("displayWidth",(double)1280)); - amfreply.getContentP(1)->addContent(AMFType("displayHeight",(double)720)); - //amfreply.getContentP(1)->addContent(AMFType("moovposition",(double)35506700)); - //amfreply.getContentP(1)->addContent(AMFType("duration",(double)596.458)); + AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMF::Object("onMetaData",AMF::AMF0_STRING)); + amfreply.addContent(AMF::Object("",AMF::AMF0_ECMA_ARRAY)); + amfreply.getContentP(1)->addContent(AMF::Object("trackinfo", AMF::AMF0_STRICT_ARRAY)); + amfreply.getContentP(1)->getContentP(0)->addContent(AMF::Object("arrVal")); + //amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMF::Object("timescale",(double)1000)); + //amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMF::Object("length",(double)59641700)); + amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMF::Object("language","eng")); + amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); + amfreply.getContentP(1)->getContentP(0)->getContentP(0)->getContentP(1)->addContent(AMF::Object("arrVal")); + amfreply.getContentP(1)->getContentP(0)->getContentP(0)->getContentP(1)->getContentP(0)->addContent(AMF::Object("sampletype","avc1")); + amfreply.getContentP(1)->getContentP(0)->addContent(AMF::Object("arrVal")); + //amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMF::Object("timescale",(double)44100)); + //amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMF::Object("length",(double)28630000)); + amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMF::Object("language","eng")); + amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); + amfreply.getContentP(1)->getContentP(0)->getContentP(1)->getContentP(1)->addContent(AMF::Object("arrVal")); + amfreply.getContentP(1)->getContentP(0)->getContentP(1)->getContentP(1)->getContentP(0)->addContent(AMF::Object("sampletype","mp4a")); + amfreply.getContentP(1)->addContent(AMF::Object("audiochannels",(double)2)); + amfreply.getContentP(1)->addContent(AMF::Object("audiosamplerate",(double)44100)); + amfreply.getContentP(1)->addContent(AMF::Object("videoframerate",(double)25)); + amfreply.getContentP(1)->addContent(AMF::Object("aacaot",(double)2)); + amfreply.getContentP(1)->addContent(AMF::Object("avclevel",(double)12)); + amfreply.getContentP(1)->addContent(AMF::Object("avcprofile",(double)77)); + amfreply.getContentP(1)->addContent(AMF::Object("audiocodecid","mp4a")); + amfreply.getContentP(1)->addContent(AMF::Object("videocodecid","avc1")); + amfreply.getContentP(1)->addContent(AMF::Object("width",(double)1280)); + amfreply.getContentP(1)->addContent(AMF::Object("height",(double)720)); + amfreply.getContentP(1)->addContent(AMF::Object("frameWidth",(double)1280)); + amfreply.getContentP(1)->addContent(AMF::Object("frameHeight",(double)720)); + amfreply.getContentP(1)->addContent(AMF::Object("displayWidth",(double)1280)); + amfreply.getContentP(1)->addContent(AMF::Object("displayHeight",(double)720)); + //amfreply.getContentP(1)->addContent(AMF::Object("moovposition",(double)35506700)); + //amfreply.getContentP(1)->addContent(AMF::Object("duration",(double)596.458)); return amfreply.Pack( ); }//getMetaData diff --git a/Connector_RTMP/Makefile b/Connector_RTMP/Makefile index f33e7118..e8cc8757 100644 --- a/Connector_RTMP/Makefile +++ b/Connector_RTMP/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp +SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp ../util/amf.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Conn_RTMP INCLUDES = @@ -13,7 +13,7 @@ LIBS = -lssl -lcrypto default: $(OUT) .cpp.o: $(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ -$(OUT): $(OBJ) chunkstream.cpp parsechunks.cpp handshake.cpp crypto.cpp amf.cpp +$(OUT): $(OBJ) chunkstream.cpp parsechunks.cpp handshake.cpp crypto.cpp $(CC) -o $(OUT) $(OBJ) $(STATIC) $(LIBS) clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ diff --git a/Connector_RTMP/amf.cpp b/Connector_RTMP/amf.cpp deleted file mode 120000 index 20bd6c3e..00000000 --- a/Connector_RTMP/amf.cpp +++ /dev/null @@ -1 +0,0 @@ -../util/amf.cpp \ No newline at end of file diff --git a/util/amf.cpp b/util/amf.cpp index a600d12a..fdff660f 100644 --- a/util/amf.cpp +++ b/util/amf.cpp @@ -1,264 +1,256 @@ -#include -#include -#include +#include "amf.h" -#define AMF0_NUMBER 0x00 -#define AMF0_BOOL 0x01 -#define AMF0_STRING 0x02 -#define AMF0_OBJECT 0x03 -#define AMF0_MOVIECLIP 0x04 -#define AMF0_NULL 0x05 -#define AMF0_UNDEFINED 0x06 -#define AMF0_REFERENCE 0x07 -#define AMF0_ECMA_ARRAY 0x08 -#define AMF0_OBJ_END 0x09 -#define AMF0_STRICT_ARRAY 0x0A -#define AMF0_DATE 0x0B -#define AMF0_LONGSTRING 0x0C -#define AMF0_UNSUPPORTED 0x0D -#define AMF0_RECORDSET 0x0E -#define AMF0_XMLDOC 0x0F -#define AMF0_TYPED_OBJ 0x10 -#define AMF0_UPGRADE 0x11 -#define AMF0_DDV_CONTAINER 0xFF +/// Returns the std::string Indice for the current object, if available. +/// Returns an empty string if no indice exists. +std::string AMF::Object::Indice(){return myIndice;}; -class AMFType { - public: - std::string Indice(){return myIndice;}; - unsigned char GetType(){return myType;}; - double NumValue(){return numval;}; - std::string StrValue(){return strval;}; - const char * Str(){return strval.c_str();}; - int hasContent(){ - if (!contents){return 0;} - return contents->size(); - }; - void addContent(AMFType c){if (contents != 0){contents->push_back(c);}}; - AMFType* getContentP(int i){if (contents != 0){return &contents->at(i);}else{return 0;}}; - AMFType getContent(int i){if (contents != 0){return contents->at(i);}else{return AMFType("error");}}; - AMFType* getContentP(std::string s){ - if (contents != 0){ - for (std::vector::iterator it = contents->begin(); it != contents->end(); it++){ - if (it->Indice() == s){ - return &(*it); - } +/// Returns the AMF::obj0type AMF0 object type for this object. +AMF::obj0type AMF::Object::GetType(){return myType;}; + +/// Returns the numeric value of this object, if available. +/// If this object holds no numeric value, 0 is returned. +double AMF::Object::NumValue(){return numval;}; + +/// Returns the std::string value of this object, if available. +/// If this object holds no string value, an empty string is returned. +std::string AMF::Object::StrValue(){return strval;}; + +/// Returns the C-string value of this object, if available. +/// If this object holds no string value, an empty C-string is returned. +const char * AMF::Object::Str(){return strval.c_str();}; + +/// Returns a count of the amount of objects this object currently holds. +/// If this object is not a container type, this function will always return 0. +int AMF::Object::hasContent(){return contents.size();}; + +/// Adds an AMF::Object to this object. Works for all types, but only makes sense for container types. +void AMF::Object::addContent(AMF::Object c){contents.push_back(c);}; + +/// Returns a pointer to the object held at indice i. +/// Returns AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice. +/// \param i The indice of the object in this container. +AMF::Object* AMF::Object::getContentP(int i){return &contents.at(i);}; + +/// Returns a copy of the object held at indice i. +/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice. +/// \param i The indice of the object in this container. +AMF::Object AMF::Object::getContent(int i){return contents.at(i);}; + +/// Returns a pointer to the object held at indice s. +/// Returns NULL if no object is held at this indice. +/// \param s The indice of the object in this container. +AMF::Object* AMF::Object::getContentP(std::string s){ + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ + if (it->Indice() == s){return &(*it);} + } + return this; +}; + +/// Returns a copy of the object held at indice s. +/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice. +/// \param s The indice of the object in this container. +AMF::Object AMF::Object::getContent(std::string s){ + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ + if (it->Indice() == s){return *it;} + } + return AMF::Object("error", AMF0_DDV_CONTAINER); +}; + +/// Constructor for numeric objects. +/// The object type is by default AMF::AMF0_NUMBER, but this can be forced to a different value. +/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic. +/// \param val The numeric value of this object. Numeric AMF0 objects only support double-type values. +/// \param setType The object type to force this object to. +AMF::Object::Object(std::string indice, double val, AMF::obj0type setType){//num type initializer + myIndice = indice; + myType = setType; + strval = ""; + numval = val; +}; + +/// Constructor for string objects. +/// The object type is by default AMF::AMF0_STRING, but this can be forced to a different value. +/// There is no need to manually change the type to AMF::AMF0_LONGSTRING, this will be done automatically. +/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic. +/// \param val The string value of this object. +/// \param setType The object type to force this object to. +AMF::Object::Object(std::string indice, std::string val, AMF::obj0type setType){//str type initializer + myIndice = indice; + myType = setType; + strval = val; + numval = 0; +}; + +/// Constructor for container objects. +/// The object type is by default AMF::AMF0_OBJECT, but this can be forced to a different value. +/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic. +/// \param setType The object type to force this object to. +AMF::Object::Object(std::string indice, AMF::obj0type setType){//object type initializer + myIndice = indice; + myType = setType; + strval = ""; + numval = 0; +}; + +/// Prints the contents of this object to std::cerr. +/// If this object contains other objects, it will call itself recursively +/// and print all nested content in a nice human-readable format. +void AMF::Object::Print(std::string indent){ + std::cerr << indent; + // print my type + switch (myType){ + case AMF::AMF0_NUMBER: std::cerr << "Number"; break; + case AMF::AMF0_BOOL: std::cerr << "Bool"; break; + case AMF::AMF0_STRING://short string + case AMF::AMF0_LONGSTRING: std::cerr << "String"; break; + case AMF::AMF0_OBJECT: std::cerr << "Object"; break; + case AMF::AMF0_MOVIECLIP: std::cerr << "MovieClip"; break; + case AMF::AMF0_NULL: std::cerr << "Null"; break; + case AMF::AMF0_UNDEFINED: std::cerr << "Undefined"; break; + case AMF::AMF0_REFERENCE: std::cerr << "Reference"; break; + case AMF::AMF0_ECMA_ARRAY: std::cerr << "ECMA Array"; break; + case AMF::AMF0_OBJ_END: std::cerr << "Object end"; break; + case AMF::AMF0_STRICT_ARRAY: std::cerr << "Strict Array"; break; + case AMF::AMF0_DATE: std::cerr << "Date"; break; + case AMF::AMF0_UNSUPPORTED: std::cerr << "Unsupported"; break; + case AMF::AMF0_RECORDSET: std::cerr << "Recordset"; break; + case AMF::AMF0_XMLDOC: std::cerr << "XML Document"; break; + case AMF::AMF0_TYPED_OBJ: std::cerr << "Typed Object"; break; + case AMF::AMF0_UPGRADE: std::cerr << "Upgrade to AMF3"; break; + case AMF::AMF0_DDV_CONTAINER: std::cerr << "DDVTech Container"; break; + } + // print my string indice, if available + std::cerr << " " << myIndice << " "; + // print my numeric or string contents + switch (myType){ + case AMF::AMF0_NUMBER: case AMF::AMF0_BOOL: case AMF::AMF0_REFERENCE: case AMF::AMF0_DATE: std::cerr << numval; break; + case AMF::AMF0_STRING: case AMF::AMF0_LONGSTRING: case AMF::AMF0_XMLDOC: case AMF::AMF0_TYPED_OBJ: std::cerr << strval; break; + default: break;//we don't care about the rest, and don't want a compiler warning... + } + std::cerr << std::endl; + // if I hold other objects, print those too, recursively. + if (contents.size() > 0){ + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){it->Print(indent+" ");} + } +};//print + +/// Packs the AMF object to a std::string for transfer over the network. +/// If the object is a container type, this function will call itself recursively and contain all contents. +/// Tip: When sending multiple AMF objects in one go, put them in a single AMF::AMF0_DDV_CONTAINER for easy transfer. +std::string AMF::Object::Pack(){ + std::string r = ""; + //check for string/longstring conversion + if ((myType == AMF::AMF0_STRING) && (strval.size() > 0xFFFF)){myType = AMF::AMF0_LONGSTRING;} + //skip output of DDV container types, they do not exist. Only output their contents. + if (myType != AMF::AMF0_DDV_CONTAINER){r += myType;} + //output the properly formatted AMF0 data stream for this object's contents. + switch (myType){ + case AMF::AMF0_NUMBER: + r += *(((char*)&numval)+7); r += *(((char*)&numval)+6); + r += *(((char*)&numval)+5); r += *(((char*)&numval)+4); + r += *(((char*)&numval)+3); r += *(((char*)&numval)+2); + r += *(((char*)&numval)+1); r += *(((char*)&numval)); + break; + case AMF::AMF0_DATE: + r += *(((char*)&numval)+7); r += *(((char*)&numval)+6); + r += *(((char*)&numval)+5); r += *(((char*)&numval)+4); + r += *(((char*)&numval)+3); r += *(((char*)&numval)+2); + r += *(((char*)&numval)+1); r += *(((char*)&numval)); + r += (char)0;//timezone always 0 + r += (char)0;//timezone always 0 + break; + case AMF::AMF0_BOOL: + r += (char)numval; + break; + case AMF::AMF0_STRING: + r += strval.size() / 256; + r += strval.size() % 256; + r += strval; + break; + case AMF::AMF0_LONGSTRING: + case AMF::AMF0_XMLDOC://is always a longstring + r += strval.size() / (256*256*256); + r += strval.size() / (256*256); + r += strval.size() / 256; + r += strval.size() % 256; + r += strval; + break; + case AMF::AMF0_TYPED_OBJ: + r += Indice().size() / 256; + r += Indice().size() % 256; + r += Indice(); + //is an object, with the classname first + case AMF::AMF0_OBJECT: + if (contents.size() > 0){ + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ + r += it->Indice().size() / 256; + r += it->Indice().size() % 256; + r += it->Indice(); + r += it->Pack(); } } - return this; - }; - AMFType getContent(std::string s){ - if (contents != 0){ - for (std::vector::iterator it = contents->begin(); it != contents->end(); it++){ - if (it->Indice() == s){ - return *it; - } - } - } - return AMFType("error"); - }; - AMFType(std::string indice, double val, unsigned char setType = AMF0_NUMBER){//num type initializer - myIndice = indice; - myType = setType; - strval = ""; - numval = val; - contents = 0; - }; - AMFType(std::string indice, std::string val, unsigned char setType = AMF0_STRING){//str type initializer - myIndice = indice; - myType = setType; - strval = val; - numval = 0; - contents = 0; - }; - AMFType(std::string indice, unsigned char setType = AMF0_OBJECT){//object type initializer - myIndice = indice; - myType = setType; - strval = ""; - numval = 0; - contents = new std::vector; - }; - ~AMFType(){if (contents != 0){delete contents;contents=0;}}; - AMFType& operator=(const AMFType &a) { - myIndice = a.myIndice; - myType = a.myType; - strval = a.strval; - numval = a.numval; - if (contents){ - if (a.contents != contents){ - delete contents; - if (a.contents){ - contents = new std::vector; - for (std::vector::iterator it = a.contents->begin(); it < a.contents->end(); it++){ - contents->push_back(*it); - } - }else{ - contents = 0; - } + r += (char)0; r += (char)0; r += (char)9; + break; + case AMF::AMF0_MOVIECLIP: + case AMF::AMF0_OBJ_END: + case AMF::AMF0_UPGRADE: + case AMF::AMF0_NULL: + case AMF::AMF0_UNDEFINED: + case AMF::AMF0_RECORDSET: + case AMF::AMF0_UNSUPPORTED: + //no data to add + break; + case AMF::AMF0_REFERENCE: + r += (char)((int)numval / 256); + r += (char)((int)numval % 256); + break; + case AMF::AMF0_ECMA_ARRAY:{ + int arrlen = 0; + if (contents.size() > 0){ + arrlen = contents.size(); + r += arrlen / (256*256*256); r += arrlen / (256*256); r += arrlen / 256; r += arrlen % 256; + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ + r += it->Indice().size() / 256; + r += it->Indice().size() % 256; + r += it->Indice(); + r += it->Pack(); } }else{ - if (a.contents){ - contents = new std::vector; - for (std::vector::iterator it = a.contents->begin(); it < a.contents->end(); it++){ - contents->push_back(*it); - } + r += (char)0; r += (char)0; r += (char)0; r += (char)0; + } + r += (char)0; r += (char)0; r += (char)9; + } break; + case AMF::AMF0_STRICT_ARRAY:{ + int arrlen = 0; + if (contents.size() > 0){ + arrlen = contents.size(); + r += arrlen / (256*256*256); r += arrlen / (256*256); r += arrlen / 256; r += arrlen % 256; + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ + r += it->Pack(); + } + }else{ + r += (char)0; r += (char)0; r += (char)0; r += (char)0; + } + } break; + case AMF::AMF0_DDV_CONTAINER://only send contents + if (contents.size() > 0){ + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ + r += it->Pack(); } } - return *this; - };//= operator - AMFType(const AMFType &a){ - myIndice = a.myIndice; - myType = a.myType; - strval = a.strval; - numval = a.numval; - if (a.contents){ - contents = new std::vector; - for (std::vector::iterator it = a.contents->begin(); it < a.contents->end(); it++){ - contents->push_back(*it); - } - }else{contents = 0;} - };//copy constructor - void Print(std::string indent = ""){ - std::cerr << indent; - switch (myType){ - case AMF0_NUMBER: std::cerr << "Number"; break; - case AMF0_BOOL: std::cerr << "Bool"; break; - case AMF0_STRING://short string - case AMF0_LONGSTRING: std::cerr << "String"; break; - case AMF0_OBJECT: std::cerr << "Object"; break; - case AMF0_MOVIECLIP: std::cerr << "MovieClip"; break; - case AMF0_NULL: std::cerr << "Null"; break; - case AMF0_UNDEFINED: std::cerr << "Undefined"; break; - case AMF0_REFERENCE: std::cerr << "Reference"; break; - case AMF0_ECMA_ARRAY: std::cerr << "ECMA Array"; break; - case AMF0_OBJ_END: std::cerr << "Object end"; break; - case AMF0_STRICT_ARRAY: std::cerr << "Strict Array"; break; - case AMF0_DATE: std::cerr << "Date"; break; - case AMF0_UNSUPPORTED: std::cerr << "Unsupported"; break; - case AMF0_RECORDSET: std::cerr << "Recordset"; break; - case AMF0_XMLDOC: std::cerr << "XML Document"; break; - case AMF0_TYPED_OBJ: std::cerr << "Typed Object"; break; - case AMF0_UPGRADE: std::cerr << "Upgrade to AMF3"; break; - case AMF0_DDV_CONTAINER: std::cerr << "DDVTech Container"; break; - } - std::cerr << " " << myIndice << " "; - switch (myType){ - case AMF0_NUMBER: case AMF0_BOOL: case AMF0_REFERENCE: case AMF0_DATE: std::cerr << numval; break; - case AMF0_STRING: case AMF0_LONGSTRING: case AMF0_XMLDOC: case AMF0_TYPED_OBJ: std::cerr << strval; break; - } - std::cerr << std::endl; - if (contents){ - for (std::vector::iterator it = contents->begin(); it != contents->end(); it++){it->Print(indent+" ");} - } - };//print - std::string Pack(){ - std::string r = ""; - if ((myType == AMF0_STRING) && (strval.size() > 0xFFFF)){myType = AMF0_LONGSTRING;} - if (myType != AMF0_DDV_CONTAINER){r += myType;} - switch (myType){ - case AMF0_NUMBER: - r += *(((char*)&numval)+7); r += *(((char*)&numval)+6); - r += *(((char*)&numval)+5); r += *(((char*)&numval)+4); - r += *(((char*)&numval)+3); r += *(((char*)&numval)+2); - r += *(((char*)&numval)+1); r += *(((char*)&numval)); - break; - case AMF0_DATE: - r += *(((char*)&numval)+7); r += *(((char*)&numval)+6); - r += *(((char*)&numval)+5); r += *(((char*)&numval)+4); - r += *(((char*)&numval)+3); r += *(((char*)&numval)+2); - r += *(((char*)&numval)+1); r += *(((char*)&numval)); - r += (char)0;//timezone always 0 - r += (char)0;//timezone always 0 - break; - case AMF0_BOOL: - r += (char)numval; - break; - case AMF0_STRING: - r += strval.size() / 256; - r += strval.size() % 256; - r += strval; - break; - case AMF0_LONGSTRING: - case AMF0_XMLDOC://is always a longstring - r += strval.size() / (256*256*256); - r += strval.size() / (256*256); - r += strval.size() / 256; - r += strval.size() % 256; - r += strval; - break; - case AMF0_TYPED_OBJ: - r += Indice().size() / 256; - r += Indice().size() % 256; - r += Indice(); - //is an object, with the classname first - case AMF0_OBJECT: - if (contents){ - for (std::vector::iterator it = contents->begin(); it != contents->end(); it++){ - r += it->Indice().size() / 256; - r += it->Indice().size() % 256; - r += it->Indice(); - r += it->Pack(); - } - } - r += (char)0; r += (char)0; r += (char)9; - break; - case AMF0_MOVIECLIP: - case AMF0_NULL: - case AMF0_UNDEFINED: - case AMF0_RECORDSET: - case AMF0_UNSUPPORTED: - //no data to add - break; - case AMF0_REFERENCE: - r += (char)((int)numval / 256); - r += (char)((int)numval % 256); - break; - case AMF0_ECMA_ARRAY:{ - int arrlen = 0; - if (contents){ - arrlen = contents->size(); - r += arrlen / (256*256*256); r += arrlen / (256*256); r += arrlen / 256; r += arrlen % 256; - for (std::vector::iterator it = contents->begin(); it != contents->end(); it++){ - r += it->Indice().size() / 256; - r += it->Indice().size() % 256; - r += it->Indice(); - r += it->Pack(); - } - }else{ - r += (char)0; r += (char)0; r += (char)0; r += (char)0; - } - r += (char)0; r += (char)0; r += (char)9; - } break; - case AMF0_STRICT_ARRAY:{ - int arrlen = 0; - if (contents){ - arrlen = contents->size(); - r += arrlen / (256*256*256); r += arrlen / (256*256); r += arrlen / 256; r += arrlen % 256; - for (std::vector::iterator it = contents->begin(); it != contents->end(); it++){ - r += it->Pack(); - } - }else{ - r += (char)0; r += (char)0; r += (char)0; r += (char)0; - } - } break; - case AMF0_DDV_CONTAINER://only send contents - if (contents){ - for (std::vector::iterator it = contents->begin(); it != contents->end(); it++){ - r += it->Pack(); - } - } - break; - } - return r; - };//pack - protected: - std::string myIndice; - unsigned char myType; - std::string strval; - double numval; - std::vector * contents; -};//AMFType + break; + } + return r; +};//pack -AMFType parseOneAMF(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name){ +/// Parses a single AMF0 type - used recursively by the AMF::parse() functions. +/// This function updates i every call with the new position in the data. +/// \param data The raw data to parse. +/// \param len The size of the raw data. +/// \param i Current parsing position in the raw data. +/// \param name Indice name for any new object created. +/// \returns A single AMF::Object, parsed from the raw data. +AMF::Object AMF::parseOne(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name){ std::string tmpstr; unsigned int tmpi = 0; unsigned char tmpdbl[8]; @@ -266,7 +258,7 @@ AMFType parseOneAMF(const unsigned char *& data, unsigned int &len, unsigned int fprintf(stderr, "Note: AMF type %hhx found. %i bytes left\n", data[i], len-i); #endif switch (data[i]){ - case AMF0_NUMBER: + case AMF::AMF0_NUMBER: tmpdbl[7] = data[i+1]; tmpdbl[6] = data[i+2]; tmpdbl[5] = data[i+3]; @@ -276,9 +268,9 @@ AMFType parseOneAMF(const unsigned char *& data, unsigned int &len, unsigned int tmpdbl[1] = data[i+7]; tmpdbl[0] = data[i+8]; i+=9;//skip 8(a double)+1 forwards - return AMFType(name, *(double*)tmpdbl, AMF0_NUMBER); + return AMF::Object(name, *(double*)tmpdbl, AMF::AMF0_NUMBER); break; - case AMF0_DATE: + case AMF::AMF0_DATE: tmpdbl[7] = data[i+1]; tmpdbl[6] = data[i+2]; tmpdbl[5] = data[i+3]; @@ -288,97 +280,97 @@ AMFType parseOneAMF(const unsigned char *& data, unsigned int &len, unsigned int tmpdbl[1] = data[i+7]; tmpdbl[0] = data[i+8]; i+=11;//skip 8(a double)+1+timezone(2) forwards - return AMFType(name, *(double*)tmpdbl, AMF0_DATE); + return AMF::Object(name, *(double*)tmpdbl, AMF::AMF0_DATE); break; - case AMF0_BOOL: + case AMF::AMF0_BOOL: i+=2;//skip bool+1 forwards if (data[i-1] == 0){ - return AMFType(name, (double)0, AMF0_BOOL); + return AMF::Object(name, (double)0, AMF::AMF0_BOOL); }else{ - return AMFType(name, (double)1, AMF0_BOOL); + return AMF::Object(name, (double)1, AMF::AMF0_BOOL); } break; - case AMF0_REFERENCE: + case AMF::AMF0_REFERENCE: tmpi = data[i+1]*256+data[i+2];//get the ref number value as a double i+=3;//skip ref+1 forwards - return AMFType(name, (double)tmpi, AMF0_REFERENCE); + return AMF::Object(name, (double)tmpi, AMF::AMF0_REFERENCE); break; - case AMF0_XMLDOC: + case AMF::AMF0_XMLDOC: tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to UTF-8-long length tmpstr.clear();//clean tmpstr, just to be sure tmpstr.append((const char *)data+i+5, (size_t)tmpi);//add the string data i += tmpi + 5;//skip length+size+1 forwards - return AMFType(name, tmpstr, AMF0_XMLDOC); + return AMF::Object(name, tmpstr, AMF::AMF0_XMLDOC); break; - case AMF0_LONGSTRING: + case AMF::AMF0_LONGSTRING: tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to UTF-8-long length tmpstr.clear();//clean tmpstr, just to be sure tmpstr.append((const char *)data+i+5, (size_t)tmpi);//add the string data i += tmpi + 5;//skip length+size+1 forwards - return AMFType(name, tmpstr, AMF0_LONGSTRING); + return AMF::Object(name, tmpstr, AMF::AMF0_LONGSTRING); break; - case AMF0_STRING: + case AMF::AMF0_STRING: tmpi = data[i+1]*256+data[i+2];//set tmpi to UTF-8 length tmpstr.clear();//clean tmpstr, just to be sure tmpstr.append((const char *)data+i+3, (size_t)tmpi);//add the string data i += tmpi + 3;//skip length+size+1 forwards - return AMFType(name, tmpstr, AMF0_STRING); + return AMF::Object(name, tmpstr, AMF::AMF0_STRING); break; - case AMF0_NULL: - case AMF0_UNDEFINED: - case AMF0_UNSUPPORTED: + case AMF::AMF0_NULL: + case AMF::AMF0_UNDEFINED: + case AMF::AMF0_UNSUPPORTED: ++i; - return AMFType(name, (double)0, data[i-1]); + return AMF::Object(name, (double)0, (AMF::obj0type)data[i-1]); break; - case AMF0_OBJECT:{ + case AMF::AMF0_OBJECT:{ ++i; - AMFType ret = AMFType(name, (unsigned char)AMF0_OBJECT); + AMF::Object ret(name, AMF::AMF0_OBJECT); while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x000009) tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length tmpstr.clear();//clean tmpstr, just to be sure tmpstr.append((const char*)data+i+2, (size_t)tmpi);//add the string data i += tmpi + 2;//skip length+size forwards - ret.addContent(parseOneAMF(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr + ret.addContent(AMF::parseOne(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr } i += 3;//skip 0x000009 return ret; } break; - case AMF0_TYPED_OBJ:{ + case AMF::AMF0_TYPED_OBJ:{ ++i; tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length tmpstr.clear();//clean tmpstr, just to be sure tmpstr.append((const char*)data+i+2, (size_t)tmpi);//add the string data - AMFType ret = AMFType(tmpstr, (unsigned char)AMF0_TYPED_OBJ);//the object is not named "name" but tmpstr + AMF::Object ret(tmpstr, AMF::AMF0_TYPED_OBJ);//the object is not named "name" but tmpstr while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x000009) tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length tmpstr.clear();//clean tmpstr, just to be sure tmpstr.append((const char*)data+i+2, (size_t)tmpi);//add the string data i += tmpi + 2;//skip length+size forwards - ret.addContent(parseOneAMF(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr + ret.addContent(AMF::parseOne(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr } i += 3;//skip 0x000009 return ret; } break; - case AMF0_ECMA_ARRAY:{ + case AMF::AMF0_ECMA_ARRAY:{ ++i; - AMFType ret = AMFType(name, (unsigned char)AMF0_ECMA_ARRAY); + AMF::Object ret(name, AMF::AMF0_ECMA_ARRAY); i += 4;//ignore the array length, we re-calculate it while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x000009) tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length tmpstr.clear();//clean tmpstr, just to be sure tmpstr.append((const char*)data+i+2, (size_t)tmpi);//add the string data i += tmpi + 2;//skip length+size forwards - ret.addContent(parseOneAMF(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr + ret.addContent(AMF::parseOne(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr } i += 3;//skip 0x000009 return ret; } break; - case AMF0_STRICT_ARRAY:{ - AMFType ret = AMFType(name, (unsigned char)AMF0_STRICT_ARRAY); + case AMF::AMF0_STRICT_ARRAY:{ + AMF::Object ret(name, AMF::AMF0_STRICT_ARRAY); tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to array length i += 5;//skip size+1 forwards while (tmpi > 0){//while not done parsing array - ret.addContent(parseOneAMF(data, len, i, "arrVal"));//add content, recursively parsed, updating i + ret.addContent(AMF::parseOne(data, len, i, "arrVal"));//add content, recursively parsed, updating i --tmpi; } return ret; @@ -387,17 +379,25 @@ AMFType parseOneAMF(const unsigned char *& data, unsigned int &len, unsigned int #if DEBUG >= 2 fprintf(stderr, "Error: Unimplemented AMF type %hhx - returning.\n", data[i]); #endif - return AMFType("error", (unsigned char)0xFF); -}//parseOneAMF + return AMF::Object("error", AMF::AMF0_DDV_CONTAINER); +}//parseOne -AMFType parseAMF(const unsigned char * data, unsigned int len){ - AMFType ret("returned", (unsigned char)0xFF);//container type +/// Parses a C-string to a valid AMF::Object. +/// This function will find all AMF objects in the string and return +/// them all packed in a single AMF::AMF0_DDV_CONTAINER AMF::Object. +AMF::Object AMF::parse(const unsigned char * data, unsigned int len){ + AMF::Object ret("returned", AMF::AMF0_DDV_CONTAINER);//container type unsigned int i = 0, j = 0; while (i < len){ - ret.addContent(parseOneAMF(data, len, i, "")); + ret.addContent(AMF::parseOne(data, len, i, "")); if (i > j){j = i;}else{return ret;} } return ret; -}//parseAMF -AMFType parseAMF(std::string data){return parseAMF((const unsigned char*)data.c_str(), data.size());} +}//parse +/// Parses a std::string to a valid AMF::Object. +/// This function will find all AMF objects in the string and return +/// them all packed in a single AMF::AMF0_DDV_CONTAINER AMF::Object. +AMF::Object AMF::parse(std::string data){ + return AMF::parse((const unsigned char*)data.c_str(), data.size()); +}//parse diff --git a/util/amf.h b/util/amf.h new file mode 100644 index 00000000..81ab9881 --- /dev/null +++ b/util/amf.h @@ -0,0 +1,68 @@ +#pragma once +#include +#include +//#include +#include + +/// Holds all AMF parsing and creation related functions and classes. +namespace AMF{ + + /// Enumerates all possible AMF0 types, adding a special DDVTECH container type for ease of use. + enum obj0type { + AMF0_NUMBER = 0x00, + AMF0_BOOL = 0x01, + AMF0_STRING = 0x02, + AMF0_OBJECT = 0x03, + AMF0_MOVIECLIP = 0x04, + AMF0_NULL = 0x05, + AMF0_UNDEFINED = 0x06, + AMF0_REFERENCE = 0x07, + AMF0_ECMA_ARRAY = 0x08, + AMF0_OBJ_END = 0x09, + AMF0_STRICT_ARRAY = 0x0A, + AMF0_DATE = 0x0B, + AMF0_LONGSTRING = 0x0C, + AMF0_UNSUPPORTED = 0x0D, + AMF0_RECORDSET = 0x0E, + AMF0_XMLDOC = 0x0F, + AMF0_TYPED_OBJ = 0x10, + AMF0_UPGRADE = 0x11, + AMF0_DDV_CONTAINER = 0xFF + }; + + /// Recursive class that holds AMF0 objects. + /// It supports all AMF0 types (defined in AMF::obj0type), adding support for a special DDVTECH container type. + class Object { + public: + std::string Indice(); + obj0type GetType(); + double NumValue(); + std::string StrValue(); + const char * Str(); + int hasContent(); + void addContent(AMF::Object c); + Object* getContentP(int i); + Object getContent(int i); + Object* getContentP(std::string s); + Object getContent(std::string s); + Object(std::string indice, double val, obj0type setType = AMF0_NUMBER); + Object(std::string indice, std::string val, obj0type setType = AMF0_STRING); + Object(std::string indice, obj0type setType = AMF0_OBJECT); + void Print(std::string indent = ""); + std::string Pack(); + protected: + std::string myIndice; ///< Holds this objects indice, if any. + obj0type myType; ///< Holds this objects AMF0 type. + std::string strval; ///< Holds this objects string value, if any. + double numval; ///< Holds this objects numeric value, if any. + std::vector contents; ///< Holds this objects contents, if any (for container types). + };//AMFType + + /// Parses a C-string to a valid AMF::Object. + Object parse(const unsigned char * data, unsigned int len); + /// Parses a std::string to a valid AMF::Object. + Object parse(std::string data); + /// Parses a single AMF0 type - used recursively by the AMF::parse() functions. + Object parseOne(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name); + +};//AMF namespace From b592442d6b5ad603277e62f35d65df2110870b5b Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 10 Apr 2011 22:32:40 +0200 Subject: [PATCH 05/24] SWSocket library niet meer nodig! Jeej! --- sockets/SocketW.h | 22 -- sockets/sw_base.cpp | 764 ------------------------------------------ sockets/sw_base.h | 205 ------------ sockets/sw_inet.cpp | 249 -------------- sockets/sw_inet.h | 48 --- sockets/sw_internal.h | 75 ----- sockets/sw_unix.cpp | 99 ------ sockets/sw_unix.h | 41 --- 8 files changed, 1503 deletions(-) delete mode 100644 sockets/SocketW.h delete mode 100644 sockets/sw_base.cpp delete mode 100644 sockets/sw_base.h delete mode 100644 sockets/sw_inet.cpp delete mode 100644 sockets/sw_inet.h delete mode 100644 sockets/sw_internal.h delete mode 100644 sockets/sw_unix.cpp delete mode 100644 sockets/sw_unix.h diff --git a/sockets/SocketW.h b/sockets/SocketW.h deleted file mode 100644 index b7096c0c..00000000 --- a/sockets/SocketW.h +++ /dev/null @@ -1,22 +0,0 @@ -// C++ Socket Wrapper -// -// Started 020316 -// -// License: LGPL v2.1+ (see the file LICENSE) -// (c)2002-2003 Anders Lindstr�m - -/*********************************************************************** - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - ***********************************************************************/ - -#ifndef SocketW_H -#define SocketW_H - -#include "sw_base.h" -#include "sw_unix.h" -#include "sw_inet.h" - -#endif //SocketW_H diff --git a/sockets/sw_base.cpp b/sockets/sw_base.cpp deleted file mode 100644 index 23ca1eb6..00000000 --- a/sockets/sw_base.cpp +++ /dev/null @@ -1,764 +0,0 @@ -// C++ Socket Wrapper -// SocketW base class -// -// Started 020316 -// -// License: LGPL v2.1+ (see the file LICENSE) -// (c)2002-2003 Anders Lindstr�m - -/*********************************************************************** - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - ***********************************************************************/ - -#include "sw_base.h" -#include -#include -#include -#include -#include - -#ifndef __WIN32__ - #include - #include - #include - #include - #include - - #define INVALID_SOCKET -1 //avoid M$ braindamage -#else - //why use POSIX standards when we can make our own - //and frustrate people? (well known M$ policy) - - #ifndef EBADF - #define EBADF WSAEBADF - #endif - - #define ENOTSOCK WSAENOTSOCK - #define EOPNOTSUPP WSAEOPNOTSUPP - #define EADDRINUSE WSAEADDRINUSE - #define EWOULDBLOCK WSAEWOULDBLOCK - #define EMSGSIZE WSAEMSGSIZE - #define EINPROGRESS WSAEINPROGRESS - #define EALREADY WSAEALREADY - #define ECONNREFUSED WSAECONNREFUSED - #define ETIMEDOUT WSAETIMEDOUT - #define ENOTCONN WSAENOTCONN - - #ifndef EINTR - #define EINTR WSAEINTR - #endif -#endif - -#ifndef MSG_NOSIGNAL - #define MSG_NOSIGNAL 0 -#endif - -// Socklen hack -#if defined(__linux__) || defined(__FreeBSD__) // || defined(__bsdi__) || defined(__NetBSD__) too, perhaps? Bugreports, please! - #define sw_socklen_t socklen_t -#elif defined(__WIN32__) || defined(__osf__) - #define sw_socklen_t int -#else - #define sw_socklen_t unsigned int -#endif - - -using namespace std; - -#ifdef __WIN32__ -//Win32 braindamage -int close(int fd) -{ - return closesocket(fd); -} - -int fcntl(int fd, int cmd, long arg) -{ - unsigned long mode = arg; - - return WSAIoctl(fd, cmd, &mode, sizeof(unsigned long), NULL, 0, NULL, NULL, NULL); -} - -void WSA_exit(void) -{ - WSACleanup(); -} -#endif - - -//==================================================================== -//== Error handling mode -//==================================================================== -bool sw_DoThrow = false; -bool sw_Verbose = true; - -void sw_setThrowMode(bool throw_errors) -{ - sw_DoThrow = throw_errors; -} - -void sw_setVerboseMode(bool verbose) -{ - sw_Verbose = verbose; -} - -bool sw_getThrowMode(void) -{ - return sw_DoThrow; -} - -bool sw_getVerboseMode(void) -{ - return sw_Verbose; -} - - -//==================================================================== -//== Base error class -//==================================================================== -SWBaseSocket::SWBaseError::SWBaseError() -{ - be = ok; - error_string = ""; - failed_class = NULL; -} - -SWBaseSocket::SWBaseError::SWBaseError(base_error e) -{ - be = e; - error_string = ""; - failed_class = NULL; -} - -string SWBaseSocket::SWBaseError::get_error() -{ - return error_string; -} - -SWBaseSocket* SWBaseSocket::SWBaseError::get_failedClass(void) -{ - return failed_class; -} - -void SWBaseSocket::SWBaseError::set_errorString(string msg) -{ - error_string = msg; -} - -void SWBaseSocket::SWBaseError::set_failedClass(SWBaseSocket *pnt) -{ - failed_class = pnt; -} - -bool SWBaseSocket::SWBaseError::operator==(SWBaseError e) -{ - return be == e.be; -} - -bool SWBaseSocket::SWBaseError::operator!=(SWBaseError e) -{ - return be != e.be; -} - - -//==================================================================== -//== SWBaseSocket -//== Base class for sockets -//==================================================================== -SWBaseSocket::SWBaseSocket() -{ - //indicate nonopen - myfd = -1; - recv_close = false; - - //init values - error_string = ""; - block_mode = blocking; - fsend_ready = true; - frecv_ready = true; - tsec = 0; - tusec = 0; - - #ifdef __WIN32__ - //kick winsock awake - static bool firstuse = true; - if( firstuse == true ){ - WSAData wsaData; - int nCode; - if( (nCode = WSAStartup(MAKEWORD(1, 1), &wsaData)) != 0 ){ - handle_errno(NULL, "SWBaseSocket - WSAStartup() failed: "); - exit(-1); // Should never happend - } - - //cleanup at exit - atexit(WSA_exit); - firstuse = false; - } - #endif /* __WIN32__ */ -} - -SWBaseSocket::~SWBaseSocket() -{ - if(myfd > 0) - close(myfd); -} - -bool SWBaseSocket::listen(int qLimit, SWBaseError *error) -{ - get_socket(); - - //Avoid "Address already in use" thingie - char yes=1; - setsockopt(myfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); - - if(::listen(myfd, qLimit) == -1){ - handle_errno(error, "SWBaseSocket::listen() error: "); - return false; - } - - no_error(error); - return true; -} - -SWBaseSocket* SWBaseSocket::accept(SWBaseError *error) -{ - int remotefd = -1; - sockaddr remoteAdr; - - if( !waitRead(error) ) - return NULL; - - sw_socklen_t ssize = sizeof(sockaddr); - - if((remotefd = ::accept(myfd, &remoteAdr, &ssize)) == int(INVALID_SOCKET)){ - handle_errno(error, "SWBaseSocket::accept() error: "); - return NULL; - } - - //nonblocking? - if( block_mode == nonblocking ) - fcntl(remotefd, F_SETFL, O_NONBLOCK); - - /* Create new class*/ - SWBaseSocket* remoteClass = create(remotefd, error); - if( remoteClass == NULL ) - return NULL; - - no_error(error); - return remoteClass; -} - -bool SWBaseSocket::disconnect(SWBaseError *error) -{ - int n = 0; - char buf[256]; - - if(myfd < 0){ - set_error(error, notConnected, "SWBaseSocket::disconnect() - No connection"); - return false; - } - - //close WR (this signals the peer) - if( shutdown(myfd, 1) != 0 ){ - handle_errno(error, "SWBaseSocket::disconnect() error: "); - return false; - } - - - SWBaseError err; - - //wait for close signal from peer - if( recv_close == false ){ - while(true){ - if( !waitRead(error) ) - return false; - - n = recv(buf, 256, &err); - - if( n <= 0 ) - break; - if(block_mode == noWait){ - //we don't want to block - set_error(error, notReady, "SWBaseSocket::disconnect() - Need more time, call again"); - return false; - } - } - } - - if( n != 0 ){ - set_error(error, err, error_string); - return false; //error - } - - //reset state - reset(); - - close(myfd); - myfd = -1; - - no_error(error); - return true; -} - -bool SWBaseSocket::close_fd() -{ - if( myfd > 0 ){ - close(myfd); - myfd = -1; - - //reset state - reset(); - - return true; - } - return false; -} - -int SWBaseSocket::send(const char *buf, int bytes, SWBaseError *error) -{ - int ret; - - if(myfd < 0){ - set_error(error, notConnected, "SWBaseSocket::send() - No connection"); - return -1; - } - - if( !waitWrite(error) ) - return -1; - - ret = ::send(myfd, buf, bytes, MSG_NOSIGNAL); - - if( ret < 0 ) - handle_errno(error, "SWBaseSocket::send() error: "); - else - no_error(error); - - return ret; -} - -int SWBaseSocket::fsend(const char *buf, int bytes, SWBaseError *error) -{ - int n; - int bytessent; - - if(fsend_ready){ - //First call - fsend_bytesleft = bytes; - fsend_total = fsend_bytesleft; //global var needed for resume - bytessent = 0; - fsend_ready = false; //point of no return - } - else{ - //resume - bytessent = fsend_total - fsend_bytesleft; - } - - //send package - while( fsend_bytesleft > 0 ){ - n = send( buf + bytessent , fsend_bytesleft, error ); - - //return on error, wouldblock or nowait - if( n < 0 ) - return ( (bytessent > 0 )? -bytessent : -1 ); - - bytessent += n; - fsend_bytesleft -= n; - - if ( block_mode == noWait && fsend_bytesleft > 0 ){ - set_error(error, notReady, "SWBaseSocket::fsend() - Need more time, call again"); - return -bytessent; - } - } - - fsend_ready = true; - - no_error(error); - return fsend_total; -} - -int SWBaseSocket::sendmsg(const string msg, SWBaseError *error) -{ - return send(msg.c_str(), msg.size(), error); -} - -int SWBaseSocket::fsendmsg(const string msg, SWBaseError *error) -{ - return fsend(msg.c_str(), msg.size(), error); -} - -int SWBaseSocket::recv(char *buf, int bytes, SWBaseError *error) -{ - int ret; - - if(myfd < 0){ - set_error(error, notConnected, "SWBaseSocket::recv() - No connection"); - return -1; - } - - if( !waitRead(error) ) - return -1; - - ret = ::recv(myfd, buf, bytes, MSG_NOSIGNAL); - - if( ret < 0 ) - handle_errno(error, "SWBaseSocket::recv() error: "); - else if( ret == 0 ){ - recv_close = true; //we recived a close signal from peer - set_error(error, terminated, "SWBaseSocket::recv() - Connection terminated by peer"); - }else - no_error(error); - - return ret; -} - -int SWBaseSocket::frecv(char *buf, int bytes, SWBaseError *error) -{ - int n; - int bytesrecv; - - if(frecv_ready){ - //First call - frecv_bytesleft = bytes; - frecv_total = frecv_bytesleft; //global var needed for resume - bytesrecv = 0; - frecv_ready = false; //point of no return - } - else{ - //resume - bytesrecv = frecv_total - frecv_bytesleft; - } - - - //recv package - while( frecv_bytesleft > 0 ){ - n = recv( buf + bytesrecv , frecv_bytesleft, error ); - - //return on error, wouldblock, nowait or timeout - if( n < 0 ) - return ( (bytesrecv > 0 )? -bytesrecv : -1 ); - if( n == 0 ) - return 0; // terminated - - bytesrecv += n; - frecv_bytesleft -= n; - - if ( block_mode == noWait && frecv_bytesleft > 0 ){ - set_error(error, notReady, "SWBaseSocket::frecv() - Need more time, call again"); - return -bytesrecv; - } - } - - frecv_ready = true; - - no_error(error); - return frecv_total; -} - -string SWBaseSocket::recvmsg(int bytes, SWBaseError *error) -{ - char *buf = new char[bytes+1]; - - SWBaseError err; - string msg = ""; - int ret = recv(buf, bytes, &err); - - if( ret > 0 ){ - buf[ret]='\0'; // Make sure the string is null terminated - msg = buf; - no_error(error); - } - delete[] buf; - - if( ret < 1 ) - set_error(error, err, err.get_error()); - - return msg; -} - -int SWBaseSocket::get_fd(SWBaseError *error) -{ - if( myfd > 0 ){ - no_error(error); - return myfd; - } - - set_error(error, notConnected, "SWBaseSocket::get_fd() - No descriptor"); - return -1; -} - -bool SWBaseSocket::get_host(sockaddr *host, SWBaseError *error) -{ - if( host == NULL){ - set_error(error, fatal, "SWBaseSocket::get_host() - Got NULL pointer"); - return false; - } - - if(myfd < 0){ - set_error(error, notConnected, "SWBaseSocket::get_host() - No socket"); - return false; - } - - sw_socklen_t tmp = sizeof(sockaddr); - if( getsockname(myfd, host, &tmp) != 0 ){ - handle_errno(error, "SWBaseSocket::get_host() error: "); - return false; - } - - no_error(error); - return true; -} - -bool SWBaseSocket::get_peer(sockaddr *peer, SWBaseError *error) -{ - if( peer == NULL){ - set_error(error, fatal, "SWBaseSocket::get_peer() - Got NULL pointer"); - return false; - } - - if(myfd > 0){ - sw_socklen_t tmp = sizeof(sockaddr); - if( getpeername(myfd, peer, &tmp) != 0 ){ - handle_errno(error, "SWBaseSocket::get_peer() error: "); - return false; - } - }else{ - set_error(error, notConnected, "SWBaseSocket::get_peer() - No connection"); - return false; - } - - no_error(error); - return true; -} - -void SWBaseSocket::reset() -{ - // Reset flags - recv_close = false; - - fsend_ready = true; - frecv_ready = true; -} - -bool SWBaseSocket::waitIO(io_type &type, SWBaseError *error) -{ - if( block_mode != blocking ){ - no_error(error); - return true; - } - - // We prefere to wait with select() even if no timeout is set - // as select() behaves more predictable - - timeval t; - timeval *to = NULL; // Indicate "wait for ever" - t.tv_sec = tsec; - t.tv_usec = tusec; - - if( tsec > 0 || tusec > 0 ) - to = &t; - - fd_set readfds, writefds, exceptfds; - - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&exceptfds); - FD_SET(myfd, &readfds); - FD_SET(myfd, &writefds); - FD_SET(myfd, &exceptfds); - - int ret = 0; - - switch (type){ - case read: - ret = select(myfd+1, &readfds, NULL, NULL, to); - break; - case write: - ret = select(myfd+1, NULL, &writefds, NULL, to); - break; - case except: - ret = select(myfd+1, NULL, NULL, &exceptfds, to); - break; - case rw: - ret = select(myfd+1, &readfds, &writefds, NULL, to); - break; - case all: - ret = select(myfd+1, &readfds, &writefds, &exceptfds, to); - break; - } - - if( ret < 0 ){ - handle_errno(error, "SWBaseSocket::waitIO() error: "); - return false; - } - if( ret == 0 ){ - set_error(error, timeout, "SWBaseSocket::waitIO() timeout"); - return false; - } - - if( FD_ISSET(myfd, &readfds) ){ - no_error(error); - type = read; - return true; - } - if( FD_ISSET(myfd, &writefds) ){ - no_error(error); - type = write; - return true; - } - if( FD_ISSET(myfd, &exceptfds) ){ - no_error(error); - type = except; - return true; - } - - set_error(error, fatal, "SWBaseSocket::waitIO() failed on select()"); - return false; -} - -bool SWBaseSocket::waitRead(SWBaseError *error) -{ - io_type tmp = read; - return waitIO(tmp, error); -} - -bool SWBaseSocket::waitWrite(SWBaseError *error) -{ - io_type tmp = write; - return waitIO(tmp, error); -} - -void SWBaseSocket::print_error() -{ - if( error_string.size() > 0 ) - fprintf(stderr, "%s!\n", error_string.c_str()); -} - -void SWBaseSocket::handle_errno(SWBaseError *error, string msg) -{ - #ifndef __WIN32__ - msg += strerror(errno); - #else - //stupid stupid stupid stupid M$ - switch (WSAGetLastError()){ - case 0: msg += "No error"; break; - case WSAEINTR: msg += "Interrupted system call"; break; - case WSAEBADF: msg += "Bad file number"; break; - case WSAEACCES: msg += "Permission denied"; break; - case WSAEFAULT: msg += "Bad address"; break; - case WSAEINVAL: msg += "Invalid argument"; break; - case WSAEMFILE: msg += "Too many open sockets"; break; - case WSAEWOULDBLOCK: msg += "Operation would block"; break; - case WSAEINPROGRESS: msg += "Operation now in progress"; break; - case WSAEALREADY: msg += "Operation already in progress"; break; - case WSAENOTSOCK: msg += "Socket operation on non-socket"; break; - case WSAEDESTADDRREQ: msg += "Destination address required"; break; - case WSAEMSGSIZE: msg += "Message too long"; break; - case WSAEPROTOTYPE: msg += "Protocol wrong type for socket"; break; - case WSAENOPROTOOPT: msg += "Bad protocol option"; break; - case WSAEPROTONOSUPPORT: msg += "Protocol not supported"; break; - case WSAESOCKTNOSUPPORT: msg += "Socket type not supported"; break; - case WSAEOPNOTSUPP: msg += "Operation not supported on socket"; break; - case WSAEPFNOSUPPORT: msg += "Protocol family not supported"; break; - case WSAEAFNOSUPPORT: msg += "Address family not supported"; break; - case WSAEADDRINUSE: msg += "Address already in use"; break; - case WSAEADDRNOTAVAIL: msg += "Can't assign requested address"; break; - case WSAENETDOWN: msg += "Network is down"; break; - case WSAENETUNREACH: msg += "Network is unreachable"; break; - case WSAENETRESET: msg += "Net connection reset"; break; - case WSAECONNABORTED: msg += "Software caused connection abort"; break; - case WSAECONNRESET: msg += "Connection reset by peer"; break; - case WSAENOBUFS: msg += "No buffer space available"; break; - case WSAEISCONN: msg += "Socket is already connected"; break; - case WSAENOTCONN: msg += "Socket is not connected"; break; - case WSAESHUTDOWN: msg += "Can't send after socket shutdown"; break; - case WSAETOOMANYREFS: msg += "Too many references"; break; - case WSAETIMEDOUT: msg += "Connection timed out"; break; - case WSAECONNREFUSED: msg += "Connection refused"; break; - case WSAELOOP: msg += "Too many levels of symbolic links"; break; - case WSAENAMETOOLONG: msg += "File name too long"; break; - case WSAEHOSTDOWN: msg += "Host is down"; break; - case WSAEHOSTUNREACH: msg += "No route to host"; break; - case WSAENOTEMPTY: msg += "Directory not empty"; break; - case WSAEPROCLIM: msg += "Too many processes"; break; - case WSAEUSERS: msg += "Too many users"; break; - case WSAEDQUOT: msg += "Disc quota exceeded"; break; - case WSAESTALE: msg += "Stale NFS file handle"; break; - case WSAEREMOTE: msg += "Too many levels of remote in path"; break; - case WSASYSNOTREADY: msg += "Network system is unavailable"; break; - case WSAVERNOTSUPPORTED: msg += "Winsock version out of range"; break; - case WSANOTINITIALISED: msg += "WSAStartup not yet called"; break; - case WSAEDISCON: msg += "Graceful shutdown in progress"; break; - case WSAHOST_NOT_FOUND: msg += "Host not found"; break; - case WSANO_DATA: msg += "No host data of that type was found"; break; - default: msg += "Unknown Winsock error: " + WSAGetLastError(); break; - } - #endif - - int errorno; - - //Win32 braindamage - #ifdef __WIN32__ - errorno = WSAGetLastError(); - #else - errorno = errno; - #endif - - SWBaseError e; - - if( errorno == EADDRINUSE ) - e = portInUse; - else if( errorno == EAGAIN || errorno == EWOULDBLOCK ) - e = notReady; - else if( errorno == EMSGSIZE ) - e = msgTooLong; - else if( errorno == EINPROGRESS || errorno == EALREADY ) - e = notReady; - else if( errorno == ECONNREFUSED || errorno == ETIMEDOUT ) - e = noResponse; - else if( errorno == ENOTCONN || errorno == EBADF || errorno == ENOTSOCK ) - e = notConnected; - else if( errorno == EPIPE ){ - e = terminated; - recv_close = true; - }else if( errorno == EINTR ) - e = interrupted; - else - e = fatal; //default - - set_error(error, e, msg); -} - -void SWBaseSocket::no_error(SWBaseError *error) -{ - if(error != NULL){ - *error = ok; - error->error_string = ""; - error->failed_class = NULL; - } -} - -void SWBaseSocket::set_error(SWBaseError *error, SWBaseError name, string msg) -{ - error_string = msg; - - if(error != NULL){ - *error = name; - error->error_string = msg; - error->failed_class = this; - }else{ - if( sw_Verbose ) - print_error(); - - if( sw_DoThrow ){ - SWBaseError e; - e = name; - e.error_string = msg; - e.failed_class = this; - throw e; - } - } -} - diff --git a/sockets/sw_base.h b/sockets/sw_base.h deleted file mode 100644 index 2c4e33ef..00000000 --- a/sockets/sw_base.h +++ /dev/null @@ -1,205 +0,0 @@ -// C++ Socket Wrapper -// SocketW base socket header -// -// Started 020316 -// -// License: LGPL v2.1+ (see the file LICENSE) -// (c)2002-2003 Anders Lindström - -/*********************************************************************** - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - ***********************************************************************/ - -#ifndef sw_base_H -#define sw_base_H - -#include "sw_internal.h" - -#include -#include - -// Set error handling mode -// throw_errors == true : Throws the error class on unhandled errors -// throw_errors == false : Exit on unhandled errors -// verbose == true : Prints the error message to stderr on unhandled errors -// -// Default is throw_errors == false and verbose == true -void sw_setThrowMode(bool throw_errors); -void sw_setVerboseMode(bool verbose); -bool sw_getThrowMode(void); -bool sw_getVerboseMode(void); - - -// Abstract base class for streaming sockets -class DECLSPEC SWBaseSocket -{ -public: - SWBaseSocket(); - virtual ~SWBaseSocket(); - - // Error types - // ok - operation succesful - // fatal - unspecified error - // notReady - you should call the function again - // indicates that the function would block (if nowait/nonblocking) - // portInUse - this port is used by another socket ( on listen() ) - // notConnected - socket not connected (or valid) - // msgTooLong - the message size it too big for send() - // terminated - connection terminated (by peer) - // noResponse - can't connect() to peer - // timeout - a read/write operation timed out (only if a timeout value is set and if in blocking mode) - // interrupted - operation was interrupted by a nonblocked signal - enum base_error{ok, fatal, notReady, portInUse, notConnected, msgTooLong, terminated, noResponse, timeout, interrupted}; - - class DECLSPEC SWBaseError - { - public: - SWBaseError(); - SWBaseError(base_error e); - - virtual ~SWBaseError(){;} - - virtual std::string get_error(); - virtual SWBaseSocket* get_failedClass(void); - - virtual bool operator==(SWBaseError e); - virtual bool operator!=(SWBaseError e); - - virtual void set_errorString(std::string msg); - virtual void set_failedClass(SWBaseSocket *pnt); - protected: - friend class SWBaseSocket; - - // The base error type - base_error be; - - // Human readable error string - std::string error_string; - - // A pointer to the class causing the error - SWBaseSocket *failed_class; - }; - - - // Note: If no SWBaseError class is provided with a method call (==NULL), - // SocketW will print the error to stderr and exit or throw on errors. - - // Note: All bool functions returns true on success. - - // Block mode - // blocking - everythings blocks until completly done - // noWait - operations block but only once - // useful with blocking w. select() - // nonblocking - don't block (you should use select()) - enum block_type{nonblocking, noWait, blocking}; - - - // Connection methods - // qLimit - the maximum length the queue of pending connections. - // Accept returns a new socket class connected with peer (should be - // freed with delete) or NULL on failure. You can cast the class to - // the correct type if you need to ( eg. (SWInetSocket *)mysocket ). - virtual bool listen(int qLimit = 5, SWBaseError *error = NULL); - virtual SWBaseSocket* accept(SWBaseError *error = NULL); - // bind() and connect() are implemented in child classes - - // do the disconnect ritual (signal peer, wait for close singal and close socket) - virtual bool disconnect(SWBaseError *error = NULL); - - // force close socket - virtual bool close_fd(); //use with care, disconnect() is cleaner - - // Direct I/O (raw) - // Can send/recv less bytes than specified! - // Returns the actual amount of bytes sent/recv on sucess - // and an negative integer on failure. - virtual int send(const char *buf, int bytes, SWBaseError *error = NULL); - virtual int sendmsg(const std::string msg, SWBaseError *error = NULL); - virtual int recv(char *buf, int bytes, SWBaseError *error = NULL); - virtual std::string recvmsg(int bytes = 256, SWBaseError *error = NULL); - - // Forced I/O - // Force system to send/recv the specified amount of bytes. - // On nowait/nonblocking: might return with notReady and then you - // MUST call the same method again (eg. wait with select() to know when) - // with the same parameters until the operation is finished. - // Returns 'bytes' when finished, negative integer on failure and - // 'notReady'. In the 'notReady' case, -(return value) is the amount of - // bytes sent/recv so far. - virtual int fsend(const char *buf, int bytes, SWBaseError *error = NULL); - virtual int fsendmsg(const std::string msg, SWBaseError *error = NULL); - virtual int frecv(char *buf, int bytes, SWBaseError *error = NULL); - - // Tools - // get_fd() - get socket descriptor, can be used with select() - // returns -1 on failure. - // get_host/peer fills the provided structures with info about the - // host/peer (see man unix & ip). - // SWInetSocket has some more tools for TCP/IP sockets. - virtual int get_fd(SWBaseError *error); - virtual bool get_host(sockaddr *host, SWBaseError *error = NULL); - virtual bool get_peer(sockaddr *peer, SWBaseError *error = NULL); - - // Set recv timeout (only in blocking mode). - // set_timeout(0,0) means wait forever (default). - // This affects the functions recv(), send(), accept() and disconnect() - // and others that use those, i.e. all frecvmsg(). - void set_timeout(Uint32 sec, Uint32 usec){ tsec = sec, tusec = usec; } - - // Error handling - virtual void print_error(); //prints the last error if any to stderr - virtual std::string get_error(){return error_string;} //returns a human readable error string - -protected: - // get a new socket if myfd < 0 - virtual void get_socket()=0; - - // create a new class for accept() using socketdescriptor - virtual SWBaseSocket* create(int socketdescriptor, SWBaseError *error)=0; - - // reset state - virtual void reset(); - - // wait for I/O (with timeout) - enum io_type{read, write, except, rw, all}; - virtual bool waitIO(io_type &type, SWBaseError *error); - bool waitRead(SWBaseError *error); - bool waitWrite(SWBaseError *error); - - // internal error handling - virtual void handle_errno(SWBaseError *error, std::string msg); - virtual void no_error(SWBaseError *error); - virtual void set_error(SWBaseError *error, SWBaseError name, std::string msg); - - // our socket descriptor - int myfd; - - // last error - std::string error_string; - - // data for fsend - bool fsend_ready; - int fsend_total; - int fsend_bytesleft; - - // data for frecv - bool frecv_ready; - int frecv_total; - int frecv_bytesleft; - - // have we recived a shutdown signal? - bool recv_close; - - //blocking mode (set by child classes) - block_type block_mode; - - //timeout for waitIO() - int tsec; - int tusec; -}; - - -#endif /* sw_base_H */ diff --git a/sockets/sw_inet.cpp b/sockets/sw_inet.cpp deleted file mode 100644 index c056722e..00000000 --- a/sockets/sw_inet.cpp +++ /dev/null @@ -1,249 +0,0 @@ -// C++ Socket Wrapper -// SocketW Inet socket -// -// Started 020316 -// -// License: LGPL v2.1+ (see the file LICENSE) -// (c)2002-2003 Anders Lindström - -/*********************************************************************** - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - ***********************************************************************/ - -#include "sw_inet.h" - -#ifndef __WIN32__ - #include - #include - #include - #include - #include -#else - #define F_SETFL FIONBIO - #define O_NONBLOCK 1 - - //Defined in sw_base.cxx - extern int close(int fd); - extern int fcntl(int fd, int cmd, long arg); -#endif - -using namespace std; - -//==================================================================== -//== SWInetSocket -//== Inet (TCP/IP) streaming sockets -//==================================================================== -SWInetSocket::SWInetSocket(block_type block) -{ - block_mode = block; -} - -SWInetSocket::~SWInetSocket() -{ - -} - -void SWInetSocket::get_socket() -{ - if( myfd < 0 ){ - myfd = socket(PF_INET, SOCK_STREAM, 0); - - if( block_mode == nonblocking ) - fcntl(myfd, F_SETFL, O_NONBLOCK); - - //reset state - reset(); - } -} - -SWBaseSocket* SWInetSocket::create(int socketdescriptor, SWBaseError *error) -{ - SWInetSocket* remoteClass; - - /* Create new class*/ - remoteClass = new SWInetSocket(block_mode); - remoteClass->myfd = socketdescriptor; - - no_error(error); - return remoteClass; -} - -bool SWInetSocket::bind(int port, SWBaseError *error) -{ - return bind(port, "", error); -} - -bool SWInetSocket::bind(int port, string host, SWBaseError *error) -{ - hostent *h; - in_addr inp; - - if( host.size() > 0 ){ - // Bind to a specific address - - if( (h = gethostbyname(host.c_str())) == NULL ){ - set_error(error, fatal, "SWInetSocket::bind() - Can't get host by name"); - return false; - } - - inp = *((in_addr *)h->h_addr); - }else{ - // Bind to any - inp.s_addr = INADDR_ANY; - } - - - get_socket(); - - sockaddr_in myAdr; - - memset(&myAdr, 0, sizeof(myAdr)); - myAdr.sin_family = AF_INET; - myAdr.sin_port = htons(port); - myAdr.sin_addr.s_addr = inp.s_addr; - - if(::bind(myfd, (sockaddr *)&myAdr, sizeof(myAdr)) == -1){ - handle_errno(error, "SWInetSocket::bind() error: "); - return false; - } - - no_error(error); - return true; -} - -bool SWInetSocket::connect(int port, string hostname, SWBaseError *error) -{ - get_socket(); - - hostent *host; - - if( (host = gethostbyname(hostname.c_str())) == NULL ){ - set_error(error, fatal, "SWInetSocket::connect() - Can't get host by name"); - return false; - } - - sockaddr_in remoteAdr; - - memset(&remoteAdr, 0, sizeof(remoteAdr)); - remoteAdr.sin_family = AF_INET; - remoteAdr.sin_port = htons(port); - remoteAdr.sin_addr = *((in_addr *)host->h_addr); - - if(::connect(myfd, (sockaddr *)&remoteAdr, sizeof(remoteAdr)) == -1){ - handle_errno(error, "SWInetSocket::connect() error: "); - return false; - } - - no_error(error); - return true; -} - - -string SWInetSocket::get_peerAddr(SWBaseError *error) -{ - sockaddr_in adr; - - if( !get_peer((sockaddr *)&adr, error) ) - return ""; - - char *pnt; - - if( (pnt = inet_ntoa(adr.sin_addr)) == NULL ){ - set_error(error, fatal, "SWInetSocket::get_peerName() - Can't get peer address"); - return ""; - } - string name(pnt); - - no_error(error); - return name; -} - -int SWInetSocket::get_peerPort(SWBaseError *error) -{ - sockaddr_in adr; - - if( !get_peer((sockaddr *)&adr, error) ) - return -1; - - no_error(error); - - return ntohs(adr.sin_port); -} - -string SWInetSocket::get_peerName(SWBaseError *error) -{ - string name = get_peerAddr(error); - if(name.size() < 1) - return ""; - - - hostent *h; - - if( (h = gethostbyname(name.c_str())) == NULL ){ - set_error(error, fatal, "SWInetSocket::get_peerName() - Can't get peer by address"); - return ""; - } - string host_name(h->h_name); - - no_error(error); - return host_name; -} - -string SWInetSocket::get_hostAddr(SWBaseError *error) -{ - //We need to get the real address, so we must - //first get this computers host name and then - //translate that into an address! - - string name = get_hostName(error); - if( name.size() < 1 ) - return ""; - - hostent *host; - - if( (host = gethostbyname(name.c_str())) == NULL ){ - set_error(error, fatal, "SWInetSocket::get_hostAddr() - Can't get host by name"); - return ""; - } - - char *pnt; - - if( (pnt = inet_ntoa(*((in_addr *)host->h_addr))) == NULL){ - set_error(error, fatal, "SWInetSocket::get_hostAddr() - Can't get host address"); - return ""; - } - - string adr(pnt); - - return adr; -} - -int SWInetSocket::get_hostPort(SWBaseError *error) -{ - sockaddr_in adr; - - if( !get_host((sockaddr *)&adr, error) ) - return -1; - - no_error(error); - - return ntohs(adr.sin_port); -} - -string SWInetSocket::get_hostName(SWBaseError *error) -{ - char buf[256]; - - if( gethostname(buf, 256) != 0 ){ - handle_errno(error, "SWInetSocket::gethostname() error: "); - return ""; - } - - string msg(buf); - - no_error(error); - return msg; -} diff --git a/sockets/sw_inet.h b/sockets/sw_inet.h deleted file mode 100644 index 26050845..00000000 --- a/sockets/sw_inet.h +++ /dev/null @@ -1,48 +0,0 @@ -// C++ Socket Wrapper -// SocketW Inet socket header -// -// Started 020316 -// -// License: LGPL v2.1+ (see the file LICENSE) -// (c)2002-2003 Anders Lindström - -/*********************************************************************** - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - ***********************************************************************/ - -#ifndef sw_inet_H -#define sw_inet_H - -#include "sw_internal.h" -#include "sw_base.h" -#include - -// Simple streaming TCP/IP class -class DECLSPEC SWInetSocket : public SWBaseSocket -{ -public: - SWInetSocket(block_type block=blocking); - virtual ~SWInetSocket(); - - virtual bool bind(int port, SWBaseError *error = NULL); //use port=0 to get any free port - virtual bool bind(int port, std::string host, SWBaseError *error = NULL); //you can also specify the host interface to use - virtual bool connect(int port, std::string hostname, SWBaseError *error = NULL); - - // Tools - // Gets IP addr, name or port. - virtual std::string get_peerAddr(SWBaseError *error = NULL); - virtual int get_peerPort(SWBaseError *error = NULL); - virtual std::string get_peerName(SWBaseError *error = NULL); - virtual std::string get_hostAddr(SWBaseError *error = NULL); - virtual int get_hostPort(SWBaseError *error = NULL); - virtual std::string get_hostName(SWBaseError *error = NULL); - -protected: - virtual void get_socket(); - virtual SWBaseSocket* create(int socketdescriptor, SWBaseError *error); -}; - -#endif /* sw_inet_H */ diff --git a/sockets/sw_internal.h b/sockets/sw_internal.h deleted file mode 100644 index 94313bcd..00000000 --- a/sockets/sw_internal.h +++ /dev/null @@ -1,75 +0,0 @@ -// C++ Socket Wrapper -// SocketW internal header -// -// Started 030823 -// -// License: LGPL v2.1+ (see the file LICENSE) -// (c)2002-2003 Anders Lindstr�m - -/*********************************************************************** - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - ***********************************************************************/ - -#ifndef sw_internal_H -#define sw_internal_H - -// This header is included in all *.h files - -#ifndef __WIN32__ - #include - #include - #include - #include -#else - #include - - #define F_SETFL FIONBIO - #define O_NONBLOCK 1 -#endif - -#ifndef _SDL_H - -// Define general types -typedef unsigned char Uint8; -typedef signed char Sint8; -typedef unsigned short Uint16; -typedef signed short Sint16; -typedef unsigned int Uint32; -typedef signed int Sint32; - -// It's VERY important that these types really have the right sizes! -// This black magic is from SDL -#define COMPILE_TIME_ASSERT(name, x) \ - typedef int _dummy_ ## name[(x) * 2 - 1] -COMPILE_TIME_ASSERT(uint8, sizeof(Uint8) == 1); -COMPILE_TIME_ASSERT(sint8, sizeof(Sint8) == 1); -COMPILE_TIME_ASSERT(uint16, sizeof(Uint16) == 2); -COMPILE_TIME_ASSERT(sint16, sizeof(Sint16) == 2); -COMPILE_TIME_ASSERT(uint32, sizeof(Uint32) == 4); -COMPILE_TIME_ASSERT(sint32, sizeof(Sint32) == 4); -#undef COMPILE_TIME_ASSERT - -#endif /* _SDL_H */ - -// Some compilers use a special export keyword -#ifndef DECLSPEC - #ifdef __BEOS__ - #if defined(__GNUC__) - #define DECLSPEC __declspec(dllexport) - #else - #define DECLSPEC __declspec(export) - #endif - #else - #ifdef WIN32 - #define DECLSPEC __declspec(dllexport) - #else - #define DECLSPEC - #endif - #endif -#endif - - -#endif /* sw_internal_H */ diff --git a/sockets/sw_unix.cpp b/sockets/sw_unix.cpp deleted file mode 100644 index 07e2dd10..00000000 --- a/sockets/sw_unix.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// C++ Socket Wrapper -// SocketW Unix socket -// -// Started 020316 -// -// License: LGPL v2.1+ (see the file LICENSE) -// (c)2002-2003 Anders Lindström - -/*********************************************************************** - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - ***********************************************************************/ - -#include "sw_unix.h" -#include - -using namespace std; - -//==================================================================== -//== SWUnixSocket -//== Unix streaming sockets -//==================================================================== -#ifndef __WIN32__ - -SWUnixSocket::SWUnixSocket(block_type block) -{ - block_mode = block; -} - -SWUnixSocket::~SWUnixSocket() -{ - //nothing here -} - -void SWUnixSocket::get_socket() -{ - if( myfd < 0 ){ - myfd = socket(PF_UNIX, SOCK_STREAM, 0); - - if( block_mode == nonblocking ) - fcntl(myfd, F_SETFL, O_NONBLOCK); - - //reset state - reset(); - } -} - - -SWBaseSocket* SWUnixSocket::create(int socketdescriptor, SWBaseError *error) -{ - SWUnixSocket* remoteClass; - - /* Create new class*/ - remoteClass = new SWUnixSocket(block_mode); - remoteClass->myfd = socketdescriptor; - - no_error(error); - return remoteClass; -} - -bool SWUnixSocket::bind(string path, SWBaseError *error) -{ - get_socket(); - - sockaddr_un myAdr; - - myAdr.sun_family = AF_UNIX; - strncpy(myAdr.sun_path, path.c_str(), path.size()+1); - - if(::bind(myfd, (sockaddr *)&myAdr, sizeof(myAdr)) == -1){ - handle_errno(error, "SWUnixSocket::bind() error: "); - return false; - } - - no_error(error); - return true; -} - -bool SWUnixSocket::connect(string path, SWBaseError *error) -{ - get_socket(); - - sockaddr_un remoteAdr; - - remoteAdr.sun_family = AF_UNIX; - strncpy(remoteAdr.sun_path, path.c_str(), path.size()+1); - - if(::connect(myfd, (sockaddr *)&remoteAdr, sizeof(remoteAdr)) == -1){ - handle_errno(error, "SWUnixSocket::connect() error: "); - return false; - } - - no_error(error); - return true; -} - -#endif /* __WIN32__ */ diff --git a/sockets/sw_unix.h b/sockets/sw_unix.h deleted file mode 100644 index 576f14c0..00000000 --- a/sockets/sw_unix.h +++ /dev/null @@ -1,41 +0,0 @@ -// C++ Socket Wrapper -// SocketW Unix socket header -// -// Started 020316 -// -// License: LGPL v2.1+ (see the file LICENSE) -// (c)2002-2003 Anders Lindström - -/*********************************************************************** - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Lesser General Public * - * License as published by the Free Software Foundation; either * - * version 2.1 of the License, or (at your option) any later version. * - ***********************************************************************/ - -#ifndef sw_unix_H -#define sw_unix_H -#ifndef __WIN32__ - -#include "sw_internal.h" -#include "sw_base.h" -#include - -// Simple streaming Unix class -class DECLSPEC SWUnixSocket : public SWBaseSocket -{ -public: - SWUnixSocket(block_type block=blocking); - ~SWUnixSocket(); - - // bind and connect to the socket file "path" - virtual bool bind(std::string path, SWBaseError *error = NULL); - virtual bool connect(std::string path, SWBaseError *error = NULL); - -protected: - virtual void get_socket(); - virtual SWBaseSocket* create(int socketdescriptor, SWBaseError *error); -}; - -#endif /* __WIN32__ */ -#endif /* sw_unix_H */ From 6db68295450766988cee039bd5bfb0560961b609 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 11 Apr 2011 17:05:36 +0200 Subject: [PATCH 06/24] 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. --- ABST_Parser/main.cpp | 5 + AMF_Tester/main.cpp | 6 + Admin/main.cpp | 5 + Buffer/main.cpp | 462 ++++++++++++------------ Connector_HTTP/main.cpp | 13 +- Connector_RAW/main.cpp | 9 + Connector_RTMP/Makefile | 4 +- Connector_RTMP/RTMP_Conn | 38 -- Connector_RTMP/chunkstream.cpp | 501 -------------------------- Connector_RTMP/crypto.h | 45 --- Connector_RTMP/handshake.cpp | 137 -------- Connector_RTMP/main.cpp | 521 ++++++++++++++++++++-------- Connector_RTMP/parsechunks.cpp | 254 -------------- Doxyfile | 2 +- HTTP_Box_Parser/main.cpp | 16 +- Makefile | 2 +- util/amf.cpp | 3 + util/amf.h | 3 + {Connector_RTMP => util}/crypto.cpp | 3 + util/crypto.h | 56 +++ util/ddv_socket.cpp | 18 + util/ddv_socket.h | 4 + util/flv_tag.cpp | 22 +- util/flv_tag.h | 6 +- util/http_parser.cpp | 132 +++++-- util/http_parser.h | 74 ++-- util/rtmpchunks.cpp | 442 +++++++++++++++++++++++ util/rtmpchunks.h | 65 ++++ util/server_setup.cpp | 40 ++- 29 files changed, 1451 insertions(+), 1437 deletions(-) delete mode 100755 Connector_RTMP/RTMP_Conn delete mode 100644 Connector_RTMP/chunkstream.cpp delete mode 100644 Connector_RTMP/crypto.h delete mode 100644 Connector_RTMP/handshake.cpp delete mode 100644 Connector_RTMP/parsechunks.cpp rename {Connector_RTMP => util}/crypto.cpp (99%) create mode 100644 util/crypto.h create mode 100644 util/rtmpchunks.cpp create mode 100644 util/rtmpchunks.h diff --git a/ABST_Parser/main.cpp b/ABST_Parser/main.cpp index 18391e95..bcc649b5 100644 --- a/ABST_Parser/main.cpp +++ b/ABST_Parser/main.cpp @@ -1,3 +1,8 @@ +/// \file ABST_Parser/main.cpp +/// Debugging tool for ABST boxes. +/// Expects ABST data through stdin, outputs human-readable information to stderr. +/// \todo Erik, update, delete or properly document this file. + #include #include #include diff --git a/AMF_Tester/main.cpp b/AMF_Tester/main.cpp index 5f6fe992..bf5ae674 100644 --- a/AMF_Tester/main.cpp +++ b/AMF_Tester/main.cpp @@ -1,3 +1,7 @@ +/// \file AMF_Tester/main.cpp +/// Debugging tool for AMF data. +/// Expects AMF data through stdin, outputs human-readable information to stderr. + #define DEBUG 10 //maximum debugging level #include #include @@ -5,6 +9,8 @@ #include #include "../util/amf.h" +/// Debugging tool for AMF data. +/// Expects AMF data through stdin, outputs human-readable information to stderr. int main() { std::string temp; while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp diff --git a/Admin/main.cpp b/Admin/main.cpp index 227aa710..7286af79 100644 --- a/Admin/main.cpp +++ b/Admin/main.cpp @@ -1,3 +1,8 @@ +/// \file Admin/main.cpp +/// Attempted administration tool for DDVTECH Clients. +/// Never finished - perhaps now obsolete...? +/// \todo This could serve as a basis for a new, more robust, control method for gearbox / the API. + #include #include #include diff --git a/Buffer/main.cpp b/Buffer/main.cpp index 10eee8b1..6d806d9c 100644 --- a/Buffer/main.cpp +++ b/Buffer/main.cpp @@ -1,3 +1,6 @@ +/// \file Buffer/main.cpp +/// Contains the main code for the Buffer. + #include #include #include @@ -12,264 +15,267 @@ #include +/// Holds all code unique to the Buffer. namespace Buffer{ -void termination_handler (int signum){ - switch (signum){ - case SIGPIPE: return; break; - default: return; break; + ///A simple signal handler that ignores all signals. + void termination_handler (int signum){ + switch (signum){ + case SIGPIPE: return; break; + default: return; break; + } } -} -///holds FLV::Tag objects and their numbers -struct buffer{ - int number; - FLV::Tag FLV; -};//buffer + ///holds FLV::Tag objects and their numbers + struct buffer{ + int number; + FLV::Tag FLV; + };//buffer -/// Holds connected users. -/// Keeps track of what buffer users are using and the connection status. -class user{ - public: - int MyBuffer; ///< Index of currently used buffer. - int MyBuffer_num; ///< Number of currently used buffer. - int MyBuffer_len; ///< Length in bytes of currently used buffer. - int MyNum; ///< User ID of this user. - int currsend; ///< Current amount of bytes sent. - bool gotproperaudio; ///< Whether the user received proper audio yet. - void * lastpointer; ///< Pointer to data part of current buffer. - static int UserCount; ///< Global user counter. - DDV::Socket S; ///< Connection to user - /// Creates a new user from a newly connected socket. - /// Also prints "User connected" text to stdout. - user(DDV::Socket fd){ - S = fd; - MyNum = UserCount++; - gotproperaudio = false; - std::cout << "User " << MyNum << " connected" << std::endl; - }//constructor - /// Disconnects the current user. Doesn't do anything if already disconnected. - /// Prints "Disconnected user" to stdout if disconnect took place. - void Disconnect(std::string reason) { - if (S.connected()) { - S.close(); - std::cout << "Disconnected user " << MyNum << ": " << reason << std::endl; - } - }//Disconnect - /// Tries to send the current buffer, returns true if success, false otherwise. - /// Has a side effect of dropping the connection if send will never complete. - bool doSend(){ - int r = S.iwrite((char*)lastpointer+currsend, MyBuffer_len-currsend); - if (r <= 0){ - if ((r < 0) && (errno == EWOULDBLOCK)){return false;} - Disconnect("Connection closed"); - return false; - } - currsend += r; - return (currsend == MyBuffer_len); - }//doSend - /// Try to send data to this user. Disconnects if any problems occur. - /// \param ringbuf Array of buffers (FLV:Tag with ID attached) - /// \param buffers Count of elements in ringbuf - void Send(buffer ** ringbuf, int buffers){ - //TODO: Bij MP3: gotproperaudio - if false, stuur alleen als eerste byte is 0xFF en set op true - if (!S.connected()){return;}//cancel if not connected - - //still waiting for next buffer? check it - if (MyBuffer_num < 0){ - MyBuffer_num = ringbuf[MyBuffer]->number; + /// Holds connected users. + /// Keeps track of what buffer users are using and the connection status. + class user{ + public: + int MyBuffer; ///< Index of currently used buffer. + int MyBuffer_num; ///< Number of currently used buffer. + int MyBuffer_len; ///< Length in bytes of currently used buffer. + int MyNum; ///< User ID of this user. + int currsend; ///< Current amount of bytes sent. + bool gotproperaudio; ///< Whether the user received proper audio yet. + void * lastpointer; ///< Pointer to data part of current buffer. + static int UserCount; ///< Global user counter. + DDV::Socket S; ///< Connection to user + /// Creates a new user from a newly connected socket. + /// Also prints "User connected" text to stdout. + user(DDV::Socket fd){ + S = fd; + MyNum = UserCount++; + gotproperaudio = false; + std::cout << "User " << MyNum << " connected" << std::endl; + }//constructor + /// Disconnects the current user. Doesn't do anything if already disconnected. + /// Prints "Disconnected user" to stdout if disconnect took place. + void Disconnect(std::string reason) { + if (S.connected()) { + S.close(); + std::cout << "Disconnected user " << MyNum << ": " << reason << std::endl; + } + }//Disconnect + /// Tries to send the current buffer, returns true if success, false otherwise. + /// Has a side effect of dropping the connection if send will never complete. + bool doSend(){ + int r = S.iwrite((char*)lastpointer+currsend, MyBuffer_len-currsend); + if (r <= 0){ + if ((r < 0) && (errno == EWOULDBLOCK)){return false;} + Disconnect("Connection closed"); + return false; + } + currsend += r; + return (currsend == MyBuffer_len); + }//doSend + /// Try to send data to this user. Disconnects if any problems occur. + /// \param ringbuf Array of buffers (FLV:Tag with ID attached) + /// \param buffers Count of elements in ringbuf + void Send(buffer ** ringbuf, int buffers){ + /// \todo For MP3: gotproperaudio - if false, only send if first byte is 0xFF and set to true + if (!S.connected()){return;}//cancel if not connected + + //still waiting for next buffer? check it if (MyBuffer_num < 0){ - return; //still waiting? don't crash - wait longer. - }else{ - MyBuffer_len = ringbuf[MyBuffer]->FLV.len; - lastpointer = ringbuf[MyBuffer]->FLV.data; + MyBuffer_num = ringbuf[MyBuffer]->number; + if (MyBuffer_num < 0){ + return; //still waiting? don't crash - wait longer. + }else{ + MyBuffer_len = ringbuf[MyBuffer]->FLV.len; + lastpointer = ringbuf[MyBuffer]->FLV.data; + } } - } - - //do check for buffer resizes - if (lastpointer != ringbuf[MyBuffer]->FLV.data){ - Disconnect("Buffer resize at wrong time... had to disconnect"); - return; - } - - //try to complete a send - if (doSend()){ - //switch to next buffer - if ((ringbuf[MyBuffer]->number != MyBuffer_num)){ - //if corrupt data, warn and find keyframe - std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl; - int nocrashcount = 0; - do{ + + //do check for buffer resizes + if (lastpointer != ringbuf[MyBuffer]->FLV.data){ + Disconnect("Buffer resize at wrong time... had to disconnect"); + return; + } + + //try to complete a send + if (doSend()){ + //switch to next buffer + if ((ringbuf[MyBuffer]->number != MyBuffer_num)){ + //if corrupt data, warn and find keyframe + std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl; + int nocrashcount = 0; + do{ + MyBuffer++; + nocrashcount++; + MyBuffer %= buffers; + }while(!ringbuf[MyBuffer]->FLV.isKeyframe && (nocrashcount < buffers)); + //if keyframe not available, try again later + if (nocrashcount >= buffers){ + std::cout << "Warning: No keyframe found in buffers! Skipping search for now..." << std::endl; + return; + } + }else{ MyBuffer++; - nocrashcount++; MyBuffer %= buffers; - }while(!ringbuf[MyBuffer]->FLV.isKeyframe && (nocrashcount < buffers)); - //if keyframe not available, try again later - if (nocrashcount >= buffers){ - std::cout << "Warning: No keyframe found in buffers! Skipping search for now..." << std::endl; - return; } - }else{ - MyBuffer++; - MyBuffer %= buffers; - } - MyBuffer_num = -1; - lastpointer = 0; - currsend = 0; - }//completed a send - }//send -}; -int user::UserCount = 0; + MyBuffer_num = -1; + lastpointer = 0; + currsend = 0; + }//completed a send + }//send + }; + int user::UserCount = 0; -/// Starts a loop, waiting for connections to send video data to. -int Start(int argc, char ** argv) { - //first make sure no segpipe signals will kill us - struct sigaction new_action; - new_action.sa_handler = termination_handler; - sigemptyset (&new_action.sa_mask); - new_action.sa_flags = 0; - sigaction (SIGPIPE, &new_action, NULL); + /// Starts a loop, waiting for connections to send video data to. + int Start(int argc, char ** argv) { + //first make sure no segpipe signals will kill us + struct sigaction new_action; + new_action.sa_handler = termination_handler; + sigemptyset (&new_action.sa_mask); + new_action.sa_flags = 0; + sigaction (SIGPIPE, &new_action, NULL); - //then check and parse the commandline - if (argc < 3) { - std::cout << "usage: " << argv[0] << " buffers_count streamname" << std::endl; - return 1; - } - std::string shared_socket = "/tmp/shared_socket_"; - shared_socket += argv[2]; + //then check and parse the commandline + if (argc < 3) { + std::cout << "usage: " << argv[0] << " buffers_count streamname" << std::endl; + return 1; + } + std::string shared_socket = "/tmp/shared_socket_"; + shared_socket += argv[2]; - DDV::ServerSocket SS(shared_socket, true); - FLV::Tag metadata; - FLV::Tag video_init; - FLV::Tag audio_init; - int buffers = atoi(argv[1]); - buffer ** ringbuf = (buffer**) calloc (buffers,sizeof(buffer*)); - std::vector users; - std::vector::iterator usersIt; - for (int i = 0; i < buffers; ++i) ringbuf[i] = new buffer; - int current_buffer = 0; - int lastproper = 0;//last properly finished buffer number - unsigned int loopcount = 0; - DDV::Socket incoming; - - unsigned char packtype; - bool gotVideoInfo = false; - bool gotAudioInfo = false; - - int infile = fileno(stdin);//get file number for stdin - - //add stdin to an epoll - int poller = epoll_create(1); - struct epoll_event ev; - ev.events = EPOLLIN; - ev.data.fd = infile; - epoll_ctl(poller, EPOLL_CTL_ADD, infile, &ev); - struct epoll_event events[1]; + DDV::ServerSocket SS(shared_socket, true); + FLV::Tag metadata; + FLV::Tag video_init; + FLV::Tag audio_init; + int buffers = atoi(argv[1]); + buffer ** ringbuf = (buffer**) calloc (buffers,sizeof(buffer*)); + std::vector users; + std::vector::iterator usersIt; + for (int i = 0; i < buffers; ++i) ringbuf[i] = new buffer; + int current_buffer = 0; + int lastproper = 0;//last properly finished buffer number + unsigned int loopcount = 0; + DDV::Socket incoming; + + unsigned char packtype; + bool gotVideoInfo = false; + bool gotAudioInfo = false; + + int infile = fileno(stdin);//get file number for stdin + + //add stdin to an epoll + int poller = epoll_create(1); + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = infile; + epoll_ctl(poller, EPOLL_CTL_ADD, infile, &ev); + struct epoll_event events[1]; - while(!feof(stdin) && !FLV::Parse_Error){ - //invalidate the current buffer - ringbuf[current_buffer]->number = -1; - if ((epoll_wait(poller, events, 1, 10) > 0) && ringbuf[current_buffer]->FLV.FileLoader(stdin)){ - loopcount++; - packtype = ringbuf[current_buffer]->FLV.data[0]; - //store metadata, if available - if (packtype == 0x12){ - metadata = ringbuf[current_buffer]->FLV; - std::cout << "Received metadata!" << std::endl; - if (gotVideoInfo && gotAudioInfo){ - FLV::Parse_Error = true; - std::cout << "... after proper video and audio? Cancelling broadcast!" << std::endl; + while(!feof(stdin) && !FLV::Parse_Error){ + //invalidate the current buffer + ringbuf[current_buffer]->number = -1; + if ((epoll_wait(poller, events, 1, 10) > 0) && ringbuf[current_buffer]->FLV.FileLoader(stdin)){ + loopcount++; + packtype = ringbuf[current_buffer]->FLV.data[0]; + //store metadata, if available + if (packtype == 0x12){ + metadata = ringbuf[current_buffer]->FLV; + std::cout << "Received metadata!" << std::endl; + if (gotVideoInfo && gotAudioInfo){ + FLV::Parse_Error = true; + std::cout << "... after proper video and audio? Cancelling broadcast!" << std::endl; + } + gotVideoInfo = false; + gotAudioInfo = false; } - gotVideoInfo = false; - gotAudioInfo = false; - } - //store video init data, if available - if (!gotVideoInfo && ringbuf[current_buffer]->FLV.isKeyframe){ - if ((ringbuf[current_buffer]->FLV.data[11] & 0x0f) == 7){//avc packet - if (ringbuf[current_buffer]->FLV.data[12] == 0){ + //store video init data, if available + if (!gotVideoInfo && ringbuf[current_buffer]->FLV.isKeyframe){ + if ((ringbuf[current_buffer]->FLV.data[11] & 0x0f) == 7){//avc packet + if (ringbuf[current_buffer]->FLV.data[12] == 0){ + ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero + video_init = ringbuf[current_buffer]->FLV; + gotVideoInfo = true; + std::cout << "Received video configuration!" << std::endl; + } + }else{gotVideoInfo = true;}//non-avc = no config... + } + //store audio init data, if available + if (!gotAudioInfo && (packtype == 0x08)){ + if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 10){//aac packet ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero - video_init = ringbuf[current_buffer]->FLV; - gotVideoInfo = true; - std::cout << "Received video configuration!" << std::endl; + audio_init = ringbuf[current_buffer]->FLV; + gotAudioInfo = true; + std::cout << "Received audio configuration!" << std::endl; + }else{gotAudioInfo = true;}//no aac = no config... + } + //on keyframe set possible start point + if (packtype == 0x09){ + if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 1){ + lastproper = current_buffer; } - }else{gotVideoInfo = true;}//non-avc = no config... - } - //store audio init data, if available - if (!gotAudioInfo && (packtype == 0x08)){ - if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 10){//aac packet - ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero - audio_init = ringbuf[current_buffer]->FLV; - gotAudioInfo = true; - std::cout << "Received audio configuration!" << std::endl; - }else{gotAudioInfo = true;}//no aac = no config... - } - //on keyframe set possible start point - if (packtype == 0x09){ - if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 1){ - lastproper = current_buffer; } + //keep track of buffers + ringbuf[current_buffer]->number = loopcount; + current_buffer++; + current_buffer %= buffers; } - //keep track of buffers - ringbuf[current_buffer]->number = loopcount; - current_buffer++; - current_buffer %= buffers; - } - //check for new connections, accept them if there are any - incoming = SS.accept(true); - if (incoming.connected()){ - users.push_back(incoming); - //send the FLV header - users.back().currsend = 0; - users.back().MyBuffer = lastproper; - users.back().MyBuffer_num = -1; - //TODO: Do this more nicely? - if (!incoming.write(FLV::Header, 13)){ - users.back().Disconnect("failed to receive the header!"); - }else{ - if (!incoming.write(metadata.data, metadata.len)){ - users.back().Disconnect("failed to receive metadata!"); - } - if (!incoming.write(video_init.data, video_init.len)){ - users.back().Disconnect("failed to receive video init!"); - } - if (!incoming.write(audio_init.data, audio_init.len)){ - users.back().Disconnect("failed to receive audio init!"); - } - } - } - - //send all connections what they need, if and when they need it - if (users.size() > 0){ - for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ - if (!(*usersIt).S.connected()){ - users.erase(usersIt); break; + //check for new connections, accept them if there are any + incoming = SS.accept(true); + if (incoming.connected()){ + users.push_back(incoming); + //send the FLV header + users.back().currsend = 0; + users.back().MyBuffer = lastproper; + users.back().MyBuffer_num = -1; + /// \todo Do this more nicely? + if (!incoming.write(FLV::Header, 13)){ + users.back().Disconnect("failed to receive the header!"); }else{ - (*usersIt).Send(ringbuf, buffers); + if (!incoming.write(metadata.data, metadata.len)){ + users.back().Disconnect("failed to receive metadata!"); + } + if (!incoming.write(video_init.data, video_init.len)){ + users.back().Disconnect("failed to receive video init!"); + } + if (!incoming.write(audio_init.data, audio_init.len)){ + users.back().Disconnect("failed to receive audio init!"); + } } } - } - }//main loop - // disconnect listener - if (FLV::Parse_Error){ - std::cout << "FLV parse error" << std::endl; - }else{ - std::cout << "Reached EOF of input" << std::endl; - } - SS.close(); - while (users.size() > 0){ - for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ - (*usersIt).Disconnect("Shutting down..."); - if (!(*usersIt).S.connected()){users.erase(usersIt);break;} + //send all connections what they need, if and when they need it + if (users.size() > 0){ + for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ + if (!(*usersIt).S.connected()){ + users.erase(usersIt); break; + }else{ + (*usersIt).Send(ringbuf, buffers); + } + } + } + }//main loop + + // disconnect listener + if (FLV::Parse_Error){ + std::cout << "FLV parse error" << std::endl; + }else{ + std::cout << "Reached EOF of input" << std::endl; } + SS.close(); + while (users.size() > 0){ + for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ + (*usersIt).Disconnect("Shutting down..."); + if (!(*usersIt).S.connected()){users.erase(usersIt);break;} + } + } + return 0; } - return 0; -} };//Buffer namespace +/// Entry point for Buffer, simply calls Buffer::Start(). int main(int argc, char ** argv){ Buffer::Start(argc, argv); }//main diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index ee517cf2..a9495465 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -1,9 +1,12 @@ +/// \file Connector_HTTP/main.cpp +/// Contains the main code for the HTTP Connector + /// Sets the global debugging level. -/// debugging level 0 = nothing -/// debugging level 1 = critical errors -/// debugging level 2 = errors -/// debugging level 3 = status information -/// debugging level 4 = extremely verbose status information +// debugging level 0 = nothing +// debugging level 1 = critical errors +// debugging level 2 = errors +// debugging level 3 = status information +// debugging level 4 = extremely verbose status information #define DEBUG 4 #include diff --git a/Connector_RAW/main.cpp b/Connector_RAW/main.cpp index 51898ec0..c90d6498 100644 --- a/Connector_RAW/main.cpp +++ b/Connector_RAW/main.cpp @@ -1,6 +1,12 @@ +/// \file Connector_RAW/main.cpp +/// Contains the main code for the RAW connector. + #include #include "../util/ddv_socket.h" +/// Contains the main code for the RAW connector. +/// Expects a single commandline argument telling it which stream to connect to, +/// then outputs the raw stream to stdout. int main(int argc, char ** argv) { if (argc < 2){ std::cout << "Usage: " << argv[0] << " stream_name" << std::endl; @@ -8,11 +14,14 @@ int main(int argc, char ** argv) { } std::string input = "/tmp/shared_socket_"; input += argv[1]; + //connect to the proper stream DDV::Socket S(input); if (!S.connected()){ std::cout << "Could not open stream " << argv[1] << std::endl; return 1; } + //transport ~50kb at a time + //this is a nice tradeoff between CPU usage and speed char buffer[50000]; while(std::cout.good() && S.read(buffer,50000)){std::cout.write(buffer,50000);} S.close(); diff --git a/Connector_RTMP/Makefile b/Connector_RTMP/Makefile index e8cc8757..dfb274a8 100644 --- a/Connector_RTMP/Makefile +++ b/Connector_RTMP/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp ../util/amf.cpp +SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Conn_RTMP INCLUDES = @@ -13,7 +13,7 @@ LIBS = -lssl -lcrypto default: $(OUT) .cpp.o: $(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ -$(OUT): $(OBJ) chunkstream.cpp parsechunks.cpp handshake.cpp crypto.cpp +$(OUT): $(OBJ) $(CC) -o $(OUT) $(OBJ) $(STATIC) $(LIBS) clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ diff --git a/Connector_RTMP/RTMP_Conn b/Connector_RTMP/RTMP_Conn deleted file mode 100755 index 1f38e92b..00000000 --- a/Connector_RTMP/RTMP_Conn +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh -# -# description: DDVTech RTMP Connector -# processname: Connector_RTMP - -prog="Connector_RTMP" -fullprog="/usr/bin/Connector_RTMP" -RETVAL=0 - -start() { - echo "Starting $prog" - $fullprog - return $? -} - -stop() { - echo "Stopping $prog" - killall $prog - return $? -} - -case "$1" in - start) - start - ;; - stop) - stop - ;; - restart) - stop - start - ;; - *) - echo "Usage: $0 {start|stop|restart}" - RETVAL=1 -esac - -exit $RETVAL diff --git a/Connector_RTMP/chunkstream.cpp b/Connector_RTMP/chunkstream.cpp deleted file mode 100644 index d27cbd45..00000000 --- a/Connector_RTMP/chunkstream.cpp +++ /dev/null @@ -1,501 +0,0 @@ -#include -#include -#include -#include -#include - -unsigned int getNowMS(){ - timeval t; - gettimeofday(&t, 0); - return t.tv_sec + t.tv_usec/1000; -} - - -unsigned int chunk_rec_max = 128; -unsigned int chunk_snd_max = 128; -unsigned int rec_window_size = 0xFA00; -unsigned int snd_window_size = 1024*500; -unsigned int rec_window_at = 0; -unsigned int snd_window_at = 0; -unsigned int rec_cnt = 0; -unsigned int snd_cnt = 0; - -unsigned int firsttime; - -struct chunkinfo { - unsigned int cs_id; - unsigned int timestamp; - unsigned int len; - unsigned int real_len; - unsigned int len_left; - unsigned char msg_type_id; - unsigned int msg_stream_id; -};//chunkinfo - -struct chunkpack { - unsigned char chunktype; - unsigned int cs_id; - unsigned int timestamp; - unsigned int len; - unsigned int real_len; - unsigned int len_left; - unsigned char msg_type_id; - unsigned int msg_stream_id; - unsigned char * data; -};//chunkpack - -//clean a chunk so that it may be re-used without memory leaks -void scrubChunk(struct chunkpack c){ - if (c.data){free(c.data);} - c.data = 0; - c.real_len = 0; -}//scrubChunk - - -//ugly global, but who cares... -std::map prevmap; -//return previous packet of this cs_id -chunkinfo GetPrev(unsigned int cs_id){ - return prevmap[cs_id]; -}//GetPrev -//store packet information of last packet of this cs_id -void PutPrev(chunkpack prev){ - prevmap[prev.cs_id].timestamp = prev.timestamp; - prevmap[prev.cs_id].len = prev.len; - prevmap[prev.cs_id].real_len = prev.real_len; - prevmap[prev.cs_id].len_left = prev.len_left; - prevmap[prev.cs_id].msg_type_id = prev.msg_type_id; - prevmap[prev.cs_id].msg_stream_id = prev.msg_stream_id; -}//PutPrev - -//ugly global, but who cares... -std::map sndprevmap; -//return previous packet of this cs_id -chunkinfo GetSndPrev(unsigned int cs_id){ - return sndprevmap[cs_id]; -}//GetPrev -//store packet information of last packet of this cs_id -void PutSndPrev(chunkpack prev){ - sndprevmap[prev.cs_id].cs_id = prev.cs_id; - sndprevmap[prev.cs_id].timestamp = prev.timestamp; - sndprevmap[prev.cs_id].len = prev.len; - sndprevmap[prev.cs_id].real_len = prev.real_len; - sndprevmap[prev.cs_id].len_left = prev.len_left; - sndprevmap[prev.cs_id].msg_type_id = prev.msg_type_id; - sndprevmap[prev.cs_id].msg_stream_id = prev.msg_stream_id; -}//PutPrev - - - -//sends the chunk over the network -void SendChunk(chunkpack ch){ - unsigned char tmp; - unsigned int tmpi; - unsigned char chtype = 0x00; - chunkinfo prev = GetSndPrev(ch.cs_id); - ch.timestamp -= firsttime; - if (prev.cs_id == ch.cs_id){ - if (ch.msg_stream_id == prev.msg_stream_id){ - chtype = 0x40;//do not send msg_stream_id - if (ch.len == prev.len){ - if (ch.msg_type_id == prev.msg_type_id){ - chtype = 0x80;//do not send len and msg_type_id - if (ch.timestamp == prev.timestamp){ - chtype = 0xC0;//do not send timestamp - } - } - } - } - } - if (ch.cs_id <= 63){ - tmp = chtype | ch.cs_id; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=1; - }else{ - if (ch.cs_id <= 255+64){ - tmp = chtype | 0; DDV_write(&tmp, 1, 1, CONN_fd); - tmp = ch.cs_id - 64; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=2; - }else{ - tmp = chtype | 1; DDV_write(&tmp, 1, 1, CONN_fd); - tmpi = ch.cs_id - 64; - tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd); - tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=3; - } - } - unsigned int ntime = 0; - if (chtype != 0xC0){ - //timestamp or timestamp diff - if (chtype == 0x00){ - tmpi = ch.timestamp; - if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;} - tmp = tmpi / (256*256); DDV_write(&tmp, 1, 1, CONN_fd); - tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd); - tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=3; - }else{ - tmpi = ch.timestamp - prev.timestamp; - if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;} - tmp = tmpi / (256*256); DDV_write(&tmp, 1, 1, CONN_fd); - tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd); - tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=3; - } - if (chtype != 0x80){ - //len - tmpi = ch.len; - tmp = tmpi / (256*256); DDV_write(&tmp, 1, 1, CONN_fd); - tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd); - tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=3; - //msg type id - tmp = ch.msg_type_id; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=1; - if (chtype != 0x40){ - //msg stream id - tmp = ch.msg_stream_id % 256; DDV_write(&tmp, 1, 1, CONN_fd); - tmp = ch.msg_stream_id / 256; DDV_write(&tmp, 1, 1, CONN_fd); - tmp = ch.msg_stream_id / (256*256); DDV_write(&tmp, 1, 1, CONN_fd); - tmp = ch.msg_stream_id / (256*256*256); DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=4; - } - } - } - //support for 0x00ffffff timestamps - if (ntime){ - tmp = ntime / (256*256*256); DDV_write(&tmp, 1, 1, CONN_fd); - tmp = ntime / (256*256); DDV_write(&tmp, 1, 1, CONN_fd); - tmp = ntime / 256; DDV_write(&tmp, 1, 1, CONN_fd); - tmp = ntime % 256; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=4; - } - ch.len_left = 0; - while (ch.len_left < ch.len){ - tmpi = ch.len - ch.len_left; - if (tmpi > chunk_snd_max){tmpi = chunk_snd_max;} - DDV_write((ch.data + ch.len_left), 1, tmpi, CONN_fd); - snd_cnt+=tmpi; - ch.len_left += tmpi; - if (ch.len_left < ch.len){ - if (ch.cs_id <= 63){ - tmp = 0xC0 + ch.cs_id; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=1; - }else{ - if (ch.cs_id <= 255+64){ - tmp = 0xC0; DDV_write(&tmp, 1, 1, CONN_fd); - tmp = ch.cs_id - 64; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=2; - }else{ - tmp = 0xC1; DDV_write(&tmp, 1, 1, CONN_fd); - tmpi = ch.cs_id - 64; - tmp = tmpi % 256; DDV_write(&tmp, 1, 1, CONN_fd); - tmp = tmpi / 256; DDV_write(&tmp, 1, 1, CONN_fd); - snd_cnt+=4; - } - } - } - } - PutSndPrev(ch); -}//SendChunk - -//sends a chunk -void SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data){ - chunkpack ch; - ch.cs_id = cs_id; - ch.timestamp = getNowMS(); - ch.len = data.size(); - ch.real_len = data.size(); - ch.len_left = 0; - ch.msg_type_id = msg_type_id; - ch.msg_stream_id = msg_stream_id; - ch.data = (unsigned char*)malloc(data.size()); - memcpy(ch.data, data.c_str(), data.size()); - SendChunk(ch); - free(ch.data); -}//SendChunk - -//sends a media chunk -void SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts){ - chunkpack ch; - ch.cs_id = msg_type_id; - ch.timestamp = ts; - ch.len = len; - ch.real_len = len; - ch.len_left = 0; - ch.msg_type_id = msg_type_id; - ch.msg_stream_id = 1; - ch.data = (unsigned char*)malloc(len); - memcpy(ch.data, data, len); - SendChunk(ch); - free(ch.data); -}//SendMedia - -//sends a control message -void SendCTL(unsigned char type, unsigned int data){ - chunkpack ch; - ch.cs_id = 2; - ch.timestamp = getNowMS(); - ch.len = 4; - ch.real_len = 4; - ch.len_left = 0; - ch.msg_type_id = type; - ch.msg_stream_id = 0; - ch.data = (unsigned char*)malloc(4); - data = htonl(data); - memcpy(ch.data, &data, 4); - SendChunk(ch); - free(ch.data); -}//SendCTL - -//sends a control message -void SendCTL(unsigned char type, unsigned int data, unsigned char data2){ - chunkpack ch; - ch.cs_id = 2; - ch.timestamp = getNowMS(); - ch.len = 5; - ch.real_len = 5; - ch.len_left = 0; - ch.msg_type_id = type; - ch.msg_stream_id = 0; - ch.data = (unsigned char*)malloc(5); - data = htonl(data); - memcpy(ch.data, &data, 4); - ch.data[4] = data2; - SendChunk(ch); - free(ch.data); -}//SendCTL - -//sends a usr control message -void SendUSR(unsigned char type, unsigned int data){ - chunkpack ch; - ch.cs_id = 2; - ch.timestamp = getNowMS(); - ch.len = 6; - ch.real_len = 6; - ch.len_left = 0; - ch.msg_type_id = 4; - ch.msg_stream_id = 0; - ch.data = (unsigned char*)malloc(6); - data = htonl(data); - memcpy(ch.data+2, &data, 4); - ch.data[0] = 0; - ch.data[1] = type; - SendChunk(ch); - free(ch.data); -}//SendUSR - -//sends a usr control message -void SendUSR(unsigned char type, unsigned int data, unsigned int data2){ - chunkpack ch; - ch.cs_id = 2; - ch.timestamp = getNowMS(); - ch.len = 10; - ch.real_len = 10; - ch.len_left = 0; - ch.msg_type_id = 4; - ch.msg_stream_id = 0; - ch.data = (unsigned char*)malloc(10); - data = htonl(data); - data2 = htonl(data2); - memcpy(ch.data+2, &data, 4); - memcpy(ch.data+6, &data2, 4); - ch.data[0] = 0; - ch.data[1] = type; - SendChunk(ch); - free(ch.data); -}//SendUSR - -//get a chunk from standard input -struct chunkpack getChunk(){ - gettimeofday(&lastrec, 0); - struct chunkpack ret; - unsigned char temp; - DDV_read(&(ret.chunktype), 1, 1, CONN_fd); - rec_cnt++; - //read the chunkstream ID properly - switch (ret.chunktype & 0x3F){ - case 0: - DDV_read(&temp, 1, 1, CONN_fd); - rec_cnt++; - ret.cs_id = temp + 64; - break; - case 1: - DDV_read(&temp, 1, 1, CONN_fd); - ret.cs_id = temp + 64; - DDV_read(&temp, 1, 1, CONN_fd); - ret.cs_id += temp * 256; - rec_cnt+=2; - break; - default: - ret.cs_id = ret.chunktype & 0x3F; - break; - } - chunkinfo prev = GetPrev(ret.cs_id); - //process the rest of the header, for each chunk type - switch (ret.chunktype & 0xC0){ - case 0x00: - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp = temp*256*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp += temp*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp += temp; - DDV_read(&temp, 1, 1, CONN_fd); - ret.len = temp*256*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.len += temp*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.len += temp; - ret.len_left = 0; - DDV_read(&temp, 1, 1, CONN_fd); - ret.msg_type_id = temp; - DDV_read(&temp, 1, 1, CONN_fd); - ret.msg_stream_id = temp; - DDV_read(&temp, 1, 1, CONN_fd); - ret.msg_stream_id += temp*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.msg_stream_id += temp*256*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.msg_stream_id += temp*256*256*256; - rec_cnt+=11; - break; - case 0x40: - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp = temp*256*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp += temp*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp += temp; - ret.timestamp += prev.timestamp; - DDV_read(&temp, 1, 1, CONN_fd); - ret.len = temp*256*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.len += temp*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.len += temp; - ret.len_left = 0; - DDV_read(&temp, 1, 1, CONN_fd); - ret.msg_type_id = temp; - ret.msg_stream_id = prev.msg_stream_id; - rec_cnt+=7; - break; - case 0x80: - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp = temp*256*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp += temp*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp += temp; - ret.timestamp += prev.timestamp; - ret.len = prev.len; - ret.len_left = prev.len_left; - ret.msg_type_id = prev.msg_type_id; - ret.msg_stream_id = prev.msg_stream_id; - rec_cnt+=3; - break; - case 0xC0: - ret.timestamp = prev.timestamp; - ret.len = prev.len; - ret.len_left = prev.len_left; - ret.msg_type_id = prev.msg_type_id; - ret.msg_stream_id = prev.msg_stream_id; - break; - } - //calculate chunk length, real length, and length left till complete - if (ret.len_left > 0){ - ret.real_len = ret.len_left; - ret.len_left -= ret.real_len; - }else{ - ret.real_len = ret.len; - } - if (ret.real_len > chunk_rec_max){ - ret.len_left += ret.real_len - chunk_rec_max; - ret.real_len = chunk_rec_max; - } - //read extended timestamp, if neccesary - if (ret.timestamp == 0x00ffffff){ - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp = temp*256*256*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp += temp*256*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp += temp*256; - DDV_read(&temp, 1, 1, CONN_fd); - ret.timestamp += temp; - rec_cnt+=4; - } - //read data if length > 0, and allocate it - if (ret.real_len > 0){ - ret.data = (unsigned char*)malloc(ret.real_len); - DDV_read(ret.data, 1, ret.real_len, CONN_fd); - rec_cnt+=ret.real_len; - }else{ - ret.data = 0; - } - PutPrev(ret); - return ret; -}//getChunk - -//adds newchunk to global list of unfinished chunks, re-assembling them complete -//returns pointer to chunk when a chunk is finished, 0 otherwise -//removes pointed to chunk from internal list if returned, without cleanup -// (cleanup performed in getWholeChunk function) -chunkpack * AddChunkPart(chunkpack newchunk){ - chunkpack * p; - unsigned char * tmpdata = 0; - static std::map ch_lst; - std::map::iterator it; - it = ch_lst.find(newchunk.cs_id); - if (it == ch_lst.end()){ - p = (chunkpack*)malloc(sizeof(chunkpack)); - *p = newchunk; - p->data = (unsigned char*)malloc(p->real_len); - memcpy(p->data, newchunk.data, p->real_len); - if (p->len_left == 0){return p;} - ch_lst[newchunk.cs_id] = p; - }else{ - p = it->second; - tmpdata = (unsigned char*)realloc(p->data, p->real_len + newchunk.real_len); - if (tmpdata == 0){ - #if DEBUG >= 1 - fprintf(stderr, "Error allocating memory!\n"); - #endif - return 0; - } - p->data = tmpdata; - memcpy(p->data+p->real_len, newchunk.data, newchunk.real_len); - p->real_len += newchunk.real_len; - p->len_left -= newchunk.real_len; - if (p->len_left <= 0){ - ch_lst.erase(it); - return p; - }else{ - ch_lst[newchunk.cs_id] = p;//pointer may have changed - } - } - return 0; -}//AddChunkPart - -//grabs chunks until a whole one comes in, then returns that -chunkpack getWholeChunk(){ - static chunkpack gwc_next, gwc_complete; - static bool clean = false; - int counter = 0; - if (!clean){gwc_complete.data = 0; clean = true;}//prevent brain damage - chunkpack * ret = 0; - scrubChunk(gwc_complete); - while (counter < 1000){ - gwc_next = getChunk(); - ret = AddChunkPart(gwc_next); - scrubChunk(gwc_next); - if (ret){ - gwc_complete = *ret; - free(ret);//cleanup returned chunk - return gwc_complete; - } - if (socketError || socketBlocking){break;} - counter++; - } - gwc_complete.msg_type_id = 0; - return gwc_complete; -}//getWholeChunk diff --git a/Connector_RTMP/crypto.h b/Connector_RTMP/crypto.h deleted file mode 100644 index 8d36188b..00000000 --- a/Connector_RTMP/crypto.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef _CRYPTO_H -#define _CRYPTO_H -#define DLLEXP - -#include -#include -#include -#include -#include -#include -#include -#include - -class DLLEXP DHWrapper { -private: - int32_t _bitsCount; - DH *_pDH; - uint8_t *_pSharedKey; - int32_t _sharedKeyLength; - BIGNUM *_peerPublickey; -public: - DHWrapper(int32_t bitsCount); - virtual ~DHWrapper(); - - bool Initialize(); - bool CopyPublicKey(uint8_t *pDst, int32_t dstLength); - bool CopyPrivateKey(uint8_t *pDst, int32_t dstLength); - bool CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length); - bool CopySharedKey(uint8_t *pDst, int32_t dstLength); -private: - void Cleanup(); - bool CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength); -}; - - -DLLEXP void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, - RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut); -DLLEXP std::string md5(std::string source, bool textResult); -DLLEXP std::string b64(std::string source); -DLLEXP std::string b64(uint8_t *pBuffer, uint32_t length); -DLLEXP std::string unb64(std::string source); -DLLEXP std::string unb64(uint8_t *pBuffer, uint32_t length); - -#endif /* _CRYPTO_H */ - diff --git a/Connector_RTMP/handshake.cpp b/Connector_RTMP/handshake.cpp deleted file mode 100644 index 24095c3b..00000000 --- a/Connector_RTMP/handshake.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#undef OLDHANDSHAKE //change to #define for old handshake method - -char versionstring[] = "WWW.DDVTECH.COM "; - -#ifdef OLDHANDSHAKE -struct Handshake { - char Time[4]; - char Zero[4]; - char Random[1528]; -};//Handshake - -bool doHandshake(){ - char Version; - Handshake Client; - Handshake Server; - /** Read C0 **/ - DDV_read(&(Version), 1, 1, CONN_fd); - /** Read C1 **/ - DDV_read(Client.Time, 1, 4, CONN_fd); - DDV_read(Client.Zero, 1, 4, CONN_fd); - DDV_read(Client.Random, 1, 1528, CONN_fd); - rec_cnt+=1537; - /** Build S1 Packet **/ - Server.Time[0] = 0; Server.Time[1] = 0; Server.Time[2] = 0; Server.Time[3] = 0; - Server.Zero[0] = 0; Server.Zero[1] = 0; Server.Zero[2] = 0; Server.Zero[3] = 0; - for (int i = 0; i < 1528; i++){ - Server.Random[i] = versionstring[i%sizeof(versionstring)]; - } - /** Send S0 **/ - DDV_write(&(Version), 1, 1, CONN_fd); - /** Send S1 **/ - DDV_write(Server.Time, 1, 4, CONN_fd); - DDV_write(Server.Zero, 1, 4, CONN_fd); - DDV_write(Server.Random, 1, 1528, CONN_fd); - /** Flush output, just for certainty **/ - //fflush(CONN_fd); - snd_cnt+=1537; - /** Send S2 **/ - DDV_write(Client.Time, 1, 4, CONN_fd); - DDV_write(Client.Time, 1, 4, CONN_fd); - DDV_write(Client.Random, 1, 1528, CONN_fd); - snd_cnt+=1536; - /** Flush, necessary in order to work **/ - //fflush(CONN_fd); - /** Read and discard C2 **/ - DDV_read(Client.Time, 1, 4, CONN_fd); - DDV_read(Client.Zero, 1, 4, CONN_fd); - DDV_read(Client.Random, 1, 1528, CONN_fd); - rec_cnt+=1536; - return true; -}//doHandshake - -#else - -#include "crypto.cpp" //cryptography for handshaking - -bool doHandshake(){ - char Version; - /** Read C0 **/ - DDV_read(&Version, 1, 1, CONN_fd); - uint8_t Client[1536]; - uint8_t Server[3072]; - DDV_read(&Client, 1, 1536, CONN_fd); - rec_cnt+=1537; - - /** Build S1 Packet **/ - *((uint32_t*)Server) = 0;//time zero - *(((uint32_t*)(Server+4))) = htonl(0x01020304);//version 1 2 3 4 - for (int i = 8; i < 3072; ++i){Server[i] = versionstring[i%13];}//"random" data - - bool encrypted = (Version == 6); - #if DEBUG >= 4 - fprintf(stderr, "Handshake version is %hhi\n", Version); - #endif - uint8_t _validationScheme = 5; - if (ValidateClientScheme(Client, 0)) _validationScheme = 0; - if (ValidateClientScheme(Client, 1)) _validationScheme = 1; - - #if DEBUG >= 4 - fprintf(stderr, "Handshake type is %hhi, encryption is %s\n", _validationScheme, encrypted?"on":"off"); - #endif - - //**** FIRST 1536 bytes from server response ****// - //compute DH key position - uint32_t serverDHOffset = GetDHOffset(Server, _validationScheme); - uint32_t clientDHOffset = GetDHOffset(Client, _validationScheme); - - //generate DH key - DHWrapper dhWrapper(1024); - if (!dhWrapper.Initialize()) return false; - if (!dhWrapper.CreateSharedKey(Client + clientDHOffset, 128)) return false; - if (!dhWrapper.CopyPublicKey(Server + serverDHOffset, 128)) return false; - - if (encrypted) { - uint8_t secretKey[128]; - if (!dhWrapper.CopySharedKey(secretKey, sizeof (secretKey))) return false; - RC4_KEY _pKeyIn; - RC4_KEY _pKeyOut; - InitRC4Encryption(secretKey, (uint8_t*) & Client[clientDHOffset], (uint8_t*) & Server[serverDHOffset], &_pKeyIn, &_pKeyOut); - uint8_t data[1536]; - RC4(&_pKeyIn, 1536, data, data); - RC4(&_pKeyOut, 1536, data, data); - } - //generate the digest - uint32_t serverDigestOffset = GetDigestOffset(Server, _validationScheme); - uint8_t *pTempBuffer = new uint8_t[1536 - 32]; - memcpy(pTempBuffer, Server, serverDigestOffset); - memcpy(pTempBuffer + serverDigestOffset, Server + serverDigestOffset + 32, 1536 - serverDigestOffset - 32); - uint8_t *pTempHash = new uint8_t[512]; - HMACsha256(pTempBuffer, 1536 - 32, genuineFMSKey, 36, pTempHash); - memcpy(Server + serverDigestOffset, pTempHash, 32); - delete[] pTempBuffer; - delete[] pTempHash; - - //**** SECOND 1536 bytes from server response ****// - uint32_t keyChallengeIndex = GetDigestOffset(Client, _validationScheme); - pTempHash = new uint8_t[512]; - HMACsha256(Client + keyChallengeIndex, 32, genuineFMSKey, 68, pTempHash); - uint8_t *pLastHash = new uint8_t[512]; - HMACsha256(Server + 1536, 1536 - 32, pTempHash, 32, pLastHash); - memcpy(Server + 1536 * 2 - 32, pLastHash, 32); - delete[] pTempHash; - delete[] pLastHash; - //***** DONE BUILDING THE RESPONSE ***// - /** Send response **/ - DDV_write(&Version, 1, 1, CONN_fd); - DDV_write(&Server, 1, 3072, CONN_fd); - snd_cnt+=3073; - /** Flush, necessary in order to work **/ - //fflush(CONN_fd); - /** Read and discard C2 **/ - DDV_read(Client, 1, 1536, CONN_fd); - rec_cnt+=1536; - return true; -} - -#endif diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 3cc33d29..6fe57bfe 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -1,9 +1,11 @@ +/// \file Connector_RTMP/main.cpp +/// Contains the main code for the RTMP Connector + //debugging level 0 = nothing //debugging level 1 = critical errors //debugging level 2 = errors //debugging level 3 = status information //debugging level 4 = extremely verbose status information -//debugging level 5 = save all streams to FLV files #define DEBUG 4 #include @@ -18,155 +20,402 @@ #include #include "../util/ddv_socket.h" #include "../util/flv_tag.h" - -#include "parsechunks.cpp" //chunkstream parsing -#include "handshake.cpp" //handshaking +#include "../util/amf.h" +#include "../util/rtmpchunks.h" /// Holds all functions and data unique to the RTMP Connector namespace Connector_RTMP{ - + //for connection to server bool ready4data = false; ///< Set to true when streaming starts. bool inited = false; ///< Set to true when ready to connect to Buffer. bool stopparsing = false; ///< Set to true when all parsing needs to be cancelled. - timeval lastrec; ///< Timestamp of last received data. - - DDV::Socket Socket; ///< Socket connected to user - - /// Main Connector_RTMP function - int Connector_RTMP(DDV::Socket conn){ - Socket = conn; - unsigned int ts; - unsigned int fts = 0; - unsigned int ftst; - DDV::Socket SS; - FLV::Tag tag = 0; - - //first timestamp set - firsttime = getNowMS(); - - if (doHandshake()){ - #if DEBUG >= 4 - fprintf(stderr, "Handshake succcess!\n"); - #endif - }else{ - #if DEBUG >= 1 - fprintf(stderr, "Handshake fail!\n"); - #endif - return 0; - } - - int retval; - int poller = epoll_create(1); - int sspoller = epoll_create(1); - struct epoll_event ev; - ev.events = EPOLLIN; - ev.data.fd = CONN_fd; - epoll_ctl(poller, EPOLL_CTL_ADD, CONN_fd, &ev); - struct epoll_event events[1]; - #if DEBUG >= 5 - //for writing whole stream to a file - FILE * tmpfile = 0; - char tmpstr[200]; - #endif - while (Socket.connected() && !FLV::Parse_Error){ - //only parse input if available or not yet init'ed - //rightnow = getNowMS(); - retval = epoll_wait(poller, events, 1, 1); - if ((retval > 0) || !ready4data){// || (snd_cnt - snd_window_at >= snd_window_size) - switch (Socket.ready()){ - case -1: break; //disconnected - case 0: break; //not ready yet - default: parseChunk(); break; //new data is waiting - } - } - if (ready4data){ - if (!inited){ - //we are ready, connect the socket! - SS = DDV::Socket(streamname); - if (!SS.connected()){ - #if DEBUG >= 1 - fprintf(stderr, "Could not connect to server!\n"); - #endif - Socket.close();//disconnect user - break; - } - ev.events = EPOLLIN; - ev.data.fd = SS.getSocket(); - epoll_ctl(sspoller, EPOLL_CTL_ADD, SS.getSocket(), &ev); - #if DEBUG >= 3 - fprintf(stderr, "Everything connected, starting to send video data...\n"); - #endif - inited = true; - } + DDV::Socket Socket; ///< Socket connected to user + std::string streamname = "/tmp/shared_socket"; ///< Stream that will be opened + void parseChunk(); + int Connector_RTMP(DDV::Socket conn); +};//Connector_RTMP namespace; - retval = epoll_wait(sspoller, events, 1, 1); - switch (SS.ready()){ - case -1: - #if DEBUG >= 1 - fprintf(stderr, "Source socket is disconnected.\n"); - #endif - Socket.close();//disconnect user - break; - case 0: break;//not ready yet - default: - if (tag.SockLoader(SS)){//able to read a full packet? - ts = tag.tagTime(); - if (ts != 0){ - if (fts == 0){fts = ts;ftst = getNowMS();} - ts -= fts; - tag.tagTime(ts); - ts += ftst; - }else{ - ftst = getNowMS(); - tag.tagTime(ftst); - } - SendMedia((unsigned char)tag.data[0], (unsigned char *)tag.data+11, tag.len-15, ts); - #if DEBUG >= 5 - //write whole stream to a file - if (tmpfile == 0){ - sprintf(tmpstr, "./tmpfile_socket_%i.flv", CONN_fd); - tmpfile = fopen(tmpstr, "w"); - fwrite(FLVHeader, 13, 1, tmpfile); - } - fwrite(tag->data, tag->len, 1, tmpfile); - #endif - #if DEBUG >= 4 - fprintf(stderr, "Sent a tag to %i\n", CONN_fd); - #endif - } - break; - } - } - //send ACK if we received a whole window - if ((rec_cnt - rec_window_at > rec_window_size)){ - rec_window_at = rec_cnt; - SendCTL(3, rec_cnt);//send ack (msg 3) - } - } - SS.close(); - Socket.close(); - #if DEBUG >= 5 - fclose(tmpfile); + +/// Main Connector_RTMP function +int Connector_RTMP::Connector_RTMP(DDV::Socket conn){ + Socket = conn; + unsigned int ts; + unsigned int fts = 0; + unsigned int ftst; + DDV::Socket SS; + FLV::Tag tag; + + //first timestamp set + RTMPStream::firsttime = RTMPStream::getNowMS(); + + while (RTMPStream::handshake_in.size() < 1537){ + Socket.read(RTMPStream::handshake_in); + } + if (RTMPStream::doHandshake()){ + Socket.write(RTMPStream::handshake_out); + Socket.read((char*)RTMPStream::handshake_in.c_str(), 1536); + RTMPStream::rec_cnt += 1536; + #if DEBUG >= 4 + fprintf(stderr, "Handshake succcess!\n"); #endif + }else{ #if DEBUG >= 1 - if (FLV::Parse_Error){fprintf(stderr, "FLV Parse Error\n");} - fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); - if (inited){ - fprintf(stderr, "Status was: inited\n"); - }else{ - if (ready4data){ - fprintf(stderr, "Status was: ready4data\n"); - }else{ - fprintf(stderr, "Status was: connected\n"); - } - } + fprintf(stderr, "Handshake fail!\n"); #endif return 0; - }//Connector_RTMP + } + + int retval; + int poller = epoll_create(1); + int sspoller = epoll_create(1); + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = Socket.getSocket(); + epoll_ctl(poller, EPOLL_CTL_ADD, Socket.getSocket(), &ev); + struct epoll_event events[1]; + + while (Socket.connected() && !FLV::Parse_Error){ + //only parse input if available or not yet init'ed + //rightnow = getNowMS(); + retval = epoll_wait(poller, events, 1, 1); + if ((retval > 0) || !ready4data){// || (snd_cnt - snd_window_at >= snd_window_size) + switch (Socket.ready()){ + case -1: break; //disconnected + case 0: break; //not ready yet + default: parseChunk(); break; //new data is waiting + } + } + if (ready4data){ + if (!inited){ + //we are ready, connect the socket! + SS = DDV::Socket(streamname); + if (!SS.connected()){ + #if DEBUG >= 1 + fprintf(stderr, "Could not connect to server!\n"); + #endif + Socket.close();//disconnect user + break; + } + ev.events = EPOLLIN; + ev.data.fd = SS.getSocket(); + epoll_ctl(sspoller, EPOLL_CTL_ADD, SS.getSocket(), &ev); + #if DEBUG >= 3 + fprintf(stderr, "Everything connected, starting to send video data...\n"); + #endif + inited = true; + } + retval = epoll_wait(sspoller, events, 1, 1); + switch (SS.ready()){ + case -1: + #if DEBUG >= 1 + fprintf(stderr, "Source socket is disconnected.\n"); + #endif + Socket.close();//disconnect user + break; + case 0: break;//not ready yet + default: + if (tag.SockLoader(SS)){//able to read a full packet? + ts = tag.tagTime(); + if (ts != 0){ + if (fts == 0){fts = ts;ftst = RTMPStream::getNowMS();} + ts -= fts; + tag.tagTime(ts); + ts += ftst; + }else{ + ftst = RTMPStream::getNowMS(); + tag.tagTime(ftst); + } + Socket.write(RTMPStream::SendMedia((unsigned char)tag.data[0], (unsigned char *)tag.data+11, tag.len-15, ts)); + #if DEBUG >= 4 + fprintf(stderr, "Sent a tag to %i\n", Socket.getSocket()); + #endif + } + break; + } + } + } + SS.close(); + Socket.close(); + #if DEBUG >= 1 + if (FLV::Parse_Error){fprintf(stderr, "FLV Parse Error: %s\n", FLV::Error_Str.c_str());} + fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); + if (inited){ + fprintf(stderr, "Status was: inited\n"); + }else{ + if (ready4data){ + fprintf(stderr, "Status was: ready4data\n"); + }else{ + fprintf(stderr, "Status was: connected\n"); + } + } + #endif + return 0; +}//Connector_RTMP + +/// Tries to get and parse one RTMP chunk at a time. +void Connector_RTMP::parseChunk(){ + static RTMPStream::Chunk next; + static std::string inbuffer; + static AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER); + static AMF::Object amfelem("empty", AMF::AMF0_DDV_CONTAINER); + if (!Connector_RTMP::Socket.read(inbuffer)){return;} //try to get more data + + while (next.Parse(inbuffer)){ + + //send ACK if we received a whole window + if ((RTMPStream::rec_cnt - RTMPStream::rec_window_at > RTMPStream::rec_window_size)){ + RTMPStream::rec_window_at = RTMPStream::rec_cnt; + Socket.write(RTMPStream::SendCTL(3, RTMPStream::rec_cnt));//send ack (msg 3) + } + + switch (next.msg_type_id){ + case 0://does not exist + break;//happens when connection breaks unexpectedly + case 1://set chunk size + RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str()); + #if DEBUG >= 4 + fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max); + #endif + break; + case 2://abort message - we ignore this one + #if DEBUG >= 4 + fprintf(stderr, "CTRL: Abort message\n"); + #endif + //4 bytes of stream id to drop + break; + case 3://ack + #if DEBUG >= 4 + fprintf(stderr, "CTRL: Acknowledgement\n"); + #endif + RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str()); + RTMPStream::snd_window_at = RTMPStream::snd_cnt; + break; + case 4:{ + #if DEBUG >= 4 + short int ucmtype = ntohs(*(short int*)next.data.c_str()); + fprintf(stderr, "CTRL: User control message %hi\n", ucmtype); + #endif + //2 bytes event type, rest = event data + //types: + //0 = stream begin, 4 bytes ID + //1 = stream EOF, 4 bytes ID + //2 = stream dry, 4 bytes ID + //3 = setbufferlen, 4 bytes ID, 4 bytes length + //4 = streamisrecorded, 4 bytes ID + //6 = pingrequest, 4 bytes data + //7 = pingresponse, 4 bytes data + //we don't need to process this + } break; + case 5://window size of other end + #if DEBUG >= 4 + fprintf(stderr, "CTRL: Window size\n"); + #endif + RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str()); + RTMPStream::rec_window_at = RTMPStream::rec_cnt; + Socket.write(RTMPStream::SendCTL(3, RTMPStream::rec_cnt));//send ack (msg 3) + break; + case 6: + #if DEBUG >= 4 + fprintf(stderr, "CTRL: Set peer bandwidth\n"); + #endif + //4 bytes window size, 1 byte limit type (ignored) + RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str()); + Socket.write(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5) + break; + case 8: + #if DEBUG >= 4 + fprintf(stderr, "Received audio data\n"); + #endif + break; + case 9: + #if DEBUG >= 4 + fprintf(stderr, "Received video data\n"); + #endif + break; + case 15: + #if DEBUG >= 4 + fprintf(stderr, "Received AFM3 data message\n"); + #endif + break; + case 16: + #if DEBUG >= 4 + fprintf(stderr, "Received AFM3 shared object\n"); + #endif + break; + case 17: + #if DEBUG >= 4 + fprintf(stderr, "Received AFM3 command message\n"); + #endif + break; + case 18: + #if DEBUG >= 4 + fprintf(stderr, "Received AFM0 data message\n"); + #endif + break; + case 19: + #if DEBUG >= 4 + fprintf(stderr, "Received AFM0 shared object\n"); + #endif + break; + case 20:{//AMF0 command message + bool parsed = false; + amfdata = AMF::parse(next.data); + #if DEBUG >= 4 + amfdata.Print(); + #endif + if (amfdata.getContentP(0)->StrValue() == "connect"){ + #if DEBUG >= 4 + int tmpint; + tmpint = amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue(); + if (tmpint & 0x04){fprintf(stderr, "Sorensen video support detected\n");} + if (tmpint & 0x80){fprintf(stderr, "H264 video support detected\n");} + tmpint = amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue(); + if (tmpint & 0x04){fprintf(stderr, "MP3 audio support detected\n");} + if (tmpint & 0x400){fprintf(stderr, "AAC video support detected\n");} + #endif + Socket.write(RTMPStream::SendCTL(6, RTMPStream::rec_window_size, 0));//send peer bandwidth (msg 6) + Socket.write(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5) + Socket.write(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 + //send a _result reply + AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMF::Object("", "_result"));//result success + amfreply.addContent(amfdata.getContent(1));//same transaction ID + // amfreply.addContent(AMFType("", (double)0, 0x05));//null - command info + amfreply.addContent(AMF::Object(""));//server properties + amfreply.getContentP(2)->addContent(AMF::Object("fmsVer", "FMS/3,5,2,654"));//stolen from examples + amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)31));//stolen from examples + amfreply.getContentP(2)->addContent(AMF::Object("mode", (double)1));//stolen from examples + amfreply.getContentP(2)->addContent(AMF::Object("objectEncoding", (double)0)); + amfreply.addContent(AMF::Object(""));//info + amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); + amfreply.getContentP(3)->addContent(AMF::Object("code", "NetConnection.Connect.Success")); + amfreply.getContentP(3)->addContent(AMF::Object("description", "Connection succeeded.")); + #if DEBUG >= 4 + amfreply.Print(); + #endif + Socket.write(RTMPStream::SendChunk(3, 20, next.msg_stream_id, amfreply.Pack())); + //send onBWDone packet + //amfreply = AMFType("container", (unsigned char)0xFF); + //amfreply.addContent(AMFType("", "onBWDone"));//result success + //amfreply.addContent(AMFType("", (double)0));//zero + //amfreply.addContent(AMFType("", (double)0, 0x05));//null + //SendChunk(3, 20, next.msg_stream_id, amfreply.Pack()); + parsed = true; + }//connect + if (amfdata.getContentP(0)->StrValue() == "createStream"){ + //send a _result reply + AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMF::Object("", "_result"));//result success + amfreply.addContent(amfdata.getContent(1));//same transaction ID + amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info + amfreply.addContent(AMF::Object("", (double)1));//stream ID - we use 1 + #if DEBUG >= 4 + amfreply.Print(); + #endif + Socket.write(RTMPStream::SendChunk(3, 20, next.msg_stream_id, amfreply.Pack())); + Socket.write(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 + parsed = true; + }//createStream + if ((amfdata.getContentP(0)->StrValue() == "getStreamLength") || (amfdata.getContentP(0)->StrValue() == "getMovLen")){ + //send a _result reply + AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMF::Object("", "_result"));//result success + amfreply.addContent(amfdata.getContent(1));//same transaction ID + amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info + amfreply.addContent(AMF::Object("", (double)0));//zero length + #if DEBUG >= 4 + amfreply.Print(); + #endif + Socket.write(RTMPStream::SendChunk(3, 20, next.msg_stream_id, amfreply.Pack())); + parsed = true; + }//getStreamLength + if (amfdata.getContentP(0)->StrValue() == "checkBandwidth"){ + //send a _result reply + AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMF::Object("", "_result"));//result success + amfreply.addContent(amfdata.getContent(1));//same transaction ID + amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info + amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info + #if DEBUG >= 4 + amfreply.Print(); + #endif + Socket.write(RTMPStream::SendChunk(3, 20, 1, amfreply.Pack())); + parsed = true; + }//checkBandwidth + if ((amfdata.getContentP(0)->StrValue() == "play") || (amfdata.getContentP(0)->StrValue() == "play2")){ + //send streambegin + streamname = amfdata.getContentP(3)->StrValue(); + for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){ + if (!isalpha(*i) && !isdigit(*i)){streamname.erase(i);}else{*i=tolower(*i);} + } + streamname = "/tmp/shared_socket_" + streamname; + Socket.write(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 + //send a status reply + AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMF::Object("", "onStatus"));//status reply + amfreply.addContent(amfdata.getContent(1));//same transaction ID + amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info + amfreply.addContent(AMF::Object(""));//info + amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); + amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Reset")); + amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing and resetting...")); + amfreply.getContentP(3)->addContent(AMF::Object("details", "PLS")); + amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1)); + #if DEBUG >= 4 + amfreply.Print(); + #endif + Socket.write(RTMPStream::SendChunk(4, 20, next.msg_stream_id, amfreply.Pack())); + amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMF::Object("", "onStatus"));//status reply + amfreply.addContent(amfdata.getContent(1));//same transaction ID + amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info + amfreply.addContent(AMF::Object(""));//info + amfreply.getContentP(3)->addContent(AMF::Object("level", "status")); + amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Start")); + amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing!")); + amfreply.getContentP(3)->addContent(AMF::Object("details", "PLS")); + amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1)); + #if DEBUG >= 4 + amfreply.Print(); + #endif + Socket.write(RTMPStream::SendChunk(4, 20, 1, amfreply.Pack())); + //No clue what this does. Most real servers send it, though... + // amfreply = AMFType("container", (unsigned char)0xFF); + // amfreply.addContent(AMFType("", "|RtmpSampleAccess"));//status reply + // amfreply.addContent(AMFType("", (double)1, 0x01));//bool true - audioaccess + // amfreply.addContent(AMFType("", (double)1, 0x01));//bool true - videoaccess + // SendChunk(4, 20, next.msg_stream_id, amfreply.Pack()); + RTMPStream::chunk_snd_max = 1024*1024; + Socket.write(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max));//send chunk size max (msg 1) + Connector_RTMP::ready4data = true;//start sending video data! + parsed = true; + }//createStream + #if DEBUG >= 3 + fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str()); + #endif + if (!parsed){ + #if DEBUG >= 2 + fprintf(stderr, "AMF0 command not processed! :(\n"); + #endif + } + } break; + case 22: + #if DEBUG >= 4 + fprintf(stderr, "Received aggregate message\n"); + #endif + break; + default: + #if DEBUG >= 1 + fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n"); + #endif + Connector_RTMP::stopparsing = true; + break; + } + } +}//parseChunk -};//Connector_RTMP namespace // Load main server setup file, default port 1935, handler is Connector_RTMP::Connector_RTMP #define DEFAULT_PORT 1935 diff --git a/Connector_RTMP/parsechunks.cpp b/Connector_RTMP/parsechunks.cpp deleted file mode 100644 index 6b5fae0f..00000000 --- a/Connector_RTMP/parsechunks.cpp +++ /dev/null @@ -1,254 +0,0 @@ -#include "chunkstream.cpp" //chunkstream decoding -#include "amf.cpp" //simple AMF0 parsing -std::string streamname = "/tmp/shared_socket"; - -//gets and parses one chunk -void parseChunk(){ - static chunkpack next; - static AMFType amfdata("empty", (unsigned char)AMF0_DDV_CONTAINER); - static AMFType amfelem("empty", (unsigned char)AMF0_DDV_CONTAINER); - next = getWholeChunk(); - switch (next.msg_type_id){ - case 0://does not exist - break;//happens when connection breaks unexpectedly - case 1://set chunk size - chunk_rec_max = ntohl(*(int*)next.data); - #if DEBUG >= 4 - fprintf(stderr, "CTRL: Set chunk size: %i\n", chunk_rec_max); - #endif - break; - case 2://abort message - we ignore this one - #if DEBUG >= 4 - fprintf(stderr, "CTRL: Abort message\n"); - #endif - //4 bytes of stream id to drop - break; - case 3://ack - #if DEBUG >= 4 - fprintf(stderr, "CTRL: Acknowledgement\n"); - #endif - snd_window_at = ntohl(*(int*)next.data); - snd_window_at = snd_cnt; - break; - case 4:{ - #if DEBUG >= 4 - short int ucmtype = ntohs(*(short int*)next.data); - fprintf(stderr, "CTRL: User control message %hi\n", ucmtype); - #endif - //2 bytes event type, rest = event data - //types: - //0 = stream begin, 4 bytes ID - //1 = stream EOF, 4 bytes ID - //2 = stream dry, 4 bytes ID - //3 = setbufferlen, 4 bytes ID, 4 bytes length - //4 = streamisrecorded, 4 bytes ID - //6 = pingrequest, 4 bytes data - //7 = pingresponse, 4 bytes data - //we don't need to process this - } break; - case 5://window size of other end - #if DEBUG >= 4 - fprintf(stderr, "CTRL: Window size\n"); - #endif - rec_window_size = ntohl(*(int*)next.data); - rec_window_at = rec_cnt; - SendCTL(3, rec_cnt);//send ack (msg 3) - break; - case 6: - #if DEBUG >= 4 - fprintf(stderr, "CTRL: Set peer bandwidth\n"); - #endif - //4 bytes window size, 1 byte limit type (ignored) - snd_window_size = ntohl(*(int*)next.data); - SendCTL(5, snd_window_size);//send window acknowledgement size (msg 5) - break; - case 8: - #if DEBUG >= 4 - fprintf(stderr, "Received audio data\n"); - #endif - break; - case 9: - #if DEBUG >= 4 - fprintf(stderr, "Received video data\n"); - #endif - break; - case 15: - #if DEBUG >= 4 - fprintf(stderr, "Received AFM3 data message\n"); - #endif - break; - case 16: - #if DEBUG >= 4 - fprintf(stderr, "Received AFM3 shared object\n"); - #endif - break; - case 17: - #if DEBUG >= 4 - fprintf(stderr, "Received AFM3 command message\n"); - #endif - break; - case 18: - #if DEBUG >= 4 - fprintf(stderr, "Received AFM0 data message\n"); - #endif - break; - case 19: - #if DEBUG >= 4 - fprintf(stderr, "Received AFM0 shared object\n"); - #endif - break; - case 20:{//AMF0 command message - bool parsed = false; - amfdata = parseAMF(next.data, next.real_len); - #if DEBUG >= 4 - amfdata.Print(); - #endif - if (amfdata.getContentP(0)->StrValue() == "connect"){ - #if DEBUG >= 4 - int tmpint; - tmpint = amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue(); - if (tmpint & 0x04){fprintf(stderr, "Sorensen video support detected\n");} - if (tmpint & 0x80){fprintf(stderr, "H264 video support detected\n");} - tmpint = amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue(); - if (tmpint & 0x04){fprintf(stderr, "MP3 audio support detected\n");} - if (tmpint & 0x400){fprintf(stderr, "AAC video support detected\n");} - #endif - SendCTL(6, rec_window_size, 0);//send peer bandwidth (msg 6) - SendCTL(5, snd_window_size);//send window acknowledgement size (msg 5) - SendUSR(0, 1);//send UCM StreamBegin (0), stream 1 - //send a _result reply - AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER); - amfreply.addContent(AMFType("", "_result"));//result success - amfreply.addContent(amfdata.getContent(1));//same transaction ID -// amfreply.addContent(AMFType("", (double)0, 0x05));//null - command info - amfreply.addContent(AMFType(""));//server properties - amfreply.getContentP(2)->addContent(AMFType("fmsVer", "FMS/3,5,2,654"));//stolen from examples - amfreply.getContentP(2)->addContent(AMFType("capabilities", (double)31));//stolen from examples - amfreply.getContentP(2)->addContent(AMFType("mode", (double)1));//stolen from examples - amfreply.getContentP(2)->addContent(AMFType("objectEncoding", (double)0)); - amfreply.addContent(AMFType(""));//info - amfreply.getContentP(3)->addContent(AMFType("level", "status")); - amfreply.getContentP(3)->addContent(AMFType("code", "NetConnection.Connect.Success")); - amfreply.getContentP(3)->addContent(AMFType("description", "Connection succeeded.")); - #if DEBUG >= 4 - amfreply.Print(); - #endif - SendChunk(3, 20, next.msg_stream_id, amfreply.Pack()); - //send onBWDone packet - //amfreply = AMFType("container", (unsigned char)0xFF); - //amfreply.addContent(AMFType("", "onBWDone"));//result success - //amfreply.addContent(AMFType("", (double)0));//zero - //amfreply.addContent(AMFType("", (double)0, 0x05));//null - //SendChunk(3, 20, next.msg_stream_id, amfreply.Pack()); - parsed = true; - }//connect - if (amfdata.getContentP(0)->StrValue() == "createStream"){ - //send a _result reply - AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER); - amfreply.addContent(AMFType("", "_result"));//result success - amfreply.addContent(amfdata.getContent(1));//same transaction ID - amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info - amfreply.addContent(AMFType("", (double)1));//stream ID - we use 1 - #if DEBUG >= 4 - amfreply.Print(); - #endif - SendChunk(3, 20, next.msg_stream_id, amfreply.Pack()); - SendUSR(0, 1);//send UCM StreamBegin (0), stream 1 - parsed = true; - }//createStream - if ((amfdata.getContentP(0)->StrValue() == "getStreamLength") || (amfdata.getContentP(0)->StrValue() == "getMovLen")){ - //send a _result reply - AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER); - amfreply.addContent(AMFType("", "_result"));//result success - amfreply.addContent(amfdata.getContent(1));//same transaction ID - amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info - amfreply.addContent(AMFType("", (double)0));//zero length - #if DEBUG >= 4 - amfreply.Print(); - #endif - SendChunk(3, 20, next.msg_stream_id, amfreply.Pack()); - parsed = true; - }//getStreamLength - if (amfdata.getContentP(0)->StrValue() == "checkBandwidth"){ - //send a _result reply - AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER); - amfreply.addContent(AMFType("", "_result"));//result success - amfreply.addContent(amfdata.getContent(1));//same transaction ID - amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info - amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info - #if DEBUG >= 4 - amfreply.Print(); - #endif - SendChunk(3, 20, 1, amfreply.Pack()); - parsed = true; - }//checkBandwidth - if ((amfdata.getContentP(0)->StrValue() == "play") || (amfdata.getContentP(0)->StrValue() == "play2")){ - //send streambegin - streamname = amfdata.getContentP(3)->StrValue(); - for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){ - if (!isalpha(*i) && !isdigit(*i)){streamname.erase(i);}else{*i=tolower(*i);} - } - streamname = "/tmp/shared_socket_" + streamname; - SendUSR(0, 1);//send UCM StreamBegin (0), stream 1 - //send a status reply - AMFType amfreply("container", (unsigned char)AMF0_DDV_CONTAINER); - amfreply.addContent(AMFType("", "onStatus"));//status reply - amfreply.addContent(amfdata.getContent(1));//same transaction ID - amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info - amfreply.addContent(AMFType(""));//info - amfreply.getContentP(3)->addContent(AMFType("level", "status")); - amfreply.getContentP(3)->addContent(AMFType("code", "NetStream.Play.Reset")); - amfreply.getContentP(3)->addContent(AMFType("description", "Playing and resetting...")); - amfreply.getContentP(3)->addContent(AMFType("details", "PLS")); - amfreply.getContentP(3)->addContent(AMFType("clientid", (double)1)); - #if DEBUG >= 4 - amfreply.Print(); - #endif - SendChunk(4, 20, next.msg_stream_id, amfreply.Pack()); - amfreply = AMFType("container", (unsigned char)AMF0_DDV_CONTAINER); - amfreply.addContent(AMFType("", "onStatus"));//status reply - amfreply.addContent(amfdata.getContent(1));//same transaction ID - amfreply.addContent(AMFType("", (double)0, AMF0_NULL));//null - command info - amfreply.addContent(AMFType(""));//info - amfreply.getContentP(3)->addContent(AMFType("level", "status")); - amfreply.getContentP(3)->addContent(AMFType("code", "NetStream.Play.Start")); - amfreply.getContentP(3)->addContent(AMFType("description", "Playing!")); - amfreply.getContentP(3)->addContent(AMFType("details", "PLS")); - amfreply.getContentP(3)->addContent(AMFType("clientid", (double)1)); - #if DEBUG >= 4 - amfreply.Print(); - #endif - SendChunk(4, 20, 1, amfreply.Pack()); -//No clue what this does. Most real servers send it, though... -// amfreply = AMFType("container", (unsigned char)0xFF); -// amfreply.addContent(AMFType("", "|RtmpSampleAccess"));//status reply -// amfreply.addContent(AMFType("", (double)1, 0x01));//bool true - audioaccess -// amfreply.addContent(AMFType("", (double)1, 0x01));//bool true - videoaccess -// SendChunk(4, 20, next.msg_stream_id, amfreply.Pack()); - chunk_snd_max = 1024*1024; - SendCTL(1, chunk_snd_max);//send chunk size max (msg 1) - ready4data = true;//start sending video data! - parsed = true; - }//createStream - #if DEBUG >= 3 - fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str()); - #endif - if (!parsed){ - #if DEBUG >= 2 - fprintf(stderr, "AMF0 command not processed! :(\n"); - #endif - } - } break; - case 22: - #if DEBUG >= 4 - fprintf(stderr, "Received aggregate message\n"); - #endif - break; - default: - #if DEBUG >= 1 - fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n"); - #endif - stopparsing = true; - break; - } -}//parseChunk diff --git a/Doxyfile b/Doxyfile index dd8ba205..b6edff54 100644 --- a/Doxyfile +++ b/Doxyfile @@ -43,7 +43,7 @@ SYMBOL_CACHE_SIZE = 0 # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = YES -EXTRACT_PRIVATE = NO +EXTRACT_PRIVATE = YES EXTRACT_STATIC = YES EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_METHODS = NO diff --git a/HTTP_Box_Parser/main.cpp b/HTTP_Box_Parser/main.cpp index 6cc55223..432bbd35 100644 --- a/HTTP_Box_Parser/main.cpp +++ b/HTTP_Box_Parser/main.cpp @@ -1,3 +1,11 @@ +/// \file HTTP_Box_Parser/main.cpp +/// Debugging tool for F4M HTTP streaming data. +/// Expects raw TCP data through stdin, outputs human-readable information to stderr. +/// This will attempt to read either HTTP requests or responses from stdin, and if the body is more than +/// 10,000 bytes long will attempt to parse the data as a MP4 box. (Other cases show a message about the fragment being too small) +/// Then it will take the payload of this box, print the first four bytes, and attempt to parse the whole payload as FLV data. +/// The parsed FLV data is then pretty-printed, containing information about the codec parameters and types of tags it encounters. + #include #include #include @@ -6,6 +14,12 @@ #include "../util/MP4/box_includes.h" #include "../util/flv_tag.h" +/// Debugging tool for F4M HTTP streaming data. +/// Expects raw TCP data through stdin, outputs human-readable information to stderr. +/// This will attempt to read either HTTP requests or responses from stdin, and if the body is more than +/// 10,000 bytes long will attempt to parse the data as a MP4 box. (Other cases show a message about the fragment being too small) +/// Then it will take the payload of this box, print the first four bytes, and attempt to parse the whole payload as FLV data. +/// The parsed FLV data is then pretty-printed, containing information about the codec parameters and types of tags it encounters. int main(){ HTTPReader H; FLV::Tag F; @@ -28,4 +42,4 @@ int main(){ std::cout << "Skipped too small fragment of size " << H.body.size() << std::endl; } } -} +}//main diff --git a/Makefile b/Makefile index 71a53364..0389a1ad 100644 --- a/Makefile +++ b/Makefile @@ -18,5 +18,5 @@ client-install: client-clean client cd Connector_RAW; $(MAKE) install cd Buffer; $(MAKE) install docs: - doxygen ./Doxyfile + doxygen ./Doxyfile > /dev/null diff --git a/util/amf.cpp b/util/amf.cpp index fdff660f..97d97ef5 100644 --- a/util/amf.cpp +++ b/util/amf.cpp @@ -1,3 +1,6 @@ +/// \file amf.cpp +/// Holds all code for the AMF namespace. + #include "amf.h" /// Returns the std::string Indice for the current object, if available. diff --git a/util/amf.h b/util/amf.h index 81ab9881..8ae95cd4 100644 --- a/util/amf.h +++ b/util/amf.h @@ -1,3 +1,6 @@ +/// \file amf.h +/// Holds all headers for the AMF namespace. + #pragma once #include #include diff --git a/Connector_RTMP/crypto.cpp b/util/crypto.cpp similarity index 99% rename from Connector_RTMP/crypto.cpp rename to util/crypto.cpp index bc1e616c..c523c680 100644 --- a/Connector_RTMP/crypto.cpp +++ b/util/crypto.cpp @@ -1,3 +1,6 @@ +/// \file crypto.cpp +/// Holds all code needed for RTMP cryptography. + #define STR(x) (((std::string)(x)).c_str()) #include "crypto.h" diff --git a/util/crypto.h b/util/crypto.h new file mode 100644 index 00000000..f4daa4bb --- /dev/null +++ b/util/crypto.h @@ -0,0 +1,56 @@ +/// \file crypto.h +/// Holds all headers needed for RTMP cryptography functions. + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class DHWrapper { +private: + int32_t _bitsCount; + DH *_pDH; + uint8_t *_pSharedKey; + int32_t _sharedKeyLength; + BIGNUM *_peerPublickey; +public: + DHWrapper(int32_t bitsCount); + virtual ~DHWrapper(); + + bool Initialize(); + bool CopyPublicKey(uint8_t *pDst, int32_t dstLength); + bool CopyPrivateKey(uint8_t *pDst, int32_t dstLength); + bool CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length); + bool CopySharedKey(uint8_t *pDst, int32_t dstLength); +private: + void Cleanup(); + bool CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength); +}; + + +void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut); +std::string md5(std::string source, bool textResult); +std::string b64(std::string source); +std::string b64(uint8_t *pBuffer, uint32_t length); +std::string unb64(std::string source); +std::string unb64(uint8_t *pBuffer, uint32_t length); + +void HMACsha256(const void *pData, uint32_t dataLength, const void *pKey, uint32_t keyLength, void *pResult); + +uint32_t GetDigestOffset0(uint8_t *pBuffer); +uint32_t GetDigestOffset1(uint8_t *pBuffer); +uint32_t GetDigestOffset(uint8_t *pBuffer, uint8_t scheme); +uint32_t GetDHOffset0(uint8_t *pBuffer); +uint32_t GetDHOffset1(uint8_t *pBuffer); +uint32_t GetDHOffset(uint8_t *pBuffer, uint8_t scheme); + +extern uint8_t genuineFMSKey[]; + +bool ValidateClientScheme(uint8_t * pBuffer, uint8_t scheme); diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index 76c2ee8a..e82ea8ae 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -1,3 +1,6 @@ +/// \file ddv_socket.cpp +/// Holds all code for the DDV namespace. + #include "ddv_socket.h" /// Create a new base socket. This is a basic constructor for converting any valid socket to a DDV::Socket. @@ -207,6 +210,21 @@ int DDV::Socket::iread(void * buffer, int len){ return r; }//DDV::Socket::iread +/// Read call that is compatible with std::string. +/// Data is read using iread (which is nonblocking if the DDV::Socket itself is), +/// then appended to end of buffer. +/// \param buffer std::string to append data to. +/// \return True if new data arrived, false otherwise. +bool DDV::Socket::read(std::string & buffer){ + char cbuffer[5000]; + int num = iread(cbuffer, 5000); + if (num > 0){ + buffer.append(cbuffer, num); + return true; + } + return false; +}//read + /// Create a new base ServerSocket. The socket is never connected, and a placeholder for later connections. DDV::ServerSocket::ServerSocket(){ sock = -1; diff --git a/util/ddv_socket.h b/util/ddv_socket.h index 298b5954..5d58e0bd 100644 --- a/util/ddv_socket.h +++ b/util/ddv_socket.h @@ -1,3 +1,6 @@ +/// \file ddv_socket.h +/// Holds all headers for the DDV namespace. + #pragma once #include #include @@ -33,6 +36,7 @@ namespace DDV{ bool write(const std::string data); ///< Write call that is compatible with std::string. int iwrite(void * buffer, int len); ///< Incremental write call. int iread(void * buffer, int len); ///< Incremental read call. + bool read(std::string & buffer); ///< Read call that is compatible with std::string. void close(); ///< Close connection. int getSocket(); ///< Returns internal socket number. }; diff --git a/util/flv_tag.cpp b/util/flv_tag.cpp index 70321b17..08fed05b 100644 --- a/util/flv_tag.cpp +++ b/util/flv_tag.cpp @@ -1,3 +1,6 @@ +/// \file flv_tag.cpp +/// Holds all code for the FLV namespace. + #include "flv_tag.h" #include //for Tag::FileLoader #include //for Tag::FileLoader @@ -8,6 +11,7 @@ char FLV::Header[13]; ///< Holds the last FLV header parsed. bool FLV::Parse_Error = false; ///< This variable is set to true if a problem is encountered while parsing the FLV. +std::string FLV::Error_Str = ""; /// Checks a FLV Header for validness. Returns true if the header is valid, false /// if the header is not. Not valid can mean: @@ -219,7 +223,7 @@ bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){ if (FLV::check_header(data)){ sofar = 0; memcpy(FLV::Header, data, 13); - }else{FLV::Parse_Error = true; return false;} + }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} } }else{ //if a tag header, calculate length and read tag body @@ -227,7 +231,7 @@ bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){ len += (data[2] << 8); len += (data[1] << 16); if (buf < len){data = (char*)realloc(data, len); buf = len;} - if (data[0] > 0x12){FLV::Parse_Error = true; return false;} + if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;} done = false; } } @@ -259,7 +263,7 @@ bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & s if (r < 0){ if (errno != EWOULDBLOCK){ FLV::Parse_Error = true; - fprintf(stderr, "ReadUntil fail: %s. All Hell Broke Loose!\n", strerror(errno)); + Error_Str = "Error reading from socket."; } return false; } @@ -267,7 +271,7 @@ bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & s if (sofar == count){return true;} if (sofar > count){ FLV::Parse_Error = true; - fprintf(stderr, "ReadUntil fail: %s. Read too much. All Hell Broke Loose!\n", strerror(errno)); + Error_Str = "Socket buffer overflow."; } return false; }//Tag::SockReadUntil @@ -289,7 +293,7 @@ bool FLV::Tag::SockLoader(DDV::Socket sock){ if (FLV::check_header(data)){ sofar = 0; memcpy(FLV::Header, data, 13); - }else{FLV::Parse_Error = true; return false;} + }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} } }else{ //if a tag header, calculate length and read tag body @@ -297,7 +301,7 @@ bool FLV::Tag::SockLoader(DDV::Socket sock){ len += (data[2] << 8); len += (data[1] << 16); if (buf < len){data = (char*)realloc(data, len); buf = len;} - if (data[0] > 0x12){FLV::Parse_Error = true; return false;} + if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;} done = false; } } @@ -335,7 +339,7 @@ bool FLV::Tag::FileReadUntil(char * buffer, unsigned int count, unsigned int & s if (sofar >= count){return true;} int r = 0; r = fread(buffer + sofar,1,count-sofar,f); - if (r < 0){FLV::Parse_Error = true; return false;} + if (r < 0){FLV::Parse_Error = true; Error_Str = "File reading error."; return false;} sofar += r; if (sofar >= count){return true;} return false; @@ -363,7 +367,7 @@ bool FLV::Tag::FileLoader(FILE * f){ if (FLV::check_header(data)){ sofar = 0; memcpy(FLV::Header, data, 13); - }else{FLV::Parse_Error = true;} + }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} } }else{ //if a tag header, calculate length and read tag body @@ -371,7 +375,7 @@ bool FLV::Tag::FileLoader(FILE * f){ len += (data[2] << 8); len += (data[1] << 16); if (buf < len){data = (char*)realloc(data, len); buf = len;} - if (data[0] > 0x12){FLV::Parse_Error = true;} + if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;} done = false; } } diff --git a/util/flv_tag.h b/util/flv_tag.h index d91cabb5..e824228c 100644 --- a/util/flv_tag.h +++ b/util/flv_tag.h @@ -1,3 +1,6 @@ +/// \file flv_tag.h +/// Holds all headers for the FLV namespace. + #pragma once #include "ddv_socket.h" #include @@ -7,7 +10,8 @@ namespace FLV { //variables extern char Header[13]; ///< Holds the last FLV header parsed. extern bool Parse_Error; ///< This variable is set to true if a problem is encountered while parsing the FLV. - + extern std::string Error_Str; ///< This variable is set if a problem is encountered while parsing the FLV. + //functions bool check_header(char * header); ///< Checks a FLV Header for validness. bool is_header(char * header); ///< Checks the first 3 bytes for the string "FLV". diff --git a/util/http_parser.cpp b/util/http_parser.cpp index dd01e9b2..60d370e7 100644 --- a/util/http_parser.cpp +++ b/util/http_parser.cpp @@ -1,8 +1,14 @@ -#include "http_parser.h" -#include "ddv_socket.h" +/// \file http_parser.cpp +/// Holds all code for the HTTP namespace. -HTTPReader::HTTPReader(){Clean();} -void HTTPReader::Clean(){ +#include "http_parser.h" + +/// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing. +/// All this constructor does is call HTTP::Parser::Clean(). +HTTP::Parser::Parser(){Clean();} + +/// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing usage. +void HTTP::Parser::Clean(){ seenHeaders = false; seenReq = false; method = "GET"; @@ -11,11 +17,14 @@ void HTTPReader::Clean(){ body = ""; length = 0; HTTPbuffer = ""; - headers.erase(headers.begin(), headers.end()); - vars.erase(vars.begin(), vars.end()); + headers.clear(); + vars.clear(); } -bool HTTPReader::CleanForNext(){ +/// Re-initializes the HTTP::Parser, leaving the internal data buffer alone, then tries to parse a new request or response. +/// First does the same as HTTP::Parser::Clean(), but does not clear the internal data buffer. +/// This function then calls the HTTP::Parser::parse() function, and returns that functions return value. +bool HTTP::Parser::CleanForNext(){ seenHeaders = false; seenReq = false; method = "GET"; @@ -23,12 +32,19 @@ bool HTTPReader::CleanForNext(){ protocol = "HTTP/1.1"; body = ""; length = 0; - headers.erase(headers.begin(), headers.end()); - vars.erase(vars.begin(), vars.end()); + headers.clear(); + vars.clear(); return parse(); } -std::string HTTPReader::BuildRequest(){ +/// Returns a string containing a valid HTTP 1.0 or 1.1 request, ready for sending. +/// The request is build from internal variables set before this call is made. +/// To be precise, method, url, protocol, headers and the internal data buffer are used, +/// where the internal data buffer is used as the body of the request. +/// This means you cannot mix receiving and sending, because the body would get corrupted. +/// \return A string containing a valid HTTP 1.0 or 1.1 request, ready for sending. +std::string HTTP::Parser::BuildRequest(){ + /// \todo Include GET/POST variable parsing? std::map::iterator it; std::string tmp = method+" "+url+" "+protocol+"\n"; for (it=headers.begin(); it != headers.end(); it++){ @@ -39,7 +55,16 @@ std::string HTTPReader::BuildRequest(){ return tmp; } -std::string HTTPReader::BuildResponse(std::string code, std::string message){ +/// Returns a string containing a valid HTTP 1.0 or 1.1 response, ready for sending. +/// The response is partly build from internal variables set before this call is made. +/// To be precise, protocol, headers and the internal data buffer are used, +/// where the internal data buffer is used as the body of the response. +/// This means you cannot mix receiving and sending, because the body would get corrupted. +/// \param code The HTTP response code. Usually you want 200. +/// \param message The HTTP response message. Usually you want "OK". +/// \return A string containing a valid HTTP 1.0 or 1.1 response, ready for sending. +std::string HTTP::Parser::BuildResponse(std::string code, std::string message){ + /// \todo Include GET/POST variable parsing? std::map::iterator it; std::string tmp = protocol+" "+code+" "+message+"\n"; for (it=headers.begin(); it != headers.end(); it++){ @@ -50,47 +75,61 @@ std::string HTTPReader::BuildResponse(std::string code, std::string message){ return tmp; } -void HTTPReader::Trim(std::string & s){ +/// Trims any whitespace at the front or back of the string. +/// Used when getting/setting headers. +/// \param s The string to trim. The string itself will be changed, not returned. +void HTTP::Parser::Trim(std::string & s){ size_t startpos = s.find_first_not_of(" \t"); size_t endpos = s.find_last_not_of(" \t"); if ((std::string::npos == startpos) || (std::string::npos == endpos)){s = "";}else{s = s.substr(startpos, endpos-startpos+1);} } -void HTTPReader::SetBody(std::string s){ +/// Function that sets the body of a response or request, along with the correct Content-Length header. +/// \param s The string to set the body to. +void HTTP::Parser::SetBody(std::string s){ HTTPbuffer = s; SetHeader("Content-Length", s.length()); } -void HTTPReader::SetBody(char * buffer, int len){ +/// Function that sets the body of a response or request, along with the correct Content-Length header. +/// \param buffer The buffer data to set the body to. +/// \param len Length of the buffer data. +void HTTP::Parser::SetBody(char * buffer, int len){ HTTPbuffer = ""; HTTPbuffer.append(buffer, len); SetHeader("Content-Length", len); } +/// Returns header i, if set. +std::string HTTP::Parser::GetHeader(std::string i){return headers[i];} +/// Returns POST variable i, if set. +std::string HTTP::Parser::GetVar(std::string i){return vars[i];} -std::string HTTPReader::GetHeader(std::string i){return headers[i];} -std::string HTTPReader::GetVar(std::string i){return vars[i];} - -void HTTPReader::SetHeader(std::string i, std::string v){ +/// Sets header i to string value v. +void HTTP::Parser::SetHeader(std::string i, std::string v){ Trim(i); Trim(v); headers[i] = v; } -void HTTPReader::SetHeader(std::string i, int v){ +/// Sets header i to integer value v. +void HTTP::Parser::SetHeader(std::string i, int v){ Trim(i); char val[128]; sprintf(val, "%i", v); headers[i] = val; } -void HTTPReader::SetVar(std::string i, std::string v){ +/// Sets POST variable i to string value v. +void HTTP::Parser::SetVar(std::string i, std::string v){ Trim(i); Trim(v); vars[i] = v; } -bool HTTPReader::Read(DDV::Socket & sock){ +/// Attempt to read a whole HTTP request or response from DDV::Socket sock. +/// \return True of a whole request or response was read, false otherwise. +bool HTTP::Parser::Read(DDV::Socket & sock){ //returned true als hele http packet gelezen is int r = 0; int b = 0; @@ -111,7 +150,9 @@ bool HTTPReader::Read(DDV::Socket & sock){ return false; }//HTTPReader::ReadSocket -bool HTTPReader::Read(FILE * F){ +/// Reads a full set of HTTP responses/requests from file F. +/// \return Always false. Use HTTP::Parser::CleanForNext() to parse the contents of the file. +bool HTTP::Parser::Read(FILE * F){ //returned true als hele http packet gelezen is int b = 1; char buffer[500]; @@ -122,7 +163,11 @@ bool HTTPReader::Read(FILE * F){ return false; }//HTTPReader::ReadSocket -bool HTTPReader::parse(){ +/// Attempt to read a whole HTTP response or request from the internal data buffer. +/// If succesful, fills its own fields with the proper data and removes the response/request +/// from the internal data buffer. +/// \return True on success, false otherwise. +bool HTTP::Parser::parse(){ size_t f; std::string tmpA, tmpB, tmpC; while (HTTPbuffer != ""){ @@ -140,7 +185,7 @@ bool HTTPReader::parse(){ if (f != std::string::npos){url = tmpA.substr(0, f); tmpA.erase(0, f+1);} f = tmpA.find(' '); if (f != std::string::npos){protocol = tmpA.substr(0, f); tmpA.erase(0, f+1);} - //TODO: GET variable parsing? + /// \todo Include GET variable parsing? }else{ if (tmpA.size() == 0){ seenHeaders = true; @@ -156,7 +201,7 @@ bool HTTPReader::parse(){ } if (seenHeaders){ if (length > 0){ - //TODO: POST variable parsing? + /// \todo Include POST variable parsing? if (HTTPbuffer.length() >= length){ body = HTTPbuffer.substr(0, length); HTTPbuffer.erase(0, length); @@ -172,23 +217,40 @@ bool HTTPReader::parse(){ return false; //we should never get here... }//HTTPReader::parse -void HTTPReader::SendResponse(DDV::Socket & conn, std::string code, std::string message){ +/// Sends data as response to conn. +/// The response is automatically first build using HTTP::Parser::BuildResponse(). +/// \param conn The DDV::Socket to send the response over. +/// \param code The HTTP response code. Usually you want 200. +/// \param message The HTTP response message. Usually you want "OK". +void HTTP::Parser::SendResponse(DDV::Socket & conn, std::string code, std::string message){ std::string tmp = BuildResponse(code, message); conn.write(tmp); } -void HTTPReader::SendBodyPart(DDV::Socket & conn, char * buffer, int len){ +/// Sends data as HTTP/1.1 bodypart to conn. +/// HTTP/1.1 chunked encoding is automatically applied if needed. +/// \param conn The DDV::Socket to send the part over. +/// \param buffer The buffer to send. +/// \param len The length of the buffer. +void HTTP::Parser::SendBodyPart(DDV::Socket & conn, char * buffer, int len){ std::string tmp; tmp.append(buffer, len); SendBodyPart(conn, tmp); } -void HTTPReader::SendBodyPart(DDV::Socket & conn, std::string bodypart){ - static char len[10]; - int sizelen; - sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size()); - conn.write(len, sizelen); - conn.write(bodypart); - conn.write(len+sizelen-2, 2); +/// Sends data as HTTP/1.1 bodypart to conn. +/// HTTP/1.1 chunked encoding is automatically applied if needed. +/// \param conn The DDV::Socket to send the part over. +/// \param bodypart The data to send. +void HTTP::Parser::SendBodyPart(DDV::Socket & conn, std::string bodypart){ + if (protocol == "HTTP/1.1"){ + static char len[10]; + int sizelen; + sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size()); + conn.write(len, sizelen); + conn.write(bodypart); + conn.write(len+sizelen-2, 2); + }else{ + conn.write(bodypart); + } } - diff --git a/util/http_parser.h b/util/http_parser.h index 2741f0c4..d08cadef 100644 --- a/util/http_parser.h +++ b/util/http_parser.h @@ -1,3 +1,6 @@ +/// \file http_parser.h +/// Holds all headers for the HTTP namespace. + #pragma once #include #include @@ -5,37 +8,40 @@ #include #include "ddv_socket.h" -class HTTPReader{ - public: - HTTPReader(); - bool Read(DDV::Socket & sock); - bool Read(FILE * F); - std::string GetHeader(std::string i); - std::string GetVar(std::string i); - void SetHeader(std::string i, std::string v); - void SetHeader(std::string i, int v); - void SetVar(std::string i, std::string v); - void SetBody(std::string s); - void SetBody(char * buffer, int len); - std::string BuildRequest(); - std::string BuildResponse(std::string code, std::string message); - void SendResponse(DDV::Socket & conn, std::string code, std::string message); - void SendBodyPart(DDV::Socket & conn, char * buffer, int len); - void SendBodyPart(DDV::Socket & conn, std::string bodypart); - void Clean(); - bool CleanForNext(); - std::string body; - std::string method; - std::string url; - std::string protocol; - unsigned int length; - private: - bool seenHeaders; - bool seenReq; - bool parse(); - std::string HTTPbuffer; - std::map headers; - std::map vars; - void Trim(std::string & s); -};//HTTPReader - +/// Holds all HTTP processing related code. +namespace HTTP{ + /// Simple class for reading and writing HTTP 1.0 and 1.1. + class Parser{ + public: + Parser(); + bool Read(DDV::Socket & sock); + bool Read(FILE * F); + std::string GetHeader(std::string i); + std::string GetVar(std::string i); + void SetHeader(std::string i, std::string v); + void SetHeader(std::string i, int v); + void SetVar(std::string i, std::string v); + void SetBody(std::string s); + void SetBody(char * buffer, int len); + std::string BuildRequest(); + std::string BuildResponse(std::string code, std::string message); + void SendResponse(DDV::Socket & conn, std::string code, std::string message); + void SendBodyPart(DDV::Socket & conn, char * buffer, int len); + void SendBodyPart(DDV::Socket & conn, std::string bodypart); + void Clean(); + bool CleanForNext(); + std::string body; + std::string method; + std::string url; + std::string protocol; + unsigned int length; + private: + bool seenHeaders; + bool seenReq; + bool parse(); + std::string HTTPbuffer; + std::map headers; + std::map vars; + void Trim(std::string & s); + };//HTTP::Parser class +};//HTTP namespace diff --git a/util/rtmpchunks.cpp b/util/rtmpchunks.cpp new file mode 100644 index 00000000..d297b5e5 --- /dev/null +++ b/util/rtmpchunks.cpp @@ -0,0 +1,442 @@ +/// \file rtmpchunks.cpp +/// Holds all code for the RTMPStream namespace. + +#include "rtmpchunks.h" +#include "crypto.h" + +char versionstring[] = "WWW.DDVTECH.COM "; ///< String that is repeated in the RTMP handshake +std::string RTMPStream::handshake_in; ///< Input for the handshake. +std::string RTMPStream::handshake_out;///< Output for the handshake. + +/// Gets the current system time in milliseconds. +unsigned int RTMPStream::getNowMS(){ + timeval t; + gettimeofday(&t, 0); + return t.tv_sec + t.tv_usec/1000; +}//RTMPStream::getNowMS + + +unsigned int RTMPStream::chunk_rec_max = 128; +unsigned int RTMPStream::chunk_snd_max = 128; +unsigned int RTMPStream::rec_window_size = 0xFA00; +unsigned int RTMPStream::snd_window_size = 1024*500; +unsigned int RTMPStream::rec_window_at = 0; +unsigned int RTMPStream::snd_window_at = 0; +unsigned int RTMPStream::rec_cnt = 0; +unsigned int RTMPStream::snd_cnt = 0; + +timeval RTMPStream::lastrec; +unsigned int RTMPStream::firsttime; + +/// Holds the last sent chunk for every msg_id. +std::map RTMPStream::Chunk::lastsend; +/// Holds the last received chunk for every msg_id. +std::map RTMPStream::Chunk::lastrecv; + +/// Packs up the chunk for sending over the network. +/// \warning Do not call if you are not actually sending the resulting data! +/// \returns A std::string ready to be sent. +std::string RTMPStream::Chunk::Pack(){ + std::string output = ""; + RTMPStream::Chunk prev = lastsend[cs_id]; + unsigned int tmpi; + unsigned char chtype = 0x00; + timestamp -= firsttime; + if (prev.cs_id == cs_id){ + if (msg_stream_id == prev.msg_stream_id){ + chtype = 0x40;//do not send msg_stream_id + if (len == prev.len){ + if (msg_type_id == prev.msg_type_id){ + chtype = 0x80;//do not send len and msg_type_id + if (timestamp == prev.timestamp){ + chtype = 0xC0;//do not send timestamp + } + } + } + } + } + if (cs_id <= 63){ + output += (unsigned char)(chtype | cs_id); + }else{ + if (cs_id <= 255+64){ + output += (unsigned char)(chtype | 0); + output += (unsigned char)(cs_id - 64); + }else{ + output += (unsigned char)(chtype | 1); + output += (unsigned char)((cs_id - 64) % 256); + output += (unsigned char)((cs_id - 64) / 256); + } + } + unsigned int ntime = 0; + if (chtype != 0xC0){ + //timestamp or timestamp diff + if (chtype == 0x00){ + tmpi = timestamp; + }else{ + tmpi = timestamp - prev.timestamp; + } + if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;} + output += (unsigned char)(tmpi / (256*256)); + output += (unsigned char)(tmpi / 256); + output += (unsigned char)(tmpi % 256); + if (chtype != 0x80){ + //len + tmpi = len; + output += (unsigned char)(tmpi / (256*256)); + output += (unsigned char)(tmpi / 256); + output += (unsigned char)(tmpi % 256); + //msg type id + output += (unsigned char)msg_type_id; + if (chtype != 0x40){ + //msg stream id + output += (unsigned char)(msg_stream_id % 256); + output += (unsigned char)(msg_stream_id / 256); + output += (unsigned char)(msg_stream_id / (256*256)); + output += (unsigned char)(msg_stream_id / (256*256*256)); + } + } + } + //support for 0x00ffffff timestamps + if (ntime){ + output += (unsigned char)(ntime % 256); + output += (unsigned char)(ntime / 256); + output += (unsigned char)(ntime / (256*256)); + output += (unsigned char)(ntime / (256*256*256)); + } + len_left = 0; + while (len_left < len){ + tmpi = len - len_left; + if (tmpi > RTMPStream::chunk_snd_max){tmpi = RTMPStream::chunk_snd_max;} + output.append(data, len_left, tmpi); + len_left += tmpi; + if (len_left < len){ + if (cs_id <= 63){ + output += (unsigned char)(0xC0 + cs_id); + }else{ + if (cs_id <= 255+64){ + output += (unsigned char)(0xC0); + output += (unsigned char)(cs_id - 64); + }else{ + output += (unsigned char)(0xC1); + output += (unsigned char)((cs_id - 64) % 256); + output += (unsigned char)((cs_id - 64) / 256); + } + } + } + } + lastsend[cs_id] = *this; + RTMPStream::snd_cnt += output.size(); + return output; +}//SendChunk + +/// Default contructor, creates an empty chunk with all values initialized to zero. +RTMPStream::Chunk::Chunk(){ + cs_id = 0; + timestamp = 0; + len = 0; + real_len = 0; + len_left = 0; + msg_type_id = 0; + msg_stream_id = 0; + data = ""; +}//constructor + +/// Packs up a chunk with the given arguments as properties. +std::string RTMPStream::SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data){ + RTMPStream::Chunk ch; + ch.cs_id = cs_id; + ch.timestamp = RTMPStream::getNowMS(); + ch.len = data.size(); + ch.real_len = data.size(); + ch.len_left = 0; + ch.msg_type_id = msg_type_id; + ch.msg_stream_id = msg_stream_id; + ch.data = data; + return ch.Pack(); +}//constructor + +/// Packs up a chunk with media contents. +/// \param msg_type_id Type number of the media, as per FLV standard. +/// \param data Contents of the media data. +/// \param len Length of the media data, in bytes. +/// \param ts Timestamp of the media data, relative to current system time. +std::string RTMPStream::SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts){ + RTMPStream::Chunk ch; + ch.cs_id = msg_type_id; + ch.timestamp = ts; + ch.len = len; + ch.real_len = len; + ch.len_left = 0; + ch.msg_type_id = msg_type_id; + ch.msg_stream_id = 1; + ch.data.append((char*)data, (size_t)len); + return ch.Pack(); +}//SendMedia + +/// Packs up a chunk for a control message with 1 argument. +std::string RTMPStream::SendCTL(unsigned char type, unsigned int data){ + RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = RTMPStream::getNowMS(); + ch.len = 4; + ch.real_len = 4; + ch.len_left = 0; + ch.msg_type_id = type; + ch.msg_stream_id = 0; + ch.data.resize(4); + *(int*)((char*)ch.data.c_str()) = htonl(data); + return ch.Pack(); +}//SendCTL + +/// Packs up a chunk for a control message with 2 arguments. +std::string RTMPStream::SendCTL(unsigned char type, unsigned int data, unsigned char data2){ + RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = RTMPStream::getNowMS(); + ch.len = 5; + ch.real_len = 5; + ch.len_left = 0; + ch.msg_type_id = type; + ch.msg_stream_id = 0; + ch.data.resize(5); + *(int*)((char*)ch.data.c_str()) = htonl(data); + ch.data[4] = data2; + return ch.Pack(); +}//SendCTL + +/// Packs up a chunk for a user control message with 1 argument. +std::string RTMPStream::SendUSR(unsigned char type, unsigned int data){ + RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = RTMPStream::getNowMS(); + ch.len = 6; + ch.real_len = 6; + ch.len_left = 0; + ch.msg_type_id = 4; + ch.msg_stream_id = 0; + ch.data.resize(6); + *(int*)((char*)ch.data.c_str()+2) = htonl(data); + ch.data[0] = 0; + ch.data[1] = type; + return ch.Pack(); +}//SendUSR + +/// Packs up a chunk for a user control message with 2 arguments. +std::string RTMPStream::SendUSR(unsigned char type, unsigned int data, unsigned int data2){ + RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = RTMPStream::getNowMS(); + ch.len = 10; + ch.real_len = 10; + ch.len_left = 0; + ch.msg_type_id = 4; + ch.msg_stream_id = 0; + ch.data.resize(10); + *(int*)((char*)ch.data.c_str()+2) = htonl(data); + *(int*)((char*)ch.data.c_str()+6) = htonl(data2); + ch.data[0] = 0; + ch.data[1] = type; + return ch.Pack(); +}//SendUSR + + +/// Parses the argument string into the current chunk. +/// Tries to read a whole chunk, if successful it will remove +/// the corresponding data from the input string. +/// \param indata The input string to parse and update. +/// \warning This function will destroy the current data in this chunk! +/// \returns True if a whole chunk could be read, false otherwise. +bool RTMPStream::Chunk::Parse(std::string & indata){ + gettimeofday(&RTMPStream::lastrec, 0); + unsigned int i = 0; + if (indata.size() < 3) return false;//need at least 3 bytes to continue + + unsigned char chunktype = indata[i++]; + //read the chunkstream ID properly + switch (chunktype & 0x3F){ + case 0: + cs_id = indata[i++] + 64; + break; + case 1: + cs_id = indata[i++] + 64; + cs_id += indata[i++] * 256; + break; + default: + cs_id = chunktype & 0x3F; + break; + } + + RTMPStream::Chunk prev = lastrecv[cs_id]; + + //process the rest of the header, for each chunk type + switch (chunktype & 0xC0){ + case 0x00: + if (indata.size() < i+11) return false; //can't read whole header + timestamp = indata[i++]*256*256; + timestamp += indata[i++]*256; + timestamp += indata[i++]; + len = indata[i++]*256*256; + len += indata[i++]*256; + len += indata[i++]; + len_left = 0; + msg_type_id = indata[i++]; + msg_stream_id = indata[i++]; + msg_stream_id += indata[i++]*256; + msg_stream_id += indata[i++]*256*256; + msg_stream_id += indata[i++]*256*256*256; + break; + case 0x40: + if (indata.size() < i+7) return false; //can't read whole header + timestamp = indata[i++]*256*256; + timestamp += indata[i++]*256; + timestamp += indata[i++]; + timestamp += prev.timestamp; + len = indata[i++]*256*256; + len += indata[i++]*256; + len += indata[i++]; + len_left = 0; + msg_type_id = indata[i++]; + msg_stream_id = prev.msg_stream_id; + break; + case 0x80: + if (indata.size() < i+3) return false; //can't read whole header + timestamp = indata[i++]*256*256; + timestamp += indata[i++]*256; + timestamp += indata[i++]; + timestamp += prev.timestamp; + len = prev.len; + len_left = prev.len_left; + msg_type_id = prev.msg_type_id; + msg_stream_id = prev.msg_stream_id; + break; + case 0xC0: + timestamp = prev.timestamp; + len = prev.len; + len_left = prev.len_left; + msg_type_id = prev.msg_type_id; + msg_stream_id = prev.msg_stream_id; + break; + } + //calculate chunk length, real length, and length left till complete + if (len_left > 0){ + real_len = len_left; + len_left -= real_len; + }else{ + real_len = len; + } + if (real_len > RTMPStream::chunk_rec_max){ + len_left += real_len - RTMPStream::chunk_rec_max; + real_len = RTMPStream::chunk_rec_max; + } + //read extended timestamp, if neccesary + if (timestamp == 0x00ffffff){ + if (indata.size() < i+4) return false; //can't read whole header + timestamp = indata[i++]*256*256*256; + timestamp += indata[i++]*256*256; + timestamp += indata[i++]*256; + timestamp += indata[i++]; + } + + //read data if length > 0, and allocate it + if (real_len > 0){ + if (prev.len_left > 0){ + data = prev.data; + }else{ + data = ""; + } + if (indata.size() < i+real_len) return false;//can't read all data (yet) + data.append(indata, i, real_len); + indata = indata.substr(i+real_len); + lastrecv[cs_id] = *this; + RTMPStream::rec_cnt += i+real_len; + if (len_left == 0){ + return true; + }else{ + return false; + } + }else{ + data = ""; + indata = indata.substr(i+real_len); + lastrecv[cs_id] = *this; + RTMPStream::rec_cnt += i+real_len; + return true; + } +}//Parse + + +/// Does the handshake. Expects handshake_in to be filled, and fills handshake_out. +/// After calling this function, don't forget to read and ignore 1536 extra bytes, +/// this is the handshake response and not interesting for us because we don't do client +/// verification. +bool RTMPStream::doHandshake(){ + char Version; + //Read C0 + Version = RTMPStream::handshake_in[0]; + uint8_t * Client = (uint8_t *)RTMPStream::handshake_in.c_str() + 1; + RTMPStream::handshake_out.resize(3073); + uint8_t * Server = (uint8_t *)RTMPStream::handshake_out.c_str() + 1; + RTMPStream::rec_cnt += 1537; + + //Build S1 Packet + *((uint32_t*)Server) = 0;//time zero + *(((uint32_t*)(Server+4))) = htonl(0x01020304);//version 1 2 3 4 + for (int i = 8; i < 3072; ++i){Server[i] = versionstring[i%16];}//"random" data + + bool encrypted = (Version == 6); + #if DEBUG >= 4 + fprintf(stderr, "Handshake version is %hhi\n", Version); + #endif + uint8_t _validationScheme = 5; + if (ValidateClientScheme(Client, 0)) _validationScheme = 0; + if (ValidateClientScheme(Client, 1)) _validationScheme = 1; + + #if DEBUG >= 4 + fprintf(stderr, "Handshake type is %hhi, encryption is %s\n", _validationScheme, encrypted?"on":"off"); + #endif + + //FIRST 1536 bytes from server response + //compute DH key position + uint32_t serverDHOffset = GetDHOffset(Server, _validationScheme); + uint32_t clientDHOffset = GetDHOffset(Client, _validationScheme); + + //generate DH key + DHWrapper dhWrapper(1024); + if (!dhWrapper.Initialize()) return false; + if (!dhWrapper.CreateSharedKey(Client + clientDHOffset, 128)) return false; + if (!dhWrapper.CopyPublicKey(Server + serverDHOffset, 128)) return false; + + if (encrypted) { + uint8_t secretKey[128]; + if (!dhWrapper.CopySharedKey(secretKey, sizeof (secretKey))) return false; + RC4_KEY _pKeyIn; + RC4_KEY _pKeyOut; + InitRC4Encryption(secretKey, (uint8_t*) & Client[clientDHOffset], (uint8_t*) & Server[serverDHOffset], &_pKeyIn, &_pKeyOut); + uint8_t data[1536]; + RC4(&_pKeyIn, 1536, data, data); + RC4(&_pKeyOut, 1536, data, data); + } + //generate the digest + uint32_t serverDigestOffset = GetDigestOffset(Server, _validationScheme); + uint8_t *pTempBuffer = new uint8_t[1536 - 32]; + memcpy(pTempBuffer, Server, serverDigestOffset); + memcpy(pTempBuffer + serverDigestOffset, Server + serverDigestOffset + 32, 1536 - serverDigestOffset - 32); + uint8_t *pTempHash = new uint8_t[512]; + HMACsha256(pTempBuffer, 1536 - 32, genuineFMSKey, 36, pTempHash); + memcpy(Server + serverDigestOffset, pTempHash, 32); + delete[] pTempBuffer; + delete[] pTempHash; + + //SECOND 1536 bytes from server response + uint32_t keyChallengeIndex = GetDigestOffset(Client, _validationScheme); + pTempHash = new uint8_t[512]; + HMACsha256(Client + keyChallengeIndex, 32, genuineFMSKey, 68, pTempHash); + uint8_t *pLastHash = new uint8_t[512]; + HMACsha256(Server + 1536, 1536 - 32, pTempHash, 32, pLastHash); + memcpy(Server + 1536 * 2 - 32, pLastHash, 32); + delete[] pTempHash; + delete[] pLastHash; + //DONE BUILDING THE RESPONSE ***// + Server[-1] = Version; + RTMPStream::snd_cnt += 3073; + return true; +} diff --git a/util/rtmpchunks.h b/util/rtmpchunks.h new file mode 100644 index 00000000..b11142c8 --- /dev/null +++ b/util/rtmpchunks.h @@ -0,0 +1,65 @@ +/// \file rtmpchunks.h +/// Holds all headers for the RTMPStream namespace. + +#pragma once +#include +#include +#include +#include +#include +#include +#define DEBUG 4 + +/// Contains all functions and classes needed for RTMP connections. +namespace RTMPStream{ + + /// Gets the current system time in milliseconds. + unsigned int getNowMS(); + + extern unsigned int chunk_rec_max; ///< Maximum size for a received chunk. + extern unsigned int chunk_snd_max; ///< Maximum size for a sent chunk. + extern unsigned int rec_window_size; ///< Window size for receiving. + extern unsigned int snd_window_size; ///< Window size for sending. + extern unsigned int rec_window_at; ///< Current position of the receiving window. + extern unsigned int snd_window_at; ///< Current position of the sending window. + extern unsigned int rec_cnt; ///< Counter for total data received, in bytes. + extern unsigned int snd_cnt; ///< Counter for total data sent, in bytes. + + extern timeval lastrec; ///< Timestamp of last time data was received. + extern unsigned int firsttime; ///< Timestamp of first time a chunk was sent. + + /// Holds a single RTMP chunk, either send or receive direction. + class Chunk{ + public: + unsigned int cs_id; ///< ContentStream ID + unsigned int timestamp; ///< Timestamp of this chunk. + unsigned int len; ///< Length of the complete chunk. + unsigned int real_len; ///< Length of this particular part of it. + unsigned int len_left; ///< Length not yet received, out of complete chunk. + unsigned char msg_type_id; ///< Message Type ID + unsigned int msg_stream_id; ///< Message Stream ID + std::string data; ///< Payload of chunk. + + Chunk(); + bool Parse(std::string & data); + std::string Pack(); + + private: + static std::map lastsend; + static std::map lastrecv; + };//RTMPStream::Chunk + + std::string SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data); + std::string SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts); + std::string SendCTL(unsigned char type, unsigned int data); + std::string SendCTL(unsigned char type, unsigned int data, unsigned char data2); + std::string SendUSR(unsigned char type, unsigned int data); + std::string SendUSR(unsigned char type, unsigned int data, unsigned int data2); + + /// This value should be set to the first 1537 bytes received. + extern std::string handshake_in; + /// This value is the handshake response that is to be sent out. + extern std::string handshake_out; + /// Does the handshake. Expects handshake_in to be filled, and fills handshake_out. + bool doHandshake(); +};//RTMPStream namespace diff --git a/util/server_setup.cpp b/util/server_setup.cpp index daa9e6c6..36e09c47 100644 --- a/util/server_setup.cpp +++ b/util/server_setup.cpp @@ -1,12 +1,34 @@ -#include +/// \file server_setup.cpp +/// Contains generic functions for setting up a DDVTECH Connector. + +#ifndef MAINHANDLER + /// Handler that is called for accepted incoming connections. + #define MAINHANDLER NoHandler + #error "No handler was set!" +#endif + + +#ifndef DEFAULT_PORT + /// Default port for this server. + #define DEFAULT_PORT 0 + #error "No default port was set!" +#endif + + +#ifndef CONFIGSECT + /// Configuration file section for this server. + #define CONFIGSECT None + #error "No configuration file section was set!" +#endif + #include "ddv_socket.h" //DDVTech Socket wrapper -#include "flv_tag.h" //FLV parsing with DDVTech Socket wrapper +#include #include #include #include -#define defstr(x) #x //converts a define name to string -#define defstrh(x) "[" defstr(x) "]" //converts define name to [string] -DDV::ServerSocket server_socket(-1); +#define defstr(x) #x ///< converts a define name to string +#define defstrh(x) "[" defstr(x) "]" ///< converts define name to [string] +DDV::ServerSocket server_socket(-1); ///< Placeholder for the server socket /// Basic signal handler. Disconnects the server_socket if it receives /// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE. @@ -26,10 +48,10 @@ void signal_handler (int signum){ /// Generic main entry point and loop for DDV Connectors. /// This sets up the proper termination handler, checks commandline options, /// parses config files and opens a listening socket on the requested port. -/// Any incoming connections will be accepted and start up the function MAINHANDLER, -/// which should be #defined before including server_setup.cpp. -/// The default port is set by #define DEFAULT_PORT. -/// The configuration file section is set by #define CONFIGSECT. +/// Any incoming connections will be accepted and start up the function #MAINHANDLER, +/// which should be defined before including server_setup.cpp. +/// The default port is set by define #DEFAULT_PORT. +/// The configuration file section is set by define #CONFIGSECT. int main(int argc, char ** argv){ DDV::Socket S;//placeholder for incoming connections From 07a6c6b9cca77c9ea1064b823e0ffd626806d0e5 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 11 Apr 2011 18:10:00 +0200 Subject: [PATCH 07/24] Forgot to update some code to work with new HTTP::Parser naming scheme. --- Connector_HTTP/main.cpp | 2 +- HTTP_Box_Parser/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index a9495465..6ae3392f 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -140,7 +140,7 @@ namespace Connector_HTTP{ FLV::Tag Video_Init;///< Video initialization data, if available. bool FlashFirstVideo = false; bool FlashFirstAudio = false; - HTTPReader HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. + HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. int retval; int poller = epoll_create(1); diff --git a/HTTP_Box_Parser/main.cpp b/HTTP_Box_Parser/main.cpp index 432bbd35..e430e3de 100644 --- a/HTTP_Box_Parser/main.cpp +++ b/HTTP_Box_Parser/main.cpp @@ -21,7 +21,7 @@ /// Then it will take the payload of this box, print the first four bytes, and attempt to parse the whole payload as FLV data. /// The parsed FLV data is then pretty-printed, containing information about the codec parameters and types of tags it encounters. int main(){ - HTTPReader H; + HTTP::Parser H; FLV::Tag F; unsigned int P = 0; char * Payload = 0; From c8bc0fbafe2343e67d07c513ba73e8c69b305878 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 11 Apr 2011 18:11:49 +0200 Subject: [PATCH 08/24] Updated init files... --- Connector_HTTP/HTTP_Conn | 2 +- Connector_RTMP/RTMP_Conn | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100755 Connector_RTMP/RTMP_Conn diff --git a/Connector_HTTP/HTTP_Conn b/Connector_HTTP/HTTP_Conn index 9ad03e7f..1fda3f37 100755 --- a/Connector_HTTP/HTTP_Conn +++ b/Connector_HTTP/HTTP_Conn @@ -1,7 +1,7 @@ #!/bin/sh # # description: DDVTech HTTP Connector -# processname: Connector_HTTP +# processname: DDV_Conn_HTTP prog="DDV_Conn_HTTP" fullprog="/usr/bin/DDV_Conn_HTTP" diff --git a/Connector_RTMP/RTMP_Conn b/Connector_RTMP/RTMP_Conn new file mode 100755 index 00000000..694a21b0 --- /dev/null +++ b/Connector_RTMP/RTMP_Conn @@ -0,0 +1,38 @@ +#!/bin/sh +# +# description: DDVTech RTMP Connector +# processname: DDV_Conn_RTMP + +prog="DDV_Conn_RTMP" +fullprog="/usr/bin/DDV_Conn_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 From 66a66b155c22f3541685b60fdd4068d0f128de13 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sat, 16 Apr 2011 03:44:48 +0200 Subject: [PATCH 09/24] *evil scientist laughter* ITS ALIVE! ITS ALIVE! --- Buffer/main.cpp | 8 ++++---- Connector_HTTP/main.cpp | 2 +- util/ddv_socket.cpp | 6 ++++-- util/flv_tag.cpp | 31 +++++++++++++++---------------- util/flv_tag.h | 2 ++ 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Buffer/main.cpp b/Buffer/main.cpp index 6d806d9c..efbbc3f7 100644 --- a/Buffer/main.cpp +++ b/Buffer/main.cpp @@ -230,16 +230,16 @@ namespace Buffer{ users.back().MyBuffer = lastproper; users.back().MyBuffer_num = -1; /// \todo Do this more nicely? - if (!incoming.write(FLV::Header, 13)){ + if (!users.back().S.write(FLV::Header, 13)){ users.back().Disconnect("failed to receive the header!"); }else{ - if (!incoming.write(metadata.data, metadata.len)){ + if (!users.back().S.write(metadata.data, metadata.len)){ users.back().Disconnect("failed to receive metadata!"); } - if (!incoming.write(video_init.data, video_init.len)){ + if (!users.back().S.write(video_init.data, video_init.len)){ users.back().Disconnect("failed to receive video init!"); } - if (!incoming.write(audio_init.data, audio_init.len)){ + if (!users.back().S.write(audio_init.data, audio_init.len)){ users.back().Disconnect("failed to receive audio init!"); } } diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index 6ae3392f..a7fbf0e3 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -337,7 +337,7 @@ namespace Connector_HTTP{ conn.close(); if (inited) ss.close(); #if DEBUG >= 1 - if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error\n");} + if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());} fprintf(stderr, "User %i disconnected.\n", conn.getSocket()); if (inited){ fprintf(stderr, "Status was: inited\n"); diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index e82ea8ae..3b3567ac 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -121,8 +121,9 @@ bool DDV::Socket::write(const void * buffer, int len){ return false; break; } + }else{ + sofar += r; } - sofar += r; } return true; }//DDv::Socket::write @@ -151,8 +152,9 @@ bool DDV::Socket::read(void * buffer, int len){ return false; break; } + }else{ + sofar += r; } - sofar += r; } return true; }//DDV::Socket::read diff --git a/util/flv_tag.cpp b/util/flv_tag.cpp index 08fed05b..02494b56 100644 --- a/util/flv_tag.cpp +++ b/util/flv_tag.cpp @@ -143,7 +143,7 @@ void FLV::Tag::tagTime(unsigned int T){ /// The buffer length is initialized to 0, and later automatically /// increased if neccesary. FLV::Tag::Tag(){ - len = 0; buf = 0; data = 0; isKeyframe = false; + len = 0; buf = 0; data = 0; isKeyframe = false; done = true; sofar = 0; }//empty constructor /// Copy constructor, copies the contents of an existing tag. @@ -151,6 +151,8 @@ FLV::Tag::Tag(){ /// that is being copied, and later automaticallt increased if /// neccesary. FLV::Tag::Tag(const Tag& O){ + done = true; + sofar = 0; buf = O.len; len = buf; if (len > 0){ @@ -166,14 +168,18 @@ FLV::Tag::Tag(const Tag& O){ /// This operator checks for self-assignment. FLV::Tag & FLV::Tag::operator= (const FLV::Tag& O){ if (this != &O){//no self-assignment - if (data != 0){free(data);} - buf = O.len; - len = buf; + len = O.len; if (len > 0){ - data = (char*)malloc(len); + if (!data){ + data = (char*)malloc(len); + buf = len; + }else{ + if (buf < len){ + data = (char*)realloc(data, len); + buf = len; + } + } memcpy(data, O.data, len); - }else{ - data = 0; } isKeyframe = O.isKeyframe; } @@ -211,8 +217,6 @@ bool FLV::Tag::MemReadUntil(char * buffer, unsigned int count, unsigned int & so /// \param P The current position in the data buffer. Will be updated to reflect new position. /// \return True if a whole tag is succesfully read, false otherwise. bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){ - static bool done = true; - static unsigned int sofar = 0; if (buf < 15){data = (char*)realloc(data, 15); buf = 15;} if (done){ //read a header @@ -259,15 +263,14 @@ bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){ /// \return True if count bytes are read succesfully, false otherwise. bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & sofar, DDV::Socket & sock){ if (sofar == count){return true;} - int r = sock.read(buffer + sofar,count-sofar); - if (r < 0){ + if (!sock.read(buffer + sofar,count-sofar)){ if (errno != EWOULDBLOCK){ FLV::Parse_Error = true; Error_Str = "Error reading from socket."; } return false; } - sofar += r; + sofar += count-sofar; if (sofar == count){return true;} if (sofar > count){ FLV::Parse_Error = true; @@ -282,8 +285,6 @@ bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & s /// \param sock The socket to read from. /// \return True if a whole tag is succesfully read, false otherwise. bool FLV::Tag::SockLoader(DDV::Socket sock){ - static bool done = true; - static unsigned int sofar = 0; if (buf < 15){data = (char*)realloc(data, 15); buf = 15;} if (done){ if (SockReadUntil(data, 11, sofar, sock)){ @@ -354,8 +355,6 @@ bool FLV::Tag::FileLoader(FILE * f){ int preflags = fcntl(fileno(f), F_GETFL, 0); int postflags = preflags | O_NONBLOCK; fcntl(fileno(f), F_SETFL, postflags); - static bool done = true; - static unsigned int sofar = 0; if (buf < 15){data = (char*)realloc(data, 15); buf = 15;} if (done){ diff --git a/util/flv_tag.h b/util/flv_tag.h index e824228c..0ba13ea5 100644 --- a/util/flv_tag.h +++ b/util/flv_tag.h @@ -35,6 +35,8 @@ namespace FLV { bool FileLoader(FILE * f); protected: int buf; ///< Maximum length of buffer space. + bool done; ///< Body reading done? + unsigned int sofar; ///< How many bytes are read sofar? //loader helper functions bool MemReadUntil(char * buffer, unsigned int count, unsigned int & sofar, char * D, unsigned int S, unsigned int & P); bool SockReadUntil(char * buffer, unsigned int count, unsigned int & sofar, DDV::Socket & sock); From 64417b28847aa283726a34a8fed57006a56230cd Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 17 Apr 2011 00:38:04 +0200 Subject: [PATCH 10/24] RTMP edits --- Connector_RTMP/main.cpp | 24 ++++++++++++------------ util/rtmpchunks.cpp | 9 ++++++--- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 8d50081d..f3baa4a0 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -163,7 +163,6 @@ void Connector_RTMP::parseChunk(){ static AMF::Object amfelem("empty", AMF::AMF0_DDV_CONTAINER); if (!Connector_RTMP::Socket.read(inbuffer)){return;} //try to get more data - fprintf(stderr, "Current buffer is %i long.\n", inbuffer.size()); while (next.Parse(inbuffer)){ //send ACK if we received a whole window @@ -280,7 +279,6 @@ void Connector_RTMP::parseChunk(){ 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 @@ -289,24 +287,26 @@ void Connector_RTMP::parseChunk(){ 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.getContentP(2)->addContent(AMF::Object("fmsVer", "FMS/3,5,4,1004")); + amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)127)); + amfreply.getContentP(2)->addContent(AMF::Object("mode", (double)1)); 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.")); + amfreply.getContentP(3)->addContent(AMF::Object("objectEncoding", (double)0)); + amfreply.getContentP(3)->addContent(AMF::Object("data", AMF::AMF0_ECMA_ARRAY)); + amfreply.getContentP(3)->getContentP(4)->addContent(AMF::Object("version", "3,5,4,1004")); #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()); + //send onBWDone packet - no clue what it is, but real server sends it... + amfreply = AMFType("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMFType("", "onBWDone"));//result + amfreply.addContent(AMFType("", (double)0));//zero + amfreply.addContent(AMFType("", (double)0, AMF::AMF0_NULL));//null + SendChunk(3, 20, next.msg_stream_id, amfreply.Pack()); parsed = true; }//connect if (amfdata.getContentP(0)->StrValue() == "createStream"){ diff --git a/util/rtmpchunks.cpp b/util/rtmpchunks.cpp index eca2f9fb..a63a2380 100644 --- a/util/rtmpchunks.cpp +++ b/util/rtmpchunks.cpp @@ -18,8 +18,8 @@ unsigned int 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_size = 2500000; +unsigned int RTMPStream::snd_window_size = 2500000; unsigned int RTMPStream::rec_window_at = 0; unsigned int RTMPStream::snd_window_at = 0; unsigned int RTMPStream::rec_cnt = 0; @@ -243,6 +243,9 @@ std::string RTMPStream::SendUSR(unsigned char type, unsigned int data, unsigned /// 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. +/// If only part of a chunk is read, it will remove the part and call itself again. +/// This has the effect of only causing a "true" reponse in the case a *whole* chunk +/// is read, not just part of a chunk. /// \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. @@ -368,7 +371,7 @@ bool RTMPStream::Chunk::Parse(std::string & indata){ /// 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 +/// these are the handshake response and not interesting for us because we don't do client /// verification. bool RTMPStream::doHandshake(){ char Version; From f4cc2580b2c36b9f36d254d65a2be2d349be483b Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 17 Apr 2011 00:38:04 +0200 Subject: [PATCH 11/24] RTMP edits --- Connector_RTMP/main.cpp | 24 ++++++++++++------------ util/rtmpchunks.cpp | 9 ++++++--- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 8d50081d..e0d1234c 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -163,7 +163,6 @@ void Connector_RTMP::parseChunk(){ static AMF::Object amfelem("empty", AMF::AMF0_DDV_CONTAINER); if (!Connector_RTMP::Socket.read(inbuffer)){return;} //try to get more data - fprintf(stderr, "Current buffer is %i long.\n", inbuffer.size()); while (next.Parse(inbuffer)){ //send ACK if we received a whole window @@ -280,7 +279,6 @@ void Connector_RTMP::parseChunk(){ 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 @@ -289,24 +287,26 @@ void Connector_RTMP::parseChunk(){ 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.getContentP(2)->addContent(AMF::Object("fmsVer", "FMS/3,5,4,1004")); + amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)127)); + amfreply.getContentP(2)->addContent(AMF::Object("mode", (double)1)); 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.")); + amfreply.getContentP(3)->addContent(AMF::Object("objectEncoding", (double)0)); + amfreply.getContentP(3)->addContent(AMF::Object("data", AMF::AMF0_ECMA_ARRAY)); + amfreply.getContentP(3)->getContentP(4)->addContent(AMF::Object("version", "3,5,4,1004")); #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()); + //send onBWDone packet - no clue what it is, but real server sends it... + amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMFType("", "onBWDone"));//result + amfreply.addContent(AMFType("", (double)0));//zero + amfreply.addContent(AMFType("", (double)0, AMF::AMF0_NULL));//null + Socket.write(RTMPStream::SendChunk(3, 20, next.msg_stream_id, amfreply.Pack())); parsed = true; }//connect if (amfdata.getContentP(0)->StrValue() == "createStream"){ diff --git a/util/rtmpchunks.cpp b/util/rtmpchunks.cpp index eca2f9fb..a63a2380 100644 --- a/util/rtmpchunks.cpp +++ b/util/rtmpchunks.cpp @@ -18,8 +18,8 @@ unsigned int 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_size = 2500000; +unsigned int RTMPStream::snd_window_size = 2500000; unsigned int RTMPStream::rec_window_at = 0; unsigned int RTMPStream::snd_window_at = 0; unsigned int RTMPStream::rec_cnt = 0; @@ -243,6 +243,9 @@ std::string RTMPStream::SendUSR(unsigned char type, unsigned int data, unsigned /// 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. +/// If only part of a chunk is read, it will remove the part and call itself again. +/// This has the effect of only causing a "true" reponse in the case a *whole* chunk +/// is read, not just part of a chunk. /// \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. @@ -368,7 +371,7 @@ bool RTMPStream::Chunk::Parse(std::string & indata){ /// 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 +/// these are the handshake response and not interesting for us because we don't do client /// verification. bool RTMPStream::doHandshake(){ char Version; From f2d64f41396fce2bb7fc1263c117bcf3120b7e40 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 17 Apr 2011 00:54:14 +0200 Subject: [PATCH 12/24] laat me alsjeblieft geen AMF3 support hoeven coden :( --- Connector_RTMP/main.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index b7c5197f..46108b68 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -272,6 +272,10 @@ void Connector_RTMP::parseChunk(){ if (amfdata.getContentP(0)->StrValue() == "connect"){ #if DEBUG >= 4 int tmpint; + double objencoding = 0; + if (amfdata.getContentP(2)->getContentP("objectencoding")){ + objencoding = amfdata.getContentP(2)->getContentP("objectencoding")->NumValue(); + } 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");} @@ -294,7 +298,7 @@ void Connector_RTMP::parseChunk(){ 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.")); - amfreply.getContentP(3)->addContent(AMF::Object("objectEncoding", (double)0)); + amfreply.getContentP(3)->addContent(AMF::Object("objectEncoding", objencoding); amfreply.getContentP(3)->addContent(AMF::Object("data", AMF::AMF0_ECMA_ARRAY)); amfreply.getContentP(3)->getContentP(4)->addContent(AMF::Object("version", "3,5,4,1004")); #if DEBUG >= 4 From 815b14735260f89510544f0132f7d2c97e9594b0 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 17 Apr 2011 00:56:52 +0200 Subject: [PATCH 13/24] typo --- Connector_RTMP/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 46108b68..dcd91056 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -298,7 +298,7 @@ void Connector_RTMP::parseChunk(){ 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.")); - amfreply.getContentP(3)->addContent(AMF::Object("objectEncoding", objencoding); + amfreply.getContentP(3)->addContent(AMF::Object("objectEncoding", objencoding)); amfreply.getContentP(3)->addContent(AMF::Object("data", AMF::AMF0_ECMA_ARRAY)); amfreply.getContentP(3)->getContentP(4)->addContent(AMF::Object("version", "3,5,4,1004")); #if DEBUG >= 4 From b617ce6947f77063050bd6e6cc5535b8548fb86b Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 17 Apr 2011 00:58:51 +0200 Subject: [PATCH 14/24] typo 2 --- Connector_RTMP/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index dcd91056..98dec868 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -274,7 +274,7 @@ void Connector_RTMP::parseChunk(){ int tmpint; double objencoding = 0; if (amfdata.getContentP(2)->getContentP("objectencoding")){ - objencoding = amfdata.getContentP(2)->getContentP("objectencoding")->NumValue(); + objencoding = amfdata.getContentP(2)->getContentP("objectEncoding")->NumValue(); } tmpint = amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue(); if (tmpint & 0x04){fprintf(stderr, "Sorensen video support detected\n");} From ed987a3bec7f3191203571480fe41ffdf1e1658d Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 17 Apr 2011 01:02:36 +0200 Subject: [PATCH 15/24] laat me alsjeblieft geen AMF3 support hoeven coden :( --- Connector_RTMP/main.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 98dec868..b7e7bdb8 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -248,11 +248,6 @@ void Connector_RTMP::parseChunk(){ 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"); @@ -263,6 +258,10 @@ void Connector_RTMP::parseChunk(){ fprintf(stderr, "Received AFM0 shared object\n"); #endif break; + case 17: + #if DEBUG >= 4 + fprintf(stderr, "Received AFM3 command message - parsing as AMF0!\n"); + #endif case 20:{//AMF0 command message bool parsed = false; amfdata = AMF::parse(next.data); From c5397813bb6da874b4455d811828b1924106bb95 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 17 Apr 2011 04:04:00 +0200 Subject: [PATCH 16/24] AMF3 eerste poging --- Connector_RTMP/main.cpp | 17 +- util/amf.cpp | 561 +++++++++++++++++++++++++++++++++++++++- util/amf.h | 58 +++++ 3 files changed, 630 insertions(+), 6 deletions(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index b7e7bdb8..28da8993 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -161,6 +161,8 @@ void Connector_RTMP::parseChunk(){ static std::string inbuffer; static AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER); static AMF::Object amfelem("empty", AMF::AMF0_DDV_CONTAINER); + static AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER); + static AMF::Object3 amf3elem("empty", AMF::AMF3_DDV_CONTAINER); if (!Connector_RTMP::Socket.read(inbuffer)){return;} //try to get more data while (next.Parse(inbuffer)){ @@ -248,9 +250,18 @@ void Connector_RTMP::parseChunk(){ fprintf(stderr, "Received AFM3 shared object\n"); #endif break; + case 17: + #if DEBUG >= 4 + fprintf(stderr, "Received AFM3 command message\n"); + #endif + amf3data = AMF::parse3(next.data); + #if DEBUG >= 4 + amf3data.Print(); + #endif + break; case 18: #if DEBUG >= 4 - fprintf(stderr, "Received AFM0 data message\n"); + fprintf(stderr, "Received AFM0 data message (metadata)\n"); #endif break; case 19: @@ -258,10 +269,6 @@ void Connector_RTMP::parseChunk(){ fprintf(stderr, "Received AFM0 shared object\n"); #endif break; - case 17: - #if DEBUG >= 4 - fprintf(stderr, "Received AFM3 command message - parsing as AMF0!\n"); - #endif case 20:{//AMF0 command message bool parsed = false; amfdata = AMF::parse(next.data); diff --git a/util/amf.cpp b/util/amf.cpp index 97d97ef5..938770a9 100644 --- a/util/amf.cpp +++ b/util/amf.cpp @@ -46,7 +46,7 @@ AMF::Object* AMF::Object::getContentP(std::string s){ for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ if (it->Indice() == s){return &(*it);} } - return this; + return 0; }; /// Returns a copy of the object held at indice s. @@ -59,6 +59,12 @@ AMF::Object AMF::Object::getContent(std::string s){ return AMF::Object("error", AMF0_DDV_CONTAINER); }; +/// Default constructor. +/// Simply fills the data with AMF::Object("error", AMF0_DDV_CONTAINER) +AMF::Object::Object(){ + *this = AMF::Object("error", AMF0_DDV_CONTAINER); +};//default constructor + /// Constructor for numeric objects. /// The object type is by default AMF::AMF0_NUMBER, but this can be forced to a different value. /// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic. @@ -404,3 +410,556 @@ AMF::Object AMF::parse(const unsigned char * data, unsigned int len){ AMF::Object AMF::parse(std::string data){ return AMF::parse((const unsigned char*)data.c_str(), data.size()); }//parse + +/// Returns the std::string Indice for the current object, if available. +/// Returns an empty string if no indice exists. +std::string AMF::Object3::Indice(){return myIndice;}; + +/// Returns the AMF::obj0type AMF0 object type for this object. +AMF::obj3type AMF::Object3::GetType(){return myType;}; + +/// Returns the double value of this object, if available. +/// If this object holds no double value, 0 is returned. +double AMF::Object3::DblValue(){return dblval;}; + +/// Returns the integer value of this object, if available. +/// If this object holds no integer value, 0 is returned. +int AMF::Object3::IntValue(){return intval;}; + +/// Returns the std::string value of this object, if available. +/// If this object holds no string value, an empty string is returned. +std::string AMF::Object3::StrValue(){return strval;}; + +/// Returns the C-string value of this object, if available. +/// If this object holds no string value, an empty C-string is returned. +const char * AMF::Object3::Str(){return strval.c_str();}; + +/// Returns a count of the amount of objects this object currently holds. +/// If this object is not a container type, this function will always return 0. +int AMF::Object3::hasContent(){return contents.size();}; + +/// Adds an AMF::Object to this object. Works for all types, but only makes sense for container types. +void AMF::Object3::addContent(AMF::Object3 c){contents.push_back(c);}; + +/// Returns a pointer to the object held at indice i. +/// Returns AMF::AMF3_DDV_CONTAINER of indice "error" if no object is held at this indice. +/// \param i The indice of the object in this container. +AMF::Object3* AMF::Object3::getContentP(int i){return &contents.at(i);}; + +/// Returns a copy of the object held at indice i. +/// Returns a AMF::AMF3_DDV_CONTAINER of indice "error" if no object is held at this indice. +/// \param i The indice of the object in this container. +AMF::Object3 AMF::Object3::getContent(int i){return contents.at(i);}; + +/// Returns a pointer to the object held at indice s. +/// Returns NULL if no object is held at this indice. +/// \param s The indice of the object in this container. +AMF::Object3* AMF::Object3::getContentP(std::string s){ + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ + if (it->Indice() == s){return &(*it);} + } + return 0; +}; + +/// Returns a copy of the object held at indice s. +/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice. +/// \param s The indice of the object in this container. +AMF::Object3 AMF::Object3::getContent(std::string s){ + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ + if (it->Indice() == s){return *it;} + } + return AMF::Object3("error", AMF3_DDV_CONTAINER); +}; + +/// Default constructor. +/// Simply fills the data with AMF::Object3("error", AMF3_DDV_CONTAINER) +AMF::Object3::Object3(){ + *this = AMF::Object3("error", AMF3_DDV_CONTAINER); +};//default constructor + +/// Constructor for double objects. +/// The object type is by default AMF::AMF3_DOUBLE, but this can be forced to a different value. +/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic. +/// \param val The numeric value of this object. Double AMF3 objects only support double-type values. +/// \param setType The object type to force this object to. +AMF::Object3::Object3(std::string indice, double val, AMF::obj3type setType){//num type initializer + myIndice = indice; + myType = setType; + strval = ""; + dblval = val; + intval = 0; +}; + +/// Constructor for integer objects. +/// The object type is by default AMF::AMF3_INTEGER, but this can be forced to a different value. +/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic. +/// \param val The numeric value of this object. Integer AMF3 objects only support integer-type values. +/// \param setType The object type to force this object to. +AMF::Object3::Object3(std::string indice, int val, AMF::obj3type setType){//num type initializer +myIndice = indice; +myType = setType; +strval = ""; +dblval = val; +intval = 0; +}; + +/// Constructor for string objects. +/// The object type is by default AMF::AMF0_STRING, but this can be forced to a different value. +/// There is no need to manually change the type to AMF::AMF0_LONGSTRING, this will be done automatically. +/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic. +/// \param val The string value of this object. +/// \param setType The object type to force this object to. +AMF::Object3::Object3(std::string indice, std::string val, AMF::obj3type setType){//str type initializer + myIndice = indice; + myType = setType; + strval = val; + dblval = 0; + intval = 0; +}; + +/// Constructor for container objects. +/// The object type is by default AMF::AMF0_OBJECT, but this can be forced to a different value. +/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic. +/// \param setType The object type to force this object to. +AMF::Object3::Object3(std::string indice, AMF::obj3type setType){//object type initializer + myIndice = indice; + myType = setType; + strval = ""; + dblval = 0; + intval = 0; +}; + +/// Prints the contents of this object to std::cerr. +/// If this object contains other objects, it will call itself recursively +/// and print all nested content in a nice human-readable format. +void AMF::Object3::Print(std::string indent){ + std::cerr << indent; + // print my type + switch (myType){ + case AMF::AMF3_UNDEFINED: std::cerr << "Undefined"; break; + case AMF::AMF3_NULL: std::cerr << "Null"; break; + case AMF::AMF3_FALSE: std::cerr << "False"; break; + case AMF::AMF3_TRUE: std::cerr << "True"; break; + case AMF::AMF3_INTEGER: std::cerr << "Integer"; break; + case AMF::AMF3_DOUBLE: std::cerr << "Double"; break; + case AMF::AMF3_STRING: std::cerr << "String"; break; + case AMF::AMF3_XMLDOC: std::cerr << "XML Doc"; break; + case AMF::AMF3_DATE: std::cerr << "Date"; break; + case AMF::AMF3_ARRAY: std::cerr << "Array"; break; + case AMF::AMF3_OBJECT: std::cerr << "Object"; break; + case AMF::AMF3_XML: std::cerr << "XML"; break; + case AMF::AMF3_BYTES: std::cerr << "ByteArray"; break; + case AMF::AMF3_DDV_CONTAINER: std::cerr << "DDVTech Container"; break; + } + // print my string indice, if available + std::cerr << " " << myIndice << " "; + // print my numeric or string contents + switch (myType){ + case AMF::AMF3_INTEGER: std::cerr << intval; break; + case AMF::AMF3_DOUBLE: std::cerr << dblval; break; + case AMF::AMF3_STRING: case AMF::AMF3_XMLDOC: case AMF::AMF3_XML: case AMF::AMF3_BYTES: + if (intval > 0){ + std::cerr << "REF" << intval; + }else{ + std::cerr << strval; + } + break; + case AMF::AMF3_DATE: + if (intval > 0){ + std::cerr << "REF" << intval; + }else{ + std::cerr << dblval; + } + break; + case AMF::AMF3_ARRAY: case AMF::AMF3_OBJECT: + if (intval > 0){ + std::cerr << "REF" << intval; + } + break; + default: break;//we don't care about the rest, and don't want a compiler warning... + } + std::cerr << std::endl; + // if I hold other objects, print those too, recursively. + if (contents.size() > 0){ + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){it->Print(indent+" ");} + } +};//print + +/// Packs the AMF object to a std::string for transfer over the network. +/// If the object is a container type, this function will call itself recursively and contain all contents. +/// Tip: When sending multiple AMF objects in one go, put them in a single AMF::AMF0_DDV_CONTAINER for easy transfer. +std::string AMF::Object3::Pack(){ + std::string r = ""; + return r; +};//pack + +/// Parses a single AMF3 type - used recursively by the AMF::parse3() functions. +/// This function updates i every call with the new position in the data. +/// \param data The raw data to parse. +/// \param len The size of the raw data. +/// \param i Current parsing position in the raw data. +/// \param name Indice name for any new object created. +/// \returns A single AMF::Object3, parsed from the raw data. +AMF::Object3 AMF::parseOne3(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name){ + std::string tmpstr; + unsigned int tmpi = 0; + unsigned int arrsize = 0; + unsigned char tmpdbl[8]; + #if DEBUG >= 10 + fprintf(stderr, "Note: AMF type %hhx found. %i bytes left\n", data[i], len-i); + #endif + switch (data[i]){ + case AMF::AMF3_UNDEFINED: + case AMF::AMF3_NULL: + case AMF::AMF3_FALSE: + case AMF::AMF3_TRUE: + ++i; + return AMF::Object3(name, (AMF::obj3type)data[i-1]); + break; + case AMF::AMF3_INTEGER: + if (data[i+1] < 0x80){ + tmpi = data[i+1]; + i+=2; + }else{ + tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up. + if (data[i+2] < 0x80){ + tmpi |= data[i+2]; + i+=3; + }else{ + tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up. + if (data[i+3] < 0x80){ + tmpi |= data[i+3]; + i+=4; + }else{ + tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. + tmpi |= data[i+4]; + i+=5; + } + } + } + tmpi = (tmpi << 3) >> 3;//fix sign bit + return AMF::Object3(name, (int)tmpi, AMF::AMF3_INTEGER); + break; + case AMF::AMF3_DOUBLE: + tmpdbl[7] = data[i+1]; + tmpdbl[6] = data[i+2]; + tmpdbl[5] = data[i+3]; + tmpdbl[4] = data[i+4]; + tmpdbl[3] = data[i+5]; + tmpdbl[2] = data[i+6]; + tmpdbl[1] = data[i+7]; + tmpdbl[0] = data[i+8]; + i+=9;//skip 8(a double)+1 forwards + return AMF::Object3(name, *(double*)tmpdbl, AMF::AMF3_DOUBLE); + break; + case AMF::AMF3_STRING: + if (data[i+1] < 0x80){ + tmpi = data[i+1]; + i+=2; + }else{ + tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up. + if (data[i+2] < 0x80){ + tmpi |= data[i+2]; + i+=3; + }else{ + tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up. + if (data[i+3] < 0x80){ + tmpi |= data[i+3]; + i+=4; + }else{ + tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. + tmpi |= data[i+4]; + i+=5; + } + } + } + tmpi = (tmpi << 3) >> 3;//fix sign bit + if ((tmpi & 1) == 0){ + return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_STRING);//reference type + } + tmpstr.clear();//clean tmpstr, just to be sure + tmpstr.append((const char *)data+i, (size_t)(tmpi >> 1));//add the string data + i += (tmpi >> 1);//skip length+size+1 forwards + return AMF::Object3(name, tmpstr, AMF::AMF3_STRING);//normal type + break; + case AMF::AMF3_XMLDOC: + if (data[i+1] < 0x80){ + tmpi = data[i+1]; + i+=2; + }else{ + tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up. + if (data[i+2] < 0x80){ + tmpi |= data[i+2]; + i+=3; + }else{ + tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up. + if (data[i+3] < 0x80){ + tmpi |= data[i+3]; + i+=4; + }else{ + tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. + tmpi |= data[i+4]; + i+=5; + } + } + } + tmpi = (tmpi << 3) >> 3;//fix sign bit + if ((tmpi & 1) == 0){ + return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_XMLDOC);//reference type + } + tmpstr.clear();//clean tmpstr, just to be sure + tmpstr.append((const char *)data+i, (size_t)(tmpi >> 1));//add the string data + i += (tmpi >> 1);//skip length+size+1 forwards + return AMF::Object3(name, tmpstr, AMF::AMF3_XMLDOC);//normal type + break; + case AMF::AMF3_XML: + if (data[i+1] < 0x80){ + tmpi = data[i+1]; + i+=2; + }else{ + tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up. + if (data[i+2] < 0x80){ + tmpi |= data[i+2]; + i+=3; + }else{ + tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up. + if (data[i+3] < 0x80){ + tmpi |= data[i+3]; + i+=4; + }else{ + tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. + tmpi |= data[i+4]; + i+=5; + } + } + } + tmpi = (tmpi << 3) >> 3;//fix sign bit + if ((tmpi & 1) == 0){ + return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_XML);//reference type + } + tmpstr.clear();//clean tmpstr, just to be sure + tmpstr.append((const char *)data+i, (size_t)(tmpi >> 1));//add the string data + i += (tmpi >> 1);//skip length+size+1 forwards + return AMF::Object3(name, tmpstr, AMF::AMF3_XML);//normal type + break; + case AMF::AMF3_BYTES: + if (data[i+1] < 0x80){ + tmpi = data[i+1]; + i+=2; + }else{ + tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up. + if (data[i+2] < 0x80){ + tmpi |= data[i+2]; + i+=3; + }else{ + tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up. + if (data[i+3] < 0x80){ + tmpi |= data[i+3]; + i+=4; + }else{ + tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. + tmpi |= data[i+4]; + i+=5; + } + } + } + tmpi = (tmpi << 3) >> 3;//fix sign bit + if ((tmpi & 1) == 0){ + return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_BYTES);//reference type + } + tmpstr.clear();//clean tmpstr, just to be sure + tmpstr.append((const char *)data+i, (size_t)(tmpi >> 1));//add the string data + i += (tmpi >> 1);//skip length+size+1 forwards + return AMF::Object3(name, tmpstr, AMF::AMF3_BYTES);//normal type + break; + case AMF::AMF3_DATE: + if (data[i+1] < 0x80){ + tmpi = data[i+1]; + i+=2; + }else{ + tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up. + if (data[i+2] < 0x80){ + tmpi |= data[i+2]; + i+=3; + }else{ + tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up. + if (data[i+3] < 0x80){ + tmpi |= data[i+3]; + i+=4; + }else{ + tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. + tmpi |= data[i+4]; + i+=5; + } + } + } + tmpi = (tmpi << 3) >> 3;//fix sign bit + if ((tmpi & 1) == 0){ + return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_DATE);//reference type + } + tmpdbl[7] = data[i]; + tmpdbl[6] = data[i+1]; + tmpdbl[5] = data[i+2]; + tmpdbl[4] = data[i+3]; + tmpdbl[3] = data[i+4]; + tmpdbl[2] = data[i+5]; + tmpdbl[1] = data[i+6]; + tmpdbl[0] = data[i+7]; + i += 8;//skip a double forwards + return AMF::Object3(name, *(double*)tmpdbl, AMF::AMF3_DATE); + break; + case AMF::AMF3_ARRAY:{ + if (data[i+1] < 0x80){ + tmpi = data[i+1]; + i+=2; + }else{ + tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up. + if (data[i+2] < 0x80){ + tmpi |= data[i+2]; + i+=3; + }else{ + tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up. + if (data[i+3] < 0x80){ + tmpi |= data[i+3]; + i+=4; + }else{ + tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. + tmpi |= data[i+4]; + i+=5; + } + } + } + tmpi = (tmpi << 3) >> 3;//fix sign bit + if ((tmpi & 1) == 0){ + return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_ARRAY);//reference type + } + AMF::Object3 ret(name, AMF::AMF3_ARRAY); + arrsize = tmpi >> 1; + do{ + if (data[i+1] < 0x80){ + tmpi = data[i+1]; + i+=2; + }else{ + tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up. + if (data[i+2] < 0x80){ + tmpi |= data[i+2]; + i+=3; + }else{ + tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up. + if (data[i+3] < 0x80){ + tmpi |= data[i+3]; + i+=4; + }else{ + tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. + tmpi |= data[i+4]; + i+=5; + } + } + } + tmpi = (tmpi << 3) >> 4;//fix sign bit, ignore references for now... + /// \todo Fix references? + if (tmpi > 0){ + tmpstr.clear();//clean tmpstr, just to be sure + tmpstr.append((const char*)data+i, (size_t)tmpi);//add the string data + ret.addContent(AMF::parseOne3(data, len, i, tmpstr));//add content, recursively parsed, updating i + } + }while(tmpi > 0); + while (arrsize > 0){//while not done parsing array + ret.addContent(AMF::parseOne3(data, len, i, "arrVal"));//add content, recursively parsed, updating i + --arrsize; + } + return ret; + } break; + case AMF::AMF3_OBJECT:{ + if (data[i+1] < 0x80){ + tmpi = data[i+1]; + i+=2; + }else{ + tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up. + if (data[i+2] < 0x80){ + tmpi |= data[i+2]; + i+=3; + }else{ + tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up. + if (data[i+3] < 0x80){ + tmpi |= data[i+3]; + i+=4; + }else{ + tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. + tmpi |= data[i+4]; + i+=5; + } + } + } + tmpi = (tmpi << 3) >> 3;//fix sign bit + if ((tmpi & 1) == 0){ + return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_OBJECT);//reference type + } + AMF::Object3 ret(name, AMF::AMF3_OBJECT); + bool isdynamic = false; + if ((tmpi & 2) == 0){//traits by reference, skip for now + /// \todo Implement traits by reference. Or references in general, of course... + }else{ + isdynamic = ((tmpi & 8) == 8); + arrsize = tmpi >> 4;//count of sealed members + /// \todo Read in arrsize sealed member names, then arrsize sealed members. + } + if (isdynamic){ + do{ + if (data[i+1] < 0x80){ + tmpi = data[i+1]; + i+=2; + }else{ + tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up. + if (data[i+2] < 0x80){ + tmpi |= data[i+2]; + i+=3; + }else{ + tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up. + if (data[i+3] < 0x80){ + tmpi |= data[i+3]; + i+=4; + }else{ + tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. + tmpi |= data[i+4]; + i+=5; + } + } + } + tmpi = (tmpi << 3) >> 4;//fix sign bit, ignore references for now... + /// \todo Fix references? + if (tmpi > 0){ + tmpstr.clear();//clean tmpstr, just to be sure + tmpstr.append((const char*)data+i, (size_t)tmpi);//add the string data + ret.addContent(AMF::parseOne3(data, len, i, tmpstr));//add content, recursively parsed, updating i + } + }while(tmpi > 0);//keep reading dynamic values until empty string + }//dynamic types + return ret; + } break; + } + #if DEBUG >= 2 + fprintf(stderr, "Error: Unimplemented AMF3 type %hhx - returning.\n", data[i]); + #endif + return AMF::Object3("error", AMF::AMF3_DDV_CONTAINER); +}//parseOne + +/// Parses a C-string to a valid AMF::Object3. +/// This function will find all AMF3 objects in the string and return +/// them all packed in a single AMF::AMF3_DDV_CONTAINER AMF::Object3. +AMF::Object3 AMF::parse3(const unsigned char * data, unsigned int len){ + AMF::Object3 ret("returned", AMF::AMF3_DDV_CONTAINER);//container type + unsigned int i = 0, j = 0; + while (i < len){ + ret.addContent(AMF::parseOne3(data, len, i, "")); + if (i > j){j = i;}else{return ret;} + } + return ret; +}//parse + +/// Parses a std::string to a valid AMF::Object3. +/// This function will find all AMF3 objects in the string and return +/// them all packed in a single AMF::AMF3_DDV_CONTAINER AMF::Object3. +AMF::Object3 AMF::parse3(std::string data){ + return AMF::parse3((const unsigned char*)data.c_str(), data.size()); +}//parse diff --git a/util/amf.h b/util/amf.h index 8ae95cd4..61f95a8a 100644 --- a/util/amf.h +++ b/util/amf.h @@ -33,6 +33,24 @@ namespace AMF{ AMF0_DDV_CONTAINER = 0xFF }; + /// Enumerates all possible AMF3 types, adding a special DDVTECH container type for ease of use. + enum obj3type { + AMF3_UNDEFINED = 0x00, + AMF3_NULL = 0x01, + AMF3_FALSE = 0x02, + AMF3_TRUE = 0x03, + AMF3_INTEGER = 0x04, + AMF3_DOUBLE = 0x05, + AMF3_STRING = 0x06, + AMF3_XMLDOC = 0x07, + AMF3_DATE = 0x08, + AMF3_ARRAY = 0x09, + AMF3_OBJECT = 0x0A, + AMF3_XML = 0x0B, + AMF3_BYTES = 0x0C, + AMF3_DDV_CONTAINER = 0xFF + }; + /// Recursive class that holds AMF0 objects. /// It supports all AMF0 types (defined in AMF::obj0type), adding support for a special DDVTECH container type. class Object { @@ -48,6 +66,7 @@ namespace AMF{ Object getContent(int i); Object* getContentP(std::string s); Object getContent(std::string s); + Object(); Object(std::string indice, double val, obj0type setType = AMF0_NUMBER); Object(std::string indice, std::string val, obj0type setType = AMF0_STRING); Object(std::string indice, obj0type setType = AMF0_OBJECT); @@ -67,5 +86,44 @@ namespace AMF{ Object parse(std::string data); /// Parses a single AMF0 type - used recursively by the AMF::parse() functions. Object parseOne(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name); + + /// Recursive class that holds AMF3 objects. + /// It supports all AMF3 types (defined in AMF::obj3type), adding support for a special DDVTECH container type. + class Object3 { + public: + std::string Indice(); + obj3type GetType(); + double DblValue(); + int IntValue(); + std::string StrValue(); + const char * Str(); + int hasContent(); + void addContent(AMF::Object3 c); + Object3* getContentP(int i); + Object3 getContent(int i); + Object3* getContentP(std::string s); + Object3 getContent(std::string s); + Object3(); + Object3(std::string indice, int val, obj3type setType = AMF3_INTEGER); + Object3(std::string indice, double val, obj3type setType = AMF3_DOUBLE); + Object3(std::string indice, std::string val, obj3type setType = AMF3_STRING); + Object3(std::string indice, obj3type setType = AMF3_OBJECT); + void Print(std::string indent = ""); + std::string Pack(); + protected: + std::string myIndice; ///< Holds this objects indice, if any. + obj3type myType; ///< Holds this objects AMF0 type. + std::string strval; ///< Holds this objects string value, if any. + double dblval; ///< Holds this objects double value, if any. + int intval; ///< Holds this objects int value, if any. + std::vector contents; ///< Holds this objects contents, if any (for container types). + };//AMFType + + /// Parses a C-string to a valid AMF::Object3. + Object3 parse3(const unsigned char * data, unsigned int len); + /// Parses a std::string to a valid AMF::Object3. + Object3 parse3(std::string data); + /// Parses a single AMF3 type - used recursively by the AMF::parse3() functions. + Object3 parseOne3(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name); };//AMF namespace From 4004b8e1aadb2566c10cb996caf5c652bc4c5caf Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 17 Apr 2011 04:07:42 +0200 Subject: [PATCH 17/24] Typo, again. Bedtijd. --- Connector_RTMP/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 28da8993..cd224d0b 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -279,7 +279,7 @@ void Connector_RTMP::parseChunk(){ #if DEBUG >= 4 int tmpint; double objencoding = 0; - if (amfdata.getContentP(2)->getContentP("objectencoding")){ + if (amfdata.getContentP(2)->getContentP("objectEncoding")){ objencoding = amfdata.getContentP(2)->getContentP("objectEncoding")->NumValue(); } tmpint = amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue(); From 7cb55a400a758dff427bff8f0214109ace1755c2 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 17 Apr 2011 23:59:49 +0200 Subject: [PATCH 18/24] AMF3 support, RTMP werkendheid? --- Connector_RTMP/main.cpp | 168 +++++++++++++++++++++++++++++++++++++--- util/amf.cpp | 4 +- 2 files changed, 160 insertions(+), 12 deletions(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index cd224d0b..4882dfcb 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -250,15 +250,162 @@ void Connector_RTMP::parseChunk(){ fprintf(stderr, "Received AFM3 shared object\n"); #endif break; - case 17: + case 17:{ + bool parsed3 = false; #if DEBUG >= 4 fprintf(stderr, "Received AFM3 command message\n"); #endif - amf3data = AMF::parse3(next.data); - #if DEBUG >= 4 - amf3data.Print(); - #endif - break; + if (next.data[0] != 0){ + next.data = next.data.substr(1); + amf3data = AMF::parse3(next.data); + #if DEBUG >= 4 + amf3data.Print(); + #endif + }else{ + #if DEBUG >= 4 + fprintf(stderr, "Received AFM3-0 command message\n"); + #endif + next.data = next.data.substr(1); + amfdata = AMF::parse(next.data); + #if DEBUG >= 4 + amfdata.Print(); + #endif + if (amfdata.getContentP(0)->StrValue() == "connect"){ + double objencoding = 0; + if (amfdata.getContentP(2)->getContentP("objectEncoding")){ + objencoding = amfdata.getContentP(2)->getContentP("objectEncoding")->NumValue(); + } + fprintf(stderr, "Object encoding set to %e\n", objencoding); + #if DEBUG >= 4 + int tmpint; + tmpint = (int)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 = (int)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(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(AMF::Object(""));//server properties + amfreply.getContentP(2)->addContent(AMF::Object("fmsVer", "FMS/3,5,4,1004")); + amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)127)); + amfreply.getContentP(2)->addContent(AMF::Object("mode", (double)1)); + 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.")); + amfreply.getContentP(3)->addContent(AMF::Object("objectEncoding", objencoding)); + amfreply.getContentP(3)->addContent(AMF::Object("data", AMF::AMF0_ECMA_ARRAY)); + amfreply.getContentP(3)->getContentP(4)->addContent(AMF::Object("version", "3,5,4,1004")); + #if DEBUG >= 4 + amfreply.Print(); + #endif + Socket.write(RTMPStream::SendChunk(3, 17, next.msg_stream_id, (char)0+amfreply.Pack())); + //send onBWDone packet - no clue what it is, but real server sends it... + amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMF::Object("", "onBWDone"));//result + amfreply.addContent(AMF::Object("", (double)0));//zero + amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null + Socket.write(RTMPStream::SendChunk(3, 17, next.msg_stream_id, (char)0+amfreply.Pack())); + parsed3 = 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, 17, next.msg_stream_id, (char)0+amfreply.Pack())); + Socket.write(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 + parsed3 = 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, 17, next.msg_stream_id, (char)0+amfreply.Pack())); + parsed3 = 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, 17, 1, (char)0+amfreply.Pack())); + parsed3 = 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, 17, next.msg_stream_id, (char)0+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, 17, 1, (char)0+amfreply.Pack())); + RTMPStream::chunk_snd_max = 65536;//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! + parsed3 = true; + }//createStream + #if DEBUG >= 3 + fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str()); + #endif + if (!parsed3){ + #if DEBUG >= 2 + fprintf(stderr, "AMF0 command not processed! :(\n"); + #endif + } + }//parsing AMF0-style + } break; case 18: #if DEBUG >= 4 fprintf(stderr, "Received AFM0 data message (metadata)\n"); @@ -276,16 +423,17 @@ void Connector_RTMP::parseChunk(){ amfdata.Print(); #endif if (amfdata.getContentP(0)->StrValue() == "connect"){ - #if DEBUG >= 4 - int tmpint; double objencoding = 0; if (amfdata.getContentP(2)->getContentP("objectEncoding")){ objencoding = amfdata.getContentP(2)->getContentP("objectEncoding")->NumValue(); } - tmpint = amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue(); + fprintf(stderr, "Object encoding set to %e\n", objencoding); + #if DEBUG >= 4 + int tmpint; + tmpint = (int)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(); + tmpint = (int)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 diff --git a/util/amf.cpp b/util/amf.cpp index 938770a9..b82aceeb 100644 --- a/util/amf.cpp +++ b/util/amf.cpp @@ -606,7 +606,7 @@ AMF::Object3 AMF::parseOne3(const unsigned char *& data, unsigned int &len, unsi unsigned int arrsize = 0; unsigned char tmpdbl[8]; #if DEBUG >= 10 - fprintf(stderr, "Note: AMF type %hhx found. %i bytes left\n", data[i], len-i); + fprintf(stderr, "Note: AMF3 type %hhx found. %i bytes left\n", data[i], len-i); #endif switch (data[i]){ case AMF::AMF3_UNDEFINED: @@ -759,11 +759,11 @@ AMF::Object3 AMF::parseOne3(const unsigned char *& data, unsigned int &len, unsi }else{ tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up. tmpi |= data[i+4]; + tmpi = (tmpi << 3) >> 3;//fix sign bit i+=5; } } } - tmpi = (tmpi << 3) >> 3;//fix sign bit if ((tmpi & 1) == 0){ return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_BYTES);//reference type } From ca1e60b4617dc11485f46b6af4e0f81d423d118e Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 18 Apr 2011 15:09:31 +0200 Subject: [PATCH 19/24] Socket bugfix --- Connector_RTMP/main.cpp | 2 +- util/ddv_socket.cpp | 38 ++++++++++++++------------------------ 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 4882dfcb..e6265733 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -50,7 +50,7 @@ int Connector_RTMP::Connector_RTMP(DDV::Socket conn){ //first timestamp set RTMPStream::firsttime = RTMPStream::getNowMS(); - while (RTMPStream::handshake_in.size() < 1537){ + while (Socket.connected() && (RTMPStream::handshake_in.size() < 1537)){ Socket.read(RTMPStream::handshake_in); } RTMPStream::rec_cnt += 1537; diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index 3b3567ac..c54cee42 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -106,21 +106,16 @@ bool DDV::Socket::connected(){ /// \returns True if the whole write was succesfull, false otherwise. bool DDV::Socket::write(const void * buffer, int len){ int sofar = 0; - Blocking = false; + if (sock < 0){return false;} while (sofar != len){ int r = send(sock, (char*)buffer + sofar, len-sofar, 0); if (r <= 0){ - switch (errno){ - case EWOULDBLOCK: Blocking = true; break; - default: - Error = true; - #if DEBUG >= 2 - fprintf(stderr, "Could not write data! Error: %s\n", strerror(errno)); - #endif - close(); - return false; - break; - } + Error = true; + #if DEBUG >= 2 + fprintf(stderr, "Could not write data! Error: %s\n", strerror(errno)); + #endif + close(); + return false; }else{ sofar += r; } @@ -137,21 +132,16 @@ bool DDV::Socket::write(const void * buffer, int len){ /// \returns True if the whole read was succesfull, false otherwise. bool DDV::Socket::read(void * buffer, int len){ int sofar = 0; - Blocking = false; + if (sock < 0){return false;} while (sofar != len){ int r = recv(sock, (char*)buffer + sofar, len-sofar, 0); if (r <= 0){ - switch (errno){ - case EWOULDBLOCK: Blocking = true; break; - default: - Error = true; - #if DEBUG >= 2 - fprintf(stderr, "Could not read data! Error: %s\n", strerror(errno)); - #endif - close(); - return false; - break; - } + Error = true; + #if DEBUG >= 2 + fprintf(stderr, "Could not read data! Error: %s\n", strerror(errno)); + #endif + close(); + return false; }else{ sofar += r; } From 94a21fb8725b85761be700c486ca9068b1a705a6 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 18 Apr 2011 15:19:26 +0200 Subject: [PATCH 20/24] Socket fixes deel 2 --- util/ddv_socket.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index c54cee42..08bfda65 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -176,6 +176,7 @@ int DDV::Socket::iwrite(void * buffer, int len){ break; } } + if (r == 0){close();} return r; }//DDV::Socket::iwrite @@ -199,6 +200,7 @@ int DDV::Socket::iread(void * buffer, int len){ break; } } + if (r == 0){close();} return r; }//DDV::Socket::iread From 7b811219bd6535a09b61700ff8168cfd25313626 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 18 Apr 2011 15:42:14 +0200 Subject: [PATCH 21/24] Socket fixes 3 --- util/ddv_socket.cpp | 10 ++++++---- util/http_parser.cpp | 16 ++-------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index 08bfda65..72ed286e 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -211,12 +211,14 @@ int DDV::Socket::iread(void * buffer, int len){ /// \return True if new data arrived, false otherwise. bool DDV::Socket::read(std::string & buffer){ char cbuffer[5000]; - int num = iread(cbuffer, 5000); + if (!read(cbuffer, 1)){return false;} + int num = iread(cbuffer+1, 4999); if (num > 0){ - buffer.append(cbuffer, num); - return true; + buffer.append(cbuffer, num+1); + }else{ + buffer.append(cbuffer, 1); } - return false; + return true; }//read /// Create a new base ServerSocket. The socket is never connected, and a placeholder for later connections. diff --git a/util/http_parser.cpp b/util/http_parser.cpp index 60d370e7..8c9ed2db 100644 --- a/util/http_parser.cpp +++ b/util/http_parser.cpp @@ -134,20 +134,8 @@ bool HTTP::Parser::Read(DDV::Socket & sock){ int r = 0; int b = 0; char buffer[500]; - while (true){ - r = sock.ready(); - if (r < 1){ - if (r == -1){ - #if DEBUG >= 1 - fprintf(stderr, "User socket is disconnected.\n"); - #endif - } - return parse(); - } - b = sock.iread(buffer, 500); - HTTPbuffer.append(buffer, b); - } - return false; + sock.read(HTTPbuffer); + return parse(); }//HTTPReader::ReadSocket /// Reads a full set of HTTP responses/requests from file F. From bf0b976999d135225ae5c4f00e2731db0802fd0e Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 18 Apr 2011 16:08:55 +0200 Subject: [PATCH 22/24] Hopelijk laatste socket fix --- Connector_HTTP/main.cpp | 4 ++-- util/http_parser.cpp | 9 ++++----- util/http_parser.h | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index a7fbf0e3..4ba94342 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -160,7 +160,7 @@ namespace Connector_HTTP{ while (conn.connected() && !FLV::Parse_Error){ //only parse input if available or not yet init'ed - if (HTTP_R.Read(conn)){ + if (HTTP_R.Read(conn, ready4data)){ handler = HANDLER_PROGRESSIVE; if ((HTTP_R.url.find("Seg") != std::string::npos) && (HTTP_R.url.find("Frag") != std::string::npos)){handler = HANDLER_FLASH;} if (HTTP_R.url.find("f4m") != std::string::npos){handler = HANDLER_FLASH;} @@ -174,7 +174,7 @@ namespace Connector_HTTP{ printf("Sending crossdomain.xml file\n"); #endif } - if(handler == HANDLER_FLASH){ + if (handler == HANDLER_FLASH){ if (HTTP_R.url.find("f4m") == std::string::npos){ Movie = HTTP_R.url.substr(1); Movie = Movie.substr(0,Movie.find("/")); diff --git a/util/http_parser.cpp b/util/http_parser.cpp index 8c9ed2db..4fe7b94f 100644 --- a/util/http_parser.cpp +++ b/util/http_parser.cpp @@ -128,12 +128,11 @@ void HTTP::Parser::SetVar(std::string i, std::string v){ } /// Attempt to read a whole HTTP request or response from DDV::Socket sock. +/// \param sock The socket to use. +/// \param nonblock When true, will not block even if the socket is blocking. /// \return True of a whole request or response was read, false otherwise. -bool HTTP::Parser::Read(DDV::Socket & sock){ - //returned true als hele http packet gelezen is - int r = 0; - int b = 0; - char buffer[500]; +bool HTTP::Parser::Read(DDV::Socket & sock, bool nonblock){ + if (nonblock && (sock.ready() < 1)){return parse();} sock.read(HTTPbuffer); return parse(); }//HTTPReader::ReadSocket diff --git a/util/http_parser.h b/util/http_parser.h index d08cadef..ed8f64fe 100644 --- a/util/http_parser.h +++ b/util/http_parser.h @@ -14,7 +14,7 @@ namespace HTTP{ class Parser{ public: Parser(); - bool Read(DDV::Socket & sock); + bool Read(DDV::Socket & sock, bool nonblock = true); bool Read(FILE * F); std::string GetHeader(std::string i); std::string GetVar(std::string i); From 42befc2498298bdcaaeccf2cbf4348c61997306e Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 18 Apr 2011 16:29:48 +0200 Subject: [PATCH 23/24] New compilation method --- Buffer/Makefile | 4 +++- Connector_HTTP/Makefile | 4 +++- Connector_HTTP/main.cpp | 8 -------- Connector_RAW/Makefile | 4 +++- Connector_RTMP/Makefile | 4 +++- Connector_RTMP/main.cpp | 7 ------- Connector_RTSP/Makefile | 4 +++- Makefile | 6 ++++++ 8 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Buffer/Makefile b/Buffer/Makefile index 63cc2524..6cbfba70 100644 --- a/Buffer/Makefile +++ b/Buffer/Makefile @@ -2,7 +2,9 @@ SRC = main.cpp ../util/ddv_socket.cpp ../util/flv_tag.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Buffer INCLUDES = -CCFLAGS = -Wall -Wextra -funsigned-char -g +DEBUG = 4 +OPTIMIZE = -g +CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar diff --git a/Connector_HTTP/Makefile b/Connector_HTTP/Makefile index 3ce05f27..2a9f9168 100644 --- a/Connector_HTTP/Makefile +++ b/Connector_HTTP/Makefile @@ -2,7 +2,9 @@ SRC = main.cpp ../util/ddv_socket.cpp ../util/http_parser.cpp ../util/flv_tag.cp OBJ = $(SRC:.cpp=.o) OUT = DDV_Conn_HTTP INCLUDES = -CCFLAGS = -Wall -Wextra -funsigned-char -g +DEBUG = 4 +OPTIMIZE = -g +CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index 4ba94342..8c222c54 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -1,14 +1,6 @@ /// \file Connector_HTTP/main.cpp /// Contains the main code for the HTTP Connector -/// Sets the global debugging level. -// debugging level 0 = nothing -// debugging level 1 = critical errors -// debugging level 2 = errors -// debugging level 3 = status information -// debugging level 4 = extremely verbose status information -#define DEBUG 4 - #include #include #include diff --git a/Connector_RAW/Makefile b/Connector_RAW/Makefile index aaf541a1..7d61804a 100644 --- a/Connector_RAW/Makefile +++ b/Connector_RAW/Makefile @@ -2,7 +2,9 @@ SRC = main.cpp ../util/ddv_socket.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Conn_RAW INCLUDES = -CCFLAGS = -Wall -Wextra -funsigned-char -g +DEBUG = 4 +OPTIMIZE = -g +CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar diff --git a/Connector_RTMP/Makefile b/Connector_RTMP/Makefile index dfb274a8..f13d743c 100644 --- a/Connector_RTMP/Makefile +++ b/Connector_RTMP/Makefile @@ -3,7 +3,9 @@ OBJ = $(SRC:.cpp=.o) OUT = DDV_Conn_RTMP INCLUDES = STATIC = -CCFLAGS = -Wall -Wextra -funsigned-char -g +DEBUG = 4 +OPTIMIZE = -g +CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index e6265733..91c25e19 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -1,13 +1,6 @@ /// \file Connector_RTMP/main.cpp /// Contains the main code for the RTMP Connector -//debugging level 0 = nothing -//debugging level 1 = critical errors -//debugging level 2 = errors -//debugging level 3 = status information -//debugging level 4 = extremely verbose status information -#define DEBUG 4 - #include #include #include diff --git a/Connector_RTSP/Makefile b/Connector_RTSP/Makefile index 425a9613..f628e588 100644 --- a/Connector_RTSP/Makefile +++ b/Connector_RTSP/Makefile @@ -2,7 +2,9 @@ SRC = main.cpp ../sockets/sw_base.cpp ../sockets/sw_inet.cpp ../sockets/sw_unix. OBJ = $(SRC:.cpp=.o) OUT = Connector_RTSP INCLUDES = -I/usr/local/include/jthread/ -CCFLAGS = -Wall -Wextra -funsigned-char -g +DEBUG = 4 +OPTIMIZE = -g +CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar diff --git a/Makefile b/Makefile index 0389a1ad..ab5dea23 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,12 @@ client-clean: cd Connector_RAW; $(MAKE) clean cd Buffer; $(MAKE) clean clean: client-clean +client-release: client-clean + cd Connector_HTTP; $(MAKE) DEBUG=0 OPTIMIZE=-O2 + cd Connector_RTMP; $(MAKE) DEBUG=0 OPTIMIZE=-O2 + cd Connector_RAW; $(MAKE) DEBUG=0 OPTIMIZE=-O2 + cd Buffer; $(MAKE) DEBUG=0 OPTIMIZE=-O2 +release: client-release client-install: client-clean client cd Connector_RTMP; $(MAKE) install cd Connector_HTTP; $(MAKE) install From f8b253c55317585af5a2ae3367d3e13f6117ca1e Mon Sep 17 00:00:00 2001 From: Erik Zandvliet Date: Sun, 29 May 2011 11:27:09 +0200 Subject: [PATCH 24/24] GitIgnore Aangepast --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d81a8893..198f1314 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ Connector_HTTP/DDV_Conn_HTTP Buffer/DDV_Buffer Connector_RTMP/DDV_Conn_RTMP Connector_RTSP/DDV_Conn_RTSP +Connector_RTSP/Connector_RTSP Connector_RAW/DDV_Conn_RAW *~ bin/*