From b068d53e2a63250ef3a75114eef566f2607bd88c Mon Sep 17 00:00:00 2001 From: Lekensteyn Date: Wed, 14 Mar 2012 18:08:23 +0100 Subject: [PATCH 01/24] Fix build errors due to missing dtsc dependency --- Connector_RTMP/Makefile | 2 +- Connector_RTSP/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Connector_RTMP/Makefile b/Connector_RTMP/Makefile index 2148c6d5..45937f40 100644 --- a/Connector_RTMP/Makefile +++ b/Connector_RTMP/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/socket.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp ../util/util.cpp +SRC = main.cpp ../util/socket.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp ../util/util.cpp ../util/dtsc.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Conn_RTMP INCLUDES = diff --git a/Connector_RTSP/Makefile b/Connector_RTSP/Makefile index 273fd7b0..9a96197b 100644 --- a/Connector_RTSP/Makefile +++ b/Connector_RTSP/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/util.cpp +SRC = main.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/util.cpp ../util/dtsc.cpp OBJ = $(SRC:.cpp=.o) OUT = Connector_RTSP INCLUDES = $(shell pkg-config --cflags jrtplib) From ad31299def6b50e47645b959d0827930fab160f3 Mon Sep 17 00:00:00 2001 From: Lekensteyn Date: Wed, 14 Mar 2012 18:23:27 +0100 Subject: [PATCH 02/24] RTSP: Choose random port, extra debugging messages --- Connector_RTSP/main.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Connector_RTSP/main.cpp b/Connector_RTSP/main.cpp index b0b0edd4..89494009 100644 --- a/Connector_RTSP/main.cpp +++ b/Connector_RTSP/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -116,26 +117,34 @@ int RTSP_Handler( Socket::Connection conn ) { } else { HTTP_S.SetHeader( "CSeq", HTTP_R.GetHeader( "CSeq" ).c_str() ); HTTP_S.SetHeader( "Session", time(NULL) ); - /// \todo "Random" generation of server_ports /// \todo Add support for audio // if( HTTP_R.url.find( "audio" ) != std::string::npos ) { // HTTP_S.SetHeader( "Transport", HTTP_R.GetHeader( "Transport" ) + ";server_port=50002-50003" ); // } else { - //send video data - HTTP_S.SetHeader( "Transport", HTTP_R.GetHeader( "Transport" ) + ";server_port=50000-50001" ); //Stub data for testing purposes. This should now be extracted somehow from DTSC::DTMI VideoParams.SetOwnTimestampUnit( ( 1.0 / 29.917 ) * 90000.0 ); VideoParams.SetMaximumPacketSize( 10000 ); - //pick the right port here - VideoTransParams.SetPortbase( 50000 ); //create a JRTPlib session - int VideoStatus = VideoSession.Create( VideoParams, &VideoTransParams, jrtplib::RTPTransmitter::IPv6UDPProto ); + int VideoStatus; + uint16_t pbase; + //after 20 retries, just give up, most ports are likely in use + int retries = 20; + do { + //pick the right port here in the range 5000 to 5000 + 2 * 500 = 6000 + pbase = 5000 + 2 * (rand() % 500); + VideoTransParams.SetPortbase( pbase ); + VideoStatus = VideoSession.Create( VideoParams, &VideoTransParams, jrtplib::RTPTransmitter::IPv6UDPProto ); + } while(VideoStatus < 0 && --retries > 0); if( VideoStatus < 0 ) { - std::cerr << jrtplib::RTPGetErrorString( VideoStatus ) << std::endl; + std::cerr << "Video session could not be created: " << jrtplib::RTPGetErrorString( VideoStatus ) << std::endl; exit( -1 ); } else { - std::cerr << "Created video session\n"; + std::cerr << "Created video session using ports " << pbase << " and " << (pbase+1) << "\n"; } + //send video data + std::stringstream transport; + transport << HTTP_R.GetHeader( "Transport" ) << ";server_port=" << pbase << "-" << (pbase+1); + HTTP_S.SetHeader( "Transport", transport.str() ); /// \todo Connect with clients other than localhost uint8_t localip[32]; @@ -147,7 +156,7 @@ int RTSP_Handler( Socket::Connection conn ) { //add the destination address to the VideoSession VideoStatus = VideoSession.AddDestination(addr); if (VideoStatus < 0) { - std::cerr << jrtplib::RTPGetErrorString(VideoStatus) << std::endl; + std::cerr << "Destination could not be set: " << jrtplib::RTPGetErrorString(VideoStatus) << std::endl; exit(-1); } else { std::cerr << "Destination Set\n"; From 7e3f96d14af646fd44951f56fbb30810ae854f79 Mon Sep 17 00:00:00 2001 From: Lekensteyn Date: Fri, 16 Mar 2012 17:06:04 +0100 Subject: [PATCH 03/24] Document sdp response for RTSP --- Connector_RTSP/main.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Connector_RTSP/main.cpp b/Connector_RTSP/main.cpp index 89494009..cf1f94d9 100644 --- a/Connector_RTSP/main.cpp +++ b/Connector_RTSP/main.cpp @@ -90,17 +90,22 @@ int RTSP_Handler( Socket::Connection conn ) { /// \todo Add audio to SDP file. //This is just a dummy with data that was supposedly right for our teststream. //SDP Docs: http://tools.ietf.org/html/rfc4566 - //v=0 - //o=- 0 0 IN IP4 ddvtech.com - //s=Fifa Test - //c=IN IP4 127.0.0.1 - //t=0 0 - //a=recvonly - //m=video 0 RTP/AVP 98 - //a=control:rtsp://localhost/fifa/video - //a=rtpmap:98 H264/90000 - //a=fmtp:98 packetization-mode=0 - HTTP_S.SetBody( "v=0\r\no=- 0 0 IN IP4 ddvtech.com\r\ns=Fifa Test\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\na=recvonly\r\nm=video 0 RTP/AVP 98\r\na=control:rtsp://localhost/fifa/video\r\na=rtpmap:98 H264/90000\r\na=fmtp:98 packetization-mode=0\r\n\r\n");//m=audio 0 RTP/AAP 96\r\na=control:rtsp://localhost/fifa/audio\r\na=rtpmap:96 mpeg4-generic/16000/2\r\n\r\n"); + HTTP_S.SetBody( "v=0\r\n" //protocol version + "o=- 0 0 IN IP4 ddvtech.com\r\n" //originator and session identifier (5.2): + //username sess-id sess-version nettype addrtype unicast-addr + //"-": no concept of User IDs, nettype IN(ternet) + //IP4: following address is a FQDN for IPv4 + "s=Fifa Test\r\n" //session name (5.3) + "c=IN IP4 127.0.0.1\r\n" //connection information -- not required if included in all media + //nettype addrtype connection-address + "t=0 0\r\n" //time the session is active: start-time stop-time; "0 0"=permanent session + "a=recvonly\r\n"//zero or more session attribute lines + "m=video 0 RTP/AVP 98\r\n"//media name and transport address: media port proto fmt ... + "a=control:rtsp://localhost/fifa/video\r\n"//rfc2326 C.1.1, URL for aggregate control on session level + "a=rtpmap:98 H264/90000\r\n"//rfc2326 C.1.3, dynamic payload type; see also http://tools.ietf.org/html/rfc1890#section-5 + "a=fmtp:98 packetization-mode=0"//codec-specific parameters + "\r\n\r\n");//m=audio 0 RTP/AAP 96\r\na=control:rtsp://localhost/fifa/audio\r\na=rtpmap:96 mpeg4-generic/16000/2\r\n\r\n"); + //important information when supporting multiple streams http://tools.ietf.org/html/rfc2326#appendix-C.3 fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "200", "OK" ).c_str() ); conn.write( HTTP_S.BuildResponse( "200", "OK" ) ); } From 856474bf0f4a64c6c83a42d525b015ee51d2b03a Mon Sep 17 00:00:00 2001 From: Lekensteyn Date: Fri, 16 Mar 2012 17:31:38 +0100 Subject: [PATCH 04/24] Specify null IP address for connection, rfc2326 C.1.7 --- Connector_RTSP/main.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Connector_RTSP/main.cpp b/Connector_RTSP/main.cpp index cf1f94d9..b08debbc 100644 --- a/Connector_RTSP/main.cpp +++ b/Connector_RTSP/main.cpp @@ -96,8 +96,9 @@ int RTSP_Handler( Socket::Connection conn ) { //"-": no concept of User IDs, nettype IN(ternet) //IP4: following address is a FQDN for IPv4 "s=Fifa Test\r\n" //session name (5.3) - "c=IN IP4 127.0.0.1\r\n" //connection information -- not required if included in all media - //nettype addrtype connection-address + //"c" - destination is specified in SETUP per rfc2326 C.1.7, set null as recommended + "c=IN IP4 0.0.0.0\r\n" //connection information -- not required if included in all media + //nettype addrtype connection-address "t=0 0\r\n" //time the session is active: start-time stop-time; "0 0"=permanent session "a=recvonly\r\n"//zero or more session attribute lines "m=video 0 RTP/AVP 98\r\n"//media name and transport address: media port proto fmt ... From cb51c1114928cd3b047a60c952a5afadeb09c573 Mon Sep 17 00:00:00 2001 From: Lekensteyn Date: Sat, 17 Mar 2012 16:27:15 +0100 Subject: [PATCH 05/24] Fix compile/syntax errors in HTTP connector (guessed) --- Connector_HTTP/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index da297cf8..fc780d96 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -316,7 +316,7 @@ namespace Connector_HTTP{ }//FLASH handler if (handler == HANDLER_PROGRESSIVE){ //in het geval progressive nemen we aan dat de URL de streamname is, met .flv erachter - extension = HTTP_R.url.substr(HTTP_R.url.size()-4); + std::string extension = HTTP_R.url.substr(HTTP_R.url.size()-4); 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) && *i != '_'){streamname.erase(i);}else{*i=tolower(*i);}//strip nonalphanumeric @@ -356,6 +356,7 @@ namespace Connector_HTTP{ fprintf(stderr, "Sending a video fragment. %i left in buffer, %i requested\n", (int)Flash_FragBuffer.size(), Flash_RequestPending); #endif } + */ if (inited){ unsigned int now = time(0); if (now != lastStats){ From abb6d2735045d99f8f337a00eb778c2ba5ca2bf4 Mon Sep 17 00:00:00 2001 From: Lekensteyn Date: Sat, 17 Mar 2012 16:57:23 +0100 Subject: [PATCH 06/24] WIP for RTSP support (GH-8) --- Connector_RTSP/main.cpp | 61 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/Connector_RTSP/main.cpp b/Connector_RTSP/main.cpp index b08debbc..71171ab0 100644 --- a/Connector_RTSP/main.cpp +++ b/Connector_RTSP/main.cpp @@ -55,6 +55,7 @@ int RTSP_Handler( Socket::Connection conn ) { jrtplib::RTPSessionParams VideoParams; jrtplib::RTPUDPv6TransmissionParams VideoTransParams; std::string PreviousRequest = ""; + std::string streamname; Socket::Connection ss(-1); HTTP::Parser HTTP_R, HTTP_S; //Some clients appear to expect a single request per connection. Don't know which ones. @@ -102,7 +103,7 @@ int RTSP_Handler( Socket::Connection conn ) { "t=0 0\r\n" //time the session is active: start-time stop-time; "0 0"=permanent session "a=recvonly\r\n"//zero or more session attribute lines "m=video 0 RTP/AVP 98\r\n"//media name and transport address: media port proto fmt ... - "a=control:rtsp://localhost/fifa/video\r\n"//rfc2326 C.1.1, URL for aggregate control on session level + "a=control:" + HTTP_R.url + "\r\n"//rfc2326 C.1.1, URL for aggregate control on session level "a=rtpmap:98 H264/90000\r\n"//rfc2326 C.1.3, dynamic payload type; see also http://tools.ietf.org/html/rfc1890#section-5 "a=fmtp:98 packetization-mode=0"//codec-specific parameters "\r\n\r\n");//m=audio 0 RTP/AAP 96\r\na=control:rtsp://localhost/fifa/audio\r\na=rtpmap:96 mpeg4-generic/16000/2\r\n\r\n"); @@ -111,6 +112,7 @@ int RTSP_Handler( Socket::Connection conn ) { conn.write( HTTP_S.BuildResponse( "200", "OK" ) ); } } else if ( HTTP_R.method == "SETUP" ) { + bool setup_session = false;//whether a session should be setup or not std::string temp = HTTP_R.GetHeader("Transport"); //Extract the random UTP pair for video data ( RTP/RTCP) int ClientRTPLoc = temp.find( "client_port=" ) + 12; @@ -121,6 +123,36 @@ int RTSP_Handler( Socket::Connection conn ) { fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "459", "Aggregate Operation Not Allowed" ).c_str() ); conn.write( HTTP_S.BuildResponse( "459", "Aggregate Operation Not Allowed" ) ); } else { + do{ + if (!ss.connected()){ + /// \todo Put stream name-to-file mapping in a separate util file or even class + streamname = std::string(HTTP_R.url.c_str()); + unsigned int slash_pos = streamname.rfind('/'); + if (slash_pos != std::string::npos) streamname.erase(0, slash_pos); + for (std::string::iterator i=streamname.begin(); i != streamname.end(); ++i){ + if (*i == '?'){ + streamname.erase(i, streamname.end()); + break; + } + if (!isalpha(*i) && !isdigit(*i) && *i != '_'){ + streamname.erase(i); + --i; + }else{ + *i = tolower(*i); + } + } + streamname = "/tmp/shared_socket_" + streamname; + ss = Socket::Connection(streamname); + if (!ss.connected()){ + streamname = ""; + HTTP_R.BuildResponse("404", "Not Found"); + break; //skip the session below + } + } + setup_session = true; + }while(0); + } + if (setup_session) { HTTP_S.SetHeader( "CSeq", HTTP_R.GetHeader( "CSeq" ).c_str() ); HTTP_S.SetHeader( "Session", time(NULL) ); /// \todo Add support for audio @@ -216,13 +248,34 @@ int RTSP_Handler( Socket::Connection conn ) { } } if( PlayVideo ) { - /// \todo Select correct source. This should become the DTSC::DTMI or the DTSC::Stream, whatever seems more natural. - std::string VideoBuf = ReadNALU( ); - if( VideoBuf == "" ) { + bool no_data_ignore = false; + std::string VideoBuf; + ss.canRead(); + switch (ss.ready()) { + case -1: + std::cerr << "Buffer socket is disconnected\n"; + break; + case 0://not ready + no_data_ignore = true; + break; + default: + ///\todo Make it work! + DTSC::Stream ds; + ss.spool(); + if (ds.parsePacket(ss.Received())){ + VideoBuf = ds.lastData(); + }else{ + std::cerr << "Failed to parse packet" << std::endl; + no_data_ignore = true;//perhaps corrupt? + } + break; + } + if(no_data_ignore){}else if( VideoBuf == "" ) { //videobuffer is empty, no more data. jrtplib::RTPTime delay = jrtplib::RTPTime(10.0); VideoSession.BYEDestroy(delay,"Out of data",11); conn.close(); + std::cerr << "Buffer empty - closing connection" << std::endl; } else { //Send a single NALU (H264 block) here. VideoSession.SendPacket( VideoBuf.c_str(), VideoBuf.size(), 98, false, ( 1.0 / 29.917 ) * 90000 ); From 3a96785cdde670b1f399f125502851514900414e Mon Sep 17 00:00:00 2001 From: Thulinma Date: Thu, 22 Mar 2012 13:36:34 +0100 Subject: [PATCH 07/24] Default port set in server control page. --- server.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.html b/server.html index 743cbc33..cb7905a0 100644 --- a/server.html +++ b/server.html @@ -199,7 +199,7 @@ From f01c686997700495ba1d7c2a3bdeb49cc3c6d9d4 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 27 Mar 2012 14:07:10 +0200 Subject: [PATCH 08/24] Fix compiling of DTSC version branch. --- Connector_RAW/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Connector_RAW/main.cpp b/Connector_RAW/main.cpp index b77fc5d3..5ef11a50 100644 --- a/Connector_RAW/main.cpp +++ b/Connector_RAW/main.cpp @@ -22,7 +22,7 @@ int main(int argc, char ** argv) { } //transport ~50kb at a time //this is a nice tradeoff between CPU usage and speed - char buffer[50000]; + const char buffer[50000] = {0}; while(std::cout.good() && S.read(buffer,50000)){std::cout.write(buffer,50000);} S.close(); return 0; From 77af63ebe44a350c3c9f323834a29fd7ff65e199 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Fri, 30 Mar 2012 13:20:08 +0200 Subject: [PATCH 09/24] Added DTSC rate limiter. Closes #12 --- Buffer/main.cpp | 27 +++++++++++++++++++++++---- util/dtsc.cpp | 6 ++++++ util/dtsc.h | 1 + 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Buffer/main.cpp b/Buffer/main.cpp index 6476a778..14cc1cc3 100644 --- a/Buffer/main.cpp +++ b/Buffer/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "../util/dtsc.h" //DTSC support #include "../util/socket.h" //Socket lib #include "../util/json/json.h" @@ -18,6 +19,14 @@ /// Holds all code unique to the Buffer. namespace Buffer{ + /// Gets the current system time in milliseconds. + unsigned int getNowMS(){ + timeval t; + gettimeofday(&t, 0); + return t.tv_sec + t.tv_usec/1000; + }//getNowMS + + Json::Value Storage = Json::Value(Json::objectValue); ///< Global storage of data. ///A simple signal handler that ignores all signals. @@ -185,6 +194,9 @@ namespace Buffer{ char charBuffer[1024*10]; unsigned int charCount; unsigned int stattimer = 0; + unsigned int lastPacketTime = 0;//time in MS last packet was parsed + unsigned int currPacketTime = 0;//time of the last parsed packet (current packet) + unsigned int prevPacketTime = 0;//time of the previously parsed packet (current packet - 1) Socket::Connection incoming; Socket::Connection std_input(fileno(stdin)); Socket::Connection StatsSocket = Socket::Connection("/tmp/ddv_statistics", true); @@ -224,10 +236,17 @@ namespace Buffer{ } //invalidate the current buffer if ( (!ip_waiting && std_input.canRead()) || (ip_waiting && ip_input.connected()) ){ - std::cin.read(charBuffer, 1024*10); - charCount = std::cin.gcount(); - inBuffer.append(charBuffer, charCount); - Strm->parsePacket(inBuffer); + //slow down packet receiving to real-time + if ((getNowMS() - lastPacketTime > currPacketTime - prevPacketTime) || (currPacketTime <= prevPacketTime)){ + std::cin.read(charBuffer, 1024*10); + charCount = std::cin.gcount(); + inBuffer.append(charBuffer, charCount); + if (Strm->parsePacket(inBuffer)){ + lastPacketTime = getNowMS(); + prevPacketTime = currPacketTime; + currPacketTime = Strm->getTime(); + } + } } //check for new connections, accept them if there are any diff --git a/util/dtsc.cpp b/util/dtsc.cpp index 85e1ce47..a5eb05e3 100644 --- a/util/dtsc.cpp +++ b/util/dtsc.cpp @@ -23,6 +23,12 @@ DTSC::Stream::Stream(unsigned int rbuffers){ buffercount = rbuffers; } +/// Returns the time in milliseconds of the last received packet. +/// This is _not_ the time this packet was received, only the stored time. +unsigned int DTSC::Stream::getTime(){ + return buffers.front().getContentP("time")->NumValue(); +} + /// Attempts to parse a packet from the given std::string buffer. /// Returns true if successful, removing the parsed part from the buffer string. /// Returns false if invalid or not enough data is in the buffer. diff --git a/util/dtsc.h b/util/dtsc.h index 3d690d53..f721e6c4 100644 --- a/util/dtsc.h +++ b/util/dtsc.h @@ -129,6 +129,7 @@ namespace DTSC{ std::string & outPacket(unsigned int num); std::string & outHeader(); Ring * getRing(); + unsigned int getTime(); void dropRing(Ring * ptr); private: std::deque buffers; From e3ecdb1e4b3163df3a6e48fcc512f7cf6d6e1202 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sat, 7 Apr 2012 23:45:03 +0200 Subject: [PATCH 10/24] Restructured some for clarity, got rid of the crappy JSON library, general awesomeness. --- Buffer/Makefile | 2 +- Buffer/main.cpp | 14 +- Connector_HTTP/Makefile | 4 +- Connector_HTTP/main.cpp | 69 +- Connector_RTMP/Makefile | 4 +- DDV_Controller/Makefile | 4 +- DDV_Controller/main.cpp | 381 +++---- Makefile | 5 + util/auth.cpp | 45 + util/auth.h | 11 + util/base64.cpp | 64 ++ util/base64.h | 11 + util/config.cpp | 150 +++ util/config.h | 37 + util/json.cpp | 421 ++++++++ util/json.h | 73 ++ util/json/autolink.h | 19 - util/json/config.h | 43 - util/json/features.h | 42 - util/json/forwards.h | 39 - util/json/json.h | 10 - util/json/json_batchallocator.h | 125 --- util/json/json_internalarray.inl | 448 -------- util/json/json_internalmap.inl | 607 ----------- util/json/json_reader.cpp | 885 --------------- util/json/json_value.cpp | 1718 ------------------------------ util/json/json_valueiterator.inl | 292 ----- util/json/json_writer.cpp | 829 -------------- util/json/reader.h | 196 ---- util/json/value.h | 1069 ------------------- util/json/writer.h | 174 --- util/{util.cpp => procs.cpp} | 136 +-- util/{util.h => procs.h} | 32 +- util/server_setup.cpp | 2 +- 34 files changed, 965 insertions(+), 6996 deletions(-) create mode 100644 util/auth.cpp create mode 100644 util/auth.h create mode 100644 util/base64.cpp create mode 100644 util/base64.h create mode 100644 util/config.cpp create mode 100644 util/config.h create mode 100644 util/json.cpp create mode 100644 util/json.h delete mode 100644 util/json/autolink.h delete mode 100644 util/json/config.h delete mode 100644 util/json/features.h delete mode 100644 util/json/forwards.h delete mode 100644 util/json/json.h delete mode 100644 util/json/json_batchallocator.h delete mode 100644 util/json/json_internalarray.inl delete mode 100644 util/json/json_internalmap.inl delete mode 100644 util/json/json_reader.cpp delete mode 100644 util/json/json_value.cpp delete mode 100644 util/json/json_valueiterator.inl delete mode 100644 util/json/json_writer.cpp delete mode 100644 util/json/reader.h delete mode 100644 util/json/value.h delete mode 100644 util/json/writer.h rename util/{util.cpp => procs.cpp} (55%) rename util/{util.h => procs.h} (55%) diff --git a/Buffer/Makefile b/Buffer/Makefile index 604cd26d..c6ae0cdc 100644 --- a/Buffer/Makefile +++ b/Buffer/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/json/json_reader.cpp ../util/json/json_value.cpp ../util/json/json_writer.cpp ../util/socket.cpp ../util/dtsc.cpp +SRC = main.cpp ../util/json.cpp ../util/socket.cpp ../util/dtsc.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Buffer INCLUDES = diff --git a/Buffer/main.cpp b/Buffer/main.cpp index cf27191e..630c5eb9 100644 --- a/Buffer/main.cpp +++ b/Buffer/main.cpp @@ -14,7 +14,7 @@ #include #include "../util/dtsc.h" //DTSC support #include "../util/socket.h" //Socket lib -#include "../util/json/json.h" +#include "../util/json.h" /// Holds all code unique to the Buffer. namespace Buffer{ @@ -27,7 +27,7 @@ namespace Buffer{ }//getNowMS - Json::Value Storage = Json::Value(Json::objectValue); ///< Global storage of data. + JSON::Value Storage; ///< Global storage of data. ///A simple signal handler that ignores all signals. void termination_handler (int signum){ @@ -201,9 +201,9 @@ namespace Buffer{ Socket::Connection std_input(fileno(stdin)); Socket::Connection StatsSocket = Socket::Connection("/tmp/ddv_statistics", true); - Storage["log"] = Json::Value(Json::objectValue); - Storage["curr"] = Json::Value(Json::objectValue); - Storage["totals"] = Json::Value(Json::objectValue); + Storage["log"] = JSON::Value(); + Storage["curr"] = JSON::Value(); + Storage["totals"] = JSON::Value(); while (!feof(stdin) || ip_waiting){ @@ -228,8 +228,8 @@ namespace Buffer{ StatsSocket = Socket::Connection("/tmp/ddv_statistics", true); } if (StatsSocket.connected()){ - StatsSocket.write(Storage.toStyledString()+"\n\n"); - Storage["log"].clear(); + StatsSocket.write(Storage.toString()+"\n\n"); + Storage["log"].null(); } } //invalidate the current buffer diff --git a/Connector_HTTP/Makefile b/Connector_HTTP/Makefile index a57fa022..7ccbfc72 100644 --- a/Connector_HTTP/Makefile +++ b/Connector_HTTP/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/dtsc.cpp ../util/util.cpp +SRC = main.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/dtsc.cpp ../util/config.cpp ../util/base64.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Conn_HTTP INCLUDES = @@ -22,5 +22,5 @@ clean: install: $(OUT) cp -f ./$(OUT) /usr/bin/ cversion: - rm -rf ../util/util.o + rm -rf ../util/config.o diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index fc780d96..b1b6e7cc 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -16,76 +16,15 @@ #include "../util/dtsc.h" #include "../util/flv_tag.h" #include "../util/MP4/interface.cpp" +#include "../util/base64.h" #include "../util/amf.h" /// Holds everything unique to HTTP Connector. namespace Connector_HTTP{ /// Defines the type of handler used to process this request. - enum {HANDLER_NONE, HANDLER_PROGRESSIVE, HANDLER_FLASH, HANDLER_APPLE, HANDLER_MICRO}; + enum {HANDLER_NONE, HANDLER_PROGRESSIVE, HANDLER_FLASH, HANDLER_APPLE, HANDLER_MICRO, HANDLER_JSCRIPT}; - /// Needed for base64_encode function - static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - /// Helper for base64_decode function - static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); - } - - /// 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 - - /// Used to base64 decode data. Input is the encoded data as std::string, output is the plaintext data as std::string. - /// \param input Base64 encoded data to decode. - /// \returns Plaintext decoded data. - std::string base64_decode(std::string const& encoded_string) { - int in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; - while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; in_++; - if (i ==4) { - for (i = 0; i <4; i++){char_array_4[i] = base64_chars.find(char_array_4[i]);} - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (i = 0; (i < 3); i++){ret += char_array_3[i];} - i = 0; - } - } - if (i) { - for (j = i; j <4; j++){char_array_4[j] = 0;} - for (j = 0; j <4; j++){char_array_4[j] = base64_chars.find(char_array_4[j]);} - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } - return ret; - } /// Returns AMF-format metadata for Adobe HTTP Dynamic Streaming. std::string GetMetaData( ) { @@ -136,13 +75,13 @@ namespace Connector_HTTP{ Result += "live\n"; Result += "streaming\n"; Result += ""; - Result += base64_encode(temp->GenerateLiveBootstrap(1)); + Result += Base64::encode(temp->GenerateLiveBootstrap(1)); Result += "\n"; Result += "\n"; Result += ""; - Result += base64_encode(GetMetaData()); + Result += Base64::encode(GetMetaData()); Result += "\n"; Result += "\n"; Result += "\n"; diff --git a/Connector_RTMP/Makefile b/Connector_RTMP/Makefile index 45937f40..0a337458 100644 --- a/Connector_RTMP/Makefile +++ b/Connector_RTMP/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/socket.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp ../util/util.cpp ../util/dtsc.cpp +SRC = main.cpp ../util/socket.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp ../util/config.cpp ../util/dtsc.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Conn_RTMP INCLUDES = @@ -23,5 +23,5 @@ clean: install: $(OUT) cp -f ./$(OUT) /usr/bin/ cversion: - rm -rf ../util/util.o + rm -rf ../util/config.o diff --git a/DDV_Controller/Makefile b/DDV_Controller/Makefile index bd52641d..2782d63f 100644 --- a/DDV_Controller/Makefile +++ b/DDV_Controller/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/json/json_reader.cpp ../util/json/json_value.cpp ../util/json/json_writer.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/md5.cpp ../util/util.cpp +SRC = main.cpp ../util/json.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/md5.cpp ../util/config.cpp ../util/procs.cpp ../util/base64.cpp ../util/auth.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Controller INCLUDES = @@ -23,5 +23,5 @@ $(OUT): $(OBJ) clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ cversion: - rm -rf ../util/util.o + rm -rf ../util/config.o diff --git a/DDV_Controller/main.cpp b/DDV_Controller/main.cpp index 8caea2a0..f95d84cf 100644 --- a/DDV_Controller/main.cpp +++ b/DDV_Controller/main.cpp @@ -23,16 +23,16 @@ #include "../util/socket.h" #include "../util/http_parser.h" #include "../util/md5.h" -#include "../util/json/json.h" -#include "../util/util.h" - -#include -#include +#include "../util/json.h" +#include "../util/procs.h" +#include "../util/config.h" +#include "../util/auth.h" #define UPLINK_INTERVAL 30 Socket::Server API_Socket; ///< Main connection socket. std::map lastBuffer; ///< Last moment of contact with all buffers. +Auth keychecker; ///< Checks key authorization. /// Basic signal handler. Disconnects the server_socket if it receives /// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE. @@ -61,114 +61,8 @@ void signal_handler (int signum){ }//signal_handler -/// Needed for base64_encode function -static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -/// Helper for base64_decode function -static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); -} - -/// 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 - -/// Used to base64 decode data. Input is the encoded data as std::string, output is the plaintext data as std::string. -/// \param input Base64 encoded data to decode. -/// \returns Plaintext decoded data. -std::string base64_decode(std::string const& encoded_string) { - int in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; - while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; in_++; - if (i ==4) { - for (i = 0; i <4; i++){char_array_4[i] = base64_chars.find(char_array_4[i]);} - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (i = 0; (i < 3); i++){ret += char_array_3[i];} - i = 0; - } - } - if (i) { - for (j = i; j <4; j++){char_array_4[j] = 0;} - for (j = 0; j <4; j++){char_array_4[j] = base64_chars.find(char_array_4[j]);} - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } - return ret; -} - -unsigned char __gbv2keypub_der[] = { - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe5, 0xd7, 0x9c, - 0x7d, 0x73, 0xc6, 0xe6, 0xfb, 0x35, 0x7e, 0xd7, 0x57, 0x99, 0x07, 0xdb, - 0x99, 0x70, 0xc9, 0xd0, 0x3e, 0x53, 0x57, 0x3c, 0x1e, 0x55, 0xda, 0x0f, - 0x69, 0xbf, 0x26, 0x79, 0xc7, 0xb6, 0xdd, 0x8e, 0x83, 0x32, 0x65, 0x74, - 0x0d, 0x74, 0x48, 0x42, 0x49, 0x22, 0x52, 0x58, 0x56, 0xc3, 0xe4, 0x49, - 0x5d, 0xac, 0x6a, 0x94, 0xb1, 0x64, 0x14, 0xbf, 0x4d, 0xd5, 0xd7, 0x3a, - 0xca, 0x5c, 0x1e, 0x6f, 0x42, 0x30, 0xac, 0x29, 0xaa, 0xa0, 0x85, 0xd2, - 0x16, 0xa2, 0x8e, 0x89, 0x12, 0xc4, 0x92, 0x06, 0xea, 0xed, 0x48, 0xf6, - 0xdb, 0xed, 0x4f, 0x62, 0x6c, 0xfa, 0xcf, 0xc2, 0xb9, 0x8d, 0x04, 0xb2, - 0xba, 0x63, 0xc9, 0xcc, 0xee, 0x23, 0x64, 0x46, 0x14, 0x12, 0xc8, 0x38, - 0x67, 0x69, 0x6b, 0xaf, 0xd1, 0x7c, 0xb1, 0xb5, 0x79, 0xe4, 0x4e, 0x3a, - 0xa7, 0xe8, 0x28, 0x89, 0x25, 0xc0, 0xd0, 0xd8, 0xc7, 0xd2, 0x26, 0xaa, - 0xf5, 0xbf, 0x36, 0x55, 0x01, 0x89, 0x58, 0x1f, 0x1e, 0xf5, 0xa5, 0x42, - 0x8f, 0x60, 0x2e, 0xc2, 0xd8, 0x21, 0x0b, 0x6c, 0x8d, 0xbb, 0x72, 0xf2, - 0x19, 0x30, 0xe3, 0x4c, 0x3e, 0x80, 0xe7, 0xf2, 0xe3, 0x89, 0x4f, 0xd4, - 0xee, 0x96, 0x3e, 0x4a, 0x9b, 0xe5, 0x16, 0x01, 0xf1, 0x98, 0xc9, 0x0b, - 0xd6, 0xdf, 0x8a, 0x64, 0x47, 0xc4, 0x44, 0xcc, 0x92, 0x69, 0x28, 0xee, - 0x7d, 0xac, 0xdc, 0x30, 0x56, 0x3a, 0xe7, 0xbc, 0xba, 0x45, 0x16, 0x2c, - 0x4c, 0x46, 0x6b, 0x2b, 0x20, 0xfb, 0x3d, 0x20, 0x35, 0xbb, 0x48, 0x49, - 0x13, 0x65, 0xc9, 0x9a, 0x38, 0x10, 0x84, 0x1a, 0x8c, 0xc9, 0xd7, 0xde, - 0x07, 0x10, 0x5a, 0xfb, 0xb4, 0x95, 0xae, 0x18, 0xf2, 0xe3, 0x15, 0xe8, - 0xad, 0x7e, 0xe5, 0x3c, 0xa8, 0x47, 0x85, 0xd6, 0x1f, 0x54, 0xb5, 0xa3, - 0x79, 0x02, 0x03, 0x01, 0x00, 0x01 -}; ///< The GBv2 public key file. -unsigned int __gbv2keypub_der_len = 294; ///< Length of GBv2 public key data - -RSA * pubkey = 0; ///< Holds the public key. -/// Attempts to load the GBv2 public key. -void RSA_Load(){ - const unsigned char * key = __gbv2keypub_der; - pubkey = d2i_RSAPublicKey(0, &key, __gbv2keypub_der_len); -} - -/// Attempts to verify RSA signature using the public key loaded with RSA_Load(). -/// Assumes basesign argument is base64 encoded RSA signature for data. -/// Returns true if the data could be verified, false otherwise. -bool RSA_check(std::string & data, std::string basesign){ - std::string sign = base64_decode(basesign); - return (RSA_verify(NID_md5, (unsigned char*)data.c_str(), data.size(), (unsigned char*)sign.c_str(), sign.size(), pubkey) == 1); -} - -Json::Value Storage = Json::Value(Json::objectValue); ///< Global storage of data. +JSON::Value Storage; ///< Global storage of data. void WriteFile( std::string Filename, std::string contents ) { std::ofstream File; @@ -205,15 +99,15 @@ class ConnectedUser{ }; void Log(std::string kind, std::string message){ - Json::Value m; - m.append((Json::Value::UInt)time(0)); + JSON::Value m; + m.append((long long int)time(0)); m.append(kind); m.append(message); Storage["log"].append(m); std::cout << "[" << kind << "] " << message << std::endl; } -void Authorize( Json::Value & Request, Json::Value & Response, ConnectedUser & conn ) { +void Authorize( JSON::Value & Request, JSON::Value & Response, ConnectedUser & conn ) { time_t Time = time(0); tm * TimeInfo = localtime(&Time); std::stringstream Date; @@ -221,9 +115,9 @@ void Authorize( Json::Value & Request, Json::Value & Response, ConnectedUser & c Date << TimeInfo->tm_mday << "-" << TimeInfo->tm_mon << "-" << TimeInfo->tm_year + 1900; std::string Challenge = md5( Date.str().c_str() + conn.C.getHost() ); if( Request.isMember( "authorize" ) ) { - std::string UserID = Request["authorize"]["username"].asString(); + std::string UserID = Request["authorize"]["username"]; if (Storage["account"].isMember(UserID)){ - if( md5( Storage["account"][UserID]["password"].asString() + Challenge ) == Request["authorize"]["password"].asString() ) { + if( md5( (std::string)Storage["account"][UserID]["password"] + Challenge ) == (std::string)Request["authorize"]["password"] ) { Response["authorize"]["status"] = "OK"; conn.Username = UserID; conn.Authorized = true; @@ -232,7 +126,7 @@ void Authorize( Json::Value & Request, Json::Value & Response, ConnectedUser & c } if (Storage["authorize"].isMember("key")){ UserID = "gearbox"; - if (RSA_check(Challenge, Storage["authorize"]["key"].asString())){ + if (keychecker.PubKey_Check(Challenge, Storage["authorize"]["key"])){ Response["authorize"]["status"] = "OK"; conn.Username = UserID; conn.Authorized = true; @@ -251,14 +145,14 @@ void Authorize( Json::Value & Request, Json::Value & Response, ConnectedUser & c return; } -void CheckProtocols(Json::Value & p){ +void CheckProtocols(JSON::Value & p){ static std::map connports; bool seenHTTP = false; bool seenRTMP = false; std::string tmp; - for (Json::ValueIterator jit = p.begin(); jit != p.end(); jit++){ - if (jit.memberName() == std::string("HTTP")){ - tmp = p[jit.memberName()]["port"].asString(); + for (JSON::ObjIter jit = p.ObjBegin(); jit != p.ObjEnd(); jit++){ + if (jit->first == "HTTP"){ + tmp = (std::string)jit->second["port"]; seenHTTP = true; if (connports["HTTP"] != tmp){Util::Procs::Stop("HTTP");} connports["HTTP"] = tmp; @@ -266,8 +160,8 @@ void CheckProtocols(Json::Value & p){ Util::Procs::Start("HTTP", std::string("DDV_Conn_HTTP -n -p ")+tmp); } } - if (jit.memberName() == std::string("RTMP")){ - tmp = p[jit.memberName()]["port"].asString(); + if (jit->first == "RTMP"){ + tmp = (std::string)jit->second["port"]; seenRTMP = true; if (connports["RTMP"] != tmp){Util::Procs::Stop("RTMP");} connports["RTMP"] = tmp; @@ -280,39 +174,35 @@ void CheckProtocols(Json::Value & p){ if (!seenRTMP){Util::Procs::Stop("RTMP");} } -void CheckConfig(Json::Value & in, Json::Value & out){ - if (in.isObject() && (in.size() > 0)){ - for (Json::ValueIterator jit = in.begin(); jit != in.end(); jit++){ - if (out.isObject() && out.isMember(jit.memberName())){ - if (in[jit.memberName()] != out[jit.memberName()]){ - Log("CONF", std::string("Updated configuration value ")+jit.memberName()); - } - }else{ - Log("CONF", std::string("New configuration value ")+jit.memberName()); +void CheckConfig(JSON::Value & in, JSON::Value & out){ + for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){ + if (out.isMember(jit->first)){ + if (jit->second != out[jit->first]){ + Log("CONF", std::string("Updated configuration value ")+jit->first); } + }else{ + Log("CONF", std::string("New configuration value ")+jit->first); } - if (out.isObject() && (out.size() > 0)){ - for (Json::ValueIterator jit = out.begin(); jit != out.end(); jit++){ - if (!in.isMember(jit.memberName())){ - Log("CONF", std::string("Deleted configuration value ")+jit.memberName()); - } - } + } + for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){ + if (!in.isMember(jit->first)){ + Log("CONF", std::string("Deleted configuration value ")+jit->first); } } out = in; out["version"] = TOSTRING(VERSION); } -bool streamsEqual(Json::Value & one, Json::Value & two){ - if (one["channel"]["URL"].asString() != two["channel"]["URL"].asString()){return false;} - if (one["preset"]["cmd"].asString() != two["preset"]["cmd"].asString()){return false;} +bool streamsEqual(JSON::Value & one, JSON::Value & two){ + if (one["channel"]["URL"] != two["channel"]["URL"]){return false;} + if (one["preset"]["cmd"] != two["preset"]["cmd"]){return false;} return true; } -void startStream(std::string name, Json::Value & data){ +void startStream(std::string name, JSON::Value & data){ Log("BUFF", "(re)starting stream buffer "+name); - std::string URL = data["channel"]["URL"].asString(); - std::string preset = data["preset"]["cmd"].asString(); + std::string URL = data["channel"]["URL"]; + std::string preset = data["preset"]["cmd"]; std::string cmd1, cmd2; if (URL.substr(0, 4) == "push"){ std::string pusher = URL.substr(7); @@ -325,41 +215,37 @@ void startStream(std::string name, Json::Value & data){ } } -void CheckAllStreams(Json::Value & data){ +void CheckAllStreams(JSON::Value & data){ unsigned int currTime = time(0); - for (Json::ValueIterator jit = data.begin(); jit != data.end(); jit++){ - if (!Util::Procs::isActive(jit.memberName())){ - startStream(jit.memberName(), data[jit.memberName()]); + for (JSON::ObjIter jit = data.ObjBegin(); jit != data.ObjEnd(); jit++){ + if (!Util::Procs::isActive(jit->first)){ + startStream(jit->first, jit->second); } - if (currTime - lastBuffer[jit.memberName()] > 5){ - data[jit.memberName()]["online"] = 0; + if (currTime - lastBuffer[jit->first] > 5){ + jit->second["online"] = 0; }else{ - data[jit.memberName()]["online"] = 1; + jit->second["online"] = 1; } } } -void CheckStreams(Json::Value & in, Json::Value & out){ - if (in.isObject() && (in.size() > 0)){ - for (Json::ValueIterator jit = in.begin(); jit != in.end(); jit++){ - if (out.isObject() && out.isMember(jit.memberName())){ - if (!streamsEqual(in[jit.memberName()], out[jit.memberName()])){ - Log("STRM", std::string("Updated stream ")+jit.memberName()); - Util::Procs::Stop(jit.memberName()); - startStream(jit.memberName(), in[jit.memberName()]); - } - }else{ - Log("STRM", std::string("New stream ")+jit.memberName()); - startStream(jit.memberName(), in[jit.memberName()]); +void CheckStreams(JSON::Value & in, JSON::Value & out){ + for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){ + if (out.isMember(jit->first)){ + if (!streamsEqual(jit->second, out[jit->first])){ + Log("STRM", std::string("Updated stream ")+jit->first); + Util::Procs::Stop(jit->first); + startStream(jit->first, jit->second); } + }else{ + Log("STRM", std::string("New stream ")+jit->first); + startStream(jit->first, jit->second); } } - if (out.isObject() && (out.size() > 0)){ - for (Json::ValueIterator jit = out.begin(); jit != out.end(); jit++){ - if (!in.isMember(jit.memberName())){ - Log("STRM", std::string("Deleted stream ")+jit.memberName()); - Util::Procs::Stop(jit.memberName()); - } + for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){ + if (!in.isMember(jit->first)){ + Log("STRM", std::string("Deleted stream ")+jit->first); + Util::Procs::Stop(jit->first); } } out = in; @@ -376,7 +262,6 @@ int main(int argc, char ** argv){ sigaction(SIGTERM, &new_action, NULL); sigaction(SIGPIPE, &new_action, NULL); - RSA_Load(); // Load GearBox public key Util::Config C; C.confsection = "API"; C.parseArgs(argc, argv); @@ -392,15 +277,11 @@ int main(int argc, char ** argv){ Socket::Connection Incoming; std::vector< ConnectedUser > users; std::vector buffers; - Json::Value Request = Json::Value(Json::objectValue); - Json::Value Response = Json::Value(Json::objectValue); - Json::Reader JsonParse; + JSON::Value Request; + JSON::Value Response; std::string jsonp; ConnectedUser * uplink = 0; - JsonParse.parse(ReadFile("config.json"), Storage, false); - if (!Storage.isMember("config")){Storage["config"] = Json::Value(Json::objectValue);} - if (!Storage.isMember("log")){Storage["log"] = Json::Value(Json::arrayValue);} - if (!Storage.isMember("statistics")){Storage["statistics"] = Json::Value(Json::objectValue);} + Storage = JSON::fromString(ReadFile("config.json")); while (API_Socket.connected()){ usleep(100000); //sleep for 100 ms - prevents 100% CPU time @@ -433,14 +314,14 @@ int main(int argc, char ** argv){ } } if (gotUplink){ - Response.clear(); //make sure no data leaks from previous requests + Response.null(); //make sure no data leaks from previous requests Response["config"] = Storage["config"]; Response["streams"] = Storage["streams"]; Response["log"] = Storage["log"]; Response["statistics"] = Storage["statistics"]; Response["now"] = (unsigned int)lastuplink; uplink->H.Clean(); - uplink->H.SetBody("command="+HTTP::Parser::urlencode(Response.toStyledString())); + uplink->H.SetBody("command="+HTTP::Parser::urlencode(Response.toString())); uplink->H.BuildRequest(); uplink->writebuffer += uplink->H.BuildResponse("200", "OK"); uplink->H.Clean(); @@ -465,24 +346,18 @@ int main(int argc, char ** argv){ if (it->Received() != ""){ size_t newlines = it->Received().find("\n\n"); while (newlines != std::string::npos){ - if (JsonParse.parse(it->Received().substr(0, newlines), Request, false)){ - if (Request.isMember("totals") && Request["totals"].isMember("buffer")){ - std::string thisbuffer = Request["totals"]["buffer"].asString(); - lastBuffer[thisbuffer] = time(0); - Storage["statistics"][thisbuffer]["curr"] = Request["curr"]; - std::stringstream st; - st << Request["totals"]["now"].asUInt(); - std::string nowstr = st.str(); - Storage["statistics"][thisbuffer]["totals"][nowstr] = Request["totals"]; - if (!Storage["statistics"][thisbuffer].isMember("log")){ - Storage["statistics"][thisbuffer]["log"] = Json::Value(Json::arrayValue); - } - for (Json::ValueIterator jit = Request["log"].begin(); jit != Request["log"].end(); jit++){ - Storage["statistics"][thisbuffer]["log"].append(Request["log"][jit.memberName()]); - } + Request = it->Received().substr(0, newlines); + if (Request.isMember("totals") && Request["totals"].isMember("buffer")){ + std::string thisbuffer = Request["totals"]["buffer"]; + lastBuffer[thisbuffer] = time(0); + Storage["statistics"][thisbuffer]["curr"] = Request["curr"]; + std::stringstream st; + st << (long long int)Request["totals"]["now"]; + std::string nowstr = st.str(); + Storage["statistics"][thisbuffer]["totals"][nowstr] = Request["totals"]; + for (JSON::ObjIter jit = Request["log"].ObjBegin(); jit != Request["log"].ObjEnd(); jit++){ + Storage["statistics"][thisbuffer]["log"].append(jit->second); } - }else{ - Log("STAT", "Failed to parse stats info from buffer: "+it->Received().substr(0, newlines)); } it->Received().erase(0, newlines+2); newlines = it->Received().find("\n\n"); @@ -501,71 +376,63 @@ int main(int argc, char ** argv){ it->C.iwrite(it->writebuffer); } if (it->H.Read(it->C)){ - Response.clear(); //make sure no data leaks from previous requests + Response.null(); //make sure no data leaks from previous requests if (it->clientMode){ // In clientMode, requests are reversed. These are connections we initiated to GearBox. // They are assumed to be authorized, but authorization to gearbox is still done. // This authorization uses the compiled-in username and password (account). - if (!JsonParse.parse(it->H.body, Request, false)){ - Log("UPLK", "Failed to parse body JSON: "+it->H.body); - Response["authorize"]["status"] = "INVALID"; + Request = JSON::fromString(it->H.body); + if (Request["authorize"]["status"] != "OK"){ + if (Request["authorize"].isMember("challenge")){ + it->logins++; + if (it->logins > 2){ + Log("UPLK", "Max login attempts passed - dropping connection to uplink."); + it->C.close(); + }else{ + Response["config"] = Storage["config"]; + Response["streams"] = Storage["streams"]; + Response["log"] = Storage["log"]; + Response["statistics"] = Storage["statistics"]; + Response["authorize"]["username"] = TOSTRING(COMPILED_USERNAME); + Log("UPLK", "Responding to login challenge: " + (std::string)Request["authorize"]["challenge"]); + Response["authorize"]["password"] = md5(TOSTRING(COMPILED_PASSWORD) + (std::string)Request["authorize"]["challenge"]); + it->H.Clean(); + it->H.SetBody("command="+HTTP::Parser::urlencode(Response.toString())); + it->H.BuildRequest(); + it->writebuffer += it->H.BuildResponse("200", "OK"); + it->H.Clean(); + Log("UPLK", "Attempting login to uplink."); + } + } }else{ - if (Request["authorize"]["status"] != "OK"){ - if (Request["authorize"].isMember("challenge")){ - it->logins++; - if (it->logins > 2){ - Log("UPLK", "Max login attempts passed - dropping connection to uplink."); - it->C.close(); - }else{ - Response["config"] = Storage["config"]; - Response["streams"] = Storage["streams"]; - Response["log"] = Storage["log"]; - Response["statistics"] = Storage["statistics"]; - Response["authorize"]["username"] = TOSTRING(COMPILED_USERNAME); - Log("UPLK", "Responding to login challenge: " + Request["authorize"]["challenge"].asString()); - Response["authorize"]["password"] = md5(TOSTRING(COMPILED_PASSWORD) + Request["authorize"]["challenge"].asString()); - it->H.Clean(); - it->H.SetBody("command="+HTTP::Parser::urlencode(Response.toStyledString())); - it->H.BuildRequest(); - it->writebuffer += it->H.BuildResponse("200", "OK"); - it->H.Clean(); - Log("UPLK", "Attempting login to uplink."); - } - } - }else{ - if (Request.isMember("config")){CheckConfig(Request["config"], Storage["config"]);} - if (Request.isMember("streams")){CheckStreams(Request["streams"], Storage["streams"]);} - if (Request.isMember("clearstatlogs")){ - Storage["log"].clear(); - Storage["statistics"].clear(); - } + if (Request.isMember("config")){CheckConfig(Request["config"], Storage["config"]);} + if (Request.isMember("streams")){CheckStreams(Request["streams"], Storage["streams"]);} + if (Request.isMember("clearstatlogs")){ + Storage["log"].null(); + Storage["statistics"].null(); } } }else{ - if (!JsonParse.parse(it->H.GetVar("command"), Request, false)){ - Log("HTTP", "Failed to parse command JSON: "+it->H.GetVar("command")); - Response["authorize"]["status"] = "INVALID"; - }else{ - std::cout << "Request: " << Request.toStyledString() << std::endl; - Authorize(Request, Response, (*it)); - if (it->Authorized){ - //Parse config and streams from the request. - if (Request.isMember("config")){CheckConfig(Request["config"], Storage["config"]);} - if (Request.isMember("streams")){CheckStreams(Request["streams"], Storage["streams"]);} - //sent current configuration, no matter if it was changed or not - //Response["streams"] = Storage["streams"]; - Response["config"] = Storage["config"]; - Response["streams"] = Storage["streams"]; - //add required data to the current unix time to the config, for syncing reasons - Response["config"]["time"] = (Json::Value::UInt)time(0); - if (!Response["config"].isMember("serverid")){Response["config"]["serverid"] = "";} - //sent any available logs and statistics - Response["log"] = Storage["log"]; - Response["statistics"] = Storage["statistics"]; - //clear log and statistics to prevent useless data transfer - Storage["log"].clear(); - Storage["statistics"].clear(); - } + Request = JSON::fromString(it->H.GetVar("command")); + std::cout << "Request: " << Request.toString() << std::endl; + Authorize(Request, Response, (*it)); + if (it->Authorized){ + //Parse config and streams from the request. + if (Request.isMember("config")){CheckConfig(Request["config"], Storage["config"]);} + if (Request.isMember("streams")){CheckStreams(Request["streams"], Storage["streams"]);} + //sent current configuration, no matter if it was changed or not + //Response["streams"] = Storage["streams"]; + Response["config"] = Storage["config"]; + Response["streams"] = Storage["streams"]; + //add required data to the current unix time to the config, for syncing reasons + Response["config"]["time"] = (long long int)time(0); + if (!Response["config"].isMember("serverid")){Response["config"]["serverid"] = "";} + //sent any available logs and statistics + Response["log"] = Storage["log"]; + Response["statistics"] = Storage["statistics"]; + //clear log and statistics to prevent useless data transfer + Storage["log"].null(); + Storage["statistics"].null(); } jsonp = ""; if (it->H.GetVar("callback") != ""){jsonp = it->H.GetVar("callback");} @@ -574,9 +441,9 @@ int main(int argc, char ** argv){ it->H.protocol = "HTTP/1.0"; it->H.SetHeader("Content-Type", "text/javascript"); if (jsonp == ""){ - it->H.SetBody(Response.toStyledString()+"\n\n"); + it->H.SetBody(Response.toString()+"\n\n"); }else{ - it->H.SetBody(jsonp+"("+Response.toStyledString()+");\n\n"); + it->H.SetBody(jsonp+"("+Response.toString()+");\n\n"); } it->writebuffer += it->H.BuildResponse("200", "OK"); it->H.Clean(); @@ -586,7 +453,7 @@ int main(int argc, char ** argv){ } } Util::Procs::StopAll(); - WriteFile("config.json", Storage.toStyledString()); + WriteFile("config.json", Storage.toString()); std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl; return 0; } diff --git a/Makefile b/Makefile index c1e7386b..98bf11e1 100644 --- a/Makefile +++ b/Makefile @@ -6,29 +6,34 @@ client-debug: cd Connector_RTMP; $(MAKE) cd Connector_RAW; $(MAKE) cd Buffer; $(MAKE) + cd DDV_Controller; $(MAKE) client: client-debug client-clean: cd Connector_HTTP; $(MAKE) clean cd Connector_RTMP; $(MAKE) clean cd Connector_RAW; $(MAKE) clean cd Buffer; $(MAKE) clean + cd DDV_Controller; $(MAKE) clean clean: client-clean client-release: 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 + cd DDV_Controller; $(MAKE) DEBUG=0 OPTIMIZE=-O2 release: client-release release-install: client-clean client-release cd Connector_RTMP; $(MAKE) install cd Connector_HTTP; $(MAKE) install cd Connector_RAW; $(MAKE) install cd Buffer; $(MAKE) install + cd DDV_Controller; $(MAKE) install debug-install: client-clean client-debug cd Connector_RTMP; $(MAKE) install cd Connector_HTTP; $(MAKE) install cd Connector_RAW; $(MAKE) install cd Buffer; $(MAKE) install + cd DDV_Controller; $(MAKE) install docs: doxygen ./Doxyfile > /dev/null diff --git a/util/auth.cpp b/util/auth.cpp new file mode 100644 index 00000000..14be28f2 --- /dev/null +++ b/util/auth.cpp @@ -0,0 +1,45 @@ +#include "auth.h" +#include "base64.h" + +static unsigned char __gbv2keypub_der[] = { + 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, + 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe5, 0xd7, 0x9c, + 0x7d, 0x73, 0xc6, 0xe6, 0xfb, 0x35, 0x7e, 0xd7, 0x57, 0x99, 0x07, 0xdb, + 0x99, 0x70, 0xc9, 0xd0, 0x3e, 0x53, 0x57, 0x3c, 0x1e, 0x55, 0xda, 0x0f, + 0x69, 0xbf, 0x26, 0x79, 0xc7, 0xb6, 0xdd, 0x8e, 0x83, 0x32, 0x65, 0x74, + 0x0d, 0x74, 0x48, 0x42, 0x49, 0x22, 0x52, 0x58, 0x56, 0xc3, 0xe4, 0x49, + 0x5d, 0xac, 0x6a, 0x94, 0xb1, 0x64, 0x14, 0xbf, 0x4d, 0xd5, 0xd7, 0x3a, + 0xca, 0x5c, 0x1e, 0x6f, 0x42, 0x30, 0xac, 0x29, 0xaa, 0xa0, 0x85, 0xd2, + 0x16, 0xa2, 0x8e, 0x89, 0x12, 0xc4, 0x92, 0x06, 0xea, 0xed, 0x48, 0xf6, + 0xdb, 0xed, 0x4f, 0x62, 0x6c, 0xfa, 0xcf, 0xc2, 0xb9, 0x8d, 0x04, 0xb2, + 0xba, 0x63, 0xc9, 0xcc, 0xee, 0x23, 0x64, 0x46, 0x14, 0x12, 0xc8, 0x38, + 0x67, 0x69, 0x6b, 0xaf, 0xd1, 0x7c, 0xb1, 0xb5, 0x79, 0xe4, 0x4e, 0x3a, + 0xa7, 0xe8, 0x28, 0x89, 0x25, 0xc0, 0xd0, 0xd8, 0xc7, 0xd2, 0x26, 0xaa, + 0xf5, 0xbf, 0x36, 0x55, 0x01, 0x89, 0x58, 0x1f, 0x1e, 0xf5, 0xa5, 0x42, + 0x8f, 0x60, 0x2e, 0xc2, 0xd8, 0x21, 0x0b, 0x6c, 0x8d, 0xbb, 0x72, 0xf2, + 0x19, 0x30, 0xe3, 0x4c, 0x3e, 0x80, 0xe7, 0xf2, 0xe3, 0x89, 0x4f, 0xd4, + 0xee, 0x96, 0x3e, 0x4a, 0x9b, 0xe5, 0x16, 0x01, 0xf1, 0x98, 0xc9, 0x0b, + 0xd6, 0xdf, 0x8a, 0x64, 0x47, 0xc4, 0x44, 0xcc, 0x92, 0x69, 0x28, 0xee, + 0x7d, 0xac, 0xdc, 0x30, 0x56, 0x3a, 0xe7, 0xbc, 0xba, 0x45, 0x16, 0x2c, + 0x4c, 0x46, 0x6b, 0x2b, 0x20, 0xfb, 0x3d, 0x20, 0x35, 0xbb, 0x48, 0x49, + 0x13, 0x65, 0xc9, 0x9a, 0x38, 0x10, 0x84, 0x1a, 0x8c, 0xc9, 0xd7, 0xde, + 0x07, 0x10, 0x5a, 0xfb, 0xb4, 0x95, 0xae, 0x18, 0xf2, 0xe3, 0x15, 0xe8, + 0xad, 0x7e, 0xe5, 0x3c, 0xa8, 0x47, 0x85, 0xd6, 0x1f, 0x54, 0xb5, 0xa3, + 0x79, 0x02, 0x03, 0x01, 0x00, 0x01 +}; ///< The GBv2 public key file. +static unsigned int __gbv2keypub_der_len = 294; ///< Length of GBv2 public key data + +/// Attempts to load the GBv2 public key. +Auth::Auth(){ + const unsigned char * key = __gbv2keypub_der; + pubkey = d2i_RSAPublicKey(0, &key, __gbv2keypub_der_len); +} + +/// Attempts to verify RSA signature using the public key. +/// Assumes basesign argument is base64 encoded RSA signature for data. +/// Returns true if the data could be verified, false otherwise. +bool Auth::PubKey_Check(std::string & data, std::string basesign){ + std::string sign = Base64::decode(basesign); + return (RSA_verify(NID_md5, (unsigned char*)data.c_str(), data.size(), (unsigned char*)sign.c_str(), sign.size(), pubkey) == 1); +} diff --git a/util/auth.h b/util/auth.h new file mode 100644 index 00000000..5d8da7b6 --- /dev/null +++ b/util/auth.h @@ -0,0 +1,11 @@ +#include +#include +#include + +class Auth{ + private: + RSA * pubkey; ///< Holds the public key. + public: + Auth(); ///< Attempts to load the GBv2 public key. + bool PubKey_Check(std::string & data, std::string basesign); ///< Attempts to verify RSA signature using the public key. +}; diff --git a/util/base64.cpp b/util/base64.cpp new file mode 100644 index 00000000..0111823f --- /dev/null +++ b/util/base64.cpp @@ -0,0 +1,64 @@ +#include "base64.h" + +/// Needed for base64_encode function +const std::string Base64::chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /// Helper for base64_decode function +inline bool Base64::is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +/// 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] = chars[(triple[0] & 0xFC) >> 2]; // FC = 11111100 + quad[1] = chars[((triple[0] & 0x03) << 4) | ((triple[1] & 0xF0) >> 4)]; // 03 = 11 + quad[2] = chars[((triple[1] & 0x0F) << 2) | ((triple[2] & 0xC0) >> 6)]; // 0F = 1111, C0=11110 + quad[3] = 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 + +/// Used to base64 decode data. Input is the encoded data as std::string, output is the plaintext data as std::string. +/// \param input Base64 encoded data to decode. +/// \returns Plaintext decoded data. +std::string Base64::decode(std::string const& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++){char_array_4[i] = chars.find(char_array_4[i]);} + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + for (i = 0; (i < 3); i++){ret += char_array_3[i];} + i = 0; + } + } + if (i) { + for (j = i; j <4; j++){char_array_4[j] = 0;} + for (j = 0; j <4; j++){char_array_4[j] = chars.find(char_array_4[j]);} + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + return ret; +} diff --git a/util/base64.h b/util/base64.h new file mode 100644 index 00000000..2358ae98 --- /dev/null +++ b/util/base64.h @@ -0,0 +1,11 @@ +#include + +/// Holds base64 decoding and encoding functions. +class Base64{ + private: + static const std::string chars; + static inline bool is_base64(unsigned char c); + public: + static std::string encode(std::string const input); + static std::string decode(std::string const& encoded_string); +}; diff --git a/util/config.cpp b/util/config.cpp new file mode 100644 index 00000000..6d56ab7f --- /dev/null +++ b/util/config.cpp @@ -0,0 +1,150 @@ +/// \file config.cpp +/// Contains generic functions for managing configuration. + +#include "config.h" +#include +#include +#include + +#ifdef __FreeBSD__ +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/// Creates a new configuration manager. +Util::Config::Config(){ + listen_port = 4242; + daemon_mode = true; + interface = "0.0.0.0"; + configfile = "/etc/ddvtech.conf"; + username = "root"; + ignore_daemon = false; + ignore_interface = false; + ignore_port = false; + ignore_user = false; +} + +/// Parses commandline arguments. +/// Calls exit if an unknown option is encountered, printing a help message. +/// confsection must be either already set or never be set at all when this function is called. +/// In other words: do not change confsection after calling this function. +void Util::Config::parseArgs(int argc, char ** argv){ + int opt = 0; + static const char *optString = "ndvp:i:u:c:h?"; + static const struct option longOpts[] = { + {"help",0,0,'h'}, + {"port",1,0,'p'}, + {"interface",1,0,'i'}, + {"username",1,0,'u'}, + {"no-daemon",0,0,'n'}, + {"daemon",0,0,'d'}, + {"configfile",1,0,'c'}, + {"version",0,0,'v'} + }; + while ((opt = getopt_long(argc, argv, optString, longOpts, 0)) != -1){ + switch (opt){ + 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 'v': + printf("%s\n", TOSTRING(VERSION)); + exit(1); + break; + case 'h': + case '?': + std::string doingdaemon = "true"; + if (!daemon_mode){doingdaemon = "false";} + if (confsection == ""){ + printf("Options: -h[elp], -?, -v[ersion], -n[odaemon], -d[aemon], -p[ort] VAL, -i[nterface] VAL, -u[sername] VAL\n"); + printf("Defaults:\n interface: %s\n port: %i\n daemon mode: %s\n username: %s\n", interface.c_str(), listen_port, doingdaemon.c_str(), username.c_str()); + }else{ + printf("Options: -h[elp], -?, -v[ersion], -n[odaemon], -d[aemon], -p[ort] VAL, -i[nterface] VAL, -c[onfigfile] VAL, -u[sername] VAL\n"); + printf("Defaults:\n interface: %s\n port: %i\n daemon mode: %s\n configfile: %s\n username: %s\n", interface.c_str(), listen_port, doingdaemon.c_str(), configfile.c_str(), username.c_str()); + 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", confsection.c_str()); + } + printf("This is %s version %s\n", argv[0], TOSTRING(VERSION)); + exit(1); + break; + } + }//commandline options parser +} + +/// Parses the configuration file at configfile, if it exists. +/// Assumes confsection is set. +void Util::Config::parseFile(){ + 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 == confsection){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 +} + +/// Sets the current process' running user +void Util::setUser(std::string username){ + 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; + }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, "Changed user to %s\n", username.c_str()); + #endif + } + } + } +} + +/// Will turn the current process into a daemon. +/// Works by calling daemon(1,0): +/// Does not change directory to root. +/// Does redirect output to /dev/null +void Util::Daemonize(){ + #if DEBUG >= 3 + fprintf(stderr, "Going into background mode...\n"); + #endif + daemon(1, 0); +} diff --git a/util/config.h b/util/config.h new file mode 100644 index 00000000..5a0b0da9 --- /dev/null +++ b/util/config.h @@ -0,0 +1,37 @@ +/// \file config.h +/// Contains generic function headers for managing configuration. + +#include + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +/// Contains utility code, not directly related to streaming media +namespace Util{ + + /// Deals with parsing configuration from files or commandline options. + class Config{ + private: + bool ignore_daemon; + bool ignore_interface; + bool ignore_port; + bool ignore_user; + public: + std::string confsection; + std::string configfile; + bool daemon_mode; + std::string interface; + int listen_port; + std::string username; + Config(); + void parseArgs(int argc, char ** argv); + void parseFile(); + }; + + /// Will set the active user to the named username. + void setUser(std::string user); + + /// Will turn the current process into a daemon. + void Daemonize(); + +}; diff --git a/util/json.cpp b/util/json.cpp new file mode 100644 index 00000000..7542ed37 --- /dev/null +++ b/util/json.cpp @@ -0,0 +1,421 @@ +/// \file json.cpp Holds all JSON-related code. + +#include "json.h" +#include + +int JSON::Value::c2hex(int c){ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return 0; +} + + +std::string JSON::Value::read_string(int separator, std::istream & fromstream){ + std::string out; + bool escaped = false; + while (fromstream.good()){ + int c = fromstream.get(); + if (c == '\\'){ + escaped = true; + continue; + } + if (escaped){ + switch (c){ + case 'b': out += '\b'; break; + case 'f': out += '\f'; break; + case 'n': out += '\n'; break; + case 'r': out += '\r'; break; + case 't': out += '\t'; break; + case 'u':{ + int d1 = fromstream.get(); + int d2 = fromstream.get(); + int d3 = fromstream.get(); + int d4 = fromstream.get(); + c = c2hex(d4) + (c2hex(d3) << 4) + (c2hex(d2) << 8) + (c2hex(d1) << 16); + } + default: + out += (char)c; + break; + } + }else{ + if (c == separator){ + return out; + }else{ + out += (char)c; + } + } + } + return out; +} + +std::string JSON::Value::string_escape(std::string val){ + std::string out = "\""; + for (unsigned int i = 0; i < val.size(); ++i){ + switch (val[i]){ + case '"': out += "\\\""; break; + case '\\': out += "\\\\"; break; + case '\n': out += "\\n"; break; + case '\b': out += "\\b"; break; + case '\f': out += "\\f"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: out += val[i]; + } + } + out += "\""; + return out; +} + + +/// Sets this JSON::Value to null; +JSON::Value::Value(){ + null(); +} + +/// Sets this JSON::Value to read from this position in the std::istream +JSON::Value::Value(std::istream & fromstream){ + null(); + bool reading_object = false; + bool reading_obj_name = false; + bool reading_array = false; + while (fromstream.good()){ + int c = fromstream.peek(); + switch (c){ + case '{': + reading_object = true; + reading_obj_name = true; + c = fromstream.get(); + myType = OBJECT; + break; + case '[': + reading_array = true; + c = fromstream.get(); + myType = ARRAY; + append(JSON::Value(fromstream)); + break; + case '\'': + case '"': + c = fromstream.get(); + if (!reading_object || !reading_obj_name){ + myType = STRING; + strVal = read_string(c, fromstream); + return; + }else{ + std::string tmpstr = read_string(c, fromstream); + (*this)[tmpstr] = JSON::Value(fromstream); + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + c = fromstream.get(); + myType = INTEGER; + intVal *= 10; + intVal += c - '0'; + break; + case ',': + if (!reading_object && !reading_array) return; + c = fromstream.get(); + if (reading_object){ + reading_obj_name = true; + }else{ + append(JSON::Value(fromstream)); + } + break; + case '}': + if (reading_object){c = fromstream.get();} + return; + break; + case ']': + if (reading_array){c = fromstream.get();} + return; + break; + case 't': + case 'T': + myType = BOOL; + intVal = 1; + return; + break; + case 'f': + case 'F': + myType = BOOL; + intVal = 0; + return; + break; + case 'n': + case 'N': + myType = EMPTY; + return; + break; + default: + c = fromstream.get();//ignore this character + continue; + break; + } + } +} + +/// Sets this JSON::Value to the given string. +JSON::Value::Value(const std::string & val){ + myType = STRING; + strVal = val; +} + +/// Sets this JSON::Value to the given string. +JSON::Value::Value(const char * val){ + myType = STRING; + strVal = val; +} + +/// Sets this JSON::Value to the given integer. +JSON::Value::Value(long long int val){ + myType = INTEGER; + intVal = val; +} + +/// Compares a JSON::Value to another for equality. +bool JSON::Value::operator==(const JSON::Value & rhs) const{ + if (myType != rhs.myType) return false; + if (myType == INTEGER || myType == BOOL){return intVal == rhs.intVal;} + if (myType == STRING){return strVal == rhs.strVal;} + if (myType == EMPTY){return true;} + if (myType == OBJECT){ + if (objVal.size() != rhs.objVal.size()) return false; + for (std::map::const_iterator it = objVal.begin(); it != objVal.end(); ++it){ + if (!rhs.isMember(it->first)){return false;} + if (it->second != rhs.objVal.find(it->first)->second){return false;} + } + return true; + } + return true; +} + +/// Compares a JSON::Value to another for equality. +bool JSON::Value::operator!=(const JSON::Value & rhs) const{ + return !((*this) == rhs); +} + +/// Sets this JSON::Value to the given boolean. +JSON::Value & JSON::Value::operator=(const bool &rhs){ + null(); + myType = BOOL; + if (rhs) intVal = 1; + return *this; +} + +/// Sets this JSON::Value to the given string. +JSON::Value & JSON::Value::operator=(const std::string &rhs){ + null(); + myType = STRING; + strVal = rhs; + return *this; +} + +/// Sets this JSON::Value to the given string. +JSON::Value & JSON::Value::operator=(const char * rhs){ + return ((*this) = (std::string)rhs); +} + +/// Sets this JSON::Value to the given integer. +JSON::Value & JSON::Value::operator=(const long long int &rhs){ + null(); + myType = INTEGER; + intVal = rhs; + return *this; +} + +/// Sets this JSON::Value to the given integer. +JSON::Value & JSON::Value::operator=(const int &rhs){ + return ((*this) = (long long int)rhs); +} + +/// Sets this JSON::Value to the given integer. +JSON::Value & JSON::Value::operator=(const unsigned int &rhs){ + return ((*this) = (long long int)rhs); +} + +/// Automatic conversion to long long int - returns 0 if not an integer type. +JSON::Value::operator long long int(){ + return intVal; +} + + +/// Automatic conversion to std::string. +/// Returns the raw string value if available, otherwise calls toString(). +JSON::Value::operator std::string(){ + if (myType == STRING){ + return strVal; + }else{ + return toString(); + } +} + +/// Retrieves or sets the JSON::Value at this position in the object. +/// Converts destructively to object if not already an object. +JSON::Value & JSON::Value::operator[](const std::string i){ + if (myType != OBJECT){ + null(); + myType = OBJECT; + } + return objVal[i]; +} + +/// Retrieves or sets the JSON::Value at this position in the object. +/// Converts destructively to object if not already an object. +JSON::Value & JSON::Value::operator[](const char * i){ + if (myType != OBJECT){ + null(); + myType = OBJECT; + } + return objVal[i]; +} + +/// Retrieves or sets the JSON::Value at this position in the array. +/// Converts destructively to array if not already an array. +JSON::Value & JSON::Value::operator[](unsigned int i){ + if (myType != ARRAY){ + null(); + myType = ARRAY; + } + while (i >= arrVal.size()){ + append(JSON::Value()); + } + return arrVal[i]; +} + +/// Converts this JSON::Value to valid JSON notation and returns it. +/// Makes absolutely no attempts to pretty-print anything. :-) +std::string JSON::Value::toString(){ + switch (myType){ + case INTEGER: { + std::stringstream st; + st << intVal; + return st.str(); + break; + } + case STRING: { + return string_escape(strVal); + break; + } + case ARRAY: { + std::string tmp = "["; + for (ArrIter it = ArrBegin(); it != ArrEnd(); it++){ + tmp += it->toString(); + if (it + 1 != ArrEnd()){tmp += ",";} + } + tmp += "]"; + return tmp; + break; + } + case OBJECT: { + std::string tmp2 = "{"; + ObjIter it3 = ObjEnd(); + --it3; + for (ObjIter it2 = ObjBegin(); it2 != ObjEnd(); it2++){ + tmp2 += "\"" + it2->first + "\":"; + tmp2 += it2->second.toString(); + if (it2 != it3){tmp2 += ",";} + } + tmp2 += "}"; + return tmp2; + break; + } + case EMPTY: + default: + return "null"; + } + return "null";//should never get here... +} + +/// Appends the given value to the end of this JSON::Value array. +/// Turns this value into an array if it is not already one. +void JSON::Value::append(const JSON::Value & rhs){ + if (myType != ARRAY){ + null(); + myType = ARRAY; + } + arrVal.push_back(rhs); +} + +/// Prepends the given value to the beginning of this JSON::Value array. +/// Turns this value into an array if it is not already one. +void JSON::Value::prepend(const JSON::Value & rhs){ + if (myType != ARRAY){ + null(); + myType = ARRAY; + } + arrVal.push_front(rhs); +} + +/// For array and object JSON::Value objects, reduces them +/// so they contain at most size elements, throwing away +/// the first elements and keeping the last ones. +/// Does nothing for other JSON::Value types, nor does it +/// do anything if the size is already lower or equal to the +/// given size. +void JSON::Value::shrink(unsigned int size){ + if (myType == ARRAY){ + while (arrVal.size() > size){arrVal.pop_front();} + return; + } + if (myType == OBJECT){ + while (objVal.size() > size){objVal.erase(objVal.begin());} + return; + } +} + +/// For object JSON::Value objects, removes the member with +/// the given name, if it exists. Has no effect otherwise. +void JSON::Value::removeMember(const std::string & name){ + objVal.erase(name); +} + +/// For object JSON::Value objects, returns true if the +/// given name is a member. Returns false otherwise. +bool JSON::Value::isMember(const std::string & name) const{ + return objVal.count(name) > 0; +} + +/// Returns an iterator to the begin of the object map, if any. +JSON::ObjIter JSON::Value::ObjBegin(){ + return objVal.begin(); +} + +/// Returns an iterator to the end of the object map, if any. +JSON::ObjIter JSON::Value::ObjEnd(){ + return objVal.end(); +} + +/// Returns an iterator to the begin of the array, if any. +JSON::ArrIter JSON::Value::ArrBegin(){ + return arrVal.begin(); +} + +/// Returns an iterator to the end of the array, if any. +JSON::ArrIter JSON::Value::ArrEnd(){ + return arrVal.end(); +} + +/// Completely clears the contents of this value, +/// changing its type to NULL in the process. +void JSON::Value::null(){ + objVal.clear(); + arrVal.clear(); + strVal.clear(); + intVal = 0; + myType = EMPTY; +} + +/// Converts a std::string to a JSON::Value. +JSON::Value JSON::fromString(std::string json){ + std::istringstream is(json); + return JSON::Value(is); +} diff --git a/util/json.h b/util/json.h new file mode 100644 index 00000000..8de01e11 --- /dev/null +++ b/util/json.h @@ -0,0 +1,73 @@ +/// \file json.h Holds all JSON-related headers. + +#include +#include +#include +#include + +/// JSON-related classes and functions +namespace JSON{ + + /// Lists all types of JSON::Value. + enum ValueType{ EMPTY, BOOL, INTEGER, STRING, ARRAY, OBJECT }; + + class Value;//forward declaration for below typedef + + typedef std::map::iterator ObjIter; + typedef std::deque::iterator ArrIter; + + /// A JSON::Value is either a string or an integer, but may also be an object, array or null. + class Value{ + private: + ValueType myType; + long long int intVal; + std::string strVal; + std::deque arrVal; + std::map objVal; + std::string read_string(int separator, std::istream & fromstream); + std::string string_escape(std::string val); + int c2hex(int c); + public: + //constructors + Value(); + Value(std::istream & fromstream); + Value(const std::string & val); + Value(const char * val); + Value(long long int val); + Value(bool val); + //comparison operators + bool operator==(const Value &rhs) const; + bool operator!=(const Value &rhs) const; + //assignment operators + Value & operator=(const std::string &rhs); + Value & operator=(const char * rhs); + Value & operator=(const long long int &rhs); + Value & operator=(const int &rhs); + Value & operator=(const unsigned int &rhs); + Value & operator=(const bool &rhs); + //converts to basic types + operator long long int(); + operator std::string(); + operator bool(); + //array operator for maps and arrays + Value & operator[](const std::string i); + Value & operator[](const char * i); + Value & operator[](unsigned int i); + //handy functions and others + std::string toString(); + void append(const Value & rhs); + void prepend(const Value & rhs); + void shrink(unsigned int size); + void removeMember(const std::string & name); + bool isMember(const std::string & name) const; + ObjIter ObjBegin(); + ObjIter ObjEnd(); + ArrIter ArrBegin(); + ArrIter ArrEnd(); + unsigned int size(); + void null(); + }; + + Value fromString(std::string json); + +}; diff --git a/util/json/autolink.h b/util/json/autolink.h deleted file mode 100644 index 37c9258e..00000000 --- a/util/json/autolink.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef JSON_AUTOLINK_H_INCLUDED -# define JSON_AUTOLINK_H_INCLUDED - -# include "config.h" - -# ifdef JSON_IN_CPPTL -# include -# endif - -# if !defined(JSON_NO_AUTOLINK) && !defined(JSON_DLL_BUILD) && !defined(JSON_IN_CPPTL) -# define CPPTL_AUTOLINK_NAME "json" -# undef CPPTL_AUTOLINK_DLL -# ifdef JSON_DLL -# define CPPTL_AUTOLINK_DLL -# endif -# include "autolink.h" -# endif - -#endif // JSON_AUTOLINK_H_INCLUDED diff --git a/util/json/config.h b/util/json/config.h deleted file mode 100644 index 5d334cbc..00000000 --- a/util/json/config.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef JSON_CONFIG_H_INCLUDED -# define JSON_CONFIG_H_INCLUDED - -/// If defined, indicates that json library is embedded in CppTL library. -//# define JSON_IN_CPPTL 1 - -/// If defined, indicates that json may leverage CppTL library -//# define JSON_USE_CPPTL 1 -/// If defined, indicates that cpptl vector based map should be used instead of std::map -/// as Value container. -//# define JSON_USE_CPPTL_SMALLMAP 1 -/// If defined, indicates that Json specific container should be used -/// (hash table & simple deque container with customizable allocator). -/// THIS FEATURE IS STILL EXPERIMENTAL! -//# define JSON_VALUE_USE_INTERNAL_MAP 1 -/// Force usage of standard new/malloc based allocator instead of memory pool based allocator. -/// The memory pools allocator used optimization (initializing Value and ValueInternalLink -/// as if it was a POD) that may cause some validation tool to report errors. -/// Only has effects if JSON_VALUE_USE_INTERNAL_MAP is defined. -//# define JSON_USE_SIMPLE_INTERNAL_ALLOCATOR 1 - -/// If defined, indicates that Json use exception to report invalid type manipulation -/// instead of C assert macro. -# define JSON_USE_EXCEPTION 1 - -# ifdef JSON_IN_CPPTL -# include -# ifndef JSON_USE_CPPTL -# define JSON_USE_CPPTL 1 -# endif -# endif - -# ifdef JSON_IN_CPPTL -# define JSON_API CPPTL_API -# elif defined(JSON_DLL_BUILD) -# define JSON_API __declspec(dllexport) -# elif defined(JSON_DLL) -# define JSON_API __declspec(dllimport) -# else -# define JSON_API -# endif - -#endif // JSON_CONFIG_H_INCLUDED diff --git a/util/json/features.h b/util/json/features.h deleted file mode 100644 index 5a9adec1..00000000 --- a/util/json/features.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef CPPTL_JSON_FEATURES_H_INCLUDED -# define CPPTL_JSON_FEATURES_H_INCLUDED - -# include "forwards.h" - -namespace Json { - - /** \brief Configuration passed to reader and writer. - * This configuration object can be used to force the Reader or Writer - * to behave in a standard conforming way. - */ - class JSON_API Features - { - public: - /** \brief A configuration that allows all features and assumes all strings are UTF-8. - * - C & C++ comments are allowed - * - Root object can be any JSON value - * - Assumes Value strings are encoded in UTF-8 - */ - static Features all(); - - /** \brief A configuration that is strictly compatible with the JSON specification. - * - Comments are forbidden. - * - Root object must be either an array or an object value. - * - Assumes Value strings are encoded in UTF-8 - */ - static Features strictMode(); - - /** \brief Initialize the configuration like JsonConfig::allFeatures; - */ - Features(); - - /// \c true if comments are allowed. Default: \c true. - bool allowComments_; - - /// \c true if root must be either an array or an object value. Default: \c false. - bool strictRoot_; - }; - -} // namespace Json - -#endif // CPPTL_JSON_FEATURES_H_INCLUDED diff --git a/util/json/forwards.h b/util/json/forwards.h deleted file mode 100644 index d0ce8300..00000000 --- a/util/json/forwards.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef JSON_FORWARDS_H_INCLUDED -# define JSON_FORWARDS_H_INCLUDED - -# include "config.h" - -namespace Json { - - // writer.h - class FastWriter; - class StyledWriter; - - // reader.h - class Reader; - - // features.h - class Features; - - // value.h - typedef int Int; - typedef unsigned int UInt; - class StaticString; - class Path; - class PathArgument; - class Value; - class ValueIteratorBase; - class ValueIterator; - class ValueConstIterator; -#ifdef JSON_VALUE_USE_INTERNAL_MAP - class ValueAllocator; - class ValueMapAllocator; - class ValueInternalLink; - class ValueInternalArray; - class ValueInternalMap; -#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP - -} // namespace Json - - -#endif // JSON_FORWARDS_H_INCLUDED diff --git a/util/json/json.h b/util/json/json.h deleted file mode 100644 index c71ed65a..00000000 --- a/util/json/json.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef JSON_JSON_H_INCLUDED -# define JSON_JSON_H_INCLUDED - -# include "autolink.h" -# include "value.h" -# include "reader.h" -# include "writer.h" -# include "features.h" - -#endif // JSON_JSON_H_INCLUDED diff --git a/util/json/json_batchallocator.h b/util/json/json_batchallocator.h deleted file mode 100644 index 87ea5ed8..00000000 --- a/util/json/json_batchallocator.h +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef JSONCPP_BATCHALLOCATOR_H_INCLUDED -# define JSONCPP_BATCHALLOCATOR_H_INCLUDED - -# include -# include - -# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - -namespace Json { - -/* Fast memory allocator. - * - * This memory allocator allocates memory for a batch of object (specified by - * the page size, the number of object in each page). - * - * It does not allow the destruction of a single object. All the allocated objects - * can be destroyed at once. The memory can be either released or reused for future - * allocation. - * - * The in-place new operator must be used to construct the object using the pointer - * returned by allocate. - */ -template -class BatchAllocator -{ -public: - typedef AllocatedType Type; - - BatchAllocator( unsigned int objectsPerPage = 255 ) - : freeHead_( 0 ) - , objectsPerPage_( objectsPerPage ) - { -// printf( "Size: %d => %s\n", sizeof(AllocatedType), typeid(AllocatedType).name() ); - assert( sizeof(AllocatedType) * objectPerAllocation >= sizeof(AllocatedType *) ); // We must be able to store a slist in the object free space. - assert( objectsPerPage >= 16 ); - batches_ = allocateBatch( 0 ); // allocated a dummy page - currentBatch_ = batches_; - } - - ~BatchAllocator() - { - for ( BatchInfo *batch = batches_; batch; ) - { - BatchInfo *nextBatch = batch->next_; - free( batch ); - batch = nextBatch; - } - } - - /// allocate space for an array of objectPerAllocation object. - /// @warning it is the responsability of the caller to call objects constructors. - AllocatedType *allocate() - { - if ( freeHead_ ) // returns node from free list. - { - AllocatedType *object = freeHead_; - freeHead_ = *(AllocatedType **)object; - return object; - } - if ( currentBatch_->used_ == currentBatch_->end_ ) - { - currentBatch_ = currentBatch_->next_; - while ( currentBatch_ && currentBatch_->used_ == currentBatch_->end_ ) - currentBatch_ = currentBatch_->next_; - - if ( !currentBatch_ ) // no free batch found, allocate a new one - { - currentBatch_ = allocateBatch( objectsPerPage_ ); - currentBatch_->next_ = batches_; // insert at the head of the list - batches_ = currentBatch_; - } - } - AllocatedType *allocated = currentBatch_->used_; - currentBatch_->used_ += objectPerAllocation; - return allocated; - } - - /// Release the object. - /// @warning it is the responsability of the caller to actually destruct the object. - void release( AllocatedType *object ) - { - assert( object != 0 ); - *(AllocatedType **)object = freeHead_; - freeHead_ = object; - } - -private: - struct BatchInfo - { - BatchInfo *next_; - AllocatedType *used_; - AllocatedType *end_; - AllocatedType buffer_[objectPerAllocation]; - }; - - // disabled copy constructor and assignement operator. - BatchAllocator( const BatchAllocator & ); - void operator =( const BatchAllocator &); - - static BatchInfo *allocateBatch( unsigned int objectsPerPage ) - { - const unsigned int mallocSize = sizeof(BatchInfo) - sizeof(AllocatedType)* objectPerAllocation - + sizeof(AllocatedType) * objectPerAllocation * objectsPerPage; - BatchInfo *batch = static_cast( malloc( mallocSize ) ); - batch->next_ = 0; - batch->used_ = batch->buffer_; - batch->end_ = batch->buffer_ + objectsPerPage; - return batch; - } - - BatchInfo *batches_; - BatchInfo *currentBatch_; - /// Head of a single linked list within the allocated space of freeed object - AllocatedType *freeHead_; - unsigned int objectsPerPage_; -}; - - -} // namespace Json - -# endif // ifndef JSONCPP_DOC_INCLUDE_IMPLEMENTATION - -#endif // JSONCPP_BATCHALLOCATOR_H_INCLUDED - diff --git a/util/json/json_internalarray.inl b/util/json/json_internalarray.inl deleted file mode 100644 index 9b985d25..00000000 --- a/util/json/json_internalarray.inl +++ /dev/null @@ -1,448 +0,0 @@ -// included by json_value.cpp -// everything is within Json namespace - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueInternalArray -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueArrayAllocator::~ValueArrayAllocator() -{ -} - -// ////////////////////////////////////////////////////////////////// -// class DefaultValueArrayAllocator -// ////////////////////////////////////////////////////////////////// -#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -class DefaultValueArrayAllocator : public ValueArrayAllocator -{ -public: // overridden from ValueArrayAllocator - virtual ~DefaultValueArrayAllocator() - { - } - - virtual ValueInternalArray *newArray() - { - return new ValueInternalArray(); - } - - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) - { - return new ValueInternalArray( other ); - } - - virtual void destructArray( ValueInternalArray *array ) - { - delete array; - } - - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) - { - ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; - if ( minNewIndexCount > newIndexCount ) - newIndexCount = minNewIndexCount; - void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); - if ( !newIndexes ) - throw std::bad_alloc(); - indexCount = newIndexCount; - indexes = static_cast( newIndexes ); - } - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) - { - if ( indexes ) - free( indexes ); - } - - virtual Value *allocateArrayPage() - { - return static_cast( malloc( sizeof(Value) * ValueInternalArray::itemsPerPage ) ); - } - - virtual void releaseArrayPage( Value *value ) - { - if ( value ) - free( value ); - } -}; - -#else // #ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -/// @todo make this thread-safe (lock when accessign batch allocator) -class DefaultValueArrayAllocator : public ValueArrayAllocator -{ -public: // overridden from ValueArrayAllocator - virtual ~DefaultValueArrayAllocator() - { - } - - virtual ValueInternalArray *newArray() - { - ValueInternalArray *array = arraysAllocator_.allocate(); - new (array) ValueInternalArray(); // placement new - return array; - } - - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) - { - ValueInternalArray *array = arraysAllocator_.allocate(); - new (array) ValueInternalArray( other ); // placement new - return array; - } - - virtual void destructArray( ValueInternalArray *array ) - { - if ( array ) - { - array->~ValueInternalArray(); - arraysAllocator_.release( array ); - } - } - - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) - { - ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; - if ( minNewIndexCount > newIndexCount ) - newIndexCount = minNewIndexCount; - void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); - if ( !newIndexes ) - throw std::bad_alloc(); - indexCount = newIndexCount; - indexes = static_cast( newIndexes ); - } - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) - { - if ( indexes ) - free( indexes ); - } - - virtual Value *allocateArrayPage() - { - return static_cast( pagesAllocator_.allocate() ); - } - - virtual void releaseArrayPage( Value *value ) - { - if ( value ) - pagesAllocator_.release( value ); - } -private: - BatchAllocator arraysAllocator_; - BatchAllocator pagesAllocator_; -}; -#endif // #ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR - -static ValueArrayAllocator *&arrayAllocator() -{ - static DefaultValueArrayAllocator defaultAllocator; - static ValueArrayAllocator *arrayAllocator = &defaultAllocator; - return arrayAllocator; -} - -static struct DummyArrayAllocatorInitializer { - DummyArrayAllocatorInitializer() - { - arrayAllocator(); // ensure arrayAllocator() statics are initialized before main(). - } -} dummyArrayAllocatorInitializer; - -// ////////////////////////////////////////////////////////////////// -// class ValueInternalArray -// ////////////////////////////////////////////////////////////////// -bool -ValueInternalArray::equals( const IteratorState &x, - const IteratorState &other ) -{ - return x.array_ == other.array_ - && x.currentItemIndex_ == other.currentItemIndex_ - && x.currentPageIndex_ == other.currentPageIndex_; -} - - -void -ValueInternalArray::increment( IteratorState &it ) -{ - JSON_ASSERT_MESSAGE( it.array_ && - (it.currentPageIndex_ - it.array_->pages_)*itemsPerPage + it.currentItemIndex_ - != it.array_->size_, - "ValueInternalArray::increment(): moving iterator beyond end" ); - ++(it.currentItemIndex_); - if ( it.currentItemIndex_ == itemsPerPage ) - { - it.currentItemIndex_ = 0; - ++(it.currentPageIndex_); - } -} - - -void -ValueInternalArray::decrement( IteratorState &it ) -{ - JSON_ASSERT_MESSAGE( it.array_ && it.currentPageIndex_ == it.array_->pages_ - && it.currentItemIndex_ == 0, - "ValueInternalArray::decrement(): moving iterator beyond end" ); - if ( it.currentItemIndex_ == 0 ) - { - it.currentItemIndex_ = itemsPerPage-1; - --(it.currentPageIndex_); - } - else - { - --(it.currentItemIndex_); - } -} - - -Value & -ValueInternalArray::unsafeDereference( const IteratorState &it ) -{ - return (*(it.currentPageIndex_))[it.currentItemIndex_]; -} - - -Value & -ValueInternalArray::dereference( const IteratorState &it ) -{ - JSON_ASSERT_MESSAGE( it.array_ && - (it.currentPageIndex_ - it.array_->pages_)*itemsPerPage + it.currentItemIndex_ - < it.array_->size_, - "ValueInternalArray::dereference(): dereferencing invalid iterator" ); - return unsafeDereference( it ); -} - -void -ValueInternalArray::makeBeginIterator( IteratorState &it ) const -{ - it.array_ = const_cast( this ); - it.currentItemIndex_ = 0; - it.currentPageIndex_ = pages_; -} - - -void -ValueInternalArray::makeIterator( IteratorState &it, ArrayIndex index ) const -{ - it.array_ = const_cast( this ); - it.currentItemIndex_ = index % itemsPerPage; - it.currentPageIndex_ = pages_ + index / itemsPerPage; -} - - -void -ValueInternalArray::makeEndIterator( IteratorState &it ) const -{ - makeIterator( it, size_ ); -} - - -ValueInternalArray::ValueInternalArray() - : pages_( 0 ) - , size_( 0 ) - , pageCount_( 0 ) -{ -} - - -ValueInternalArray::ValueInternalArray( const ValueInternalArray &other ) - : pages_( 0 ) - , pageCount_( 0 ) - , size_( other.size_ ) -{ - PageIndex minNewPages = other.size_ / itemsPerPage; - arrayAllocator()->reallocateArrayPageIndex( pages_, pageCount_, minNewPages ); - JSON_ASSERT_MESSAGE( pageCount_ >= minNewPages, - "ValueInternalArray::reserve(): bad reallocation" ); - IteratorState itOther; - other.makeBeginIterator( itOther ); - Value *value; - for ( ArrayIndex index = 0; index < size_; ++index, increment(itOther) ) - { - if ( index % itemsPerPage == 0 ) - { - PageIndex pageIndex = index / itemsPerPage; - value = arrayAllocator()->allocateArrayPage(); - pages_[pageIndex] = value; - } - new (value) Value( dereference( itOther ) ); - } -} - - -ValueInternalArray & -ValueInternalArray::operator =( const ValueInternalArray &other ) -{ - ValueInternalArray temp( other ); - swap( temp ); - return *this; -} - - -ValueInternalArray::~ValueInternalArray() -{ - // destroy all constructed items - IteratorState it; - IteratorState itEnd; - makeBeginIterator( it); - makeEndIterator( itEnd ); - for ( ; !equals(it,itEnd); increment(it) ) - { - Value *value = &dereference(it); - value->~Value(); - } - // release all pages - PageIndex lastPageIndex = size_ / itemsPerPage; - for ( PageIndex pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex ) - arrayAllocator()->releaseArrayPage( pages_[pageIndex] ); - // release pages index - arrayAllocator()->releaseArrayPageIndex( pages_, pageCount_ ); -} - - -void -ValueInternalArray::swap( ValueInternalArray &other ) -{ - Value **tempPages = pages_; - pages_ = other.pages_; - other.pages_ = tempPages; - ArrayIndex tempSize = size_; - size_ = other.size_; - other.size_ = tempSize; - PageIndex tempPageCount = pageCount_; - pageCount_ = other.pageCount_; - other.pageCount_ = tempPageCount; -} - -void -ValueInternalArray::clear() -{ - ValueInternalArray dummy; - swap( dummy ); -} - - -void -ValueInternalArray::resize( ArrayIndex newSize ) -{ - if ( newSize == 0 ) - clear(); - else if ( newSize < size_ ) - { - IteratorState it; - IteratorState itEnd; - makeIterator( it, newSize ); - makeIterator( itEnd, size_ ); - for ( ; !equals(it,itEnd); increment(it) ) - { - Value *value = &dereference(it); - value->~Value(); - } - PageIndex pageIndex = (newSize + itemsPerPage - 1) / itemsPerPage; - PageIndex lastPageIndex = size_ / itemsPerPage; - for ( ; pageIndex < lastPageIndex; ++pageIndex ) - arrayAllocator()->releaseArrayPage( pages_[pageIndex] ); - size_ = newSize; - } - else if ( newSize > size_ ) - resolveReference( newSize ); -} - - -void -ValueInternalArray::makeIndexValid( ArrayIndex index ) -{ - // Need to enlarge page index ? - if ( index >= pageCount_ * itemsPerPage ) - { - PageIndex minNewPages = (index + 1) / itemsPerPage; - arrayAllocator()->reallocateArrayPageIndex( pages_, pageCount_, minNewPages ); - JSON_ASSERT_MESSAGE( pageCount_ >= minNewPages, "ValueInternalArray::reserve(): bad reallocation" ); - } - - // Need to allocate new pages ? - ArrayIndex nextPageIndex = - (size_ % itemsPerPage) != 0 ? size_ - (size_%itemsPerPage) + itemsPerPage - : size_; - if ( nextPageIndex <= index ) - { - PageIndex pageIndex = nextPageIndex / itemsPerPage; - PageIndex pageToAllocate = (index - nextPageIndex) / itemsPerPage + 1; - for ( ; pageToAllocate-- > 0; ++pageIndex ) - pages_[pageIndex] = arrayAllocator()->allocateArrayPage(); - } - - // Initialize all new entries - IteratorState it; - IteratorState itEnd; - makeIterator( it, size_ ); - size_ = index + 1; - makeIterator( itEnd, size_ ); - for ( ; !equals(it,itEnd); increment(it) ) - { - Value *value = &dereference(it); - new (value) Value(); // Construct a default value using placement new - } -} - -Value & -ValueInternalArray::resolveReference( ArrayIndex index ) -{ - if ( index >= size_ ) - makeIndexValid( index ); - return pages_[index/itemsPerPage][index%itemsPerPage]; -} - -Value * -ValueInternalArray::find( ArrayIndex index ) const -{ - if ( index >= size_ ) - return 0; - return &(pages_[index/itemsPerPage][index%itemsPerPage]); -} - -ValueInternalArray::ArrayIndex -ValueInternalArray::size() const -{ - return size_; -} - -int -ValueInternalArray::distance( const IteratorState &x, const IteratorState &y ) -{ - return indexOf(y) - indexOf(x); -} - - -ValueInternalArray::ArrayIndex -ValueInternalArray::indexOf( const IteratorState &iterator ) -{ - if ( !iterator.array_ ) - return ArrayIndex(-1); - return ArrayIndex( - (iterator.currentPageIndex_ - iterator.array_->pages_) * itemsPerPage - + iterator.currentItemIndex_ ); -} - - -int -ValueInternalArray::compare( const ValueInternalArray &other ) const -{ - int sizeDiff( size_ - other.size_ ); - if ( sizeDiff != 0 ) - return sizeDiff; - - for ( ArrayIndex index =0; index < size_; ++index ) - { - int diff = pages_[index/itemsPerPage][index%itemsPerPage].compare( - other.pages_[index/itemsPerPage][index%itemsPerPage] ); - if ( diff != 0 ) - return diff; - } - return 0; -} diff --git a/util/json/json_internalmap.inl b/util/json/json_internalmap.inl deleted file mode 100644 index 19771488..00000000 --- a/util/json/json_internalmap.inl +++ /dev/null @@ -1,607 +0,0 @@ -// included by json_value.cpp -// everything is within Json namespace - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueInternalMap -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -/** \internal MUST be safely initialized using memset( this, 0, sizeof(ValueInternalLink) ); - * This optimization is used by the fast allocator. - */ -ValueInternalLink::ValueInternalLink() - : previous_( 0 ) - , next_( 0 ) -{ -} - -ValueInternalLink::~ValueInternalLink() -{ - for ( int index =0; index < itemPerLink; ++index ) - { - if ( !items_[index].isItemAvailable() ) - { - if ( !items_[index].isMemberNameStatic() ) - free( keys_[index] ); - } - else - break; - } -} - - - -ValueMapAllocator::~ValueMapAllocator() -{ -} - -#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -class DefaultValueMapAllocator : public ValueMapAllocator -{ -public: // overridden from ValueMapAllocator - virtual ValueInternalMap *newMap() - { - return new ValueInternalMap(); - } - - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) - { - return new ValueInternalMap( other ); - } - - virtual void destructMap( ValueInternalMap *map ) - { - delete map; - } - - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) - { - return new ValueInternalLink[size]; - } - - virtual void releaseMapBuckets( ValueInternalLink *links ) - { - delete [] links; - } - - virtual ValueInternalLink *allocateMapLink() - { - return new ValueInternalLink(); - } - - virtual void releaseMapLink( ValueInternalLink *link ) - { - delete link; - } -}; -#else -/// @todo make this thread-safe (lock when accessign batch allocator) -class DefaultValueMapAllocator : public ValueMapAllocator -{ -public: // overridden from ValueMapAllocator - virtual ValueInternalMap *newMap() - { - ValueInternalMap *map = mapsAllocator_.allocate(); - new (map) ValueInternalMap(); // placement new - return map; - } - - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) - { - ValueInternalMap *map = mapsAllocator_.allocate(); - new (map) ValueInternalMap( other ); // placement new - return map; - } - - virtual void destructMap( ValueInternalMap *map ) - { - if ( map ) - { - map->~ValueInternalMap(); - mapsAllocator_.release( map ); - } - } - - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) - { - return new ValueInternalLink[size]; - } - - virtual void releaseMapBuckets( ValueInternalLink *links ) - { - delete [] links; - } - - virtual ValueInternalLink *allocateMapLink() - { - ValueInternalLink *link = linksAllocator_.allocate(); - memset( link, 0, sizeof(ValueInternalLink) ); - return link; - } - - virtual void releaseMapLink( ValueInternalLink *link ) - { - link->~ValueInternalLink(); - linksAllocator_.release( link ); - } -private: - BatchAllocator mapsAllocator_; - BatchAllocator linksAllocator_; -}; -#endif - -static ValueMapAllocator *&mapAllocator() -{ - static DefaultValueMapAllocator defaultAllocator; - static ValueMapAllocator *mapAllocator = &defaultAllocator; - return mapAllocator; -} - -static struct DummyMapAllocatorInitializer { - DummyMapAllocatorInitializer() - { - mapAllocator(); // ensure mapAllocator() statics are initialized before main(). - } -} dummyMapAllocatorInitializer; - - - -// h(K) = value * K >> w ; with w = 32 & K prime w.r.t. 2^32. - -/* -use linked list hash map. -buckets array is a container. -linked list element contains 6 key/values. (memory = (16+4) * 6 + 4 = 124) -value have extra state: valid, available, deleted -*/ - - -ValueInternalMap::ValueInternalMap() - : buckets_( 0 ) - , tailLink_( 0 ) - , bucketsSize_( 0 ) - , itemCount_( 0 ) -{ -} - - -ValueInternalMap::ValueInternalMap( const ValueInternalMap &other ) - : buckets_( 0 ) - , tailLink_( 0 ) - , bucketsSize_( 0 ) - , itemCount_( 0 ) -{ - reserve( other.itemCount_ ); - IteratorState it; - IteratorState itEnd; - other.makeBeginIterator( it ); - other.makeEndIterator( itEnd ); - for ( ; !equals(it,itEnd); increment(it) ) - { - bool isStatic; - const char *memberName = key( it, isStatic ); - const Value &aValue = value( it ); - resolveReference(memberName, isStatic) = aValue; - } -} - - -ValueInternalMap & -ValueInternalMap::operator =( const ValueInternalMap &other ) -{ - ValueInternalMap dummy( other ); - swap( dummy ); - return *this; -} - - -ValueInternalMap::~ValueInternalMap() -{ - if ( buckets_ ) - { - for ( BucketIndex bucketIndex =0; bucketIndex < bucketsSize_; ++bucketIndex ) - { - ValueInternalLink *link = buckets_[bucketIndex].next_; - while ( link ) - { - ValueInternalLink *linkToRelease = link; - link = link->next_; - mapAllocator()->releaseMapLink( linkToRelease ); - } - } - mapAllocator()->releaseMapBuckets( buckets_ ); - } -} - - -void -ValueInternalMap::swap( ValueInternalMap &other ) -{ - ValueInternalLink *tempBuckets = buckets_; - buckets_ = other.buckets_; - other.buckets_ = tempBuckets; - ValueInternalLink *tempTailLink = tailLink_; - tailLink_ = other.tailLink_; - other.tailLink_ = tempTailLink; - BucketIndex tempBucketsSize = bucketsSize_; - bucketsSize_ = other.bucketsSize_; - other.bucketsSize_ = tempBucketsSize; - BucketIndex tempItemCount = itemCount_; - itemCount_ = other.itemCount_; - other.itemCount_ = tempItemCount; -} - - -void -ValueInternalMap::clear() -{ - ValueInternalMap dummy; - swap( dummy ); -} - - -ValueInternalMap::BucketIndex -ValueInternalMap::size() const -{ - return itemCount_; -} - -bool -ValueInternalMap::reserveDelta( BucketIndex growth ) -{ - return reserve( itemCount_ + growth ); -} - -bool -ValueInternalMap::reserve( BucketIndex newItemCount ) -{ - if ( !buckets_ && newItemCount > 0 ) - { - buckets_ = mapAllocator()->allocateMapBuckets( 1 ); - bucketsSize_ = 1; - tailLink_ = &buckets_[0]; - } -// BucketIndex idealBucketCount = (newItemCount + ValueInternalLink::itemPerLink) / ValueInternalLink::itemPerLink; - return true; -} - - -const Value * -ValueInternalMap::find( const char *key ) const -{ - if ( !bucketsSize_ ) - return 0; - HashKey hashedKey = hash( key ); - BucketIndex bucketIndex = hashedKey % bucketsSize_; - for ( const ValueInternalLink *current = &buckets_[bucketIndex]; - current != 0; - current = current->next_ ) - { - for ( BucketIndex index=0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( current->items_[index].isItemAvailable() ) - return 0; - if ( strcmp( key, current->keys_[index] ) == 0 ) - return ¤t->items_[index]; - } - } - return 0; -} - - -Value * -ValueInternalMap::find( const char *key ) -{ - const ValueInternalMap *constThis = this; - return const_cast( constThis->find( key ) ); -} - - -Value & -ValueInternalMap::resolveReference( const char *key, - bool isStatic ) -{ - HashKey hashedKey = hash( key ); - if ( bucketsSize_ ) - { - BucketIndex bucketIndex = hashedKey % bucketsSize_; - ValueInternalLink **previous = 0; - BucketIndex index; - for ( ValueInternalLink *current = &buckets_[bucketIndex]; - current != 0; - previous = ¤t->next_, current = current->next_ ) - { - for ( index=0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( current->items_[index].isItemAvailable() ) - return setNewItem( key, isStatic, current, index ); - if ( strcmp( key, current->keys_[index] ) == 0 ) - return current->items_[index]; - } - } - } - - reserveDelta( 1 ); - return unsafeAdd( key, isStatic, hashedKey ); -} - - -void -ValueInternalMap::remove( const char *key ) -{ - HashKey hashedKey = hash( key ); - if ( !bucketsSize_ ) - return; - BucketIndex bucketIndex = hashedKey % bucketsSize_; - for ( ValueInternalLink *link = &buckets_[bucketIndex]; - link != 0; - link = link->next_ ) - { - BucketIndex index; - for ( index =0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( link->items_[index].isItemAvailable() ) - return; - if ( strcmp( key, link->keys_[index] ) == 0 ) - { - doActualRemove( link, index, bucketIndex ); - return; - } - } - } -} - -void -ValueInternalMap::doActualRemove( ValueInternalLink *link, - BucketIndex index, - BucketIndex bucketIndex ) -{ - // find last item of the bucket and swap it with the 'removed' one. - // set removed items flags to 'available'. - // if last page only contains 'available' items, then desallocate it (it's empty) - ValueInternalLink *&lastLink = getLastLinkInBucket( index ); - BucketIndex lastItemIndex = 1; // a link can never be empty, so start at 1 - for ( ; - lastItemIndex < ValueInternalLink::itemPerLink; - ++lastItemIndex ) // may be optimized with dicotomic search - { - if ( lastLink->items_[lastItemIndex].isItemAvailable() ) - break; - } - - BucketIndex lastUsedIndex = lastItemIndex - 1; - Value *valueToDelete = &link->items_[index]; - Value *valueToPreserve = &lastLink->items_[lastUsedIndex]; - if ( valueToDelete != valueToPreserve ) - valueToDelete->swap( *valueToPreserve ); - if ( lastUsedIndex == 0 ) // page is now empty - { // remove it from bucket linked list and delete it. - ValueInternalLink *linkPreviousToLast = lastLink->previous_; - if ( linkPreviousToLast != 0 ) // can not deleted bucket link. - { - mapAllocator()->releaseMapLink( lastLink ); - linkPreviousToLast->next_ = 0; - lastLink = linkPreviousToLast; - } - } - else - { - Value dummy; - valueToPreserve->swap( dummy ); // restore deleted to default Value. - valueToPreserve->setItemUsed( false ); - } - --itemCount_; -} - - -ValueInternalLink *& -ValueInternalMap::getLastLinkInBucket( BucketIndex bucketIndex ) -{ - if ( bucketIndex == bucketsSize_ - 1 ) - return tailLink_; - ValueInternalLink *&previous = buckets_[bucketIndex+1].previous_; - if ( !previous ) - previous = &buckets_[bucketIndex]; - return previous; -} - - -Value & -ValueInternalMap::setNewItem( const char *key, - bool isStatic, - ValueInternalLink *link, - BucketIndex index ) -{ - char *duplicatedKey = valueAllocator()->makeMemberName( key ); - ++itemCount_; - link->keys_[index] = duplicatedKey; - link->items_[index].setItemUsed(); - link->items_[index].setMemberNameIsStatic( isStatic ); - return link->items_[index]; // items already default constructed. -} - - -Value & -ValueInternalMap::unsafeAdd( const char *key, - bool isStatic, - HashKey hashedKey ) -{ - JSON_ASSERT_MESSAGE( bucketsSize_ > 0, "ValueInternalMap::unsafeAdd(): internal logic error." ); - BucketIndex bucketIndex = hashedKey % bucketsSize_; - ValueInternalLink *&previousLink = getLastLinkInBucket( bucketIndex ); - ValueInternalLink *link = previousLink; - BucketIndex index; - for ( index =0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( link->items_[index].isItemAvailable() ) - break; - } - if ( index == ValueInternalLink::itemPerLink ) // need to add a new page - { - ValueInternalLink *newLink = mapAllocator()->allocateMapLink(); - index = 0; - link->next_ = newLink; - previousLink = newLink; - link = newLink; - } - return setNewItem( key, isStatic, link, index ); -} - - -ValueInternalMap::HashKey -ValueInternalMap::hash( const char *key ) const -{ - HashKey hash = 0; - while ( *key ) - hash += *key++ * 37; - return hash; -} - - -int -ValueInternalMap::compare( const ValueInternalMap &other ) const -{ - int sizeDiff( itemCount_ - other.itemCount_ ); - if ( sizeDiff != 0 ) - return sizeDiff; - // Strict order guaranty is required. Compare all keys FIRST, then compare values. - IteratorState it; - IteratorState itEnd; - makeBeginIterator( it ); - makeEndIterator( itEnd ); - for ( ; !equals(it,itEnd); increment(it) ) - { - if ( !other.find( key( it ) ) ) - return 1; - } - - // All keys are equals, let's compare values - makeBeginIterator( it ); - for ( ; !equals(it,itEnd); increment(it) ) - { - const Value *otherValue = other.find( key( it ) ); - int valueDiff = value(it).compare( *otherValue ); - if ( valueDiff != 0 ) - return valueDiff; - } - return 0; -} - - -void -ValueInternalMap::makeBeginIterator( IteratorState &it ) const -{ - it.map_ = const_cast( this ); - it.bucketIndex_ = 0; - it.itemIndex_ = 0; - it.link_ = buckets_; -} - - -void -ValueInternalMap::makeEndIterator( IteratorState &it ) const -{ - it.map_ = const_cast( this ); - it.bucketIndex_ = bucketsSize_; - it.itemIndex_ = 0; - it.link_ = 0; -} - - -bool -ValueInternalMap::equals( const IteratorState &x, const IteratorState &other ) -{ - return x.map_ == other.map_ - && x.bucketIndex_ == other.bucketIndex_ - && x.link_ == other.link_ - && x.itemIndex_ == other.itemIndex_; -} - - -void -ValueInternalMap::incrementBucket( IteratorState &iterator ) -{ - ++iterator.bucketIndex_; - JSON_ASSERT_MESSAGE( iterator.bucketIndex_ <= iterator.map_->bucketsSize_, - "ValueInternalMap::increment(): attempting to iterate beyond end." ); - if ( iterator.bucketIndex_ == iterator.map_->bucketsSize_ ) - iterator.link_ = 0; - else - iterator.link_ = &(iterator.map_->buckets_[iterator.bucketIndex_]); - iterator.itemIndex_ = 0; -} - - -void -ValueInternalMap::increment( IteratorState &iterator ) -{ - JSON_ASSERT_MESSAGE( iterator.map_, "Attempting to iterator using invalid iterator." ); - ++iterator.itemIndex_; - if ( iterator.itemIndex_ == ValueInternalLink::itemPerLink ) - { - JSON_ASSERT_MESSAGE( iterator.link_ != 0, - "ValueInternalMap::increment(): attempting to iterate beyond end." ); - iterator.link_ = iterator.link_->next_; - if ( iterator.link_ == 0 ) - incrementBucket( iterator ); - } - else if ( iterator.link_->items_[iterator.itemIndex_].isItemAvailable() ) - { - incrementBucket( iterator ); - } -} - - -void -ValueInternalMap::decrement( IteratorState &iterator ) -{ - if ( iterator.itemIndex_ == 0 ) - { - JSON_ASSERT_MESSAGE( iterator.map_, "Attempting to iterate using invalid iterator." ); - if ( iterator.link_ == &iterator.map_->buckets_[iterator.bucketIndex_] ) - { - JSON_ASSERT_MESSAGE( iterator.bucketIndex_ > 0, "Attempting to iterate beyond beginning." ); - --(iterator.bucketIndex_); - } - iterator.link_ = iterator.link_->previous_; - iterator.itemIndex_ = ValueInternalLink::itemPerLink - 1; - } -} - - -const char * -ValueInternalMap::key( const IteratorState &iterator ) -{ - JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); - return iterator.link_->keys_[iterator.itemIndex_]; -} - -const char * -ValueInternalMap::key( const IteratorState &iterator, bool &isStatic ) -{ - JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); - isStatic = iterator.link_->items_[iterator.itemIndex_].isMemberNameStatic(); - return iterator.link_->keys_[iterator.itemIndex_]; -} - - -Value & -ValueInternalMap::value( const IteratorState &iterator ) -{ - JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); - return iterator.link_->items_[iterator.itemIndex_]; -} - - -int -ValueInternalMap::distance( const IteratorState &x, const IteratorState &y ) -{ - int offset = 0; - IteratorState it = x; - while ( !equals( it, y ) ) - increment( it ); - return offset; -} diff --git a/util/json/json_reader.cpp b/util/json/json_reader.cpp deleted file mode 100644 index 3623e71d..00000000 --- a/util/json/json_reader.cpp +++ /dev/null @@ -1,885 +0,0 @@ -#include "reader.h" -#include "value.h" -#include -#include -#include -#include -#include -#include - -#if _MSC_VER >= 1400 // VC++ 8.0 -#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. -#endif - -namespace Json { - -// Implementation of class Features -// //////////////////////////////// - -Features::Features() - : allowComments_( true ) - , strictRoot_( false ) -{ -} - - -Features -Features::all() -{ - return Features(); -} - - -Features -Features::strictMode() -{ - Features features; - features.allowComments_ = false; - features.strictRoot_ = true; - return features; -} - -// Implementation of class Reader -// //////////////////////////////// - - -static inline bool -in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4 ) -{ - return c == c1 || c == c2 || c == c3 || c == c4; -} - -static inline bool -in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4, Reader::Char c5 ) -{ - return c == c1 || c == c2 || c == c3 || c == c4 || c == c5; -} - - -static bool -containsNewLine( Reader::Location begin, - Reader::Location end ) -{ - for ( ;begin < end; ++begin ) - if ( *begin == '\n' || *begin == '\r' ) - return true; - return false; -} - -static std::string codePointToUTF8(unsigned int cp) -{ - std::string result; - - // based on description from http://en.wikipedia.org/wiki/UTF-8 - - if (cp <= 0x7f) - { - result.resize(1); - result[0] = static_cast(cp); - } - else if (cp <= 0x7FF) - { - result.resize(2); - result[1] = static_cast(0x80 | (0x3f & cp)); - result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); - } - else if (cp <= 0xFFFF) - { - result.resize(3); - result[2] = static_cast(0x80 | (0x3f & cp)); - result[1] = 0x80 | static_cast((0x3f & (cp >> 6))); - result[0] = 0xE0 | static_cast((0xf & (cp >> 12))); - } - else if (cp <= 0x10FFFF) - { - result.resize(4); - result[3] = static_cast(0x80 | (0x3f & cp)); - result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); - result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); - result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); - } - - return result; -} - - -// Class Reader -// ////////////////////////////////////////////////////////////////// - -Reader::Reader() - : features_( Features::all() ) -{ -} - - -Reader::Reader( const Features &features ) - : features_( features ) -{ -} - - -bool -Reader::parse( const std::string &document, - Value &root, - bool collectComments ) -{ - document_ = document; - const char *begin = document_.c_str(); - const char *end = begin + document_.length(); - return parse( begin, end, root, collectComments ); -} - - -bool -Reader::parse( std::istream& sin, - Value &root, - bool collectComments ) -{ - //std::istream_iterator begin(sin); - //std::istream_iterator end; - // Those would allow streamed input from a file, if parse() were a - // template function. - - // Since std::string is reference-counted, this at least does not - // create an extra copy. - std::string doc; - std::getline(sin, doc, (char)EOF); - return parse( doc, root, collectComments ); -} - -bool -Reader::parse( const char *beginDoc, const char *endDoc, - Value &root, - bool collectComments ) -{ - if ( !features_.allowComments_ ) - { - collectComments = false; - } - - begin_ = beginDoc; - end_ = endDoc; - collectComments_ = collectComments; - current_ = begin_; - lastValueEnd_ = 0; - lastValue_ = 0; - commentsBefore_ = ""; - errors_.clear(); - while ( !nodes_.empty() ) - nodes_.pop(); - nodes_.push( &root ); - - bool successful = readValue(); - Token token; - skipCommentTokens( token ); - if ( collectComments_ && !commentsBefore_.empty() ) - root.setComment( commentsBefore_, commentAfter ); - if ( features_.strictRoot_ ) - { - if ( !root.isArray() && !root.isObject() ) - { - // Set error location to start of doc, ideally should be first token found in doc - token.type_ = tokenError; - token.start_ = beginDoc; - token.end_ = endDoc; - addError( "A valid JSON document must be either an array or an object value.", - token ); - return false; - } - } - return successful; -} - - -bool -Reader::readValue() -{ - Token token; - skipCommentTokens( token ); - bool successful = true; - - if ( collectComments_ && !commentsBefore_.empty() ) - { - currentValue().setComment( commentsBefore_, commentBefore ); - commentsBefore_ = ""; - } - - - switch ( token.type_ ) - { - case tokenObjectBegin: - successful = readObject( token ); - break; - case tokenArrayBegin: - successful = readArray( token ); - break; - case tokenNumber: - successful = decodeNumber( token ); - break; - case tokenString: - successful = decodeString( token ); - break; - case tokenTrue: - currentValue() = true; - break; - case tokenFalse: - currentValue() = false; - break; - case tokenNull: - currentValue() = Value(); - break; - default: - return addError( "Syntax error: value, object or array expected.", token ); - } - - if ( collectComments_ ) - { - lastValueEnd_ = current_; - lastValue_ = ¤tValue(); - } - - return successful; -} - - -void -Reader::skipCommentTokens( Token &token ) -{ - if ( features_.allowComments_ ) - { - do - { - readToken( token ); - } - while ( token.type_ == tokenComment ); - } - else - { - readToken( token ); - } -} - - -bool -Reader::expectToken( TokenType type, Token &token, const char *message ) -{ - readToken( token ); - if ( token.type_ != type ) - return addError( message, token ); - return true; -} - - -bool -Reader::readToken( Token &token ) -{ - skipSpaces(); - token.start_ = current_; - Char c = getNextChar(); - bool ok = true; - switch ( c ) - { - case '{': - token.type_ = tokenObjectBegin; - break; - case '}': - token.type_ = tokenObjectEnd; - break; - case '[': - token.type_ = tokenArrayBegin; - break; - case ']': - token.type_ = tokenArrayEnd; - break; - case '"': - token.type_ = tokenString; - ok = readString(); - break; - case '/': - token.type_ = tokenComment; - ok = readComment(); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - token.type_ = tokenNumber; - readNumber(); - break; - case 't': - token.type_ = tokenTrue; - ok = match( "rue", 3 ); - break; - case 'f': - token.type_ = tokenFalse; - ok = match( "alse", 4 ); - break; - case 'n': - token.type_ = tokenNull; - ok = match( "ull", 3 ); - break; - case ',': - token.type_ = tokenArraySeparator; - break; - case ':': - token.type_ = tokenMemberSeparator; - break; - case 0: - token.type_ = tokenEndOfStream; - break; - default: - ok = false; - break; - } - if ( !ok ) - token.type_ = tokenError; - token.end_ = current_; - return true; -} - - -void -Reader::skipSpaces() -{ - while ( current_ != end_ ) - { - Char c = *current_; - if ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) - ++current_; - else - break; - } -} - - -bool -Reader::match( Location pattern, - int patternLength ) -{ - if ( end_ - current_ < patternLength ) - return false; - int index = patternLength; - while ( index-- ) - if ( current_[index] != pattern[index] ) - return false; - current_ += patternLength; - return true; -} - - -bool -Reader::readComment() -{ - Location commentBegin = current_ - 1; - Char c = getNextChar(); - bool successful = false; - if ( c == '*' ) - successful = readCStyleComment(); - else if ( c == '/' ) - successful = readCppStyleComment(); - if ( !successful ) - return false; - - if ( collectComments_ ) - { - CommentPlacement placement = commentBefore; - if ( lastValueEnd_ && !containsNewLine( lastValueEnd_, commentBegin ) ) - { - if ( c != '*' || !containsNewLine( commentBegin, current_ ) ) - placement = commentAfterOnSameLine; - } - - addComment( commentBegin, current_, placement ); - } - return true; -} - - -void -Reader::addComment( Location begin, - Location end, - CommentPlacement placement ) -{ - assert( collectComments_ ); - if ( placement == commentAfterOnSameLine ) - { - assert( lastValue_ != 0 ); - lastValue_->setComment( std::string( begin, end ), placement ); - } - else - { - if ( !commentsBefore_.empty() ) - commentsBefore_ += "\n"; - commentsBefore_ += std::string( begin, end ); - } -} - - -bool -Reader::readCStyleComment() -{ - while ( current_ != end_ ) - { - Char c = getNextChar(); - if ( c == '*' && *current_ == '/' ) - break; - } - return getNextChar() == '/'; -} - - -bool -Reader::readCppStyleComment() -{ - while ( current_ != end_ ) - { - Char c = getNextChar(); - if ( c == '\r' || c == '\n' ) - break; - } - return true; -} - - -void -Reader::readNumber() -{ - while ( current_ != end_ ) - { - if ( !(*current_ >= '0' && *current_ <= '9') && - !in( *current_, '.', 'e', 'E', '+', '-' ) ) - break; - ++current_; - } -} - -bool -Reader::readString() -{ - Char c = 0; - while ( current_ != end_ ) - { - c = getNextChar(); - if ( c == '\\' ) - getNextChar(); - else if ( c == '"' ) - break; - } - return c == '"'; -} - - -bool -Reader::readObject( Token &tokenStart ) -{ - Token tokenName; - std::string name; - currentValue() = Value( objectValue ); - while ( readToken( tokenName ) ) - { - bool initialTokenOk = true; - while ( tokenName.type_ == tokenComment && initialTokenOk ) - initialTokenOk = readToken( tokenName ); - if ( !initialTokenOk ) - break; - if ( tokenName.type_ == tokenObjectEnd && name.empty() ) // empty object - return true; - if ( tokenName.type_ != tokenString ) - break; - - name = ""; - if ( !decodeString( tokenName, name ) ) - return recoverFromError( tokenObjectEnd ); - - Token colon; - if ( !readToken( colon ) || colon.type_ != tokenMemberSeparator ) - { - return addErrorAndRecover( "Missing ':' after object member name", - colon, - tokenObjectEnd ); - } - Value &value = currentValue()[ name ]; - nodes_.push( &value ); - bool ok = readValue(); - nodes_.pop(); - if ( !ok ) // error already set - return recoverFromError( tokenObjectEnd ); - - Token comma; - if ( !readToken( comma ) - || ( comma.type_ != tokenObjectEnd && - comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment ) ) - { - return addErrorAndRecover( "Missing ',' or '}' in object declaration", - comma, - tokenObjectEnd ); - } - bool finalizeTokenOk = true; - while ( comma.type_ == tokenComment && - finalizeTokenOk ) - finalizeTokenOk = readToken( comma ); - if ( comma.type_ == tokenObjectEnd ) - return true; - } - return addErrorAndRecover( "Missing '}' or object member name", - tokenName, - tokenObjectEnd ); -} - - -bool -Reader::readArray( Token &tokenStart ) -{ - currentValue() = Value( arrayValue ); - skipSpaces(); - if ( *current_ == ']' ) // empty array - { - Token endArray; - readToken( endArray ); - return true; - } - int index = 0; - while ( true ) - { - Value &value = currentValue()[ index++ ]; - nodes_.push( &value ); - bool ok = readValue(); - nodes_.pop(); - if ( !ok ) // error already set - return recoverFromError( tokenArrayEnd ); - - Token token; - // Accept Comment after last item in the array. - ok = readToken( token ); - while ( token.type_ == tokenComment && ok ) - { - ok = readToken( token ); - } - bool badTokenType = ( token.type_ == tokenArraySeparator && - token.type_ == tokenArrayEnd ); - if ( !ok || badTokenType ) - { - return addErrorAndRecover( "Missing ',' or ']' in array declaration", - token, - tokenArrayEnd ); - } - if ( token.type_ == tokenArrayEnd ) - break; - } - return true; -} - - -bool -Reader::decodeNumber( Token &token ) -{ - bool isDouble = false; - for ( Location inspect = token.start_; inspect != token.end_; ++inspect ) - { - isDouble = isDouble - || in( *inspect, '.', 'e', 'E', '+' ) - || ( *inspect == '-' && inspect != token.start_ ); - } - if ( isDouble ) - return decodeDouble( token ); - Location current = token.start_; - bool isNegative = *current == '-'; - if ( isNegative ) - ++current; - Value::UInt threshold = (isNegative ? Value::UInt(-Value::minInt) - : Value::maxUInt) / 10; - Value::UInt value = 0; - while ( current < token.end_ ) - { - Char c = *current++; - if ( c < '0' || c > '9' ) - return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token ); - if ( value >= threshold ) - return decodeDouble( token ); - value = value * 10 + Value::UInt(c - '0'); - } - if ( isNegative ) - currentValue() = -Value::Int( value ); - else if ( value <= Value::UInt(Value::maxInt) ) - currentValue() = Value::Int( value ); - else - currentValue() = value; - return true; -} - - -bool -Reader::decodeDouble( Token &token ) -{ - double value = 0; - const int bufferSize = 32; - int count; - int length = int(token.end_ - token.start_); - if ( length <= bufferSize ) - { - Char buffer[bufferSize]; - memcpy( buffer, token.start_, length ); - buffer[length] = 0; - count = sscanf( buffer, "%lf", &value ); - } - else - { - std::string buffer( token.start_, token.end_ ); - count = sscanf( buffer.c_str(), "%lf", &value ); - } - - if ( count != 1 ) - return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token ); - currentValue() = value; - return true; -} - - -bool -Reader::decodeString( Token &token ) -{ - std::string decoded; - if ( !decodeString( token, decoded ) ) - return false; - currentValue() = decoded; - return true; -} - - -bool -Reader::decodeString( Token &token, std::string &decoded ) -{ - decoded.reserve( token.end_ - token.start_ - 2 ); - Location current = token.start_ + 1; // skip '"' - Location end = token.end_ - 1; // do not include '"' - while ( current != end ) - { - Char c = *current++; - if ( c == '"' ) - break; - else if ( c == '\\' ) - { - if ( current == end ) - return addError( "Empty escape sequence in string", token, current ); - Char escape = *current++; - switch ( escape ) - { - case '"': decoded += '"'; break; - case '/': decoded += '/'; break; - case '\\': decoded += '\\'; break; - case 'b': decoded += '\b'; break; - case 'f': decoded += '\f'; break; - case 'n': decoded += '\n'; break; - case 'r': decoded += '\r'; break; - case 't': decoded += '\t'; break; - case 'u': - { - unsigned int unicode; - if ( !decodeUnicodeCodePoint( token, current, end, unicode ) ) - return false; - decoded += codePointToUTF8(unicode); - } - break; - default: - return addError( "Bad escape sequence in string", token, current ); - } - } - else - { - decoded += c; - } - } - return true; -} - -bool -Reader::decodeUnicodeCodePoint( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ) -{ - - if ( !decodeUnicodeEscapeSequence( token, current, end, unicode ) ) - return false; - if (unicode >= 0xD800 && unicode <= 0xDBFF) - { - // surrogate pairs - if (end - current < 6) - return addError( "additional six characters expected to parse unicode surrogate pair.", token, current ); - unsigned int surrogatePair; - if (*(current++) == '\\' && *(current++)== 'u') - { - if (decodeUnicodeEscapeSequence( token, current, end, surrogatePair )) - { - unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); - } - else - return false; - } - else - return addError( "expecting another \\u token to begin the second half of a unicode surrogate pair", token, current ); - } - return true; -} - -bool -Reader::decodeUnicodeEscapeSequence( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ) -{ - if ( end - current < 4 ) - return addError( "Bad unicode escape sequence in string: four digits expected.", token, current ); - unicode = 0; - for ( int index =0; index < 4; ++index ) - { - Char c = *current++; - unicode *= 16; - if ( c >= '0' && c <= '9' ) - unicode += c - '0'; - else if ( c >= 'a' && c <= 'f' ) - unicode += c - 'a' + 10; - else if ( c >= 'A' && c <= 'F' ) - unicode += c - 'A' + 10; - else - return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current ); - } - return true; -} - - -bool -Reader::addError( const std::string &message, - Token &token, - Location extra ) -{ - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = extra; - errors_.push_back( info ); - return false; -} - - -bool -Reader::recoverFromError( TokenType skipUntilToken ) -{ - int errorCount = int(errors_.size()); - Token skip; - while ( true ) - { - if ( !readToken(skip) ) - errors_.resize( errorCount ); // discard errors caused by recovery - if ( skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream ) - break; - } - errors_.resize( errorCount ); - return false; -} - - -bool -Reader::addErrorAndRecover( const std::string &message, - Token &token, - TokenType skipUntilToken ) -{ - addError( message, token ); - return recoverFromError( skipUntilToken ); -} - - -Value & -Reader::currentValue() -{ - return *(nodes_.top()); -} - - -Reader::Char -Reader::getNextChar() -{ - if ( current_ == end_ ) - return 0; - return *current_++; -} - - -void -Reader::getLocationLineAndColumn( Location location, - int &line, - int &column ) const -{ - Location current = begin_; - Location lastLineStart = current; - line = 0; - while ( current < location && current != end_ ) - { - Char c = *current++; - if ( c == '\r' ) - { - if ( *current == '\n' ) - ++current; - lastLineStart = current; - ++line; - } - else if ( c == '\n' ) - { - lastLineStart = current; - ++line; - } - } - // column & line start at 1 - column = int(location - lastLineStart) + 1; - ++line; -} - - -std::string -Reader::getLocationLineAndColumn( Location location ) const -{ - int line, column; - getLocationLineAndColumn( location, line, column ); - char buffer[18+16+16+1]; - sprintf( buffer, "Line %d, Column %d", line, column ); - return buffer; -} - - -std::string -Reader::getFormatedErrorMessages() const -{ - std::string formattedMessage; - for ( Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError ) - { - const ErrorInfo &error = *itError; - formattedMessage += "* " + getLocationLineAndColumn( error.token_.start_ ) + "\n"; - formattedMessage += " " + error.message_ + "\n"; - if ( error.extra_ ) - formattedMessage += "See " + getLocationLineAndColumn( error.extra_ ) + " for detail.\n"; - } - return formattedMessage; -} - - -std::istream& operator>>( std::istream &sin, Value &root ) -{ - Json::Reader reader; - bool ok = reader.parse(sin, root, true); - //JSON_ASSERT( ok ); - if (!ok) throw std::runtime_error(reader.getFormatedErrorMessages()); - return sin; -} - - -} // namespace Json diff --git a/util/json/json_value.cpp b/util/json/json_value.cpp deleted file mode 100644 index 21996f4a..00000000 --- a/util/json/json_value.cpp +++ /dev/null @@ -1,1718 +0,0 @@ -#include -#include "value.h" -#include "writer.h" -#include -#include -#include -#include -#ifdef JSON_USE_CPPTL -# include -#endif -#include // size_t -#ifndef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -# include "json_batchallocator.h" -#endif // #ifndef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR - -#define JSON_ASSERT_UNREACHABLE assert( false ) -#define JSON_ASSERT( condition ) assert( condition ); // @todo <= change this into an exception throw -#define JSON_ASSERT_MESSAGE( condition, message ) if (!( condition )) throw std::runtime_error( message ); - -namespace Json { - -const Value Value::null; -const Int Value::minInt = Int( ~(UInt(-1)/2) ); -const Int Value::maxInt = Int( UInt(-1)/2 ); -const UInt Value::maxUInt = UInt(-1); - -// A "safe" implementation of strdup. Allow null pointer to be passed. -// Also avoid warning on msvc80. -// -//inline char *safeStringDup( const char *czstring ) -//{ -// if ( czstring ) -// { -// const size_t length = (unsigned int)( strlen(czstring) + 1 ); -// char *newString = static_cast( malloc( length ) ); -// memcpy( newString, czstring, length ); -// return newString; -// } -// return 0; -//} -// -//inline char *safeStringDup( const std::string &str ) -//{ -// if ( !str.empty() ) -// { -// const size_t length = str.length(); -// char *newString = static_cast( malloc( length + 1 ) ); -// memcpy( newString, str.c_str(), length ); -// newString[length] = 0; -// return newString; -// } -// return 0; -//} - -ValueAllocator::~ValueAllocator() -{ -} - -class DefaultValueAllocator : public ValueAllocator -{ -public: - virtual ~DefaultValueAllocator() - { - } - - virtual char *makeMemberName( const char *memberName ) - { - return duplicateStringValue( memberName ); - } - - virtual void releaseMemberName( char *memberName ) - { - releaseStringValue( memberName ); - } - - virtual char *duplicateStringValue( const char *value, - unsigned int length = unknown ) - { - //@todo invesgate this old optimization - //if ( !value || value[0] == 0 ) - // return 0; - - if ( length == unknown ) - length = (unsigned int)strlen(value); - char *newString = static_cast( malloc( length + 1 ) ); - memcpy( newString, value, length ); - newString[length] = 0; - return newString; - } - - virtual void releaseStringValue( char *value ) - { - if ( value ) - free( value ); - } -}; - -static ValueAllocator *&valueAllocator() -{ - static DefaultValueAllocator defaultAllocator; - static ValueAllocator *valueAllocator = &defaultAllocator; - return valueAllocator; -} - -static struct DummyValueAllocatorInitializer { - DummyValueAllocatorInitializer() - { - valueAllocator(); // ensure valueAllocator() statics are initialized before main(). - } -} dummyValueAllocatorInitializer; - - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ValueInternals... -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -#ifdef JSON_VALUE_USE_INTERNAL_MAP -# include "json_internalarray.inl" -# include "json_internalmap.inl" -#endif // JSON_VALUE_USE_INTERNAL_MAP - -# include "json_valueiterator.inl" - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CommentInfo -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - - -Value::CommentInfo::CommentInfo() - : comment_( 0 ) -{ -} - -Value::CommentInfo::~CommentInfo() -{ - if ( comment_ ) - valueAllocator()->releaseStringValue( comment_ ); -} - - -void -Value::CommentInfo::setComment( const char *text ) -{ - if ( comment_ ) - valueAllocator()->releaseStringValue( comment_ ); - JSON_ASSERT( text ); - JSON_ASSERT_MESSAGE( text[0]=='\0' || text[0]=='/', "Comments must start with /"); - // It seems that /**/ style comments are acceptable as well. - comment_ = valueAllocator()->duplicateStringValue( text ); -} - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CZString -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -# ifndef JSON_VALUE_USE_INTERNAL_MAP - -// Notes: index_ indicates if the string was allocated when -// a string is stored. - -Value::CZString::CZString( int index ) - : cstr_( 0 ) - , index_( index ) -{ -} - -Value::CZString::CZString( const char *cstr, DuplicationPolicy allocate ) - : cstr_( allocate == duplicate ? valueAllocator()->makeMemberName(cstr) - : cstr ) - , index_( allocate ) -{ -} - -Value::CZString::CZString( const CZString &other ) -: cstr_( other.index_ != noDuplication && other.cstr_ != 0 - ? valueAllocator()->makeMemberName( other.cstr_ ) - : other.cstr_ ) - , index_( other.cstr_ ? (other.index_ == noDuplication ? noDuplication : duplicate) - : other.index_ ) -{ -} - -Value::CZString::~CZString() -{ - if ( cstr_ && index_ == duplicate ) - valueAllocator()->releaseMemberName( const_cast( cstr_ ) ); -} - -void -Value::CZString::swap( CZString &other ) -{ - std::swap( cstr_, other.cstr_ ); - std::swap( index_, other.index_ ); -} - -Value::CZString & -Value::CZString::operator =( const CZString &other ) -{ - CZString temp( other ); - swap( temp ); - return *this; -} - -bool -Value::CZString::operator<( const CZString &other ) const -{ - if ( cstr_ ) - return strcmp( cstr_, other.cstr_ ) < 0; - return index_ < other.index_; -} - -bool -Value::CZString::operator==( const CZString &other ) const -{ - if ( cstr_ ) - return strcmp( cstr_, other.cstr_ ) == 0; - return index_ == other.index_; -} - - -int -Value::CZString::index() const -{ - return index_; -} - - -const char * -Value::CZString::c_str() const -{ - return cstr_; -} - -bool -Value::CZString::isStaticString() const -{ - return index_ == noDuplication; -} - -#endif // ifndef JSON_VALUE_USE_INTERNAL_MAP - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::Value -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -/*! \internal Default constructor initialization must be equivalent to: - * memset( this, 0, sizeof(Value) ) - * This optimization is used in ValueInternalMap fast allocator. - */ -Value::Value( ValueType type ) - : type_( type ) - , allocated_( 0 ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - switch ( type ) - { - case nullValue: - break; - case intValue: - case uintValue: - value_.int_ = 0; - break; - case realValue: - value_.real_ = 0.0; - break; - case stringValue: - value_.string_ = 0; - break; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(); - break; -#else - case arrayValue: - value_.array_ = arrayAllocator()->newArray(); - break; - case objectValue: - value_.map_ = mapAllocator()->newMap(); - break; -#endif - case booleanValue: - value_.bool_ = false; - break; - default: - JSON_ASSERT_UNREACHABLE; - } -} - - -Value::Value( Int value ) - : type_( intValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.int_ = value; -} - - -Value::Value( UInt value ) - : type_( uintValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.uint_ = value; -} - -Value::Value( double value ) - : type_( realValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.real_ = value; -} - -Value::Value( const char *value ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = valueAllocator()->duplicateStringValue( value ); -} - - -Value::Value( const char *beginValue, - const char *endValue ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = valueAllocator()->duplicateStringValue( beginValue, - UInt(endValue - beginValue) ); -} - - -Value::Value( const std::string &value ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = valueAllocator()->duplicateStringValue( value.c_str(), - (unsigned int)value.length() ); - -} - -Value::Value( const StaticString &value ) - : type_( stringValue ) - , allocated_( false ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = const_cast( value.c_str() ); -} - - -# ifdef JSON_USE_CPPTL -Value::Value( const CppTL::ConstString &value ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = valueAllocator()->duplicateStringValue( value, value.length() ); -} -# endif - -Value::Value( bool value ) - : type_( booleanValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.bool_ = value; -} - - -Value::Value( const Value &other ) - : type_( other.type_ ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - switch ( type_ ) - { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - value_ = other.value_; - break; - case stringValue: - if ( other.value_.string_ ) - { - value_.string_ = valueAllocator()->duplicateStringValue( other.value_.string_ ); - allocated_ = true; - } - else - value_.string_ = 0; - break; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues( *other.value_.map_ ); - break; -#else - case arrayValue: - value_.array_ = arrayAllocator()->newArrayCopy( *other.value_.array_ ); - break; - case objectValue: - value_.map_ = mapAllocator()->newMapCopy( *other.value_.map_ ); - break; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - if ( other.comments_ ) - { - comments_ = new CommentInfo[numberOfCommentPlacement]; - for ( int comment =0; comment < numberOfCommentPlacement; ++comment ) - { - const CommentInfo &otherComment = other.comments_[comment]; - if ( otherComment.comment_ ) - comments_[comment].setComment( otherComment.comment_ ); - } - } -} - - -Value::~Value() -{ - switch ( type_ ) - { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - break; - case stringValue: - if ( allocated_ ) - valueAllocator()->releaseStringValue( value_.string_ ); - break; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - delete value_.map_; - break; -#else - case arrayValue: - arrayAllocator()->destructArray( value_.array_ ); - break; - case objectValue: - mapAllocator()->destructMap( value_.map_ ); - break; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - - if ( comments_ ) - delete[] comments_; -} - -Value & -Value::operator=( const Value &other ) -{ - Value temp( other ); - swap( temp ); - return *this; -} - -void -Value::swap( Value &other ) -{ - ValueType temp = type_; - type_ = other.type_; - other.type_ = temp; - std::swap( value_, other.value_ ); - int temp2 = allocated_; - allocated_ = other.allocated_; - other.allocated_ = temp2; -} - -ValueType -Value::type() const -{ - return type_; -} - - -int -Value::compare( const Value &other ) -{ - /* - int typeDelta = other.type_ - type_; - switch ( type_ ) - { - case nullValue: - - return other.type_ == type_; - case intValue: - if ( other.type_.isNumeric() - case uintValue: - case realValue: - case booleanValue: - break; - case stringValue, - break; - case arrayValue: - delete value_.array_; - break; - case objectValue: - delete value_.map_; - default: - JSON_ASSERT_UNREACHABLE; - } - */ - return 0; // unreachable -} - -bool -Value::operator <( const Value &other ) const -{ - int typeDelta = type_ - other.type_; - if ( typeDelta ) - return typeDelta < 0 ? true : false; - switch ( type_ ) - { - case nullValue: - return false; - case intValue: - return value_.int_ < other.value_.int_; - case uintValue: - return value_.uint_ < other.value_.uint_; - case realValue: - return value_.real_ < other.value_.real_; - case booleanValue: - return value_.bool_ < other.value_.bool_; - case stringValue: - return ( value_.string_ == 0 && other.value_.string_ ) - || ( other.value_.string_ - && value_.string_ - && strcmp( value_.string_, other.value_.string_ ) < 0 ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - { - int delta = int( value_.map_->size() - other.value_.map_->size() ); - if ( delta ) - return delta < 0; - return (*value_.map_) < (*other.value_.map_); - } -#else - case arrayValue: - return value_.array_->compare( *(other.value_.array_) ) < 0; - case objectValue: - return value_.map_->compare( *(other.value_.map_) ) < 0; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable -} - -bool -Value::operator <=( const Value &other ) const -{ - return !(other > *this); -} - -bool -Value::operator >=( const Value &other ) const -{ - return !(*this < other); -} - -bool -Value::operator >( const Value &other ) const -{ - return other < *this; -} - -bool -Value::operator ==( const Value &other ) const -{ - //if ( type_ != other.type_ ) - // GCC 2.95.3 says: - // attempt to take address of bit-field structure member `Json::Value::type_' - // Beats me, but a temp solves the problem. - int temp = other.type_; - if ( type_ != temp ) - return false; - switch ( type_ ) - { - case nullValue: - return true; - case intValue: - return value_.int_ == other.value_.int_; - case uintValue: - return value_.uint_ == other.value_.uint_; - case realValue: - return value_.real_ == other.value_.real_; - case booleanValue: - return value_.bool_ == other.value_.bool_; - case stringValue: - return ( value_.string_ == other.value_.string_ ) - || ( other.value_.string_ - && value_.string_ - && strcmp( value_.string_, other.value_.string_ ) == 0 ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - return value_.map_->size() == other.value_.map_->size() - && (*value_.map_) == (*other.value_.map_); -#else - case arrayValue: - return value_.array_->compare( *(other.value_.array_) ) == 0; - case objectValue: - return value_.map_->compare( *(other.value_.map_) ) == 0; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable -} - -bool -Value::operator !=( const Value &other ) const -{ - return !( *this == other ); -} - -const char * -Value::asCString() const -{ - JSON_ASSERT( type_ == stringValue ); - return value_.string_; -} - - -std::string -Value::asString() const -{ - switch ( type_ ) - { - case nullValue: - return ""; - case stringValue: - return value_.string_ ? value_.string_ : ""; - case booleanValue: - return value_.bool_ ? "true" : "false"; - case intValue: - case uintValue: - case realValue: - case arrayValue: - case objectValue: - JSON_ASSERT_MESSAGE( false, "Type is not convertible to string" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return ""; // unreachable -} - -# ifdef JSON_USE_CPPTL -CppTL::ConstString -Value::asConstString() const -{ - return CppTL::ConstString( asString().c_str() ); -} -# endif - -Value::Int -Value::asInt() const -{ - switch ( type_ ) - { - case nullValue: - return 0; - case intValue: - return value_.int_; - case uintValue: - JSON_ASSERT_MESSAGE( value_.uint_ < (unsigned)maxInt, "integer out of signed integer range" ); - return value_.uint_; - case realValue: - JSON_ASSERT_MESSAGE( value_.real_ >= minInt && value_.real_ <= maxInt, "Real out of signed integer range" ); - return Int( value_.real_ ); - case booleanValue: - return value_.bool_ ? 1 : 0; - case stringValue: - case arrayValue: - case objectValue: - JSON_ASSERT_MESSAGE( false, "Type is not convertible to int" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - -Value::UInt -Value::asUInt() const -{ - switch ( type_ ) - { - case nullValue: - return 0; - case intValue: - JSON_ASSERT_MESSAGE( value_.int_ >= 0, "Negative integer can not be converted to unsigned integer" ); - return value_.int_; - case uintValue: - return value_.uint_; - case realValue: - JSON_ASSERT_MESSAGE( value_.real_ >= 0 && value_.real_ <= maxUInt, "Real out of unsigned integer range" ); - return UInt( value_.real_ ); - case booleanValue: - return value_.bool_ ? 1 : 0; - case stringValue: - case arrayValue: - case objectValue: - JSON_ASSERT_MESSAGE( false, "Type is not convertible to uint" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - -double -Value::asDouble() const -{ - switch ( type_ ) - { - case nullValue: - return 0.0; - case intValue: - return value_.int_; - case uintValue: - return value_.uint_; - case realValue: - return value_.real_; - case booleanValue: - return value_.bool_ ? 1.0 : 0.0; - case stringValue: - case arrayValue: - case objectValue: - JSON_ASSERT_MESSAGE( false, "Type is not convertible to double" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - -bool -Value::asBool() const -{ - switch ( type_ ) - { - case nullValue: - return false; - case intValue: - case uintValue: - return value_.int_ != 0; - case realValue: - return value_.real_ != 0.0; - case booleanValue: - return value_.bool_; - case stringValue: - return value_.string_ && value_.string_[0] != 0; - case arrayValue: - case objectValue: - return value_.map_->size() != 0; - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable; -} - - -bool -Value::isConvertibleTo( ValueType other ) const -{ - switch ( type_ ) - { - case nullValue: - return true; - case intValue: - return ( other == nullValue && value_.int_ == 0 ) - || other == intValue - || ( other == uintValue && value_.int_ >= 0 ) - || other == realValue - || other == stringValue - || other == booleanValue; - case uintValue: - return ( other == nullValue && value_.uint_ == 0 ) - || ( other == intValue && value_.uint_ <= (unsigned)maxInt ) - || other == uintValue - || other == realValue - || other == stringValue - || other == booleanValue; - case realValue: - return ( other == nullValue && value_.real_ == 0.0 ) - || ( other == intValue && value_.real_ >= minInt && value_.real_ <= maxInt ) - || ( other == uintValue && value_.real_ >= 0 && value_.real_ <= maxUInt ) - || other == realValue - || other == stringValue - || other == booleanValue; - case booleanValue: - return ( other == nullValue && value_.bool_ == false ) - || other == intValue - || other == uintValue - || other == realValue - || other == stringValue - || other == booleanValue; - case stringValue: - return other == stringValue - || ( other == nullValue && (!value_.string_ || value_.string_[0] == 0) ); - case arrayValue: - return other == arrayValue - || ( other == nullValue && value_.map_->size() == 0 ); - case objectValue: - return other == objectValue - || ( other == nullValue && value_.map_->size() == 0 ); - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable; -} - - -/// Number of values in array or object -Value::UInt -Value::size() const -{ - switch ( type_ ) - { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - case stringValue: - return 0; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: // size of the array is highest index + 1 - if ( !value_.map_->empty() ) - { - ObjectValues::const_iterator itLast = value_.map_->end(); - --itLast; - return (*itLast).first.index()+1; - } - return 0; - case objectValue: - return Int( value_.map_->size() ); -#else - case arrayValue: - return Int( value_.array_->size() ); - case objectValue: - return Int( value_.map_->size() ); -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - - -bool -Value::empty() const -{ - if ( isNull() || isArray() || isObject() ) - return size() == 0u; - else - return false; -} - - -bool -Value::operator!() const -{ - return isNull(); -} - - -void -Value::clear() -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue || type_ == objectValue ); - - switch ( type_ ) - { -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - value_.map_->clear(); - break; -#else - case arrayValue: - value_.array_->clear(); - break; - case objectValue: - value_.map_->clear(); - break; -#endif - default: - break; - } -} - -void -Value::resize( UInt newSize ) -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); - if ( type_ == nullValue ) - *this = Value( arrayValue ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - UInt oldSize = size(); - if ( newSize == 0 ) - clear(); - else if ( newSize > oldSize ) - (*this)[ newSize - 1 ]; - else - { - for ( UInt index = newSize; index < oldSize; ++index ) - value_.map_->erase( index ); - assert( size() == newSize ); - } -#else - value_.array_->resize( newSize ); -#endif -} - - -Value & -Value::operator[]( UInt index ) -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); - if ( type_ == nullValue ) - *this = Value( arrayValue ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString key( index ); - ObjectValues::iterator it = value_.map_->lower_bound( key ); - if ( it != value_.map_->end() && (*it).first == key ) - return (*it).second; - - ObjectValues::value_type defaultValue( key, null ); - it = value_.map_->insert( it, defaultValue ); - return (*it).second; -#else - return value_.array_->resolveReference( index ); -#endif -} - - -const Value & -Value::operator[]( UInt index ) const -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); - if ( type_ == nullValue ) - return null; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString key( index ); - ObjectValues::const_iterator it = value_.map_->find( key ); - if ( it == value_.map_->end() ) - return null; - return (*it).second; -#else - Value *value = value_.array_->find( index ); - return value ? *value : null; -#endif -} - - -Value & -Value::operator[]( const char *key ) -{ - return resolveReference( key, false ); -} - - -Value & -Value::resolveReference( const char *key, - bool isStatic ) -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - *this = Value( objectValue ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString actualKey( key, isStatic ? CZString::noDuplication - : CZString::duplicateOnCopy ); - ObjectValues::iterator it = value_.map_->lower_bound( actualKey ); - if ( it != value_.map_->end() && (*it).first == actualKey ) - return (*it).second; - - ObjectValues::value_type defaultValue( actualKey, null ); - it = value_.map_->insert( it, defaultValue ); - Value &value = (*it).second; - return value; -#else - return value_.map_->resolveReference( key, isStatic ); -#endif -} - - -Value -Value::get( UInt index, - const Value &defaultValue ) const -{ - const Value *value = &((*this)[index]); - return value == &null ? defaultValue : *value; -} - - -bool -Value::isValidIndex( UInt index ) const -{ - return index < size(); -} - - - -const Value & -Value::operator[]( const char *key ) const -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - return null; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString actualKey( key, CZString::noDuplication ); - ObjectValues::const_iterator it = value_.map_->find( actualKey ); - if ( it == value_.map_->end() ) - return null; - return (*it).second; -#else - const Value *value = value_.map_->find( key ); - return value ? *value : null; -#endif -} - - -Value & -Value::operator[]( const std::string &key ) -{ - return (*this)[ key.c_str() ]; -} - - -const Value & -Value::operator[]( const std::string &key ) const -{ - return (*this)[ key.c_str() ]; -} - -Value & -Value::operator[]( const StaticString &key ) -{ - return resolveReference( key, true ); -} - - -# ifdef JSON_USE_CPPTL -Value & -Value::operator[]( const CppTL::ConstString &key ) -{ - return (*this)[ key.c_str() ]; -} - - -const Value & -Value::operator[]( const CppTL::ConstString &key ) const -{ - return (*this)[ key.c_str() ]; -} -# endif - - -Value & -Value::append( const Value &value ) -{ - return (*this)[size()] = value; -} - - -Value -Value::get( const char *key, - const Value &defaultValue ) const -{ - const Value *value = &((*this)[key]); - return value == &null ? defaultValue : *value; -} - - -Value -Value::get( const std::string &key, - const Value &defaultValue ) const -{ - return get( key.c_str(), defaultValue ); -} - -Value -Value::removeMember( const char* key ) -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - return null; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString actualKey( key, CZString::noDuplication ); - ObjectValues::iterator it = value_.map_->find( actualKey ); - if ( it == value_.map_->end() ) - return null; - Value old(it->second); - value_.map_->erase(it); - return old; -#else - Value *value = value_.map_->find( key ); - if (value){ - Value old(*value); - value_.map_.remove( key ); - return old; - } else { - return null; - } -#endif -} - -Value -Value::removeMember( const std::string &key ) -{ - return removeMember( key.c_str() ); -} - -# ifdef JSON_USE_CPPTL -Value -Value::get( const CppTL::ConstString &key, - const Value &defaultValue ) const -{ - return get( key.c_str(), defaultValue ); -} -# endif - -bool -Value::isMember( const char *key ) const -{ - const Value *value = &((*this)[key]); - return value != &null; -} - - -bool -Value::isMember( const std::string &key ) const -{ - return isMember( key.c_str() ); -} - - -# ifdef JSON_USE_CPPTL -bool -Value::isMember( const CppTL::ConstString &key ) const -{ - return isMember( key.c_str() ); -} -#endif - -Value::Members -Value::getMemberNames() const -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - return Value::Members(); - Members members; - members.reserve( value_.map_->size() ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - ObjectValues::const_iterator it = value_.map_->begin(); - ObjectValues::const_iterator itEnd = value_.map_->end(); - for ( ; it != itEnd; ++it ) - members.push_back( std::string( (*it).first.c_str() ) ); -#else - ValueInternalMap::IteratorState it; - ValueInternalMap::IteratorState itEnd; - value_.map_->makeBeginIterator( it ); - value_.map_->makeEndIterator( itEnd ); - for ( ; !ValueInternalMap::equals( it, itEnd ); ValueInternalMap::increment(it) ) - members.push_back( std::string( ValueInternalMap::key( it ) ) ); -#endif - return members; -} -// -//# ifdef JSON_USE_CPPTL -//EnumMemberNames -//Value::enumMemberNames() const -//{ -// if ( type_ == objectValue ) -// { -// return CppTL::Enum::any( CppTL::Enum::transform( -// CppTL::Enum::keys( *(value_.map_), CppTL::Type() ), -// MemberNamesTransform() ) ); -// } -// return EnumMemberNames(); -//} -// -// -//EnumValues -//Value::enumValues() const -//{ -// if ( type_ == objectValue || type_ == arrayValue ) -// return CppTL::Enum::anyValues( *(value_.map_), -// CppTL::Type() ); -// return EnumValues(); -//} -// -//# endif - - -bool -Value::isNull() const -{ - return type_ == nullValue; -} - - -bool -Value::isBool() const -{ - return type_ == booleanValue; -} - - -bool -Value::isInt() const -{ - return type_ == intValue; -} - - -bool -Value::isUInt() const -{ - return type_ == uintValue; -} - - -bool -Value::isIntegral() const -{ - return type_ == intValue - || type_ == uintValue - || type_ == booleanValue; -} - - -bool -Value::isDouble() const -{ - return type_ == realValue; -} - - -bool -Value::isNumeric() const -{ - return isIntegral() || isDouble(); -} - - -bool -Value::isString() const -{ - return type_ == stringValue; -} - - -bool -Value::isArray() const -{ - return type_ == arrayValue; -} - - -bool -Value::isObject() const -{ - return type_ == objectValue; -} - - -void -Value::setComment( const char *comment, - CommentPlacement placement ) -{ - if ( !comments_ ) - comments_ = new CommentInfo[numberOfCommentPlacement]; - comments_[placement].setComment( comment ); -} - - -void -Value::setComment( const std::string &comment, - CommentPlacement placement ) -{ - setComment( comment.c_str(), placement ); -} - - -bool -Value::hasComment( CommentPlacement placement ) const -{ - return comments_ != 0 && comments_[placement].comment_ != 0; -} - -std::string -Value::getComment( CommentPlacement placement ) const -{ - if ( hasComment(placement) ) - return comments_[placement].comment_; - return ""; -} - - -std::string -Value::toStyledString() const -{ - StyledWriter writer; - return writer.write( *this ); -} - - -Value::const_iterator -Value::begin() const -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeBeginIterator( it ); - return const_iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeBeginIterator( it ); - return const_iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return const_iterator( value_.map_->begin() ); - break; -#endif - default: - break; - } - return const_iterator(); -} - -Value::const_iterator -Value::end() const -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeEndIterator( it ); - return const_iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeEndIterator( it ); - return const_iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return const_iterator( value_.map_->end() ); - break; -#endif - default: - break; - } - return const_iterator(); -} - - -Value::iterator -Value::begin() -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeBeginIterator( it ); - return iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeBeginIterator( it ); - return iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return iterator( value_.map_->begin() ); - break; -#endif - default: - break; - } - return iterator(); -} - -Value::iterator -Value::end() -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeEndIterator( it ); - return iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeEndIterator( it ); - return iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return iterator( value_.map_->end() ); - break; -#endif - default: - break; - } - return iterator(); -} - - -// class PathArgument -// ////////////////////////////////////////////////////////////////// - -PathArgument::PathArgument() - : kind_( kindNone ) -{ -} - - -PathArgument::PathArgument( Value::UInt index ) - : index_( index ) - , kind_( kindIndex ) -{ -} - - -PathArgument::PathArgument( const char *key ) - : key_( key ) - , kind_( kindKey ) -{ -} - - -PathArgument::PathArgument( const std::string &key ) - : key_( key.c_str() ) - , kind_( kindKey ) -{ -} - -// class Path -// ////////////////////////////////////////////////////////////////// - -Path::Path( const std::string &path, - const PathArgument &a1, - const PathArgument &a2, - const PathArgument &a3, - const PathArgument &a4, - const PathArgument &a5 ) -{ - InArgs in; - in.push_back( &a1 ); - in.push_back( &a2 ); - in.push_back( &a3 ); - in.push_back( &a4 ); - in.push_back( &a5 ); - makePath( path, in ); -} - - -void -Path::makePath( const std::string &path, - const InArgs &in ) -{ - const char *current = path.c_str(); - const char *end = current + path.length(); - InArgs::const_iterator itInArg = in.begin(); - while ( current != end ) - { - if ( *current == '[' ) - { - ++current; - if ( *current == '%' ) - addPathInArg( path, in, itInArg, PathArgument::kindIndex ); - else - { - Value::UInt index = 0; - for ( ; current != end && *current >= '0' && *current <= '9'; ++current ) - index = index * 10 + Value::UInt(*current - '0'); - args_.push_back( index ); - } - if ( current == end || *current++ != ']' ) - invalidPath( path, int(current - path.c_str()) ); - } - else if ( *current == '%' ) - { - addPathInArg( path, in, itInArg, PathArgument::kindKey ); - ++current; - } - else if ( *current == '.' ) - { - ++current; - } - else - { - const char *beginName = current; - while ( current != end && !strchr( "[.", *current ) ) - ++current; - args_.push_back( std::string( beginName, current ) ); - } - } -} - - -void -Path::addPathInArg( const std::string &path, - const InArgs &in, - InArgs::const_iterator &itInArg, - PathArgument::Kind kind ) -{ - if ( itInArg == in.end() ) - { - // Error: missing argument %d - } - else if ( (*itInArg)->kind_ != kind ) - { - // Error: bad argument type - } - else - { - args_.push_back( **itInArg ); - } -} - - -void -Path::invalidPath( const std::string &path, - int location ) -{ - // Error: invalid path. -} - - -const Value & -Path::resolve( const Value &root ) const -{ - const Value *node = &root; - for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) - { - const PathArgument &arg = *it; - if ( arg.kind_ == PathArgument::kindIndex ) - { - if ( !node->isArray() || node->isValidIndex( arg.index_ ) ) - { - // Error: unable to resolve path (array value expected at position... - } - node = &((*node)[arg.index_]); - } - else if ( arg.kind_ == PathArgument::kindKey ) - { - if ( !node->isObject() ) - { - // Error: unable to resolve path (object value expected at position...) - } - node = &((*node)[arg.key_]); - if ( node == &Value::null ) - { - // Error: unable to resolve path (object has no member named '' at position...) - } - } - } - return *node; -} - - -Value -Path::resolve( const Value &root, - const Value &defaultValue ) const -{ - const Value *node = &root; - for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) - { - const PathArgument &arg = *it; - if ( arg.kind_ == PathArgument::kindIndex ) - { - if ( !node->isArray() || node->isValidIndex( arg.index_ ) ) - return defaultValue; - node = &((*node)[arg.index_]); - } - else if ( arg.kind_ == PathArgument::kindKey ) - { - if ( !node->isObject() ) - return defaultValue; - node = &((*node)[arg.key_]); - if ( node == &Value::null ) - return defaultValue; - } - } - return *node; -} - - -Value & -Path::make( Value &root ) const -{ - Value *node = &root; - for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) - { - const PathArgument &arg = *it; - if ( arg.kind_ == PathArgument::kindIndex ) - { - if ( !node->isArray() ) - { - // Error: node is not an array at position ... - } - node = &((*node)[arg.index_]); - } - else if ( arg.kind_ == PathArgument::kindKey ) - { - if ( !node->isObject() ) - { - // Error: node is not an object at position... - } - node = &((*node)[arg.key_]); - } - } - return *node; -} - - -} // namespace Json diff --git a/util/json/json_valueiterator.inl b/util/json/json_valueiterator.inl deleted file mode 100644 index 736e260e..00000000 --- a/util/json/json_valueiterator.inl +++ /dev/null @@ -1,292 +0,0 @@ -// included by json_value.cpp -// everything is within Json namespace - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueIteratorBase -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueIteratorBase::ValueIteratorBase() -#ifndef JSON_VALUE_USE_INTERNAL_MAP - : current_() - , isNull_( true ) -{ -} -#else - : isArray_( true ) - , isNull_( true ) -{ - iterator_.array_ = ValueInternalArray::IteratorState(); -} -#endif - - -#ifndef JSON_VALUE_USE_INTERNAL_MAP -ValueIteratorBase::ValueIteratorBase( const Value::ObjectValues::iterator ¤t ) - : current_( current ) - , isNull_( false ) -{ -} -#else -ValueIteratorBase::ValueIteratorBase( const ValueInternalArray::IteratorState &state ) - : isArray_( true ) -{ - iterator_.array_ = state; -} - - -ValueIteratorBase::ValueIteratorBase( const ValueInternalMap::IteratorState &state ) - : isArray_( false ) -{ - iterator_.map_ = state; -} -#endif - -Value & -ValueIteratorBase::deref() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - return current_->second; -#else - if ( isArray_ ) - return ValueInternalArray::dereference( iterator_.array_ ); - return ValueInternalMap::value( iterator_.map_ ); -#endif -} - - -void -ValueIteratorBase::increment() -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - ++current_; -#else - if ( isArray_ ) - ValueInternalArray::increment( iterator_.array_ ); - ValueInternalMap::increment( iterator_.map_ ); -#endif -} - - -void -ValueIteratorBase::decrement() -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - --current_; -#else - if ( isArray_ ) - ValueInternalArray::decrement( iterator_.array_ ); - ValueInternalMap::decrement( iterator_.map_ ); -#endif -} - - -ValueIteratorBase::difference_type -ValueIteratorBase::computeDistance( const SelfType &other ) const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP -# ifdef JSON_USE_CPPTL_SMALLMAP - return current_ - other.current_; -# else - // Iterator for null value are initialized using the default - // constructor, which initialize current_ to the default - // std::map::iterator. As begin() and end() are two instance - // of the default std::map::iterator, they can not be compared. - // To allow this, we handle this comparison specifically. - if ( isNull_ && other.isNull_ ) - { - return 0; - } - - - // Usage of std::distance is not portable (does not compile with Sun Studio 12 RogueWave STL, - // which is the one used by default). - // Using a portable hand-made version for non random iterator instead: - // return difference_type( std::distance( current_, other.current_ ) ); - difference_type myDistance = 0; - for ( Value::ObjectValues::iterator it = current_; it != other.current_; ++it ) - { - ++myDistance; - } - return myDistance; -# endif -#else - if ( isArray_ ) - return ValueInternalArray::distance( iterator_.array_, other.iterator_.array_ ); - return ValueInternalMap::distance( iterator_.map_, other.iterator_.map_ ); -#endif -} - - -bool -ValueIteratorBase::isEqual( const SelfType &other ) const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - if ( isNull_ ) - { - return other.isNull_; - } - return current_ == other.current_; -#else - if ( isArray_ ) - return ValueInternalArray::equals( iterator_.array_, other.iterator_.array_ ); - return ValueInternalMap::equals( iterator_.map_, other.iterator_.map_ ); -#endif -} - - -void -ValueIteratorBase::copy( const SelfType &other ) -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - current_ = other.current_; -#else - if ( isArray_ ) - iterator_.array_ = other.iterator_.array_; - iterator_.map_ = other.iterator_.map_; -#endif -} - - -Value -ValueIteratorBase::key() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - const Value::CZString czstring = (*current_).first; - if ( czstring.c_str() ) - { - if ( czstring.isStaticString() ) - return Value( StaticString( czstring.c_str() ) ); - return Value( czstring.c_str() ); - } - return Value( czstring.index() ); -#else - if ( isArray_ ) - return Value( ValueInternalArray::indexOf( iterator_.array_ ) ); - bool isStatic; - const char *memberName = ValueInternalMap::key( iterator_.map_, isStatic ); - if ( isStatic ) - return Value( StaticString( memberName ) ); - return Value( memberName ); -#endif -} - - -UInt -ValueIteratorBase::index() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - const Value::CZString czstring = (*current_).first; - if ( !czstring.c_str() ) - return czstring.index(); - return Value::UInt( -1 ); -#else - if ( isArray_ ) - return Value::UInt( ValueInternalArray::indexOf( iterator_.array_ ) ); - return Value::UInt( -1 ); -#endif -} - - -const char * -ValueIteratorBase::memberName() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - const char *name = (*current_).first.c_str(); - return name ? name : ""; -#else - if ( !isArray_ ) - return ValueInternalMap::key( iterator_.map_ ); - return ""; -#endif -} - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueConstIterator -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueConstIterator::ValueConstIterator() -{ -} - - -#ifndef JSON_VALUE_USE_INTERNAL_MAP -ValueConstIterator::ValueConstIterator( const Value::ObjectValues::iterator ¤t ) - : ValueIteratorBase( current ) -{ -} -#else -ValueConstIterator::ValueConstIterator( const ValueInternalArray::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} - -ValueConstIterator::ValueConstIterator( const ValueInternalMap::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} -#endif - -ValueConstIterator & -ValueConstIterator::operator =( const ValueIteratorBase &other ) -{ - copy( other ); - return *this; -} - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueIterator -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueIterator::ValueIterator() -{ -} - - -#ifndef JSON_VALUE_USE_INTERNAL_MAP -ValueIterator::ValueIterator( const Value::ObjectValues::iterator ¤t ) - : ValueIteratorBase( current ) -{ -} -#else -ValueIterator::ValueIterator( const ValueInternalArray::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} - -ValueIterator::ValueIterator( const ValueInternalMap::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} -#endif - -ValueIterator::ValueIterator( const ValueConstIterator &other ) - : ValueIteratorBase( other ) -{ -} - -ValueIterator::ValueIterator( const ValueIterator &other ) - : ValueIteratorBase( other ) -{ -} - -ValueIterator & -ValueIterator::operator =( const SelfType &other ) -{ - copy( other ); - return *this; -} diff --git a/util/json/json_writer.cpp b/util/json/json_writer.cpp deleted file mode 100644 index 2a9f0dba..00000000 --- a/util/json/json_writer.cpp +++ /dev/null @@ -1,829 +0,0 @@ -#include "writer.h" -#include -#include -#include -#include -#include -#include -#include - -#if _MSC_VER >= 1400 // VC++ 8.0 -#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. -#endif - -namespace Json { - -static bool isControlCharacter(char ch) -{ - return ch > 0 && ch <= 0x1F; -} - -static bool containsControlCharacter( const char* str ) -{ - while ( *str ) - { - if ( isControlCharacter( *(str++) ) ) - return true; - } - return false; -} -static void uintToString( unsigned int value, - char *¤t ) -{ - *--current = 0; - do - { - *--current = (value % 10) + '0'; - value /= 10; - } - while ( value != 0 ); -} - -std::string valueToString( Int value ) -{ - char buffer[32]; - char *current = buffer + sizeof(buffer); - bool isNegative = value < 0; - if ( isNegative ) - value = -value; - uintToString( UInt(value), current ); - if ( isNegative ) - *--current = '-'; - assert( current >= buffer ); - return current; -} - - -std::string valueToString( UInt value ) -{ - char buffer[32]; - char *current = buffer + sizeof(buffer); - uintToString( value, current ); - assert( current >= buffer ); - return current; -} - -std::string valueToString( double value ) -{ - char buffer[32]; -#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning. - sprintf_s(buffer, sizeof(buffer), "%#.16g", value); -#else - sprintf(buffer, "%#.16g", value); -#endif - char* ch = buffer + strlen(buffer) - 1; - if (*ch != '0') return buffer; // nothing to truncate, so save time - while(ch > buffer && *ch == '0'){ - --ch; - } - char* last_nonzero = ch; - while(ch >= buffer){ - switch(*ch){ - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - --ch; - continue; - case '.': - // Truncate zeroes to save bytes in output, but keep one. - *(last_nonzero+2) = '\0'; - return buffer; - default: - return buffer; - } - } - return buffer; -} - - -std::string valueToString( bool value ) -{ - return value ? "true" : "false"; -} - -std::string valueToQuotedString( const char *value ) -{ - // Not sure how to handle unicode... - if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter( value )) - return std::string("\"") + value + "\""; - // We have to walk value and escape any special characters. - // Appending to std::string is not efficient, but this should be rare. - // (Note: forward slashes are *not* rare, but I am not escaping them.) - unsigned maxsize = strlen(value)*2 + 3; // allescaped+quotes+NULL - std::string result; - result.reserve(maxsize); // to avoid lots of mallocs - result += "\""; - for (const char* c=value; *c != 0; ++c) - { - switch(*c) - { - case '\"': - result += "\\\""; - break; - case '\\': - result += "\\\\"; - break; - case '\b': - result += "\\b"; - break; - case '\f': - result += "\\f"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - //case '/': - // Even though \/ is considered a legal escape in JSON, a bare - // slash is also legal, so I see no reason to escape it. - // (I hope I am not misunderstanding something. - // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); - result += oss.str(); - } - else - { - result += *c; - } - break; - } - } - result += "\""; - return result; -} - -// Class Writer -// ////////////////////////////////////////////////////////////////// -Writer::~Writer() -{ -} - - -// Class FastWriter -// ////////////////////////////////////////////////////////////////// - -FastWriter::FastWriter() - : yamlCompatiblityEnabled_( false ) -{ -} - - -void -FastWriter::enableYAMLCompatibility() -{ - yamlCompatiblityEnabled_ = true; -} - - -std::string -FastWriter::write( const Value &root ) -{ - document_ = ""; - writeValue( root ); - document_ += "\n"; - return document_; -} - - -void -FastWriter::writeValue( const Value &value ) -{ - switch ( value.type() ) - { - case nullValue: - document_ += "null"; - break; - case intValue: - document_ += valueToString( value.asInt() ); - break; - case uintValue: - document_ += valueToString( value.asUInt() ); - break; - case realValue: - document_ += valueToString( value.asDouble() ); - break; - case stringValue: - document_ += valueToQuotedString( value.asCString() ); - break; - case booleanValue: - document_ += valueToString( value.asBool() ); - break; - case arrayValue: - { - document_ += "["; - int size = value.size(); - for ( int index =0; index < size; ++index ) - { - if ( index > 0 ) - document_ += ","; - writeValue( value[index] ); - } - document_ += "]"; - } - break; - case objectValue: - { - Value::Members members( value.getMemberNames() ); - document_ += "{"; - for ( Value::Members::iterator it = members.begin(); - it != members.end(); - ++it ) - { - const std::string &name = *it; - if ( it != members.begin() ) - document_ += ","; - document_ += valueToQuotedString( name.c_str() ); - document_ += yamlCompatiblityEnabled_ ? ": " - : ":"; - writeValue( value[name] ); - } - document_ += "}"; - } - break; - } -} - - -// Class StyledWriter -// ////////////////////////////////////////////////////////////////// - -StyledWriter::StyledWriter() - : rightMargin_( 74 ) - , indentSize_( 3 ) -{ -} - - -std::string -StyledWriter::write( const Value &root ) -{ - document_ = ""; - addChildValues_ = false; - indentString_ = ""; - writeCommentBeforeValue( root ); - writeValue( root ); - writeCommentAfterValueOnSameLine( root ); - document_ += "\n"; - return document_; -} - - -void -StyledWriter::writeValue( const Value &value ) -{ - switch ( value.type() ) - { - case nullValue: - pushValue( "null" ); - break; - case intValue: - pushValue( valueToString( value.asInt() ) ); - break; - case uintValue: - pushValue( valueToString( value.asUInt() ) ); - break; - case realValue: - pushValue( valueToString( value.asDouble() ) ); - break; - case stringValue: - pushValue( valueToQuotedString( value.asCString() ) ); - break; - case booleanValue: - pushValue( valueToString( value.asBool() ) ); - break; - case arrayValue: - writeArrayValue( value); - break; - case objectValue: - { - Value::Members members( value.getMemberNames() ); - if ( members.empty() ) - pushValue( "{}" ); - else - { - writeWithIndent( "{" ); - indent(); - Value::Members::iterator it = members.begin(); - while ( true ) - { - const std::string &name = *it; - const Value &childValue = value[name]; - writeCommentBeforeValue( childValue ); - writeWithIndent( valueToQuotedString( name.c_str() ) ); - document_ += " : "; - writeValue( childValue ); - if ( ++it == members.end() ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - document_ += ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "}" ); - } - } - break; - } -} - - -void -StyledWriter::writeArrayValue( const Value &value ) -{ - unsigned size = value.size(); - if ( size == 0 ) - pushValue( "[]" ); - else - { - bool isArrayMultiLine = isMultineArray( value ); - if ( isArrayMultiLine ) - { - writeWithIndent( "[" ); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index =0; - while ( true ) - { - const Value &childValue = value[index]; - writeCommentBeforeValue( childValue ); - if ( hasChildValue ) - writeWithIndent( childValues_[index] ); - else - { - writeIndent(); - writeValue( childValue ); - } - if ( ++index == size ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - document_ += ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "]" ); - } - else // output on a single line - { - assert( childValues_.size() == size ); - document_ += "[ "; - for ( unsigned index =0; index < size; ++index ) - { - if ( index > 0 ) - document_ += ", "; - document_ += childValues_[index]; - } - document_ += " ]"; - } - } -} - - -bool -StyledWriter::isMultineArray( const Value &value ) -{ - int size = value.size(); - bool isMultiLine = size*3 >= rightMargin_ ; - childValues_.clear(); - for ( int index =0; index < size && !isMultiLine; ++index ) - { - const Value &childValue = value[index]; - isMultiLine = isMultiLine || - ( (childValue.isArray() || childValue.isObject()) && - childValue.size() > 0 ); - } - if ( !isMultiLine ) // check if line length > max line length - { - childValues_.reserve( size ); - addChildValues_ = true; - int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' - for ( int index =0; index < size && !isMultiLine; ++index ) - { - writeValue( value[index] ); - lineLength += int( childValues_[index].length() ); - isMultiLine = isMultiLine && hasCommentForValue( value[index] ); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - - -void -StyledWriter::pushValue( const std::string &value ) -{ - if ( addChildValues_ ) - childValues_.push_back( value ); - else - document_ += value; -} - - -void -StyledWriter::writeIndent() -{ - if ( !document_.empty() ) - { - char last = document_[document_.length()-1]; - if ( last == ' ' ) // already indented - return; - if ( last != '\n' ) // Comments may add new-line - document_ += '\n'; - } - document_ += indentString_; -} - - -void -StyledWriter::writeWithIndent( const std::string &value ) -{ - writeIndent(); - document_ += value; -} - - -void -StyledWriter::indent() -{ - indentString_ += std::string( indentSize_, ' ' ); -} - - -void -StyledWriter::unindent() -{ - assert( int(indentString_.size()) >= indentSize_ ); - indentString_.resize( indentString_.size() - indentSize_ ); -} - - -void -StyledWriter::writeCommentBeforeValue( const Value &root ) -{ - if ( !root.hasComment( commentBefore ) ) - return; - document_ += normalizeEOL( root.getComment( commentBefore ) ); - document_ += "\n"; -} - - -void -StyledWriter::writeCommentAfterValueOnSameLine( const Value &root ) -{ - if ( root.hasComment( commentAfterOnSameLine ) ) - document_ += " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); - - if ( root.hasComment( commentAfter ) ) - { - document_ += "\n"; - document_ += normalizeEOL( root.getComment( commentAfter ) ); - document_ += "\n"; - } -} - - -bool -StyledWriter::hasCommentForValue( const Value &value ) -{ - return value.hasComment( commentBefore ) - || value.hasComment( commentAfterOnSameLine ) - || value.hasComment( commentAfter ); -} - - -std::string -StyledWriter::normalizeEOL( const std::string &text ) -{ - std::string normalized; - normalized.reserve( text.length() ); - const char *begin = text.c_str(); - const char *end = begin + text.length(); - const char *current = begin; - while ( current != end ) - { - char c = *current++; - if ( c == '\r' ) // mac or dos EOL - { - if ( *current == '\n' ) // convert dos EOL - ++current; - normalized += '\n'; - } - else // handle unix EOL & other char - normalized += c; - } - return normalized; -} - - -// Class StyledStreamWriter -// ////////////////////////////////////////////////////////////////// - -StyledStreamWriter::StyledStreamWriter( std::string indentation ) - : document_(NULL) - , rightMargin_( 74 ) - , indentation_( indentation ) -{ -} - - -void -StyledStreamWriter::write( std::ostream &out, const Value &root ) -{ - document_ = &out; - addChildValues_ = false; - indentString_ = ""; - writeCommentBeforeValue( root ); - writeValue( root ); - writeCommentAfterValueOnSameLine( root ); - *document_ << "\n"; - document_ = NULL; // Forget the stream, for safety. -} - - -void -StyledStreamWriter::writeValue( const Value &value ) -{ - switch ( value.type() ) - { - case nullValue: - pushValue( "null" ); - break; - case intValue: - pushValue( valueToString( value.asInt() ) ); - break; - case uintValue: - pushValue( valueToString( value.asUInt() ) ); - break; - case realValue: - pushValue( valueToString( value.asDouble() ) ); - break; - case stringValue: - pushValue( valueToQuotedString( value.asCString() ) ); - break; - case booleanValue: - pushValue( valueToString( value.asBool() ) ); - break; - case arrayValue: - writeArrayValue( value); - break; - case objectValue: - { - Value::Members members( value.getMemberNames() ); - if ( members.empty() ) - pushValue( "{}" ); - else - { - writeWithIndent( "{" ); - indent(); - Value::Members::iterator it = members.begin(); - while ( true ) - { - const std::string &name = *it; - const Value &childValue = value[name]; - writeCommentBeforeValue( childValue ); - writeWithIndent( valueToQuotedString( name.c_str() ) ); - *document_ << " : "; - writeValue( childValue ); - if ( ++it == members.end() ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "}" ); - } - } - break; - } -} - - -void -StyledStreamWriter::writeArrayValue( const Value &value ) -{ - unsigned size = value.size(); - if ( size == 0 ) - pushValue( "[]" ); - else - { - bool isArrayMultiLine = isMultineArray( value ); - if ( isArrayMultiLine ) - { - writeWithIndent( "[" ); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index =0; - while ( true ) - { - const Value &childValue = value[index]; - writeCommentBeforeValue( childValue ); - if ( hasChildValue ) - writeWithIndent( childValues_[index] ); - else - { - writeIndent(); - writeValue( childValue ); - } - if ( ++index == size ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "]" ); - } - else // output on a single line - { - assert( childValues_.size() == size ); - *document_ << "[ "; - for ( unsigned index =0; index < size; ++index ) - { - if ( index > 0 ) - *document_ << ", "; - *document_ << childValues_[index]; - } - *document_ << " ]"; - } - } -} - - -bool -StyledStreamWriter::isMultineArray( const Value &value ) -{ - int size = value.size(); - bool isMultiLine = size*3 >= rightMargin_ ; - childValues_.clear(); - for ( int index =0; index < size && !isMultiLine; ++index ) - { - const Value &childValue = value[index]; - isMultiLine = isMultiLine || - ( (childValue.isArray() || childValue.isObject()) && - childValue.size() > 0 ); - } - if ( !isMultiLine ) // check if line length > max line length - { - childValues_.reserve( size ); - addChildValues_ = true; - int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' - for ( int index =0; index < size && !isMultiLine; ++index ) - { - writeValue( value[index] ); - lineLength += int( childValues_[index].length() ); - isMultiLine = isMultiLine && hasCommentForValue( value[index] ); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - - -void -StyledStreamWriter::pushValue( const std::string &value ) -{ - if ( addChildValues_ ) - childValues_.push_back( value ); - else - *document_ << value; -} - - -void -StyledStreamWriter::writeIndent() -{ - /* - Some comments in this method would have been nice. ;-) - - if ( !document_.empty() ) - { - char last = document_[document_.length()-1]; - if ( last == ' ' ) // already indented - return; - if ( last != '\n' ) // Comments may add new-line - *document_ << '\n'; - } - */ - *document_ << '\n' << indentString_; -} - - -void -StyledStreamWriter::writeWithIndent( const std::string &value ) -{ - writeIndent(); - *document_ << value; -} - - -void -StyledStreamWriter::indent() -{ - indentString_ += indentation_; -} - - -void -StyledStreamWriter::unindent() -{ - assert( indentString_.size() >= indentation_.size() ); - indentString_.resize( indentString_.size() - indentation_.size() ); -} - - -void -StyledStreamWriter::writeCommentBeforeValue( const Value &root ) -{ - if ( !root.hasComment( commentBefore ) ) - return; - *document_ << normalizeEOL( root.getComment( commentBefore ) ); - *document_ << "\n"; -} - - -void -StyledStreamWriter::writeCommentAfterValueOnSameLine( const Value &root ) -{ - if ( root.hasComment( commentAfterOnSameLine ) ) - *document_ << " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); - - if ( root.hasComment( commentAfter ) ) - { - *document_ << "\n"; - *document_ << normalizeEOL( root.getComment( commentAfter ) ); - *document_ << "\n"; - } -} - - -bool -StyledStreamWriter::hasCommentForValue( const Value &value ) -{ - return value.hasComment( commentBefore ) - || value.hasComment( commentAfterOnSameLine ) - || value.hasComment( commentAfter ); -} - - -std::string -StyledStreamWriter::normalizeEOL( const std::string &text ) -{ - std::string normalized; - normalized.reserve( text.length() ); - const char *begin = text.c_str(); - const char *end = begin + text.length(); - const char *current = begin; - while ( current != end ) - { - char c = *current++; - if ( c == '\r' ) // mac or dos EOL - { - if ( *current == '\n' ) // convert dos EOL - ++current; - normalized += '\n'; - } - else // handle unix EOL & other char - normalized += c; - } - return normalized; -} - - -std::ostream& operator<<( std::ostream &sout, const Value &root ) -{ - Json::StyledStreamWriter writer; - writer.write(sout, root); - return sout; -} - - -} // namespace Json diff --git a/util/json/reader.h b/util/json/reader.h deleted file mode 100644 index ee1d6a24..00000000 --- a/util/json/reader.h +++ /dev/null @@ -1,196 +0,0 @@ -#ifndef CPPTL_JSON_READER_H_INCLUDED -# define CPPTL_JSON_READER_H_INCLUDED - -# include "features.h" -# include "value.h" -# include -# include -# include -# include - -namespace Json { - - /** \brief Unserialize a JSON document into a Value. - * - */ - class JSON_API Reader - { - public: - typedef char Char; - typedef const Char *Location; - - /** \brief Constructs a Reader allowing all features - * for parsing. - */ - Reader(); - - /** \brief Constructs a Reader allowing the specified feature set - * for parsing. - */ - Reader( const Features &features ); - - /** \brief Read a Value from a JSON document. - * \param document UTF-8 encoded string containing the document to read. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them back during - * serialization, \c false to discard comments. - * This parameter is ignored if Features::allowComments_ - * is \c false. - * \return \c true if the document was successfully parsed, \c false if an error occurred. - */ - bool parse( const std::string &document, - Value &root, - bool collectComments = true ); - - /** \brief Read a Value from a JSON document. - * \param document UTF-8 encoded string containing the document to read. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them back during - * serialization, \c false to discard comments. - * This parameter is ignored if Features::allowComments_ - * is \c false. - * \return \c true if the document was successfully parsed, \c false if an error occurred. - */ - bool parse( const char *beginDoc, const char *endDoc, - Value &root, - bool collectComments = true ); - - /// \brief Parse from input stream. - /// \see Json::operator>>(std::istream&, Json::Value&). - bool parse( std::istream &is, - Value &root, - bool collectComments = true ); - - /** \brief Returns a user friendly string that list errors in the parsed document. - * \return Formatted error message with the list of errors with their location in - * the parsed document. An empty string is returned if no error occurred - * during parsing. - */ - std::string getFormatedErrorMessages() const; - - private: - enum TokenType - { - tokenEndOfStream = 0, - tokenObjectBegin, - tokenObjectEnd, - tokenArrayBegin, - tokenArrayEnd, - tokenString, - tokenNumber, - tokenTrue, - tokenFalse, - tokenNull, - tokenArraySeparator, - tokenMemberSeparator, - tokenComment, - tokenError - }; - - class Token - { - public: - TokenType type_; - Location start_; - Location end_; - }; - - class ErrorInfo - { - public: - Token token_; - std::string message_; - Location extra_; - }; - - typedef std::deque Errors; - - bool expectToken( TokenType type, Token &token, const char *message ); - bool readToken( Token &token ); - void skipSpaces(); - bool match( Location pattern, - int patternLength ); - bool readComment(); - bool readCStyleComment(); - bool readCppStyleComment(); - bool readString(); - void readNumber(); - bool readValue(); - bool readObject( Token &token ); - bool readArray( Token &token ); - bool decodeNumber( Token &token ); - bool decodeString( Token &token ); - bool decodeString( Token &token, std::string &decoded ); - bool decodeDouble( Token &token ); - bool decodeUnicodeCodePoint( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ); - bool decodeUnicodeEscapeSequence( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ); - bool addError( const std::string &message, - Token &token, - Location extra = 0 ); - bool recoverFromError( TokenType skipUntilToken ); - bool addErrorAndRecover( const std::string &message, - Token &token, - TokenType skipUntilToken ); - void skipUntilSpace(); - Value ¤tValue(); - Char getNextChar(); - void getLocationLineAndColumn( Location location, - int &line, - int &column ) const; - std::string getLocationLineAndColumn( Location location ) const; - void addComment( Location begin, - Location end, - CommentPlacement placement ); - void skipCommentTokens( Token &token ); - - typedef std::stack Nodes; - Nodes nodes_; - Errors errors_; - std::string document_; - Location begin_; - Location end_; - Location current_; - Location lastValueEnd_; - Value *lastValue_; - std::string commentsBefore_; - Features features_; - bool collectComments_; - }; - - /** \brief Read from 'sin' into 'root'. - - Always keep comments from the input JSON. - - This can be used to read a file into a particular sub-object. - For example: - \code - Json::Value root; - cin >> root["dir"]["file"]; - cout << root; - \endcode - Result: - \verbatim - { - "dir": { - "file": { - // The input stream JSON would be nested here. - } - } - } - \endverbatim - \throw std::exception on parse error. - \see Json::operator<<() - */ - std::istream& operator>>( std::istream&, Value& ); - -} // namespace Json - -#endif // CPPTL_JSON_READER_H_INCLUDED diff --git a/util/json/value.h b/util/json/value.h deleted file mode 100644 index 58bfd88e..00000000 --- a/util/json/value.h +++ /dev/null @@ -1,1069 +0,0 @@ -#ifndef CPPTL_JSON_H_INCLUDED -# define CPPTL_JSON_H_INCLUDED - -# include "forwards.h" -# include -# include - -# ifndef JSON_USE_CPPTL_SMALLMAP -# include -# else -# include -# endif -# ifdef JSON_USE_CPPTL -# include -# endif - -/** \brief JSON (JavaScript Object Notation). - */ -namespace Json { - - /** \brief Type of the value held by a Value object. - */ - enum ValueType - { - nullValue = 0, ///< 'null' value - intValue, ///< signed integer value - uintValue, ///< unsigned integer value - realValue, ///< double value - stringValue, ///< UTF-8 string value - booleanValue, ///< bool value - arrayValue, ///< array value (ordered list) - objectValue ///< object value (collection of name/value pairs). - }; - - enum CommentPlacement - { - commentBefore = 0, ///< a comment placed on the line before a value - commentAfterOnSameLine, ///< a comment just after a value on the same line - commentAfter, ///< a comment on the line after a value (only make sense for root value) - numberOfCommentPlacement - }; - -//# ifdef JSON_USE_CPPTL -// typedef CppTL::AnyEnumerator EnumMemberNames; -// typedef CppTL::AnyEnumerator EnumValues; -//# endif - - /** \brief Lightweight wrapper to tag static string. - * - * Value constructor and objectValue member assignement takes advantage of the - * StaticString and avoid the cost of string duplication when storing the - * string or the member name. - * - * Example of usage: - * \code - * Json::Value aValue( StaticString("some text") ); - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode - */ - class JSON_API StaticString - { - public: - explicit StaticString( const char *czstring ) - : str_( czstring ) - { - } - - operator const char *() const - { - return str_; - } - - const char *c_str() const - { - return str_; - } - - private: - const char *str_; - }; - - /** \brief Represents a JSON value. - * - * This class is a discriminated union wrapper that can represents a: - * - signed integer [range: Value::minInt - Value::maxInt] - * - unsigned integer (range: 0 - Value::maxUInt) - * - double - * - UTF-8 string - * - boolean - * - 'null' - * - an ordered list of Value - * - collection of name/value pairs (javascript object) - * - * The type of the held value is represented by a #ValueType and - * can be obtained using type(). - * - * values of an #objectValue or #arrayValue can be accessed using operator[]() methods. - * Non const methods will automatically create the a #nullValue element - * if it does not exist. - * The sequence of an #arrayValue will be automatically resize and initialized - * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. - * - * The get() methods can be used to obtanis default value in the case the required element - * does not exist. - * - * It is possible to iterate over the list of a #objectValue values using - * the getMemberNames() method. - */ - class JSON_API Value - { - friend class ValueIteratorBase; -# ifdef JSON_VALUE_USE_INTERNAL_MAP - friend class ValueInternalLink; - friend class ValueInternalMap; -# endif - public: - typedef std::vector Members; - typedef ValueIterator iterator; - typedef ValueConstIterator const_iterator; - typedef Json::UInt UInt; - typedef Json::Int Int; - typedef UInt ArrayIndex; - - static const Value null; - static const Int minInt; - static const Int maxInt; - static const UInt maxUInt; - - private: -#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION -# ifndef JSON_VALUE_USE_INTERNAL_MAP - class CZString - { - public: - enum DuplicationPolicy - { - noDuplication = 0, - duplicate, - duplicateOnCopy - }; - CZString( int index ); - CZString( const char *cstr, DuplicationPolicy allocate ); - CZString( const CZString &other ); - ~CZString(); - CZString &operator =( const CZString &other ); - bool operator<( const CZString &other ) const; - bool operator==( const CZString &other ) const; - int index() const; - const char *c_str() const; - bool isStaticString() const; - private: - void swap( CZString &other ); - const char *cstr_; - int index_; - }; - - public: -# ifndef JSON_USE_CPPTL_SMALLMAP - typedef std::map ObjectValues; -# else - typedef CppTL::SmallMap ObjectValues; -# endif // ifndef JSON_USE_CPPTL_SMALLMAP -# endif // ifndef JSON_VALUE_USE_INTERNAL_MAP -#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - - public: - /** \brief Create a default Value of the given type. - - This is a very useful constructor. - To create an empty array, pass arrayValue. - To create an empty object, pass objectValue. - Another Value can then be set to this one by assignment. - This is useful since clear() and resize() will not alter types. - - Examples: - \code - Json::Value null_value; // null - Json::Value arr_value(Json::arrayValue); // [] - Json::Value obj_value(Json::objectValue); // {} - \endcode - */ - Value( ValueType type = nullValue ); - Value( Int value ); - Value( UInt value ); - Value( double value ); - Value( const char *value ); - Value( const char *beginValue, const char *endValue ); - /** \brief Constructs a value from a static string. - - * Like other value string constructor but do not duplicate the string for - * internal storage. The given string must remain alive after the call to this - * constructor. - * Example of usage: - * \code - * Json::Value aValue( StaticString("some text") ); - * \endcode - */ - Value( const StaticString &value ); - Value( const std::string &value ); -# ifdef JSON_USE_CPPTL - Value( const CppTL::ConstString &value ); -# endif - Value( bool value ); - Value( const Value &other ); - ~Value(); - - Value &operator=( const Value &other ); - /// Swap values. - /// \note Currently, comments are intentionally not swapped, for - /// both logic and efficiency. - void swap( Value &other ); - - ValueType type() const; - - bool operator <( const Value &other ) const; - bool operator <=( const Value &other ) const; - bool operator >=( const Value &other ) const; - bool operator >( const Value &other ) const; - - bool operator ==( const Value &other ) const; - bool operator !=( const Value &other ) const; - - int compare( const Value &other ); - - const char *asCString() const; - std::string asString() const; -# ifdef JSON_USE_CPPTL - CppTL::ConstString asConstString() const; -# endif - Int asInt() const; - UInt asUInt() const; - double asDouble() const; - bool asBool() const; - - bool isNull() const; - bool isBool() const; - bool isInt() const; - bool isUInt() const; - bool isIntegral() const; - bool isDouble() const; - bool isNumeric() const; - bool isString() const; - bool isArray() const; - bool isObject() const; - - bool isConvertibleTo( ValueType other ) const; - - /// Number of values in array or object - UInt size() const; - - /// \brief Return true if empty array, empty object, or null; - /// otherwise, false. - bool empty() const; - - /// Return isNull() - bool operator!() const; - - /// Remove all object members and array elements. - /// \pre type() is arrayValue, objectValue, or nullValue - /// \post type() is unchanged - void clear(); - - /// Resize the array to size elements. - /// New elements are initialized to null. - /// May only be called on nullValue or arrayValue. - /// \pre type() is arrayValue or nullValue - /// \post type() is arrayValue - void resize( UInt size ); - - /// Access an array element (zero based index ). - /// If the array contains less than index element, then null value are inserted - /// in the array so that its size is index+1. - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - Value &operator[]( UInt index ); - /// Access an array element (zero based index ) - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - const Value &operator[]( UInt index ) const; - /// If the array contains at least index+1 elements, returns the element value, - /// otherwise returns defaultValue. - Value get( UInt index, - const Value &defaultValue ) const; - /// Return true if index < size(). - bool isValidIndex( UInt index ) const; - /// \brief Append value to array at the end. - /// - /// Equivalent to jsonvalue[jsonvalue.size()] = value; - Value &append( const Value &value ); - - /// Access an object value by name, create a null member if it does not exist. - Value &operator[]( const char *key ); - /// Access an object value by name, returns null if there is no member with that name. - const Value &operator[]( const char *key ) const; - /// Access an object value by name, create a null member if it does not exist. - Value &operator[]( const std::string &key ); - /// Access an object value by name, returns null if there is no member with that name. - const Value &operator[]( const std::string &key ) const; - /** \brief Access an object value by name, create a null member if it does not exist. - - * If the object as no entry for that name, then the member name used to store - * the new entry is not duplicated. - * Example of use: - * \code - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode - */ - Value &operator[]( const StaticString &key ); -# ifdef JSON_USE_CPPTL - /// Access an object value by name, create a null member if it does not exist. - Value &operator[]( const CppTL::ConstString &key ); - /// Access an object value by name, returns null if there is no member with that name. - const Value &operator[]( const CppTL::ConstString &key ) const; -# endif - /// Return the member named key if it exist, defaultValue otherwise. - Value get( const char *key, - const Value &defaultValue ) const; - /// Return the member named key if it exist, defaultValue otherwise. - Value get( const std::string &key, - const Value &defaultValue ) const; -# ifdef JSON_USE_CPPTL - /// Return the member named key if it exist, defaultValue otherwise. - Value get( const CppTL::ConstString &key, - const Value &defaultValue ) const; -# endif - /// \brief Remove and return the named member. - /// - /// Do nothing if it did not exist. - /// \return the removed Value, or null. - /// \pre type() is objectValue or nullValue - /// \post type() is unchanged - Value removeMember( const char* key ); - /// Same as removeMember(const char*) - Value removeMember( const std::string &key ); - - /// Return true if the object has a member named key. - bool isMember( const char *key ) const; - /// Return true if the object has a member named key. - bool isMember( const std::string &key ) const; -# ifdef JSON_USE_CPPTL - /// Return true if the object has a member named key. - bool isMember( const CppTL::ConstString &key ) const; -# endif - - /// \brief Return a list of the member names. - /// - /// If null, return an empty list. - /// \pre type() is objectValue or nullValue - /// \post if type() was nullValue, it remains nullValue - Members getMemberNames() const; - -//# ifdef JSON_USE_CPPTL -// EnumMemberNames enumMemberNames() const; -// EnumValues enumValues() const; -//# endif - - /// Comments must be //... or /* ... */ - void setComment( const char *comment, - CommentPlacement placement ); - /// Comments must be //... or /* ... */ - void setComment( const std::string &comment, - CommentPlacement placement ); - bool hasComment( CommentPlacement placement ) const; - /// Include delimiters and embedded newlines. - std::string getComment( CommentPlacement placement ) const; - - std::string toStyledString() const; - - const_iterator begin() const; - const_iterator end() const; - - iterator begin(); - iterator end(); - - private: - Value &resolveReference( const char *key, - bool isStatic ); - -# ifdef JSON_VALUE_USE_INTERNAL_MAP - inline bool isItemAvailable() const - { - return itemIsUsed_ == 0; - } - - inline void setItemUsed( bool isUsed = true ) - { - itemIsUsed_ = isUsed ? 1 : 0; - } - - inline bool isMemberNameStatic() const - { - return memberNameIsStatic_ == 0; - } - - inline void setMemberNameIsStatic( bool isStatic ) - { - memberNameIsStatic_ = isStatic ? 1 : 0; - } -# endif // # ifdef JSON_VALUE_USE_INTERNAL_MAP - - private: - struct CommentInfo - { - CommentInfo(); - ~CommentInfo(); - - void setComment( const char *text ); - - char *comment_; - }; - - //struct MemberNamesTransform - //{ - // typedef const char *result_type; - // const char *operator()( const CZString &name ) const - // { - // return name.c_str(); - // } - //}; - - union ValueHolder - { - Int int_; - UInt uint_; - double real_; - bool bool_; - char *string_; -# ifdef JSON_VALUE_USE_INTERNAL_MAP - ValueInternalArray *array_; - ValueInternalMap *map_; -#else - ObjectValues *map_; -# endif - } value_; - ValueType type_ : 8; - int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. -# ifdef JSON_VALUE_USE_INTERNAL_MAP - unsigned int itemIsUsed_ : 1; // used by the ValueInternalMap container. - int memberNameIsStatic_ : 1; // used by the ValueInternalMap container. -# endif - CommentInfo *comments_; - }; - - - /** \brief Experimental and untested: represents an element of the "path" to access a node. - */ - class PathArgument - { - public: - friend class Path; - - PathArgument(); - PathArgument( UInt index ); - PathArgument( const char *key ); - PathArgument( const std::string &key ); - - private: - enum Kind - { - kindNone = 0, - kindIndex, - kindKey - }; - std::string key_; - UInt index_; - Kind kind_; - }; - - /** \brief Experimental and untested: represents a "path" to access a node. - * - * Syntax: - * - "." => root node - * - ".[n]" => elements at index 'n' of root node (an array value) - * - ".name" => member named 'name' of root node (an object value) - * - ".name1.name2.name3" - * - ".[0][1][2].name1[3]" - * - ".%" => member name is provided as parameter - * - ".[%]" => index is provied as parameter - */ - class Path - { - public: - Path( const std::string &path, - const PathArgument &a1 = PathArgument(), - const PathArgument &a2 = PathArgument(), - const PathArgument &a3 = PathArgument(), - const PathArgument &a4 = PathArgument(), - const PathArgument &a5 = PathArgument() ); - - const Value &resolve( const Value &root ) const; - Value resolve( const Value &root, - const Value &defaultValue ) const; - /// Creates the "path" to access the specified node and returns a reference on the node. - Value &make( Value &root ) const; - - private: - typedef std::vector InArgs; - typedef std::vector Args; - - void makePath( const std::string &path, - const InArgs &in ); - void addPathInArg( const std::string &path, - const InArgs &in, - InArgs::const_iterator &itInArg, - PathArgument::Kind kind ); - void invalidPath( const std::string &path, - int location ); - - Args args_; - }; - - /** \brief Experimental do not use: Allocator to customize member name and string value memory management done by Value. - * - * - makeMemberName() and releaseMemberName() are called to respectively duplicate and - * free an Json::objectValue member name. - * - duplicateStringValue() and releaseStringValue() are called similarly to - * duplicate and free a Json::stringValue value. - */ - class ValueAllocator - { - public: - enum { unknown = (unsigned)-1 }; - - virtual ~ValueAllocator(); - - virtual char *makeMemberName( const char *memberName ) = 0; - virtual void releaseMemberName( char *memberName ) = 0; - virtual char *duplicateStringValue( const char *value, - unsigned int length = unknown ) = 0; - virtual void releaseStringValue( char *value ) = 0; - }; - -#ifdef JSON_VALUE_USE_INTERNAL_MAP - /** \brief Allocator to customize Value internal map. - * Below is an example of a simple implementation (default implementation actually - * use memory pool for speed). - * \code - class DefaultValueMapAllocator : public ValueMapAllocator - { - public: // overridden from ValueMapAllocator - virtual ValueInternalMap *newMap() - { - return new ValueInternalMap(); - } - - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) - { - return new ValueInternalMap( other ); - } - - virtual void destructMap( ValueInternalMap *map ) - { - delete map; - } - - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) - { - return new ValueInternalLink[size]; - } - - virtual void releaseMapBuckets( ValueInternalLink *links ) - { - delete [] links; - } - - virtual ValueInternalLink *allocateMapLink() - { - return new ValueInternalLink(); - } - - virtual void releaseMapLink( ValueInternalLink *link ) - { - delete link; - } - }; - * \endcode - */ - class JSON_API ValueMapAllocator - { - public: - virtual ~ValueMapAllocator(); - virtual ValueInternalMap *newMap() = 0; - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) = 0; - virtual void destructMap( ValueInternalMap *map ) = 0; - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) = 0; - virtual void releaseMapBuckets( ValueInternalLink *links ) = 0; - virtual ValueInternalLink *allocateMapLink() = 0; - virtual void releaseMapLink( ValueInternalLink *link ) = 0; - }; - - /** \brief ValueInternalMap hash-map bucket chain link (for internal use only). - * \internal previous_ & next_ allows for bidirectional traversal. - */ - class JSON_API ValueInternalLink - { - public: - enum { itemPerLink = 6 }; // sizeof(ValueInternalLink) = 128 on 32 bits architecture. - enum InternalFlags { - flagAvailable = 0, - flagUsed = 1 - }; - - ValueInternalLink(); - - ~ValueInternalLink(); - - Value items_[itemPerLink]; - char *keys_[itemPerLink]; - ValueInternalLink *previous_; - ValueInternalLink *next_; - }; - - - /** \brief A linked page based hash-table implementation used internally by Value. - * \internal ValueInternalMap is a tradional bucket based hash-table, with a linked - * list in each bucket to handle collision. There is an addional twist in that - * each node of the collision linked list is a page containing a fixed amount of - * value. This provides a better compromise between memory usage and speed. - * - * Each bucket is made up of a chained list of ValueInternalLink. The last - * link of a given bucket can be found in the 'previous_' field of the following bucket. - * The last link of the last bucket is stored in tailLink_ as it has no following bucket. - * Only the last link of a bucket may contains 'available' item. The last link always - * contains at least one element unless is it the bucket one very first link. - */ - class JSON_API ValueInternalMap - { - friend class ValueIteratorBase; - friend class Value; - public: - typedef unsigned int HashKey; - typedef unsigned int BucketIndex; - -# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - struct IteratorState - { - IteratorState() - : map_(0) - , link_(0) - , itemIndex_(0) - , bucketIndex_(0) - { - } - ValueInternalMap *map_; - ValueInternalLink *link_; - BucketIndex itemIndex_; - BucketIndex bucketIndex_; - }; -# endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - - ValueInternalMap(); - ValueInternalMap( const ValueInternalMap &other ); - ValueInternalMap &operator =( const ValueInternalMap &other ); - ~ValueInternalMap(); - - void swap( ValueInternalMap &other ); - - BucketIndex size() const; - - void clear(); - - bool reserveDelta( BucketIndex growth ); - - bool reserve( BucketIndex newItemCount ); - - const Value *find( const char *key ) const; - - Value *find( const char *key ); - - Value &resolveReference( const char *key, - bool isStatic ); - - void remove( const char *key ); - - void doActualRemove( ValueInternalLink *link, - BucketIndex index, - BucketIndex bucketIndex ); - - ValueInternalLink *&getLastLinkInBucket( BucketIndex bucketIndex ); - - Value &setNewItem( const char *key, - bool isStatic, - ValueInternalLink *link, - BucketIndex index ); - - Value &unsafeAdd( const char *key, - bool isStatic, - HashKey hashedKey ); - - HashKey hash( const char *key ) const; - - int compare( const ValueInternalMap &other ) const; - - private: - void makeBeginIterator( IteratorState &it ) const; - void makeEndIterator( IteratorState &it ) const; - static bool equals( const IteratorState &x, const IteratorState &other ); - static void increment( IteratorState &iterator ); - static void incrementBucket( IteratorState &iterator ); - static void decrement( IteratorState &iterator ); - static const char *key( const IteratorState &iterator ); - static const char *key( const IteratorState &iterator, bool &isStatic ); - static Value &value( const IteratorState &iterator ); - static int distance( const IteratorState &x, const IteratorState &y ); - - private: - ValueInternalLink *buckets_; - ValueInternalLink *tailLink_; - BucketIndex bucketsSize_; - BucketIndex itemCount_; - }; - - /** \brief A simplified deque implementation used internally by Value. - * \internal - * It is based on a list of fixed "page", each page contains a fixed number of items. - * Instead of using a linked-list, a array of pointer is used for fast item look-up. - * Look-up for an element is as follow: - * - compute page index: pageIndex = itemIndex / itemsPerPage - * - look-up item in page: pages_[pageIndex][itemIndex % itemsPerPage] - * - * Insertion is amortized constant time (only the array containing the index of pointers - * need to be reallocated when items are appended). - */ - class JSON_API ValueInternalArray - { - friend class Value; - friend class ValueIteratorBase; - public: - enum { itemsPerPage = 8 }; // should be a power of 2 for fast divide and modulo. - typedef Value::ArrayIndex ArrayIndex; - typedef unsigned int PageIndex; - -# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - struct IteratorState // Must be a POD - { - IteratorState() - : array_(0) - , currentPageIndex_(0) - , currentItemIndex_(0) - { - } - ValueInternalArray *array_; - Value **currentPageIndex_; - unsigned int currentItemIndex_; - }; -# endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - - ValueInternalArray(); - ValueInternalArray( const ValueInternalArray &other ); - ValueInternalArray &operator =( const ValueInternalArray &other ); - ~ValueInternalArray(); - void swap( ValueInternalArray &other ); - - void clear(); - void resize( ArrayIndex newSize ); - - Value &resolveReference( ArrayIndex index ); - - Value *find( ArrayIndex index ) const; - - ArrayIndex size() const; - - int compare( const ValueInternalArray &other ) const; - - private: - static bool equals( const IteratorState &x, const IteratorState &other ); - static void increment( IteratorState &iterator ); - static void decrement( IteratorState &iterator ); - static Value &dereference( const IteratorState &iterator ); - static Value &unsafeDereference( const IteratorState &iterator ); - static int distance( const IteratorState &x, const IteratorState &y ); - static ArrayIndex indexOf( const IteratorState &iterator ); - void makeBeginIterator( IteratorState &it ) const; - void makeEndIterator( IteratorState &it ) const; - void makeIterator( IteratorState &it, ArrayIndex index ) const; - - void makeIndexValid( ArrayIndex index ); - - Value **pages_; - ArrayIndex size_; - PageIndex pageCount_; - }; - - /** \brief Experimental: do not use. Allocator to customize Value internal array. - * Below is an example of a simple implementation (actual implementation use - * memory pool). - \code -class DefaultValueArrayAllocator : public ValueArrayAllocator -{ -public: // overridden from ValueArrayAllocator - virtual ~DefaultValueArrayAllocator() - { - } - - virtual ValueInternalArray *newArray() - { - return new ValueInternalArray(); - } - - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) - { - return new ValueInternalArray( other ); - } - - virtual void destruct( ValueInternalArray *array ) - { - delete array; - } - - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) - { - ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; - if ( minNewIndexCount > newIndexCount ) - newIndexCount = minNewIndexCount; - void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); - if ( !newIndexes ) - throw std::bad_alloc(); - indexCount = newIndexCount; - indexes = static_cast( newIndexes ); - } - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) - { - if ( indexes ) - free( indexes ); - } - - virtual Value *allocateArrayPage() - { - return static_cast( malloc( sizeof(Value) * ValueInternalArray::itemsPerPage ) ); - } - - virtual void releaseArrayPage( Value *value ) - { - if ( value ) - free( value ); - } -}; - \endcode - */ - class JSON_API ValueArrayAllocator - { - public: - virtual ~ValueArrayAllocator(); - virtual ValueInternalArray *newArray() = 0; - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) = 0; - virtual void destructArray( ValueInternalArray *array ) = 0; - /** \brief Reallocate array page index. - * Reallocates an array of pointer on each page. - * \param indexes [input] pointer on the current index. May be \c NULL. - * [output] pointer on the new index of at least - * \a minNewIndexCount pages. - * \param indexCount [input] current number of pages in the index. - * [output] number of page the reallocated index can handle. - * \b MUST be >= \a minNewIndexCount. - * \param minNewIndexCount Minimum number of page the new index must be able to - * handle. - */ - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) = 0; - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) = 0; - virtual Value *allocateArrayPage() = 0; - virtual void releaseArrayPage( Value *value ) = 0; - }; -#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP - - - /** \brief base class for Value iterators. - * - */ - class ValueIteratorBase - { - public: - typedef unsigned int size_t; - typedef int difference_type; - typedef ValueIteratorBase SelfType; - - ValueIteratorBase(); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - explicit ValueIteratorBase( const Value::ObjectValues::iterator ¤t ); -#else - ValueIteratorBase( const ValueInternalArray::IteratorState &state ); - ValueIteratorBase( const ValueInternalMap::IteratorState &state ); -#endif - - bool operator ==( const SelfType &other ) const - { - return isEqual( other ); - } - - bool operator !=( const SelfType &other ) const - { - return !isEqual( other ); - } - - difference_type operator -( const SelfType &other ) const - { - return computeDistance( other ); - } - - /// Return either the index or the member name of the referenced value as a Value. - Value key() const; - - /// Return the index of the referenced Value. -1 if it is not an arrayValue. - UInt index() const; - - /// Return the member name of the referenced Value. "" if it is not an objectValue. - const char *memberName() const; - - protected: - Value &deref() const; - - void increment(); - - void decrement(); - - difference_type computeDistance( const SelfType &other ) const; - - bool isEqual( const SelfType &other ) const; - - void copy( const SelfType &other ); - - private: -#ifndef JSON_VALUE_USE_INTERNAL_MAP - Value::ObjectValues::iterator current_; - // Indicates that iterator is for a null value. - bool isNull_; -#else - union - { - ValueInternalArray::IteratorState array_; - ValueInternalMap::IteratorState map_; - } iterator_; - bool isArray_; -#endif - }; - - /** \brief const iterator for object and array value. - * - */ - class ValueConstIterator : public ValueIteratorBase - { - friend class Value; - public: - typedef unsigned int size_t; - typedef int difference_type; - typedef const Value &reference; - typedef const Value *pointer; - typedef ValueConstIterator SelfType; - - ValueConstIterator(); - private: - /*! \internal Use by Value to create an iterator. - */ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - explicit ValueConstIterator( const Value::ObjectValues::iterator ¤t ); -#else - ValueConstIterator( const ValueInternalArray::IteratorState &state ); - ValueConstIterator( const ValueInternalMap::IteratorState &state ); -#endif - public: - SelfType &operator =( const ValueIteratorBase &other ); - - SelfType operator++( int ) - { - SelfType temp( *this ); - ++*this; - return temp; - } - - SelfType operator--( int ) - { - SelfType temp( *this ); - --*this; - return temp; - } - - SelfType &operator--() - { - decrement(); - return *this; - } - - SelfType &operator++() - { - increment(); - return *this; - } - - reference operator *() const - { - return deref(); - } - }; - - - /** \brief Iterator for object and array value. - */ - class ValueIterator : public ValueIteratorBase - { - friend class Value; - public: - typedef unsigned int size_t; - typedef int difference_type; - typedef Value &reference; - typedef Value *pointer; - typedef ValueIterator SelfType; - - ValueIterator(); - ValueIterator( const ValueConstIterator &other ); - ValueIterator( const ValueIterator &other ); - private: - /*! \internal Use by Value to create an iterator. - */ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - explicit ValueIterator( const Value::ObjectValues::iterator ¤t ); -#else - ValueIterator( const ValueInternalArray::IteratorState &state ); - ValueIterator( const ValueInternalMap::IteratorState &state ); -#endif - public: - - SelfType &operator =( const SelfType &other ); - - SelfType operator++( int ) - { - SelfType temp( *this ); - ++*this; - return temp; - } - - SelfType operator--( int ) - { - SelfType temp( *this ); - --*this; - return temp; - } - - SelfType &operator--() - { - decrement(); - return *this; - } - - SelfType &operator++() - { - increment(); - return *this; - } - - reference operator *() const - { - return deref(); - } - }; - - -} // namespace Json - - -#endif // CPPTL_JSON_H_INCLUDED diff --git a/util/json/writer.h b/util/json/writer.h deleted file mode 100644 index 5f4b83be..00000000 --- a/util/json/writer.h +++ /dev/null @@ -1,174 +0,0 @@ -#ifndef JSON_WRITER_H_INCLUDED -# define JSON_WRITER_H_INCLUDED - -# include "value.h" -# include -# include -# include - -namespace Json { - - class Value; - - /** \brief Abstract class for writers. - */ - class JSON_API Writer - { - public: - virtual ~Writer(); - - virtual std::string write( const Value &root ) = 0; - }; - - /** \brief Outputs a Value in JSON format without formatting (not human friendly). - * - * The JSON document is written in a single line. It is not intended for 'human' consumption, - * but may be usefull to support feature such as RPC where bandwith is limited. - * \sa Reader, Value - */ - class JSON_API FastWriter : public Writer - { - public: - FastWriter(); - virtual ~FastWriter(){} - - void enableYAMLCompatibility(); - - public: // overridden from Writer - virtual std::string write( const Value &root ); - - private: - void writeValue( const Value &value ); - - std::string document_; - bool yamlCompatiblityEnabled_; - }; - - /** \brief Writes a Value in JSON format in a human friendly way. - * - * The rules for line break and indent are as follow: - * - Object value: - * - if empty then print {} without indent and line break - * - if not empty the print '{', line break & indent, print one value per line - * and then unindent and line break and print '}'. - * - Array value: - * - if empty then print [] without indent and line break - * - if the array contains no object value, empty array or some other value types, - * and all the values fit on one lines, then print the array on a single line. - * - otherwise, it the values do not fit on one line, or the array contains - * object or non empty array, then print one value per line. - * - * If the Value have comments then they are outputed according to their #CommentPlacement. - * - * \sa Reader, Value, Value::setComment() - */ - class JSON_API StyledWriter: public Writer - { - public: - StyledWriter(); - virtual ~StyledWriter(){} - - public: // overridden from Writer - /** \brief Serialize a Value in JSON format. - * \param root Value to serialize. - * \return String containing the JSON document that represents the root value. - */ - virtual std::string write( const Value &root ); - - private: - void writeValue( const Value &value ); - void writeArrayValue( const Value &value ); - bool isMultineArray( const Value &value ); - void pushValue( const std::string &value ); - void writeIndent(); - void writeWithIndent( const std::string &value ); - void indent(); - void unindent(); - void writeCommentBeforeValue( const Value &root ); - void writeCommentAfterValueOnSameLine( const Value &root ); - bool hasCommentForValue( const Value &value ); - static std::string normalizeEOL( const std::string &text ); - - typedef std::vector ChildValues; - - ChildValues childValues_; - std::string document_; - std::string indentString_; - int rightMargin_; - int indentSize_; - bool addChildValues_; - }; - - /** \brief Writes a Value in JSON format in a human friendly way, - to a stream rather than to a string. - * - * The rules for line break and indent are as follow: - * - Object value: - * - if empty then print {} without indent and line break - * - if not empty the print '{', line break & indent, print one value per line - * and then unindent and line break and print '}'. - * - Array value: - * - if empty then print [] without indent and line break - * - if the array contains no object value, empty array or some other value types, - * and all the values fit on one lines, then print the array on a single line. - * - otherwise, it the values do not fit on one line, or the array contains - * object or non empty array, then print one value per line. - * - * If the Value have comments then they are outputed according to their #CommentPlacement. - * - * \param indentation Each level will be indented by this amount extra. - * \sa Reader, Value, Value::setComment() - */ - class JSON_API StyledStreamWriter - { - public: - StyledStreamWriter( std::string indentation="\t" ); - ~StyledStreamWriter(){} - - public: - /** \brief Serialize a Value in JSON format. - * \param out Stream to write to. (Can be ostringstream, e.g.) - * \param root Value to serialize. - * \note There is no point in deriving from Writer, since write() should not return a value. - */ - void write( std::ostream &out, const Value &root ); - - private: - void writeValue( const Value &value ); - void writeArrayValue( const Value &value ); - bool isMultineArray( const Value &value ); - void pushValue( const std::string &value ); - void writeIndent(); - void writeWithIndent( const std::string &value ); - void indent(); - void unindent(); - void writeCommentBeforeValue( const Value &root ); - void writeCommentAfterValueOnSameLine( const Value &root ); - bool hasCommentForValue( const Value &value ); - static std::string normalizeEOL( const std::string &text ); - - typedef std::vector ChildValues; - - ChildValues childValues_; - std::ostream* document_; - std::string indentString_; - int rightMargin_; - std::string indentation_; - bool addChildValues_; - }; - - std::string JSON_API valueToString( Int value ); - std::string JSON_API valueToString( UInt value ); - std::string JSON_API valueToString( double value ); - std::string JSON_API valueToString( bool value ); - std::string JSON_API valueToQuotedString( const char *value ); - - /// \brief Output using the StyledStreamWriter. - /// \see Json::operator>>() - std::ostream& operator<<( std::ostream&, const Value &root ); - -} // namespace Json - - - -#endif // JSON_WRITER_H_INCLUDED diff --git a/util/util.cpp b/util/procs.cpp similarity index 55% rename from util/util.cpp rename to util/procs.cpp index 773248f7..83e3047d 100644 --- a/util/util.cpp +++ b/util/procs.cpp @@ -1,7 +1,7 @@ -/// \file util.cpp -/// Contains generic functions for managing processes and configuration. +/// \file procs.cpp +/// Contains generic functions for managing processes. -#include "util.h" +#include "procs.h" #include #include #include @@ -16,37 +16,12 @@ #include #include #include -#include #include #include -#include std::map Util::Procs::plist; bool Util::Procs::handler_set = false; -/// Sets the current process' running user -void Util::setUser(std::string username){ - 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; - }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, "Changed user to %s\n", username.c_str()); - #endif - } - } - } -} - /// Used internally to capture child signals and update plist. void Util::Procs::childsig_handler(int signum){ if (signum != SIGCHLD){return;} @@ -263,108 +238,3 @@ std::string Util::Procs::getName(pid_t name){ } return ""; } - -/// Creates a new configuration manager. -Util::Config::Config(){ - listen_port = 4242; - daemon_mode = true; - interface = "0.0.0.0"; - configfile = "/etc/ddvtech.conf"; - username = "root"; - ignore_daemon = false; - ignore_interface = false; - ignore_port = false; - ignore_user = false; -} - -/// Parses commandline arguments. -/// Calls exit if an unknown option is encountered, printing a help message. -/// confsection must be either already set or never be set at all when this function is called. -/// In other words: do not change confsection after calling this function. -void Util::Config::parseArgs(int argc, char ** argv){ - int opt = 0; - static const char *optString = "ndvp:i:u:c:h?"; - static const struct option longOpts[] = { - {"help",0,0,'h'}, - {"port",1,0,'p'}, - {"interface",1,0,'i'}, - {"username",1,0,'u'}, - {"no-daemon",0,0,'n'}, - {"daemon",0,0,'d'}, - {"configfile",1,0,'c'}, - {"version",0,0,'v'} - }; - while ((opt = getopt_long(argc, argv, optString, longOpts, 0)) != -1){ - switch (opt){ - 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 'v': - printf("%s\n", TOSTRING(VERSION)); - exit(1); - break; - case 'h': - case '?': - std::string doingdaemon = "true"; - if (!daemon_mode){doingdaemon = "false";} - if (confsection == ""){ - printf("Options: -h[elp], -?, -v[ersion], -n[odaemon], -d[aemon], -p[ort] VAL, -i[nterface] VAL, -u[sername] VAL\n"); - printf("Defaults:\n interface: %s\n port: %i\n daemon mode: %s\n username: %s\n", interface.c_str(), listen_port, doingdaemon.c_str(), username.c_str()); - }else{ - printf("Options: -h[elp], -?, -v[ersion], -n[odaemon], -d[aemon], -p[ort] VAL, -i[nterface] VAL, -c[onfigfile] VAL, -u[sername] VAL\n"); - printf("Defaults:\n interface: %s\n port: %i\n daemon mode: %s\n configfile: %s\n username: %s\n", interface.c_str(), listen_port, doingdaemon.c_str(), configfile.c_str(), username.c_str()); - 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", confsection.c_str()); - } - printf("This is %s version %s\n", argv[0], TOSTRING(VERSION)); - exit(1); - break; - } - }//commandline options parser -} - -/// Parses the configuration file at configfile, if it exists. -/// Assumes confsection is set. -void Util::Config::parseFile(){ - 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 == confsection){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 -} - -/// Will turn the current process into a daemon. -/// Works by calling daemon(1,0): -/// Does not change directory to root. -/// Does redirect output to /dev/null -void Util::Daemonize(){ - #if DEBUG >= 3 - fprintf(stderr, "Going into background mode...\n"); - #endif - daemon(1, 0); -} diff --git a/util/util.h b/util/procs.h similarity index 55% rename from util/util.h rename to util/procs.h index a92b67f0..3ec60902 100644 --- a/util/util.h +++ b/util/procs.h @@ -1,13 +1,10 @@ -/// \file util.h -/// Contains generic function headers for managing processes and configuration. +/// \file procs.h +/// Contains generic function headers for managing processes. #include #include #include -#define STRINGIFY(x) #x -#define TOSTRING(x) STRINGIFY(x) - /// Contains utility code, not directly related to streaming media namespace Util{ @@ -31,29 +28,4 @@ namespace Util{ static std::string getName(pid_t name); }; - /// Will set the active user to the named username. - void setUser(std::string user); - - /// Deals with parsing configuration from files or commandline options. - class Config{ - private: - bool ignore_daemon; - bool ignore_interface; - bool ignore_port; - bool ignore_user; - public: - std::string confsection; - std::string configfile; - bool daemon_mode; - std::string interface; - int listen_port; - std::string username; - Config(); - void parseArgs(int argc, char ** argv); - void parseFile(); - }; - - /// Will turn the current process into a daemon. - void Daemonize(); - }; diff --git a/util/server_setup.cpp b/util/server_setup.cpp index ae15ff87..47f015ac 100644 --- a/util/server_setup.cpp +++ b/util/server_setup.cpp @@ -22,7 +22,7 @@ #endif #include "socket.h" //Socket library -#include "util.h" //utilities for process spawning and config management +#include "config.h" //utilities for config management #include #include #include From 4cd8641e500c8372339c26448003c4c5b516fcdf Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 8 Apr 2012 22:56:51 +0200 Subject: [PATCH 11/24] First multithreaded version of the Buffer process. --- Buffer/Makefile | 4 +- Buffer/main.cpp | 155 +++++----- util/dtsc.cpp | 3 +- util/tinythread.cpp | 287 ++++++++++++++++++ util/tinythread.h | 696 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1074 insertions(+), 71 deletions(-) create mode 100644 util/tinythread.cpp create mode 100644 util/tinythread.h diff --git a/Buffer/Makefile b/Buffer/Makefile index c6ae0cdc..89e4f902 100644 --- a/Buffer/Makefile +++ b/Buffer/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/json.cpp ../util/socket.cpp ../util/dtsc.cpp +SRC = main.cpp ../util/json.cpp ../util/socket.cpp ../util/dtsc.cpp ../util/tinythread.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Buffer INCLUDES = @@ -8,7 +8,7 @@ CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar -LIBS = +LIBS = -lpthread .SUFFIXES: .cpp .PHONY: clean default default: $(OUT) diff --git a/Buffer/main.cpp b/Buffer/main.cpp index 630c5eb9..c7c57ef9 100644 --- a/Buffer/main.cpp +++ b/Buffer/main.cpp @@ -15,6 +15,7 @@ #include "../util/dtsc.h" //DTSC support #include "../util/socket.h" //Socket lib #include "../util/json.h" +#include "../util/tinythread.h" /// Holds all code unique to the Buffer. namespace Buffer{ @@ -38,7 +39,9 @@ namespace Buffer{ } DTSC::Stream * Strm = 0; - + std::string waiting_ip = ""; ///< IP address for media push. + Socket::Connection ip_input; ///< Connection used for media push. + /// Converts a stats line to up, down, host, connector and conntime values. class Stats{ public: @@ -81,6 +84,7 @@ namespace Buffer{ /// Keeps track of what buffer users are using and the connection status. class user{ public: + tthread::thread * Thread; ///< Holds the thread dealing with this user. DTSC::Ring * myRing; ///< Ring of the buffer for this user. int MyNum; ///< User ID of this user. std::string MyStr; ///< User ID of this user as a string. @@ -104,6 +108,7 @@ namespace Buffer{ curr_down = 0; currsend = 0; myRing = 0; + Thread = 0; std::cout << "User " << MyNum << " connected" << std::endl; }//constructor /// Drops held DTSC::Ring class, if one is held. @@ -113,8 +118,10 @@ namespace Buffer{ /// 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(); + if (S.connected()){S.close();} + if (Thread != 0){ + if (Thread->joinable()){Thread->join();} + Thread = 0; } Storage["curr"].removeMember(MyStr); Storage["log"][MyStr]["connector"] = lastStats.connector; @@ -148,7 +155,7 @@ namespace Buffer{ std::cout << "Warning: User was send corrupt video data and send to the next keyframe!" << std::endl; Strm->dropRing(myRing); myRing = Strm->getRing(); - } + } currsend = 0; //try to complete a send @@ -162,6 +169,60 @@ namespace Buffer{ }; int user::UserCount = 0; + void handleUser(void * v_usr){ + user * usr = (user*)v_usr; + std::cerr << "Thread launched for user " << usr->MyStr << ", socket number " << usr->S.getSocket() << std::endl; + + usr->myRing = Strm->getRing(); + if (!usr->S.write(Strm->outHeader())){ + usr->Disconnect("failed to receive the header!"); + return; + } + + while (usr->S.connected()){ + if (usr->S.canRead()){ + std::string tmp = ""; + char charbuf; + while ((usr->S.iread(&charbuf, 1) == 1) && charbuf != '\n' ){ + tmp += charbuf; + } + if (tmp != ""){ + if (tmp[0] == 'P'){ + std::cout << "Push attempt from IP " << tmp.substr(2) << std::endl; + if (tmp.substr(2) == waiting_ip){ + if (!ip_input.connected()){ + std::cout << "Push accepted!" << std::endl; + ip_input = usr->S; + usr->S = Socket::Connection(-1); + return; + }else{ + usr->Disconnect("Push denied - push already in progress!"); + } + }else{ + usr->Disconnect("Push denied - invalid IP address!"); + } + } + if (tmp[0] == 'S'){ + Stats tmpStats = Stats(tmp.substr(2)); + unsigned int secs = tmpStats.conntime - usr->lastStats.conntime; + if (secs < 1){secs = 1;} + usr->curr_up = (tmpStats.up - usr->lastStats.up) / secs; + usr->curr_down = (tmpStats.down - usr->lastStats.down) / secs; + usr->lastStats = tmpStats; + Storage["curr"][usr->MyStr]["connector"] = tmpStats.connector; + Storage["curr"][usr->MyStr]["up"] = tmpStats.up; + Storage["curr"][usr->MyStr]["down"] = tmpStats.down; + Storage["curr"][usr->MyStr]["conntime"] = tmpStats.conntime; + Storage["curr"][usr->MyStr]["host"] = tmpStats.host; + Storage["curr"][usr->MyStr]["start"] = (unsigned int) time(0) - tmpStats.conntime; + } + } + } + usr->Send(); + } + usr->Disconnect("Closed"); + } + /// Starts a loop, waiting for connections to send data to. int Start(int argc, char ** argv) { //first make sure no segpipe signals will kill us @@ -176,9 +237,7 @@ namespace Buffer{ std::cout << "usage: " << argv[0] << " streamName [awaiting_IP]" << std::endl; return 1; } - std::string waiting_ip = ""; bool ip_waiting = false; - Socket::Connection ip_input; if (argc >= 4){ waiting_ip += argv[2]; ip_waiting = true; @@ -201,10 +260,9 @@ namespace Buffer{ Socket::Connection std_input(fileno(stdin)); Socket::Connection StatsSocket = Socket::Connection("/tmp/ddv_statistics", true); - Storage["log"] = JSON::Value(); - Storage["curr"] = JSON::Value(); - Storage["totals"] = JSON::Value(); - + Storage["log"].null(); + Storage["curr"].null(); + Storage["totals"].null(); while (!feof(stdin) || ip_waiting){ usleep(1000); //sleep for 1 ms, to prevent 100% CPU time @@ -248,79 +306,40 @@ namespace Buffer{ } //check for new connections, accept them if there are any - incoming = SS.accept(true); + //starts a thread for every accepted connection + incoming = SS.accept(false); if (incoming.connected()){ + std::cerr << "New socket: " << incoming.getSocket() << std::endl; users.push_back(incoming); - //send the header - users.back().myRing = Strm->getRing(); - if (!users.back().S.write(Strm->outHeader())){ - /// \todo Do this more nicely? - users.back().Disconnect("failed to receive the header!"); + user * usr_ptr = &(users.back()); + usr_ptr->Thread = new tthread::thread(handleUser, (void *)usr_ptr); + } + + //erase disconnected users + if (users.size() > 0){ + for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ + if (!(*usersIt).S.connected()){users.erase(usersIt); break;} } } - //go through all users - if (users.size() > 0){ - for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ - //remove disconnected users - if (!(*usersIt).S.connected()){ - (*usersIt).Disconnect("Closed"); - users.erase(usersIt); break; - }else{ - if ((*usersIt).S.canRead()){ - std::string tmp = ""; - char charbuf; - while (((*usersIt).S.iread(&charbuf, 1) == 1) && charbuf != '\n' ){ - tmp += charbuf; - } - if (tmp != ""){ - if (tmp[0] == 'P'){ - std::cout << "Push attempt from IP " << tmp.substr(2) << std::endl; - if (tmp.substr(2) == waiting_ip){ - if (!ip_input.connected()){ - std::cout << "Push accepted!" << std::endl; - ip_input = (*usersIt).S; - users.erase(usersIt); - break; - }else{ - (*usersIt).Disconnect("Push denied - push already in progress!"); - } - }else{ - (*usersIt).Disconnect("Push denied - invalid IP address!"); - } - } - if (tmp[0] == 'S'){ - Stats tmpStats = Stats(tmp.substr(2)); - unsigned int secs = tmpStats.conntime - (*usersIt).lastStats.conntime; - if (secs < 1){secs = 1;} - (*usersIt).curr_up = (tmpStats.up - (*usersIt).lastStats.up) / secs; - (*usersIt).curr_down = (tmpStats.down - (*usersIt).lastStats.down) / secs; - (*usersIt).lastStats = tmpStats; - Storage["curr"][(*usersIt).MyStr]["connector"] = tmpStats.connector; - Storage["curr"][(*usersIt).MyStr]["up"] = tmpStats.up; - Storage["curr"][(*usersIt).MyStr]["down"] = tmpStats.down; - Storage["curr"][(*usersIt).MyStr]["conntime"] = tmpStats.conntime; - Storage["curr"][(*usersIt).MyStr]["host"] = tmpStats.host; - Storage["curr"][(*usersIt).MyStr]["start"] = (unsigned int) time(0) - tmpStats.conntime; - } - } - } - (*usersIt).Send(); - } - } - } }//main loop // disconnect listener /// \todo Deal with EOF more nicely - doesn't send the end of the stream to all users! 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;} + if ((*usersIt).S.connected()){ + (*usersIt).Disconnect("Terminating..."); + }else{ + users.erase(usersIt); + break; + } } } + delete Strm; return 0; } diff --git a/util/dtsc.cpp b/util/dtsc.cpp index a5eb05e3..fd0b6a7e 100644 --- a/util/dtsc.cpp +++ b/util/dtsc.cpp @@ -66,8 +66,9 @@ bool DTSC::Stream::parsePacket(std::string & buffer){ return true; } #if DEBUG >= 2 - std::cerr << "Error: Invalid DTMI data! I *will* get stuck!" << std::endl; + std::cerr << "Error: Invalid DTMI data: " << buffer.substr(0, 4) << std::endl; #endif + buffer.erase(0, 1); } return false; } diff --git a/util/tinythread.cpp b/util/tinythread.cpp new file mode 100644 index 00000000..eb2dce0e --- /dev/null +++ b/util/tinythread.cpp @@ -0,0 +1,287 @@ +/* +Copyright (c) 2010 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include "tinythread.h" + +#if defined(_TTHREAD_POSIX_) + #include + #include +#elif defined(_TTHREAD_WIN32_) + #include +#endif + + +namespace tthread { + +//------------------------------------------------------------------------------ +// condition_variable +//------------------------------------------------------------------------------ +// NOTE 1: The Win32 implementation of the condition_variable class is based on +// the corresponding implementation in GLFW, which in turn is based on a +// description by Douglas C. Schmidt and Irfan Pyarali: +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// +// NOTE 2: Windows Vista actually has native support for condition variables +// (InitializeConditionVariable, WakeConditionVariable, etc), but we want to +// be portable with pre-Vista Windows versions, so TinyThread++ does not use +// Vista condition variables. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_WIN32_) + #define _CONDITION_EVENT_ONE 0 + #define _CONDITION_EVENT_ALL 1 +#endif + +#if defined(_TTHREAD_WIN32_) +condition_variable::condition_variable() : mWaitersCount(0) +{ + mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); + mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); + InitializeCriticalSection(&mWaitersCountLock); +} +#endif + +#if defined(_TTHREAD_WIN32_) +condition_variable::~condition_variable() +{ + CloseHandle(mEvents[_CONDITION_EVENT_ONE]); + CloseHandle(mEvents[_CONDITION_EVENT_ALL]); + DeleteCriticalSection(&mWaitersCountLock); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::_wait() +{ + // Wait for either event to become signaled due to notify_one() or + // notify_all() being called + int result = WaitForMultipleObjects(2, mEvents, FALSE, INFINITE); + + // Check if we are the last waiter + EnterCriticalSection(&mWaitersCountLock); + -- mWaitersCount; + bool lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) && + (mWaitersCount == 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we are the last waiter to be notified to stop waiting, reset the event + if(lastWaiter) + ResetEvent(mEvents[_CONDITION_EVENT_ALL]); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::notify_one() +{ + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if(haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ONE]); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::notify_all() +{ + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if(haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ALL]); +} +#endif + + +//------------------------------------------------------------------------------ +// POSIX pthread_t to unique thread::id mapping logic. +// Note: Here we use a global thread safe std::map to convert instances of +// pthread_t to small thread identifier numbers (unique within one process). +// This method should be portable across different POSIX implementations. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_POSIX_) +static thread::id _pthread_t_to_ID(const pthread_t &aHandle) +{ + static mutex idMapLock; + static std::map idMap; + static unsigned long int idCount(1); + + lock_guard guard(idMapLock); + if(idMap.find(aHandle) == idMap.end()) + idMap[aHandle] = idCount ++; + return thread::id(idMap[aHandle]); +} +#endif // _TTHREAD_POSIX_ + + +//------------------------------------------------------------------------------ +// thread +//------------------------------------------------------------------------------ + +/// Information to pass to the new thread (what to run). +struct _thread_start_info { + void (*mFunction)(void *); ///< Pointer to the function to be executed. + void * mArg; ///< Function argument for the thread function. + thread * mThread; ///< Pointer to the thread object. +}; + +// Thread wrapper function. +#if defined(_TTHREAD_WIN32_) +unsigned WINAPI thread::wrapper_function(void * aArg) +#elif defined(_TTHREAD_POSIX_) +void * thread::wrapper_function(void * aArg) +#endif +{ + // Get thread startup information + _thread_start_info * ti = (_thread_start_info *) aArg; + + try + { + // Call the actual client thread function + ti->mFunction(ti->mArg); + } + catch(...) + { + // Uncaught exceptions will terminate the application (default behavior + // according to the C++0x draft) + std::terminate(); + } + + // The thread is no longer executing + lock_guard guard(ti->mThread->mDataMutex); + ti->mThread->mNotAThread = true; + + // The thread is responsible for freeing the startup information + delete ti; + + return 0; +} + +thread::thread(void (*aFunction)(void *), void * aArg) +{ + // Serialize access to this thread structure + lock_guard guard(mDataMutex); + + // Fill out the thread startup information (passed to the thread wrapper, + // which will eventually free it) + _thread_start_info * ti = new _thread_start_info; + ti->mFunction = aFunction; + ti->mArg = aArg; + ti->mThread = this; + + // The thread is now alive + mNotAThread = false; + + // Create the thread +#if defined(_TTHREAD_WIN32_) + mHandle = (HANDLE) _beginthreadex(0, 0, wrapper_function, (void *) ti, 0, &mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + if(pthread_create(&mHandle, NULL, wrapper_function, (void *) ti) != 0) + mHandle = 0; +#endif + + // Did we fail to create the thread? + if(!mHandle) + { + mNotAThread = true; + delete ti; + } +} + +thread::~thread() +{ + if(joinable()) + std::terminate(); +} + +void thread::join() +{ + if(joinable()) + { +#if defined(_TTHREAD_WIN32_) + WaitForSingleObject(mHandle, INFINITE); +#elif defined(_TTHREAD_POSIX_) + pthread_join(mHandle, NULL); +#endif + } +} + +bool thread::joinable() const +{ + mDataMutex.lock(); + bool result = !mNotAThread; + mDataMutex.unlock(); + return result; +} + +thread::id thread::get_id() const +{ + if(!joinable()) + return id(); +#if defined(_TTHREAD_WIN32_) + return id((unsigned long int) mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(mHandle); +#endif +} + +unsigned thread::hardware_concurrency() +{ +#if defined(_TTHREAD_WIN32_) + SYSTEM_INFO si; + GetSystemInfo(&si); + return (int) si.dwNumberOfProcessors; +#elif defined(_SC_NPROCESSORS_ONLN) + return (int) sysconf(_SC_NPROCESSORS_ONLN); +#elif defined(_SC_NPROC_ONLN) + return (int) sysconf(_SC_NPROC_ONLN); +#else + // The standard requires this function to return zero if the number of + // hardware cores could not be determined. + return 0; +#endif +} + + +//------------------------------------------------------------------------------ +// this_thread +//------------------------------------------------------------------------------ + +thread::id this_thread::get_id() +{ +#if defined(_TTHREAD_WIN32_) + return thread::id((unsigned long int) GetCurrentThreadId()); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(pthread_self()); +#endif +} + +} diff --git a/util/tinythread.h b/util/tinythread.h new file mode 100644 index 00000000..723370cf --- /dev/null +++ b/util/tinythread.h @@ -0,0 +1,696 @@ +/* +Copyright (c) 2010 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _TINYTHREAD_H_ +#define _TINYTHREAD_H_ + +/// @file +/// @mainpage TinyThread++ API Reference +/// +/// @section intro_sec Introduction +/// TinyThread++ is a minimal, portable implementation of basic threading +/// classes for C++. +/// +/// They closely mimic the functionality and naming of the C++0x standard, and +/// should be easily replaceable with the corresponding std:: variants. +/// +/// @section port_sec Portability +/// The Win32 variant uses the native Win32 API for implementing the thread +/// classes, while for other systems, the POSIX threads API (pthread) is used. +/// +/// @section class_sec Classes +/// In order to mimic the threading API of the C++0x standard, subsets of +/// several classes are provided. The fundamental classes are: +/// @li tthread::thread +/// @li tthread::mutex +/// @li tthread::recursive_mutex +/// @li tthread::condition_variable +/// @li tthread::lock_guard +/// @li tthread::fast_mutex +/// +/// @section misc_sec Miscellaneous +/// The following special keywords are available: #thread_local. +/// +/// For more detailed information (including additional classes), browse the +/// different sections of this documentation. A good place to start is: +/// tinythread.h. + +// Which platform are we on? +#if !defined(_TTHREAD_PLATFORM_DEFINED_) + #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define _TTHREAD_WIN32_ + #else + #define _TTHREAD_POSIX_ + #endif + #define _TTHREAD_PLATFORM_DEFINED_ +#endif + +// Platform specific includes +#if defined(_TTHREAD_WIN32_) + #include +#else + #include + #include + #include + #include +#endif + +// Generic includes +#include + +/// TinyThread++ version (major number). +#define TINYTHREAD_VERSION_MAJOR 1 +/// TinyThread++ version (minor number). +#define TINYTHREAD_VERSION_MINOR 0 +/// TinyThread++ version (full version). +#define TINYTHREAD_VERSION (TINYTHREAD_VERSION_MAJOR * 100 + TINYTHREAD_VERSION_MINOR) + +// Do we have a fully featured C++0x compiler? +#if (__cplusplus > 199711L) || (defined(__STDCXX_VERSION__) && (__STDCXX_VERSION__ >= 201001L)) + #define _TTHREAD_CPP0X_ +#endif + +// ...at least partial C++0x? +#if defined(_TTHREAD_CPP0X_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) + #define _TTHREAD_CPP0X_PARTIAL_ +#endif + +// Macro for disabling assignments of objects. +#ifdef _TTHREAD_CPP0X_PARTIAL_ + #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&) = delete; \ + name& operator=(const name&) = delete; +#else + #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&); \ + name& operator=(const name&); +#endif + +/// @def thread_local +/// Thread local storage keyword. +/// A variable that is declared with the \c thread_local keyword makes the +/// value of the variable local to each thread (known as thread-local storage, +/// or TLS). Example usage: +/// @code +/// // This variable is local to each thread. +/// thread_local int variable; +/// @endcode +/// @note The \c thread_local keyword is a macro that maps to the corresponding +/// compiler directive (e.g. \c __declspec(thread)). While the C++0x standard +/// allows for non-trivial types (e.g. classes with constructors and +/// destructors) to be declared with the \c thread_local keyword, most pre-C++0x +/// compilers only allow for trivial types (e.g. \c int). So, to guarantee +/// portable code, only use trivial types for thread local storage. +/// @note This directive is currently not supported on Mac OS X (it will give +/// a compiler error), since compile-time TLS is not supported in the Mac OS X +/// executable format. Also, some older versions of MinGW (before GCC 4.x) do +/// not support this directive. +/// @hideinitializer + +#if !defined(_TTHREAD_CPP0X_) && !defined(thread_local) + #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) + #define thread_local __thread + #else + #define thread_local __declspec(thread) + #endif +#endif + + +/// Main name space for TinyThread++. +/// This namespace is more or less equivalent to the \c std namespace for the +/// C++0x thread classes. For instance, the tthread::mutex class corresponds to +/// the std::mutex class. +namespace tthread { + +/// Mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is non-recursive (i.e. a +/// program may deadlock if the thread that owns a mutex object calls lock() +/// on that object). +/// @see recursive_mutex +class mutex { + public: + /// Constructor. + mutex() +#if defined(_TTHREAD_WIN32_) + : mAlreadyLocked(false) +#endif + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutex_init(&mHandle, NULL); +#endif + } + + /// Destructor. + ~mutex() + { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until \c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); + while(mAlreadyLocked) Sleep(1000); // Simulate deadlock... + mAlreadyLocked = true; +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return \c true if the lock was acquired, or \c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_TTHREAD_WIN32_) + bool ret = (TryEnterCriticalSection(&mHandle) ? true : false); + if(ret && mAlreadyLocked) + { + LeaveCriticalSection(&mHandle); + ret = false; + } + return ret; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_TTHREAD_WIN32_) + mAlreadyLocked = false; + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; + bool mAlreadyLocked; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; +}; + +/// Recursive mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is recursive (i.e. a thread +/// may lock the mutex several times, as long as it unlocks the mutex the same +/// number of times). +/// @see mutex +class recursive_mutex { + public: + /// Constructor. + recursive_mutex() + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mHandle, &attr); +#endif + } + + /// Destructor. + ~recursive_mutex() + { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until \c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return \c true if the lock was acquired, or \c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_TTHREAD_WIN32_) + return TryEnterCriticalSection(&mHandle) ? true : false; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_TTHREAD_WIN32_) + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(recursive_mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; +}; + +/// Lock guard class. +/// The constructor locks the mutex, and the destructor unlocks the mutex, so +/// the mutex will automatically be unlocked when the lock guard goes out of +/// scope. Example usage: +/// @code +/// mutex m; +/// int counter; +/// +/// void increment() +/// { +/// lock_guard guard(m); +/// ++ counter; +/// } +/// @endcode + +template +class lock_guard { + public: + typedef T mutex_type; + + lock_guard() : mMutex(0) {} + + /// The constructor locks the mutex. + explicit lock_guard(mutex_type &aMutex) + { + mMutex = &aMutex; + mMutex->lock(); + } + + /// The destructor unlocks the mutex. + ~lock_guard() + { + if(mMutex) + mMutex->unlock(); + } + + private: + mutex_type * mMutex; +}; + +/// Condition variable class. +/// This is a signalling object for synchronizing the execution flow for +/// several threads. Example usage: +/// @code +/// // Shared data and associated mutex and condition variable objects +/// int count; +/// mutex m; +/// condition_variable cond; +/// +/// // Wait for the counter to reach a certain number +/// void wait_counter(int targetCount) +/// { +/// lock_guard guard(m); +/// while(count < targetCount) +/// cond.wait(m); +/// } +/// +/// // Increment the counter, and notify waiting threads +/// void increment() +/// { +/// lock_guard guard(m); +/// ++ count; +/// cond.notify_all(); +/// } +/// @endcode +class condition_variable { + public: + /// Constructor. +#if defined(_TTHREAD_WIN32_) + condition_variable(); +#else + condition_variable() + { + pthread_cond_init(&mHandle, NULL); + } +#endif + + /// Destructor. +#if defined(_TTHREAD_WIN32_) + ~condition_variable(); +#else + ~condition_variable() + { + pthread_cond_destroy(&mHandle); + } +#endif + + /// Wait for the condition. + /// The function will block the calling thread until the condition variable + /// is woken by \c notify_one(), \c notify_all() or a spurious wake up. + /// @param[in] aMutex A mutex that will be unlocked when the wait operation + /// starts, an locked again as soon as the wait operation is finished. + template + inline void wait(_mutexT &aMutex) + { +#if defined(_TTHREAD_WIN32_) + // Increment number of waiters + EnterCriticalSection(&mWaitersCountLock); + ++ mWaitersCount; + LeaveCriticalSection(&mWaitersCountLock); + + // Release the mutex while waiting for the condition (will decrease + // the number of waiters when done)... + aMutex.unlock(); + _wait(); + aMutex.lock(); +#else + pthread_cond_wait(&mHandle, &aMutex.mHandle); +#endif + } + + /// Notify one thread that is waiting for the condition. + /// If at least one thread is blocked waiting for this condition variable, + /// one will be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_one(); +#else + inline void notify_one() + { + pthread_cond_signal(&mHandle); + } +#endif + + /// Notify all threads that are waiting for the condition. + /// All threads that are blocked waiting for this condition variable will + /// be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_all(); +#else + inline void notify_all() + { + pthread_cond_broadcast(&mHandle); + } +#endif + + _TTHREAD_DISABLE_ASSIGNMENT(condition_variable) + + private: +#if defined(_TTHREAD_WIN32_) + void _wait(); + HANDLE mEvents[2]; ///< Signal and broadcast event HANDLEs. + unsigned int mWaitersCount; ///< Count of the number of waiters. + CRITICAL_SECTION mWaitersCountLock; ///< Serialize access to mWaitersCount. +#else + pthread_cond_t mHandle; +#endif +}; + + +/// Thread class. +class thread { + public: +#if defined(_TTHREAD_WIN32_) + typedef HANDLE native_handle_type; +#else + typedef pthread_t native_handle_type; +#endif + + class id; + + /// Default constructor. + /// Construct a \c thread object without an associated thread of execution + /// (i.e. non-joinable). + thread() : mHandle(0), mNotAThread(true) +#if defined(_TTHREAD_WIN32_) + , mWin32ThreadID(0) +#endif + {} + + /// Thread starting constructor. + /// Construct a \c thread object with a new thread of execution. + /// @param[in] aFunction A function pointer to a function of type: + /// void fun(void * arg) + /// @param[in] aArg Argument to the thread function. + /// @note This constructor is not fully compatible with the standard C++ + /// thread class. It is more similar to the pthread_create() (POSIX) and + /// CreateThread() (Windows) functions. + thread(void (*aFunction)(void *), void * aArg); + + /// Destructor. + /// @note If the thread is joinable upon destruction, \c std::terminate() + /// will be called, which terminates the process. It is always wise to do + /// \c join() before deleting a thread object. + ~thread(); + + /// Wait for the thread to finish (join execution flows). + void join(); + + /// Check if the thread is joinable. + /// A thread object is joinable if it has an associated thread of execution. + bool joinable() const; + + /// Return the thread ID of a thread object. + id get_id() const; + + /// Get the native handle for this thread. + /// @note Under Windows, this is a \c HANDLE, and under POSIX systems, this + /// is a \c pthread_t. + inline native_handle_type native_handle() + { + return mHandle; + } + + /// Determine the number of threads which can possibly execute concurrently. + /// This function is useful for determining the optimal number of threads to + /// use for a task. + /// @return The number of hardware thread contexts in the system. + /// @note If this value is not defined, the function returns zero (0). + static unsigned hardware_concurrency(); + + _TTHREAD_DISABLE_ASSIGNMENT(thread) + + private: + native_handle_type mHandle; ///< Thread handle. + mutable mutex mDataMutex; ///< Serializer for access to the thread private data. + bool mNotAThread; ///< True if this object is not a thread of execution. +#if defined(_TTHREAD_WIN32_) + unsigned int mWin32ThreadID; ///< Unique thread ID (filled out by _beginthreadex). +#endif + + // This is the internal thread wrapper function. +#if defined(_TTHREAD_WIN32_) + static unsigned WINAPI wrapper_function(void * aArg); +#else + static void * wrapper_function(void * aArg); +#endif +}; + +/// Thread ID. +/// The thread ID is a unique identifier for each thread. +/// @see thread::get_id() +class thread::id { + public: + /// Default constructor. + /// The default constructed ID is that of thread without a thread of + /// execution. + id() : mId(0) {}; + + id(unsigned long int aId) : mId(aId) {}; + + id(const id& aId) : mId(aId.mId) {}; + + inline id & operator=(const id &aId) + { + mId = aId.mId; + return *this; + } + + inline friend bool operator==(const id &aId1, const id &aId2) + { + return (aId1.mId == aId2.mId); + } + + inline friend bool operator!=(const id &aId1, const id &aId2) + { + return (aId1.mId != aId2.mId); + } + + inline friend bool operator<=(const id &aId1, const id &aId2) + { + return (aId1.mId <= aId2.mId); + } + + inline friend bool operator<(const id &aId1, const id &aId2) + { + return (aId1.mId < aId2.mId); + } + + inline friend bool operator>=(const id &aId1, const id &aId2) + { + return (aId1.mId >= aId2.mId); + } + + inline friend bool operator>(const id &aId1, const id &aId2) + { + return (aId1.mId > aId2.mId); + } + + inline friend std::ostream& operator <<(std::ostream &os, const id &obj) + { + os << obj.mId; + return os; + } + + private: + unsigned long int mId; +}; + + +// Related to - minimal to be able to support chrono. +typedef long long __intmax_t; + +/// Minimal implementation of the \c ratio class. This class provides enough +/// functionality to implement some basic \c chrono classes. +template <__intmax_t N, __intmax_t D = 1> class ratio { + public: + static double _as_double() { return double(N) / double(D); } +}; + +/// Minimal implementation of the \c chrono namespace. +/// The \c chrono namespace provides types for specifying time intervals. +namespace chrono { + /// Duration template class. This class provides enough functionality to + /// implement \c this_thread::sleep_for(). + template > class duration { + private: + _Rep rep_; + public: + typedef _Rep rep; + typedef _Period period; + + /// Construct a duration object with the given duration. + template + explicit duration(const _Rep2& r) : rep_(r) {}; + + /// Return the value of the duration object. + rep count() const + { + return rep_; + } + }; + + // Standard duration types. + typedef duration<__intmax_t, ratio<1, 1000000000> > nanoseconds; ///< Duration with the unit nanoseconds. + typedef duration<__intmax_t, ratio<1, 1000000> > microseconds; ///< Duration with the unit microseconds. + typedef duration<__intmax_t, ratio<1, 1000> > milliseconds; ///< Duration with the unit milliseconds. + typedef duration<__intmax_t> seconds; ///< Duration with the unit seconds. + typedef duration<__intmax_t, ratio<60> > minutes; ///< Duration with the unit minutes. + typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours. +} + +/// The namespace \c this_thread provides methods for dealing with the +/// calling thread. +namespace this_thread { + /// Return the thread ID of the calling thread. + thread::id get_id(); + + /// Yield execution to another thread. + /// Offers the operating system the opportunity to schedule another thread + /// that is ready to run on the current processor. + inline void yield() + { +#if defined(_TTHREAD_WIN32_) + Sleep(0); +#else + sched_yield(); +#endif + } + + /// Blocks the calling thread for a period of time. + /// @param[in] aTime Minimum time to put the thread to sleep. + /// Example usage: + /// @code + /// // Sleep for 100 milliseconds + /// this_thread::sleep_for(chrono::milliseconds(100)); + /// @endcode + /// @note Supported duration types are: nanoseconds, microseconds, + /// milliseconds, seconds, minutes and hours. + template void sleep_for(const chrono::duration<_Rep, _Period>& aTime) + { +#if defined(_TTHREAD_WIN32_) + Sleep(int(double(aTime.count()) * (1000.0 * _Period::_as_double()) + 0.5)); +#else + usleep(int(double(aTime.count()) * (1000000.0 * _Period::_as_double()) + 0.5)); +#endif + } +} + +} + +// Define/macro cleanup +#undef _TTHREAD_DISABLE_ASSIGNMENT + +#endif // _TINYTHREAD_H_ From 9b6e220b88f390e1b8db80e59cfec61830106336 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 10 Apr 2012 16:26:30 +0200 Subject: [PATCH 12/24] Several major bugfixes in FLV handling, multithreaded buffer now actually works (still crashes when multiple users connect, though... needs further "tweaking"), updated toolset. --- Buffer/main.cpp | 351 ++++++++++++++---------------------- Buffer/stats.cpp | 40 ++++ Buffer/user.cpp | 97 ++++++++++ Connector_HTTP/main.cpp | 31 +--- tools/DTSC2FLV/Makefile | 23 +++ tools/DTSC2FLV/main.cpp | 64 +++++++ tools/FLV2DTSC/main.cpp | 6 +- tools/FLV_Analyser/Makefile | 23 +++ tools/FLV_Analyser/main.cpp | 52 ++++++ util/amf.cpp | 53 +++--- util/amf.h | 2 +- util/dtsc.h | 6 +- util/flv_tag.cpp | 144 ++++++++------- 13 files changed, 564 insertions(+), 328 deletions(-) create mode 100644 Buffer/stats.cpp create mode 100644 Buffer/user.cpp create mode 100644 tools/DTSC2FLV/Makefile create mode 100644 tools/DTSC2FLV/main.cpp create mode 100644 tools/FLV_Analyser/Makefile create mode 100644 tools/FLV_Analyser/main.cpp diff --git a/Buffer/main.cpp b/Buffer/main.cpp index c7c57ef9..a458cbd1 100644 --- a/Buffer/main.cpp +++ b/Buffer/main.cpp @@ -20,6 +20,20 @@ /// Holds all code unique to the Buffer. namespace Buffer{ + class user;//forward declaration + JSON::Value Storage; ///< Global storage of data. + DTSC::Stream * Strm = 0; + std::string waiting_ip = ""; ///< IP address for media push. + Socket::Connection ip_input; ///< Connection used for media push. + tthread::mutex stats_mutex; ///< Mutex for stats modifications. + tthread::mutex transfer_mutex; ///< Mutex for data transfers. + tthread::mutex socket_mutex; ///< Mutex for user deletion/work. + bool buffer_running = true; ///< Set to false when shutting down. + std::vector users; ///< All connected users. + std::vector::iterator usersIt; ///< Iterator for all connected users. + std::string name; ///< Name for this buffer. + tthread::condition_variable moreData; ///< Triggered when more data becomes available. + /// Gets the current system time in milliseconds. unsigned int getNowMS(){ timeval t; @@ -28,146 +42,50 @@ namespace Buffer{ }//getNowMS - JSON::Value Storage; ///< Global storage of data. - ///A simple signal handler that ignores all signals. void termination_handler (int signum){ switch (signum){ + case SIGKILL: buffer_running = false; break; case SIGPIPE: return; break; default: return; break; } } +} - DTSC::Stream * Strm = 0; - std::string waiting_ip = ""; ///< IP address for media push. - Socket::Connection ip_input; ///< Connection used for media push. - - /// Converts a stats line to up, down, host, connector and conntime values. - class Stats{ - public: - unsigned int up; - unsigned int down; - std::string host; - std::string connector; - unsigned int conntime; - Stats(){ - up = 0; - down = 0; - conntime = 0; - } - Stats(std::string s){ - size_t f = s.find(' '); - if (f != std::string::npos){ - host = s.substr(0, f); - s.erase(0, f+1); - } - f = s.find(' '); - if (f != std::string::npos){ - connector = s.substr(0, f); - s.erase(0, f+1); - } - f = s.find(' '); - if (f != std::string::npos){ - conntime = atoi(s.substr(0, f).c_str()); - s.erase(0, f+1); - } - f = s.find(' '); - if (f != std::string::npos){ - up = atoi(s.substr(0, f).c_str()); - s.erase(0, f+1); - down = atoi(s.c_str()); +#include "stats.cpp" +#include "user.cpp" + +namespace Buffer{ + void handleStats(void * empty){ + if (empty != 0){return;} + Socket::Connection StatsSocket = Socket::Connection("/tmp/ddv_statistics", true); + while (buffer_running){ + usleep(1000000); //sleep one second + unsigned int now = time(0); + unsigned int tot_up = 0, tot_down = 0, tot_count = 0; + stats_mutex.lock(); + if (users.size() > 0){ + for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ + tot_down += usersIt->curr_down; + tot_up += usersIt->curr_up; + tot_count++; } } - }; - - /// Holds connected users. - /// Keeps track of what buffer users are using and the connection status. - class user{ - public: - tthread::thread * Thread; ///< Holds the thread dealing with this user. - DTSC::Ring * myRing; ///< Ring of the buffer for this user. - int MyNum; ///< User ID of this user. - std::string MyStr; ///< User ID of this user as a string. - int currsend; ///< Current amount of bytes sent. - Stats lastStats; ///< Holds last known stats for this connection. - unsigned int curr_up; ///< Holds the current estimated transfer speed up. - unsigned int curr_down; ///< Holds the current estimated transfer speed down. - bool gotproperaudio; ///< Whether the user received proper audio yet. - void * lastpointer; ///< Pointer to data part of current buffer. - static int UserCount; ///< Global user counter. - Socket::Connection S; ///< Connection to user - /// Creates a new user from a newly connected socket. - /// Also prints "User connected" text to stdout. - user(Socket::Connection fd){ - S = fd; - MyNum = UserCount++; - std::stringstream st; - st << MyNum; - MyStr = st.str(); - curr_up = 0; - curr_down = 0; - currsend = 0; - myRing = 0; - Thread = 0; - std::cout << "User " << MyNum << " connected" << std::endl; - }//constructor - /// Drops held DTSC::Ring class, if one is held. - ~user(){ - Strm->dropRing(myRing); - }//destructor - /// 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();} - if (Thread != 0){ - if (Thread->joinable()){Thread->join();} - Thread = 0; - } - Storage["curr"].removeMember(MyStr); - Storage["log"][MyStr]["connector"] = lastStats.connector; - Storage["log"][MyStr]["up"] = lastStats.up; - Storage["log"][MyStr]["down"] = lastStats.down; - Storage["log"][MyStr]["conntime"] = lastStats.conntime; - Storage["log"][MyStr]["host"] = lastStats.host; - Storage["log"][MyStr]["start"] = (unsigned int)time(0) - lastStats.conntime; - std::cout << "Disconnected user " << MyStr << ": " << reason << ". " << lastStats.connector << " transferred " << lastStats.up << " up and " << lastStats.down << " down in " << lastStats.conntime << " seconds to " << lastStats.host << 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(const char * ptr, int len){ - int r = S.iwrite(ptr+currsend, len-currsend); - if (r <= 0){ - if (errno == EWOULDBLOCK){return false;} - Disconnect(S.getError()); - return false; - } - currsend += r; - return (currsend == len); - }//doSend - /// Try to send data to this user. Disconnects if any problems occur. - void Send(){ - if (!myRing){return;}//no ring! - if (!S.connected()){return;}//cancel if not connected - if (myRing->waiting){return;}//still waiting for next buffer? - - if (myRing->starved){ - //if corrupt data, warn and get new DTSC::Ring - std::cout << "Warning: User was send corrupt video data and send to the next keyframe!" << std::endl; - Strm->dropRing(myRing); - myRing = Strm->getRing(); - } - currsend = 0; - - //try to complete a send - if (doSend(Strm->outPacket(myRing->b).c_str(), Strm->outPacket(myRing->b).length())){ - //switch to next buffer - if (myRing->b <= 0){myRing->waiting = true; return;}//no next buffer? go in waiting mode. - myRing->b--; - currsend = 0; - }//completed a send - }//send - }; - int user::UserCount = 0; + Storage["totals"]["down"] = tot_down; + Storage["totals"]["up"] = tot_up; + Storage["totals"]["count"] = tot_count; + Storage["totals"]["now"] = now; + Storage["totals"]["buffer"] = name; + if (!StatsSocket.connected()){ + StatsSocket = Socket::Connection("/tmp/ddv_statistics", true); + } + if (StatsSocket.connected()){ + StatsSocket.write(Storage.toString()+"\n\n"); + Storage["log"].null(); + } + stats_mutex.unlock(); + } + } void handleUser(void * v_usr){ user * usr = (user*)v_usr; @@ -180,16 +98,17 @@ namespace Buffer{ } while (usr->S.connected()){ + usleep(5000); //sleep 5ms if (usr->S.canRead()){ - std::string tmp = ""; + usr->inbuffer.clear(); char charbuf; while ((usr->S.iread(&charbuf, 1) == 1) && charbuf != '\n' ){ - tmp += charbuf; + usr->inbuffer += charbuf; } - if (tmp != ""){ - if (tmp[0] == 'P'){ - std::cout << "Push attempt from IP " << tmp.substr(2) << std::endl; - if (tmp.substr(2) == waiting_ip){ + if (usr->inbuffer != ""){ + if (usr->inbuffer[0] == 'P'){ + std::cout << "Push attempt from IP " << usr->inbuffer.substr(2) << std::endl; + if (usr->inbuffer.substr(2) == waiting_ip){ if (!ip_input.connected()){ std::cout << "Push accepted!" << std::endl; ip_input = usr->S; @@ -202,25 +121,74 @@ namespace Buffer{ usr->Disconnect("Push denied - invalid IP address!"); } } - if (tmp[0] == 'S'){ - Stats tmpStats = Stats(tmp.substr(2)); - unsigned int secs = tmpStats.conntime - usr->lastStats.conntime; + if (usr->inbuffer[0] == 'S'){ + stats_mutex.lock(); + usr->tmpStats = Stats(usr->inbuffer.substr(2)); + unsigned int secs = usr->tmpStats.conntime - usr->lastStats.conntime; if (secs < 1){secs = 1;} - usr->curr_up = (tmpStats.up - usr->lastStats.up) / secs; - usr->curr_down = (tmpStats.down - usr->lastStats.down) / secs; - usr->lastStats = tmpStats; - Storage["curr"][usr->MyStr]["connector"] = tmpStats.connector; - Storage["curr"][usr->MyStr]["up"] = tmpStats.up; - Storage["curr"][usr->MyStr]["down"] = tmpStats.down; - Storage["curr"][usr->MyStr]["conntime"] = tmpStats.conntime; - Storage["curr"][usr->MyStr]["host"] = tmpStats.host; - Storage["curr"][usr->MyStr]["start"] = (unsigned int) time(0) - tmpStats.conntime; + usr->curr_up = (usr->tmpStats.up - usr->lastStats.up) / secs; + usr->curr_down = (usr->tmpStats.down - usr->lastStats.down) / secs; + usr->lastStats = usr->tmpStats; + Storage["curr"][usr->MyStr]["connector"] = usr->tmpStats.connector; + Storage["curr"][usr->MyStr]["up"] = usr->tmpStats.up; + Storage["curr"][usr->MyStr]["down"] = usr->tmpStats.down; + Storage["curr"][usr->MyStr]["conntime"] = usr->tmpStats.conntime; + Storage["curr"][usr->MyStr]["host"] = usr->tmpStats.host; + Storage["curr"][usr->MyStr]["start"] = (unsigned int) time(0) - usr->tmpStats.conntime; + stats_mutex.unlock(); } } } usr->Send(); } - usr->Disconnect("Closed"); + stats_mutex.lock(); + if (users.size() > 0){ + for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ + if (!(*usersIt).S.connected()){ + users.erase(usersIt); + break; + } + } + } + stats_mutex.unlock(); + std::cerr << "User " << usr->MyStr << " disconnected, socket number " << usr->S.getSocket() << std::endl; + } + + void handleStdin(void * empty){ + if (empty != 0){return;} + unsigned int lastPacketTime = 0;//time in MS last packet was parsed + unsigned int currPacketTime = 0;//time of the last parsed packet (current packet) + unsigned int prevPacketTime = 0;//time of the previously parsed packet (current packet - 1) + std::string inBuffer; + char charBuffer[1024*10]; + unsigned int charCount; + unsigned int now; + + while (std::cin.good() && buffer_running){ + //slow down packet receiving to real-time + now = getNowMS(); + if ((now - lastPacketTime > currPacketTime - prevPacketTime) || (currPacketTime <= prevPacketTime)){ + std::cin.read(charBuffer, 1024*10); + charCount = std::cin.gcount(); + inBuffer.append(charBuffer, charCount); + transfer_mutex.lock(); + if (Strm->parsePacket(inBuffer)){ + Strm->outPacket(0); + lastPacketTime = now; + prevPacketTime = currPacketTime; + currPacketTime = Strm->getTime(); + moreData.notify_all(); + } + transfer_mutex.unlock(); + }else{ + if (((currPacketTime - prevPacketTime) - (now - lastPacketTime)) > 1000){ + usleep(1000000); + }else{ + usleep(((currPacketTime - prevPacketTime) - (now - lastPacketTime)) * 1000); + } + } + } + buffer_running = false; } /// Starts a loop, waiting for connections to send data to. @@ -231,113 +199,66 @@ namespace Buffer{ sigemptyset (&new_action.sa_mask); new_action.sa_flags = 0; sigaction (SIGPIPE, &new_action, NULL); + sigaction (SIGKILL, &new_action, NULL); //then check and parse the commandline if (argc < 2) { std::cout << "usage: " << argv[0] << " streamName [awaiting_IP]" << std::endl; return 1; } + name = argv[1]; bool ip_waiting = false; if (argc >= 4){ waiting_ip += argv[2]; ip_waiting = true; } std::string shared_socket = "/tmp/shared_socket_"; - shared_socket += argv[1]; + shared_socket += name; - Socket::Server SS(shared_socket, true); + Socket::Server SS(shared_socket, false); Strm = new DTSC::Stream(5); - std::vector users; - std::vector::iterator usersIt; - std::string inBuffer; - char charBuffer[1024*10]; - unsigned int charCount; - unsigned int stattimer = 0; - unsigned int lastPacketTime = 0;//time in MS last packet was parsed - unsigned int currPacketTime = 0;//time of the last parsed packet (current packet) - unsigned int prevPacketTime = 0;//time of the previously parsed packet (current packet - 1) Socket::Connection incoming; Socket::Connection std_input(fileno(stdin)); - Socket::Connection StatsSocket = Socket::Connection("/tmp/ddv_statistics", true); Storage["log"].null(); Storage["curr"].null(); Storage["totals"].null(); - while (!feof(stdin) || ip_waiting){ - usleep(1000); //sleep for 1 ms, to prevent 100% CPU time - unsigned int now = time(0); - if (now != stattimer){ - stattimer = now; - unsigned int tot_up = 0, tot_down = 0, tot_count = 0; - if (users.size() > 0){ - for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ - tot_down += usersIt->curr_down; - tot_up += usersIt->curr_up; - tot_count++; - } - } - Storage["totals"]["down"] = tot_down; - Storage["totals"]["up"] = tot_up; - Storage["totals"]["count"] = tot_count; - Storage["totals"]["now"] = now; - Storage["totals"]["buffer"] = argv[1]; - if (!StatsSocket.connected()){ - StatsSocket = Socket::Connection("/tmp/ddv_statistics", true); - } - if (StatsSocket.connected()){ - StatsSocket.write(Storage.toString()+"\n\n"); - Storage["log"].null(); - } - } - //invalidate the current buffer - if ( (!ip_waiting && std_input.canRead()) || (ip_waiting && ip_input.connected()) ){ - //slow down packet receiving to real-time - if ((getNowMS() - lastPacketTime > currPacketTime - prevPacketTime) || (currPacketTime <= prevPacketTime)){ - std::cin.read(charBuffer, 1024*10); - charCount = std::cin.gcount(); - inBuffer.append(charBuffer, charCount); - if (Strm->parsePacket(inBuffer)){ - lastPacketTime = getNowMS(); - prevPacketTime = currPacketTime; - currPacketTime = Strm->getTime(); - } - } - } + //tthread::thread StatsThread = tthread::thread(handleStats, 0); + tthread::thread * StdinThread = 0; + if (!ip_waiting){ + StdinThread = new tthread::thread(handleStdin, 0); + } + while (buffer_running){ //check for new connections, accept them if there are any //starts a thread for every accepted connection incoming = SS.accept(false); if (incoming.connected()){ - std::cerr << "New socket: " << incoming.getSocket() << std::endl; + stats_mutex.lock(); users.push_back(incoming); user * usr_ptr = &(users.back()); + stats_mutex.unlock(); usr_ptr->Thread = new tthread::thread(handleUser, (void *)usr_ptr); } - - //erase disconnected users - if (users.size() > 0){ - for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ - if (!(*usersIt).S.connected()){users.erase(usersIt); break;} - } - } - }//main loop // disconnect listener /// \todo Deal with EOF more nicely - doesn't send the end of the stream to all users! - std::cout << "Reached EOF of input" << std::endl; + buffer_running = false; + std::cout << "Buffer shutting down" << std::endl; SS.close(); + //StatsThread.join(); + if (StdinThread){StdinThread->join();} - while (users.size() > 0){ + if (users.size() > 0){ + stats_mutex.lock(); for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ if ((*usersIt).S.connected()){ (*usersIt).Disconnect("Terminating..."); - }else{ - users.erase(usersIt); - break; } } + stats_mutex.unlock(); } delete Strm; diff --git a/Buffer/stats.cpp b/Buffer/stats.cpp new file mode 100644 index 00000000..25d244d8 --- /dev/null +++ b/Buffer/stats.cpp @@ -0,0 +1,40 @@ + +namespace Buffer{ + /// Converts a stats line to up, down, host, connector and conntime values. + class Stats{ + public: + unsigned int up; + unsigned int down; + std::string host; + std::string connector; + unsigned int conntime; + Stats(){ + up = 0; + down = 0; + conntime = 0; + } + Stats(std::string s){ + size_t f = s.find(' '); + if (f != std::string::npos){ + host = s.substr(0, f); + s.erase(0, f+1); + } + f = s.find(' '); + if (f != std::string::npos){ + connector = s.substr(0, f); + s.erase(0, f+1); + } + f = s.find(' '); + if (f != std::string::npos){ + conntime = atoi(s.substr(0, f).c_str()); + s.erase(0, f+1); + } + f = s.find(' '); + if (f != std::string::npos){ + up = atoi(s.substr(0, f).c_str()); + s.erase(0, f+1); + down = atoi(s.c_str()); + } + } + }; +} diff --git a/Buffer/user.cpp b/Buffer/user.cpp new file mode 100644 index 00000000..3fe1f065 --- /dev/null +++ b/Buffer/user.cpp @@ -0,0 +1,97 @@ +namespace Buffer{ + /// Holds connected users. + /// Keeps track of what buffer users are using and the connection status. + class user{ + public: + tthread::thread * Thread; ///< Holds the thread dealing with this user. + DTSC::Ring * myRing; ///< Ring of the buffer for this user. + int MyNum; ///< User ID of this user. + std::string MyStr; ///< User ID of this user as a string. + std::string inbuffer; ///< Used to buffer input data. + int currsend; ///< Current amount of bytes sent. + Stats lastStats; ///< Holds last known stats for this connection. + Stats tmpStats; ///< Holds temporary stats for this connection. + unsigned int curr_up; ///< Holds the current estimated transfer speed up. + unsigned int curr_down; ///< Holds the current estimated transfer speed down. + bool gotproperaudio; ///< Whether the user received proper audio yet. + void * lastpointer; ///< Pointer to data part of current buffer. + static int UserCount; ///< Global user counter. + Socket::Connection S; ///< Connection to user + /// Creates a new user from a newly connected socket. + /// Also prints "User connected" text to stdout. + user(Socket::Connection fd){ + S = fd; + MyNum = UserCount++; + std::stringstream st; + st << MyNum; + MyStr = st.str(); + curr_up = 0; + curr_down = 0; + currsend = 0; + myRing = 0; + Thread = 0; + std::cout << "User " << MyNum << " connected" << std::endl; + }//constructor + /// Drops held DTSC::Ring class, if one is held. + ~user(){ + Strm->dropRing(myRing); + }//destructor + /// 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();} + if (Thread != 0){ + if (Thread->joinable()){Thread->join();} + Thread = 0; + } + tthread::lock_guard lock(stats_mutex); + Storage["curr"].removeMember(MyStr); + Storage["log"][MyStr]["connector"] = lastStats.connector; + Storage["log"][MyStr]["up"] = lastStats.up; + Storage["log"][MyStr]["down"] = lastStats.down; + Storage["log"][MyStr]["conntime"] = lastStats.conntime; + Storage["log"][MyStr]["host"] = lastStats.host; + Storage["log"][MyStr]["start"] = (unsigned int)time(0) - lastStats.conntime; + std::cout << "Disconnected user " << MyStr << ": " << reason << ". " << lastStats.connector << " transferred " << lastStats.up << " up and " << lastStats.down << " down in " << lastStats.conntime << " seconds to " << lastStats.host << 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(const char * ptr, int len){ + int r = S.iwrite(ptr+currsend, len-currsend); + if (r <= 0){ + if (errno == EWOULDBLOCK){return false;} + Disconnect(S.getError()); + return false; + } + currsend += r; + return (currsend == len); + }//doSend + /// Try to send data to this user. Disconnects if any problems occur. + void Send(){ + if (!myRing){return;}//no ring! + if (!S.connected()){return;}//cancel if not connected + if (myRing->waiting){ + tthread::lock_guard guard(transfer_mutex); + moreData.wait(transfer_mutex); + return; + }//still waiting for next buffer? + + if (myRing->starved){ + //if corrupt data, warn and get new DTSC::Ring + std::cout << "Warning: User was send corrupt video data and send to the next keyframe!" << std::endl; + Strm->dropRing(myRing); + myRing = Strm->getRing(); + return; + } + + //try to complete a send + if (doSend(Strm->outPacket(myRing->b).c_str(), Strm->outPacket(myRing->b).length())){ + //switch to next buffer + currsend = 0; + if (myRing->b <= 0){myRing->waiting = true; return;}//no next buffer? go in waiting mode. + myRing->b--; + }//completed a send + }//send + }; + int user::UserCount = 0; +} diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index b1b6e7cc..0ba6ef71 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -304,28 +304,17 @@ namespace Connector_HTTP{ ss.write(stat); } } - ss.canRead(); - 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 (ss.iread(recBuffer)){ - if (Strm.parsePacket(recBuffer)){ - tag.DTSCLoader(Strm); - if (handler == HANDLER_FLASH){ - FlashDynamic(tag, HTTP_S, conn, Strm); - } - if (handler == HANDLER_PROGRESSIVE){ - Progressive(tag, HTTP_S, conn, Strm); - } - } + if (ss.canRead()){ + ss.spool(); + if (Strm.parsePacket(ss.Received())){ + tag.DTSCLoader(Strm); + if (handler == HANDLER_FLASH){ + FlashDynamic(tag, HTTP_S, conn, Strm); } - break; + if (handler == HANDLER_PROGRESSIVE){ + Progressive(tag, HTTP_S, conn, Strm); + } + } } } } diff --git a/tools/DTSC2FLV/Makefile b/tools/DTSC2FLV/Makefile new file mode 100644 index 00000000..23d86bbf --- /dev/null +++ b/tools/DTSC2FLV/Makefile @@ -0,0 +1,23 @@ +SRC = main.cpp ../../util/flv_tag.cpp ../../util/dtsc.cpp ../../util/amf.cpp ../../util/socket.cpp +OBJ = $(SRC:.cpp=.o) +OUT = DDV_DTSC2FLV +INCLUDES = +DEBUG = 4 +OPTIMIZE = -g +CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) +CC = $(CROSS)g++ +LD = $(CROSS)ld +AR = $(CROSS)ar +LIBS = +.SUFFIXES: .cpp +.PHONY: clean default +default: $(OUT) +.cpp.o: + $(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@ +$(OUT): $(OBJ) + $(CC) $(LIBS) -o $(OUT) $(OBJ) +clean: + rm -rf $(OBJ) $(OUT) Makefile.bak *~ +install: $(OUT) + cp -f ./$(OUT) /usr/bin/ + diff --git a/tools/DTSC2FLV/main.cpp b/tools/DTSC2FLV/main.cpp new file mode 100644 index 00000000..e2572abb --- /dev/null +++ b/tools/DTSC2FLV/main.cpp @@ -0,0 +1,64 @@ +/// \file DTSC2FLV/main.cpp +/// Contains the code that will transform any valid DTSC input into valid FLVs. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../util/flv_tag.h" //FLV support +#include "../../util/dtsc.h" //DTSC support +#include "../../util/amf.h" //AMF support + +/// Holds all code that converts filetypes to DTSC. +namespace Converters{ + + /// Reads DTSC from STDIN, outputs FLV to STDOUT. + int DTSC2FLV() { + FLV::Tag FLV_out; // Temporary storage for outgoing FLV data. + DTSC::Stream Strm; + std::string inBuffer; + char charBuffer[1024*10]; + unsigned int charCount; + bool doneheader = false; + + while (std::cin.good()){ + std::cin.read(charBuffer, 1024*10); + charCount = std::cin.gcount(); + inBuffer.append(charBuffer, charCount); + if (Strm.parsePacket(inBuffer)){ + if (!doneheader){ + doneheader = true; + std::cout.write(FLV::Header, 13); + FLV_out.DTSCMetaInit(Strm); + std::cout.write(FLV_out.data, FLV_out.len); + if (Strm.metadata.getContentP("video") && Strm.metadata.getContentP("video")->getContentP("init")){ + FLV_out.DTSCVideoInit(Strm); + std::cout.write(FLV_out.data, FLV_out.len); + } + if (Strm.metadata.getContentP("audio") && Strm.metadata.getContentP("audio")->getContentP("init")){ + FLV_out.DTSCAudioInit(Strm); + std::cout.write(FLV_out.data, FLV_out.len); + } + } + if (FLV_out.DTSCLoader(Strm)){ + std::cout.write(FLV_out.data, FLV_out.len); + } + } + } + + std::cerr << "Done!" << std::endl; + + return 0; + }//FLV2DTSC + +};//Converter namespace + +/// Entry point for DTSC2FLV, simply calls Converters::DTSC2FLV(). +int main(){ + return Converters::DTSC2FLV(); +}//main diff --git a/tools/FLV2DTSC/main.cpp b/tools/FLV2DTSC/main.cpp index 3655432e..1d7156e0 100644 --- a/tools/FLV2DTSC/main.cpp +++ b/tools/FLV2DTSC/main.cpp @@ -217,6 +217,7 @@ namespace Converters{ case 0x40: pack_out.addContent(DTSC::DTMI("keyframe", 1)); break; case 0x50: continue; break;//the video info byte we just throw away - useless to us... } + pack_out.addContent(DTSC::DTMI("time", FLV_in.tagTime())); if ((videodata & 0x0F) == 7){ switch (FLV_in.data[12]){ case 1: pack_out.addContent(DTSC::DTMI("nalu", 1)); break; @@ -225,9 +226,10 @@ namespace Converters{ int offset = (FLV_in.data[13] << 16) + (FLV_in.data[14] << 8) + FLV_in.data[15]; offset = (offset << 8) >> 8; pack_out.addContent(DTSC::DTMI("offset", offset)); + pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+16, (size_t)FLV_in.len-20))); + }else{ + pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16))); } - pack_out.addContent(DTSC::DTMI("time", FLV_in.tagTime())); - pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16))); if (sending){ std::cout << pack_out.Pack(true); }else{ diff --git a/tools/FLV_Analyser/Makefile b/tools/FLV_Analyser/Makefile new file mode 100644 index 00000000..81b2e2fa --- /dev/null +++ b/tools/FLV_Analyser/Makefile @@ -0,0 +1,23 @@ +SRC = main.cpp ../../util/flv_tag.cpp ../../util/dtsc.cpp ../../util/amf.cpp ../../util/socket.cpp +OBJ = $(SRC:.cpp=.o) +OUT = FLV_Info +INCLUDES = +DEBUG = 4 +OPTIMIZE = -g +CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) +CC = $(CROSS)g++ +LD = $(CROSS)ld +AR = $(CROSS)ar +LIBS = +.SUFFIXES: .cpp +.PHONY: clean default +default: $(OUT) +.cpp.o: + $(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@ +$(OUT): $(OBJ) + $(CC) $(LIBS) -o $(OUT) $(OBJ) +clean: + rm -rf $(OBJ) $(OUT) Makefile.bak *~ +install: $(OUT) + cp -f ./$(OUT) /usr/bin/ + diff --git a/tools/FLV_Analyser/main.cpp b/tools/FLV_Analyser/main.cpp new file mode 100644 index 00000000..1d4aef0e --- /dev/null +++ b/tools/FLV_Analyser/main.cpp @@ -0,0 +1,52 @@ +/// \file DTSC_Analyser/main.cpp +/// Contains the code for the DTSC Analysing tool. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../util/flv_tag.h" //FLV support + +/// Reads DTSC from stdin and outputs human-readable information to stderr. +int main() { + + FLV::Tag FLV_in; // Temporary storage for incoming FLV data. + + + while (!feof(stdin)){ + if (FLV_in.FileLoader(stdin)){ + std::cout << "Tag: " << FLV_in.tagType() << std::endl; + printf("%hhX %hhX %hhX %hhX %hhX %hhX %hhX %hhX %hhX %hhX\n", FLV_in.data[11], FLV_in.data[12], FLV_in.data[13], FLV_in.data[14], FLV_in.data[15], FLV_in.data[16], FLV_in.data[17], FLV_in.data[18], FLV_in.data[19], FLV_in.data[20]); + printf("%hhX %hhX %hhX %hhX %hhX %hhX %hhX %hhX %hhX %hhX\n", FLV_in.data[FLV_in.len-10], FLV_in.data[FLV_in.len-9], FLV_in.data[FLV_in.len-8], FLV_in.data[FLV_in.len-7], FLV_in.data[FLV_in.len-6], FLV_in.data[FLV_in.len-5], FLV_in.data[FLV_in.len-4], FLV_in.data[FLV_in.len-3], FLV_in.data[FLV_in.len-2], FLV_in.data[FLV_in.len-1]); + std::cout << std::endl; + } + } + + + DTSC::Stream Strm; + + std::string inBuffer; + char charBuffer[1024*10]; + unsigned int charCount; + bool doneheader = false; + + while(std::cin.good()){ + //invalidate the current buffer + std::cin.read(charBuffer, 1024*10); + charCount = std::cin.gcount(); + inBuffer.append(charBuffer, charCount); + if (Strm.parsePacket(inBuffer)){ + if (!doneheader){ + doneheader = true; + Strm.metadata.Print(); + } + Strm.getPacket().Print(); + } + } + return 0; +} diff --git a/util/amf.cpp b/util/amf.cpp index b5f51fad..a11788fb 100644 --- a/util/amf.cpp +++ b/util/amf.cpp @@ -2,6 +2,7 @@ /// Holds all code for the AMF namespace. #include "amf.h" +#include #include //needed for stderr only /// Returns the std::string Indice for the current object, if available. @@ -105,43 +106,45 @@ AMF::Object::Object(std::string indice, AMF::obj0type setType){//object type ini /// 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; +std::string AMF::Object::Print(std::string indent){ + std::stringstream st; + st << 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_NUMBER: st << "Number"; break; + case AMF::AMF0_BOOL: st << "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; + case AMF::AMF0_LONGSTRING: st << "String"; break; + case AMF::AMF0_OBJECT: st << "Object"; break; + case AMF::AMF0_MOVIECLIP: st << "MovieClip"; break; + case AMF::AMF0_NULL: st << "Null"; break; + case AMF::AMF0_UNDEFINED: st << "Undefined"; break; + case AMF::AMF0_REFERENCE: st << "Reference"; break; + case AMF::AMF0_ECMA_ARRAY: st << "ECMA Array"; break; + case AMF::AMF0_OBJ_END: st << "Object end"; break; + case AMF::AMF0_STRICT_ARRAY: st << "Strict Array"; break; + case AMF::AMF0_DATE: st << "Date"; break; + case AMF::AMF0_UNSUPPORTED: st << "Unsupported"; break; + case AMF::AMF0_RECORDSET: st << "Recordset"; break; + case AMF::AMF0_XMLDOC: st << "XML Document"; break; + case AMF::AMF0_TYPED_OBJ: st << "Typed Object"; break; + case AMF::AMF0_UPGRADE: st << "Upgrade to AMF3"; break; + case AMF::AMF0_DDV_CONTAINER: st << "DDVTech Container"; break; } // print my string indice, if available - std::cerr << " " << myIndice << " "; + st << " " << 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; + case AMF::AMF0_NUMBER: case AMF::AMF0_BOOL: case AMF::AMF0_REFERENCE: case AMF::AMF0_DATE: st << numval; break; + case AMF::AMF0_STRING: case AMF::AMF0_LONGSTRING: case AMF::AMF0_XMLDOC: case AMF::AMF0_TYPED_OBJ: st << strval; break; default: break;//we don't care about the rest, and don't want a compiler warning... } - std::cerr << std::endl; + st << 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+" ");} + for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){st << it->Print(indent+" ");} } + return st.str(); };//print /// Packs the AMF object to a std::string for transfer over the network. diff --git a/util/amf.h b/util/amf.h index 61f95a8a..836cfdb2 100644 --- a/util/amf.h +++ b/util/amf.h @@ -70,7 +70,7 @@ namespace AMF{ 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 Print(std::string indent = ""); std::string Pack(); protected: std::string myIndice; ///< Holds this objects indice, if any. diff --git a/util/dtsc.h b/util/dtsc.h index f721e6c4..138e3ab4 100644 --- a/util/dtsc.h +++ b/util/dtsc.h @@ -106,9 +106,9 @@ namespace DTSC{ class Ring { public: Ring(unsigned int v); - unsigned int b; ///< Holds current number of buffer. May and is intended to change unexpectedly! - bool waiting; ///< If true, this Ring is currently waiting for a buffer fill. - bool starved; ///< If true, this Ring can no longer receive valid data. + volatile unsigned int b; ///< Holds current number of buffer. May and is intended to change unexpectedly! + volatile bool waiting; ///< If true, this Ring is currently waiting for a buffer fill. + volatile bool starved; ///< If true, this Ring can no longer receive valid data. }; /// Holds temporary data for a DTSC stream and provides functions to utilize it. diff --git a/util/flv_tag.cpp b/util/flv_tag.cpp index cf75e549..bba87754 100644 --- a/util/flv_tag.cpp +++ b/util/flv_tag.cpp @@ -9,6 +9,7 @@ #include //for Tag::FileLoader #include //malloc #include //memcpy +#include /// Holds the last FLV header parsed. /// Defaults to a audio+video header on FLV version 0x01 if no header received yet. @@ -100,80 +101,84 @@ bool FLV::Tag::isInitData(){ /// audio, video or metadata, what encoding is used, and the details /// of the encoding itself. std::string FLV::Tag::tagType(){ - std::string R = ""; + std::stringstream R; + R << len << " bytes of "; 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 += "H264"; break; - default: R += "unknown"; break; + 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 << "H264"; break; + default: R << "unknown"; break; } - R += " video "; + 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; + 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; + 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; + 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; + 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; + case 0: R << " 8bit"; break; + case 2: R << " 16bit"; break; } switch (data[11] & 0x01){ - case 0: R += " mono"; break; - case 1: R += " stereo"; break; + case 0: R << " mono"; break; + case 1: R << " stereo"; break; } - R += " audio"; + R << " audio"; if ((data[12] == 0) && ((data[11] & 0xF0) == 0xA0)){ - R += " initdata"; + R << " initdata"; } break; - case 0x12: - R += "(meta)data"; + case 0x12:{ + R << "(meta)data: "; + AMF::Object metadata = AMF::parse((unsigned char*)data+11, len-15); + R << metadata.Print(); break; + } default: - R += "unknown"; + R << "unknown"; break; } - return R; + return R.str(); }//FLV::Tag::tagtype /// Returns the 32-bit timestamp of this tag. @@ -297,7 +302,7 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){ if (S.getPacket().getContentP("interframe")){data[11] += 0x20;} if (S.getPacket().getContentP("disposableframe")){data[11] += 0x30;} break; - case DTSC::AUDIO: + case DTSC::AUDIO:{ if ((unsigned int)len == S.lastData().length() + 16){ memcpy(data+12, S.lastData().c_str(), S.lastData().length()); }else{ @@ -307,12 +312,18 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){ data[11] = 0; if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){data[11] += 0xA0;} if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){data[11] += 0x20;} - if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 11025){data[11] += 0x04;} - if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 22050){data[11] += 0x08;} - if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 44100){data[11] += 0x0C;} + unsigned int datarate = S.metadata.getContentP("audio")->getContentP("rate")->NumValue(); + if (datarate >= 44100){ + data[11] += 0x0C; + }else if(datarate >= 22050){ + data[11] += 0x08; + }else if(datarate >= 11025){ + data[11] += 0x04; + } if (S.metadata.getContentP("audio")->getContentP("size")->NumValue() == 16){data[11] += 0x02;} if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;} break; + } case DTSC::META: memcpy(data+11, S.lastData().c_str(), S.lastData().length()); break; @@ -329,6 +340,9 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){ data[1] = ((len-15) >> 16) & 0xFF; data[2] = ((len-15) >> 8) & 0xFF; data[3] = (len-15) & 0xFF; + data[8] = 0; + data[9] = 0; + data[10] = 0; tagTime(S.getPacket().getContentP("time")->NumValue()); return true; } @@ -336,7 +350,7 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){ /// Helper function that properly sets the tag length from the internal len variable. void FLV::Tag::setLen(){ int len4 = len - 4; - int i = len-1; + int i = len; data[--i] = (len4) & 0xFF; len4 >>= 8; data[--i] = (len4) & 0xFF; @@ -375,6 +389,9 @@ bool FLV::Tag::DTSCVideoInit(DTSC::Stream & S){ data[1] = ((len-15) >> 16) & 0xFF; data[2] = ((len-15) >> 8) & 0xFF; data[3] = (len-15) & 0xFF; + data[8] = 0; + data[9] = 0; + data[10] = 0; tagTime(0); return true; } @@ -402,23 +419,25 @@ bool FLV::Tag::DTSCAudioInit(DTSC::Stream & S){ data[11] = 0; if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){data[11] += 0xA0;} if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){data[11] += 0x20;} - if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 11000){data[11] += 0x04;} - if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 22000){data[11] += 0x08;} - if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 44000){data[11] += 0x0C;} + unsigned int datarate = S.metadata.getContentP("audio")->getContentP("rate")->NumValue(); + if (datarate >= 44100){ + data[11] += 0x0C; + }else if(datarate >= 22050){ + data[11] += 0x08; + }else if(datarate >= 11025){ + data[11] += 0x04; + } if (S.metadata.getContentP("audio")->getContentP("size")->NumValue() == 16){data[11] += 0x02;} if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;} } setLen(); - switch (S.lastType()){ - case DTSC::VIDEO: data[0] = 0x09; break; - case DTSC::AUDIO: data[0] = 0x08; break; - case DTSC::META: data[0] = 0x12; break; - default: break; - } data[0] = 0x08; data[1] = ((len-15) >> 16) & 0xFF; data[2] = ((len-15) >> 8) & 0xFF; data[3] = (len-15) & 0xFF; + data[8] = 0; + data[9] = 0; + data[10] = 0; tagTime(0); return true; } @@ -501,6 +520,9 @@ bool FLV::Tag::DTSCMetaInit(DTSC::Stream & S){ data[1] = ((len-15) >> 16) & 0xFF; data[2] = ((len-15) >> 8) & 0xFF; data[3] = (len-15) & 0xFF; + data[8] = 0; + data[9] = 0; + data[10] = 0; tagTime(0); return true; } From b0880215dfd4a0a92df40fc02f3fd4234ac43040 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 10 Apr 2012 17:01:09 +0200 Subject: [PATCH 13/24] Fixed HTTP Connector F4V support. --- Connector_HTTP/main.cpp | 106 ++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 68 deletions(-) diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index 0ba6ef71..3962fe18 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -25,6 +25,10 @@ namespace Connector_HTTP{ /// Defines the type of handler used to process this request. enum {HANDLER_NONE, HANDLER_PROGRESSIVE, HANDLER_FLASH, HANDLER_APPLE, HANDLER_MICRO, HANDLER_JSCRIPT}; + std::queue Flash_FragBuffer;///\n\n"; Result += ""; @@ -100,7 +104,7 @@ namespace Connector_HTTP{ 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); - FLV::Tag tmp; + static FLV::Tag tmp; tmp.DTSCMetaInit(Strm); conn.write(tmp.data, tmp.len); if (Strm.metadata.getContentP("audio") && Strm.metadata.getContentP("audio")->getContentP("init")){ @@ -121,65 +125,28 @@ namespace Connector_HTTP{ } /// Handles Flash Dynamic HTTP streaming requests - void FlashDynamic(FLV::Tag & tag, HTTP::Parser HTTP_S, Socket::Connection & conn, DTSC::Stream & Strm){ - static std::queue Flash_FragBuffer; - static unsigned int Flash_StartTime = 0; + void FlashDynamic(FLV::Tag & tag, DTSC::Stream & Strm){ static std::string FlashBuf; - static std::string FlashMeta; - static bool FlashFirstVideo = false; - static bool FlashFirstAudio = false; - static bool Flash_ManifestSent = false; - static int Flash_RequestPending = 0; - if (tag.tagTime() > 0){ - if (Flash_StartTime == 0){ - Flash_StartTime = tag.tagTime(); + static FLV::Tag tmp; + if (Strm.getPacket(0).getContentP("keyframe")){ + 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(); + //fill buffer with init data, if needed. + if (Strm.metadata.getContentP("audio") && Strm.metadata.getContentP("audio")->getContentP("init")){ + tmp.DTSCAudioInit(Strm); + FlashBuf.append(tmp.data, tmp.len); + } + if (Strm.metadata.getContentP("video") && Strm.metadata.getContentP("video")->getContentP("init")){ + tmp.DTSCVideoInit(Strm); + FlashBuf.append(tmp.data, tmp.len); } - tag.tagTime(tag.tagTime() - Flash_StartTime); - } - if (tag.data[0] != 0x12 ) { - if (tag.isKeyframe){ - if (FlashBuf != "" && !FlashFirstVideo && !FlashFirstAudio){ - 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; - } - /// \todo Check metadata for video/audio, append if needed. - /* - 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 - if ((Video_Init.len > 0) && (Audio_Init.len > 0)){ - 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"); - } - */ } + FlashBuf.append(tag.data, tag.len); } @@ -192,14 +159,14 @@ namespace Connector_HTTP{ std::string streamname; FLV::Tag tag;///< Temporary tag buffer. std::string recBuffer = ""; - DTSC::Stream Strm;///< Incoming stream buffer. - HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. std::string Movie = ""; std::string Quality = ""; int Segment = -1; int ReqFragment = -1; int temp; + int Flash_RequestPending = 0; + bool Flash_ManifestSent = false; unsigned int lastStats = 0; //int CurrentFragment = -1; later herbruiken? @@ -207,6 +174,7 @@ namespace Connector_HTTP{ //only parse input if available or not yet init'ed if (HTTP_R.Read(conn, ready4data)){ handler = HANDLER_PROGRESSIVE; + std::cout << "Received request: " << HTTP_R.url << std::endl; 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"){ @@ -233,11 +201,7 @@ namespace Connector_HTTP{ 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 - /// \todo Handle these requests properly... - /* - Flash_ManifestSent = true;//stop manifest from being sent multiple times Flash_RequestPending++; - */ }else{ Movie = HTTP_R.url.substr(1); Movie = Movie.substr(0,Movie.find("/")); @@ -251,6 +215,15 @@ namespace Connector_HTTP{ }//strip nonalphanumeric } streamname += Movie; + if( !Flash_ManifestSent ) { + HTTP_S.Clean(); + HTTP_S.SetHeader("Content-Type","text/xml"); + HTTP_S.SetHeader("Cache-Control","no-cache"); + HTTP_S.SetBody(BuildManifest(Movie)); + HTTP_S.SendResponse(conn, "200", "OK"); + Flash_ManifestSent = true;//stop manifest from being sent multiple times + std::cout << "Sent manifest" << std::endl; + } ready4data = true; }//FLASH handler if (handler == HANDLER_PROGRESSIVE){ @@ -282,8 +255,6 @@ namespace Connector_HTTP{ #endif inited = true; } - /// \todo Send pending flash requests... - /* if ((Flash_RequestPending > 0) && !Flash_FragBuffer.empty()){ HTTP_S.Clean(); HTTP_S.SetHeader("Content-Type","video/mp4"); @@ -295,7 +266,6 @@ namespace Connector_HTTP{ fprintf(stderr, "Sending a video fragment. %i left in buffer, %i requested\n", (int)Flash_FragBuffer.size(), Flash_RequestPending); #endif } - */ if (inited){ unsigned int now = time(0); if (now != lastStats){ @@ -309,7 +279,7 @@ namespace Connector_HTTP{ if (Strm.parsePacket(ss.Received())){ tag.DTSCLoader(Strm); if (handler == HANDLER_FLASH){ - FlashDynamic(tag, HTTP_S, conn, Strm); + FlashDynamic(tag, Strm); } if (handler == HANDLER_PROGRESSIVE){ Progressive(tag, HTTP_S, conn, Strm); From f9de7f9a64456030e9a12bd4dc7cf1b314bdbd37 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 15 Apr 2012 01:54:39 +0200 Subject: [PATCH 14/24] RTMP Connector upgrade to DTSC - push mode doesn't convert to DTSC yet (it will tomorrow) and it is still untested - but should work. --- Connector_HTTP/Analysis_BuckBunny_Fifa | 48 -- Connector_RTMP/main.cpp | 715 +++++++++---------------- util/socket.cpp | 5 +- util/socket.h | 2 +- 4 files changed, 254 insertions(+), 516 deletions(-) delete mode 100644 Connector_HTTP/Analysis_BuckBunny_Fifa diff --git a/Connector_HTTP/Analysis_BuckBunny_Fifa b/Connector_HTTP/Analysis_BuckBunny_Fifa deleted file mode 100644 index 45cb2de6..00000000 --- a/Connector_HTTP/Analysis_BuckBunny_Fifa +++ /dev/null @@ -1,48 +0,0 @@ - [BuckBunny Entry1] - -trackinfo - -timescale -length -language -sampledescription -sampletype - -timescale -length -language -sampledescription -sampletype - -audiochannels -audiosamplerate -videoframerate -aacaot -avclevel -avcprofile -audiocodecid -videocodecid -width -height -frameWidth -frameHeight -displayWidth -displayHeight -moovposition -duration - - - [FIFA Entry] - -duration -width -height -videodatarate -framerate -videocodecid -audiodatarate -audiosamplerate -audiosamplesize -stero -audiocodecid -filesize \ No newline at end of file diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 28a9622b..d6eb2769 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -27,7 +27,9 @@ namespace Connector_RTMP{ Socket::Connection Socket; ///< Socket connected to user Socket::Connection SS; ///< Socket connected to server std::string streamname = "/tmp/shared_socket"; ///< Stream that will be opened - void parseChunk(); + void parseChunk(std::string & buffer);///< Parses a single RTMP chunk. + void sendCommand(AMF::Object & amfreply, int messagetype, int stream_id);///< Sends a RTMP command either in AMF or AMF3 mode. + void parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id);///< Parses a single AMF command message. int Connector_RTMP(Socket::Connection conn); };//Connector_RTMP namespace; @@ -35,8 +37,9 @@ namespace Connector_RTMP{ /// Main Connector_RTMP function int Connector_RTMP::Connector_RTMP(Socket::Connection conn){ Socket = conn; - FLV::Tag tag, viddata, auddata; - bool viddone = false, auddone = false; + FLV::Tag tag, init_tag; + DTSC::Stream Strm; + bool stream_inited = false;//true if init data for audio/video was sent //first timestamp set RTMPStream::firsttime = RTMPStream::getNowMS(); @@ -61,15 +64,10 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){ unsigned int lastStats = 0; - while (Socket.connected() && !FLV::Parse_Error){ - //only parse input if available or not yet init'ed - //rightnow = getNowMS(); - if (Socket.canRead() || !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 - } + while (Socket.connected()){ + sleep(10000);//sleep 10ms to prevent high CPU usage + if (Socket.spool()){ + parseChunk(Socket.Received()); } if (ready4data){ if (!inited){ @@ -95,54 +93,27 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){ SS.write(stat); } } - SS.canRead(); - 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: - bool justdone = false; - if (tag.SockLoader(SS)){//able to read a full packet? - //init data? parse and resent in correct order if all is received - /// \todo Check metadata for needed audio/video init or not - we now assume both video/audio are always present... - if (((tag.data[0] == 0x09) && !viddone) || ((tag.data[0] == 0x08) && !auddone)){ - if (tag.needsInitData()){ - if (tag.data[0] == 0x09){viddata = tag;}else{auddata = tag;} - } - if (tag.data[0] == 0x09){viddone = true;}else{auddone = true;} - justdone = true; + if (SS.spool()){ + if (Strm.parsePacket(SS.Received())){ + //sent init data if needed + if (!stream_inited){ + if (Strm.metadata.getContentP("audio") && Strm.metadata.getContentP("audio")->getContentP("init")){ + init_tag.DTSCAudioInit(Strm); + Socket.write(RTMPStream::SendMedia(init_tag)); } - if (viddone && auddone && justdone){ - if (viddata.len != 0){ - Socket.write(RTMPStream::SendMedia(viddata)); - #if DEBUG >= 8 - fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), viddata.tagTime(), viddata.tagType().c_str()); - #endif - } - if (auddata.len != 0){ - Socket.write(RTMPStream::SendMedia(auddata)); - #if DEBUG >= 8 - fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), auddata.tagTime(), auddata.tagType().c_str()); - #endif - } - break; + if (Strm.metadata.getContentP("video") && Strm.metadata.getContentP("video")->getContentP("init")){ + init_tag.DTSCVideoInit(Strm); + Socket.write(RTMPStream::SendMedia(init_tag)); } - //not gotten init yet? cancel this tag - if (tag.needsInitData()){ - if ((tag.data[0] == 0x09) && (viddata.len == 0)){break;} - if ((tag.data[0] == 0x08) && (auddata.len == 0)){break;} - } - //send tag normally - Socket.write(RTMPStream::SendMedia(tag)); - #if DEBUG >= 8 - fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), tag.tagTime(), tag.tagType().c_str()); - #endif + stream_inited = true; } - break; + //sent a tag + tag.DTSCLoader(Strm); + Socket.write(RTMPStream::SendMedia(tag)); + #if DEBUG >= 8 + fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), tag.tagTime(), tag.tagType().c_str()); + #endif + } } } } @@ -165,15 +136,13 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){ }//Connector_RTMP /// Tries to get and parse one RTMP chunk at a time. -void Connector_RTMP::parseChunk(){ +void Connector_RTMP::parseChunk(std::string & inbuffer){ static RTMPStream::Chunk next; - static std::string inbuffer; FLV::Tag F; 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)){ @@ -246,6 +215,7 @@ void Connector_RTMP::parseChunk(){ #if DEBUG >= 4 fprintf(stderr, "A"); #endif + /// \TODO Convert to DTSC properly. SS.write(std::string(F.data, F.len)); }else{ #if DEBUG >= 4 @@ -260,6 +230,7 @@ void Connector_RTMP::parseChunk(){ #if DEBUG >= 4 fprintf(stderr, "V"); #endif + /// \TODO Convert to DTSC properly. SS.write(std::string(F.data, F.len)); }else{ #if DEBUG >= 4 @@ -279,7 +250,6 @@ void Connector_RTMP::parseChunk(){ #endif break; case 17:{ - bool parsed3 = false; #if DEBUG >= 4 fprintf(stderr, "Received AFM3 command message\n"); #endif @@ -295,205 +265,7 @@ void Connector_RTMP::parseChunk(){ #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 audio support detected\n");} - #endif - Socket.write(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5) - Socket.write(RTMPStream::SendCTL(6, RTMPStream::rec_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,0,1,123")); - amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)31)); - //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("clientid", 1337)); - 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(amfdata.getContent(1));//same transaction ID - 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() == "closeStream") || (amfdata.getContentP(0)->StrValue() == "deleteStream")){ - if (SS.connected()){SS.close();} - } - 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() == "publish")){ - if (amfdata.getContentP(3)){ - streamname = amfdata.getContentP(3)->StrValue(); - for (std::string::iterator i=streamname.begin(); i != streamname.end(); ++i){ - if (*i == '?'){streamname.erase(i, streamname.end()); break;} - if (!isalpha(*i) && !isdigit(*i) && *i != '_'){ - streamname.erase(i); - --i; - }else{ - *i=tolower(*i); - } - } - streamname = "/tmp/shared_socket_" + streamname; - #if DEBUG >= 4 - fprintf(stderr, "Connecting to buffer %s...\n", streamname.c_str()); - #endif - SS = Socket::Connection(streamname); - if (!SS.connected()){ - #if DEBUG >= 1 - fprintf(stderr, "Could not connect to server!\n"); - #endif - Socket.close();//disconnect user - break; - } - SS.write("P "+Socket.getHost()+'\n'); - nostats = true; - #if DEBUG >= 4 - fprintf(stderr, "Connected to buffer, starting to sent data...\n"); - #endif - } - //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("", 1, AMF::AMF0_BOOL));//publish success? - #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 - //send a status reply - amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER); - amfreply.addContent(AMF::Object("", "onStatus"));//status reply - amfreply.addContent(AMF::Object("", 0, AMF::AMF0_NUMBER));//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.Publish.Start")); - amfreply.getContentP(3)->addContent(AMF::Object("description", "Stream is now published!")); - amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); - #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) && *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)1337)); - #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)1337)); - #if DEBUG >= 4 - amfreply.Print(); - #endif - Socket.write(RTMPStream::SendChunk(4, 17, 1, (char)0+amfreply.Pack())); - RTMPStream::chunk_snd_max = 102400;//100KiB - 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 - } + parseAMFCommand(amfdata, 17, next.msg_stream_id); }//parsing AMF0-style } break; case 18: @@ -511,212 +283,8 @@ void Connector_RTMP::parseChunk(){ #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"){ - 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; - if (amfdata.getContentP(2)->getContentP("videoCodecs")){ - 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");} - } - if (amfdata.getContentP(2)->getContentP("audioCodecs")){ - 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 - RTMPStream::chunk_snd_max = 4096; - Socket.write(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max));//send chunk size max (msg 1) - Socket.write(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5) - Socket.write(RTMPStream::SendCTL(6, RTMPStream::rec_window_size));//send rec window acknowledgement size (msg 6) - 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,0,1,123")); - amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)31)); - //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("clientid", 1337)); - 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, 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(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, 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() == "closeStream") || (amfdata.getContentP(0)->StrValue() == "deleteStream")){ - if (SS.connected()){SS.close();} - } - 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() == "publish")){ - if (amfdata.getContentP(3)){ - streamname = amfdata.getContentP(3)->StrValue(); - for (std::string::iterator i=streamname.begin(); i != streamname.end(); ++i){ - if (*i == '?'){streamname.erase(i, streamname.end()); break;} - if (!isalpha(*i) && !isdigit(*i) && *i != '_'){ - streamname.erase(i); - --i; - }else{ - *i=tolower(*i); - } - } - streamname = "/tmp/shared_socket_" + streamname; - #if DEBUG >= 4 - fprintf(stderr, "Connecting to buffer %s...\n", streamname.c_str()); - #endif - SS = Socket::Connection(streamname); - if (!SS.connected()){ - #if DEBUG >= 1 - fprintf(stderr, "Could not connect to server!\n"); - #endif - Socket.close();//disconnect user - break; - } - SS.write("P "+Socket.getHost()+'\n'); - nostats = true; - #if DEBUG >= 4 - fprintf(stderr, "Connected to buffer, starting to send data...\n"); - #endif - } - //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("", 1, AMF::AMF0_BOOL));//publish success? - #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 - //send a status reply - amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER); - amfreply.addContent(AMF::Object("", "onStatus"));//status reply - amfreply.addContent(AMF::Object("", 0, AMF::AMF0_NUMBER));//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.Publish.Start")); - amfreply.getContentP(3)->addContent(AMF::Object("description", "Stream is now published!")); - amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); - #if DEBUG >= 4 - amfreply.Print(); - #endif - Socket.write(RTMPStream::SendChunk(4, 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) && *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)1337)); - #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("clientid", (double)1337)); - #if DEBUG >= 4 - amfreply.Print(); - #endif - Socket.write(RTMPStream::SendChunk(4, 20, 1, amfreply.Pack())); - RTMPStream::chunk_snd_max = 102400;//100KiB; - 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 - } + parseAMFCommand(amfdata, 20, next.msg_stream_id); } break; case 22: #if DEBUG >= 4 @@ -733,6 +301,223 @@ void Connector_RTMP::parseChunk(){ } }//parseChunk +void Connector_RTMP::sendCommand(AMF::Object & amfreply, int messagetype, int stream_id){ + if (messagetype == 17){ + Socket.write(RTMPStream::SendChunk(3, messagetype, stream_id, (char)0+amfreply.Pack())); + }else{ + Socket.write(RTMPStream::SendChunk(3, messagetype, stream_id, amfreply.Pack())); + } +}//sendCommand + +void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id){ + bool parsed = false; + #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; + if (amfdata.getContentP(2)->getContentP("videoCodecs")){ + 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");} + } + if (amfdata.getContentP(2)->getContentP("audioCodecs")){ + tmpint = (int)amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue(); + if (tmpint & 0x04){fprintf(stderr, "MP3 audio support detected\n");} + if (tmpint & 0x400){fprintf(stderr, "AAC audio support detected\n");} + } + #endif + RTMPStream::chunk_snd_max = 4096; + Socket.write(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max));//send chunk size max (msg 1) + Socket.write(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5) + Socket.write(RTMPStream::SendCTL(6, RTMPStream::rec_window_size));//send rec window acknowledgement size (msg 6) + 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,0,1,123")); + amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)31)); + //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("clientid", 1337)); + 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 + sendCommand(amfreply, messagetype, stream_id); + //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(amfdata.getContent(1));//same transaction ID + amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null + sendCommand(amfreply, messagetype, stream_id); + 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 + sendCommand(amfreply, messagetype, stream_id); + Socket.write(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 + parsed = true; + }//createStream + if ((amfdata.getContentP(0)->StrValue() == "closeStream") || (amfdata.getContentP(0)->StrValue() == "deleteStream")){ + if (SS.connected()){SS.close();} + } + 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 + sendCommand(amfreply, messagetype, stream_id); + parsed = true; + }//getStreamLength + if ((amfdata.getContentP(0)->StrValue() == "publish")){ + if (amfdata.getContentP(3)){ + streamname = amfdata.getContentP(3)->StrValue(); + for (std::string::iterator i=streamname.begin(); i != streamname.end(); ++i){ + if (*i == '?'){streamname.erase(i, streamname.end()); break;} + if (!isalpha(*i) && !isdigit(*i) && *i != '_'){ + streamname.erase(i); + --i; + }else{ + *i=tolower(*i); + } + } + streamname = "/tmp/shared_socket_" + streamname; + #if DEBUG >= 4 + fprintf(stderr, "Connecting to buffer %s...\n", streamname.c_str()); + #endif + SS = Socket::Connection(streamname); + if (!SS.connected()){ + #if DEBUG >= 1 + fprintf(stderr, "Could not connect to server!\n"); + #endif + Socket.close();//disconnect user + return; + } + SS.write("P "+Socket.getHost()+'\n'); + nostats = true; + #if DEBUG >= 4 + fprintf(stderr, "Connected to buffer, starting to send data...\n"); + #endif + } + //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("", 1, AMF::AMF0_BOOL));//publish success? + #if DEBUG >= 4 + amfreply.Print(); + #endif + sendCommand(amfreply, messagetype, stream_id); + Socket.write(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1 + //send a status reply + amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER); + amfreply.addContent(AMF::Object("", "onStatus"));//status reply + amfreply.addContent(AMF::Object("", 0, AMF::AMF0_NUMBER));//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.Publish.Start")); + amfreply.getContentP(3)->addContent(AMF::Object("description", "Stream is now published!")); + amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); + #if DEBUG >= 4 + amfreply.Print(); + #endif + sendCommand(amfreply, messagetype, stream_id); + 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 + sendCommand(amfreply, messagetype, stream_id); + 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) && *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", "DDV")); + amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); + #if DEBUG >= 4 + amfreply.Print(); + #endif + sendCommand(amfreply, messagetype, stream_id); + 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", "DDV")); + amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337)); + #if DEBUG >= 4 + amfreply.Print(); + #endif + sendCommand(amfreply, messagetype, stream_id); + RTMPStream::chunk_snd_max = 102400;//100KiB + 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 + } +}//parseAMFCommand + // Load main server setup file, default port 1935, handler is Connector_RTMP::Connector_RTMP #define DEFAULT_PORT 1935 diff --git a/util/socket.cpp b/util/socket.cpp index ed669e75..e751b097 100644 --- a/util/socket.cpp +++ b/util/socket.cpp @@ -222,9 +222,10 @@ std::string Socket::Connection::getStats(std::string C){ } /// Updates the downbuffer and upbuffer internal variables. -void Socket::Connection::spool(){ - iread(downbuffer); +/// Returns true if new data was received, false otherwise. +bool Socket::Connection::spool(){ iwrite(upbuffer); + return iread(downbuffer); } /// Returns a reference to the download buffer. diff --git a/util/socket.h b/util/socket.h index 988b96c2..3a538286 100644 --- a/util/socket.h +++ b/util/socket.h @@ -48,7 +48,7 @@ namespace Socket{ bool swrite(std::string & buffer); ///< Write call that is compatible with std::string. bool iread(std::string & buffer); ///< Incremental write call that is compatible with std::string. bool iwrite(std::string & buffer); ///< Write call that is compatible with std::string. - void spool(); ///< Updates the downbuffer and upbuffer internal variables. + bool spool(); ///< Updates the downbuffer and upbuffer internal variables. std::string & Received(); ///< Returns a reference to the download buffer. void Send(std::string data); ///< Appends data to the upbuffer. void close(); ///< Close connection. From 0817b818fb62d61fa2da665836c939d5b2a97524 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 17 Apr 2012 11:11:01 +0200 Subject: [PATCH 15/24] Minor fixes in config.cpp --- util/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/config.cpp b/util/config.cpp index 6d56ab7f..b426766c 100644 --- a/util/config.cpp +++ b/util/config.cpp @@ -3,7 +3,6 @@ #include "config.h" #include -#include #include #ifdef __FreeBSD__ @@ -14,6 +13,7 @@ #include #include #include +#include #include #include #include From 2a8ce23db0173ad090f36986bc37b57cb03bd3cd Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 17 Apr 2012 21:33:43 +0200 Subject: [PATCH 16/24] A few bugfixes and improvements in the FLV2DTSC code. Also, moved conversion logic to flv_tag.cpp/h instead of keeping it inside the conversion tool itself. --- tools/FLV2DTSC/main.cpp | 201 +--------------------------------------- util/dtsc.cpp | 14 ++- util/dtsc.h | 1 + util/flv_tag.cpp | 175 ++++++++++++++++++++++++++++++++++ util/flv_tag.h | 5 + 5 files changed, 198 insertions(+), 198 deletions(-) diff --git a/tools/FLV2DTSC/main.cpp b/tools/FLV2DTSC/main.cpp index 1d7156e0..6ae6516c 100644 --- a/tools/FLV2DTSC/main.cpp +++ b/tools/FLV2DTSC/main.cpp @@ -14,62 +14,12 @@ #include "../../util/dtsc.h" //DTSC support #include "../../util/amf.h" //AMF support -// String onMetaData -// ECMA Array -// Bool hasVideo 1 -// Number videocodecid 4 (2 = H263, 4 = VP6, 7 = H264) -// Number width 320 -// Number height 240 -// Number framerate 23.976 (/ 1000) -// Number videodatarate 500.349 (kbps) -// Bool hasAudio 1 -// Bool stereo 1 -// Number audiodelay 0 -// Number audiosamplerate 11025 -// Number audiosamplesize 16 -// Number audiocodecid 2 (2 = MP3, 10 = AAC) -// Number audiodatarate 64.3269 (kbps) - - /// Holds all code that converts filetypes to DTSC. namespace Converters{ - /// Inserts std::string type metadata into the passed DTMI object. - /// \arg meta The DTMI object to put the metadata into. - /// \arg cat Metadata category to insert into. - /// \arg elem Element name to put into the category. - /// \arg val Value to put into the element name. - void Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, std::string val){ - if (meta.getContentP(cat) == 0){meta.addContent(DTSC::DTMI(cat));} - meta.getContentP(cat)->addContent(DTSC::DTMI(elem, val)); - std::cerr << "Metadata " << cat << "." << elem << " = " << val << std::endl; - } - - /// Inserts uint64_t type metadata into the passed DTMI object. - /// \arg meta The DTMI object to put the metadata into. - /// \arg cat Metadata category to insert into. - /// \arg elem Element name to put into the category. - /// \arg val Value to put into the element name. - void Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, uint64_t val){ - if (meta.getContentP(cat) == 0){meta.addContent(DTSC::DTMI(cat));} - meta.getContentP(cat)->addContent(DTSC::DTMI(elem, val)); - std::cerr << "Metadata " << cat << "." << elem << " = " << val << std::endl; - } - - /// Returns true if the named category and elementname are available in the metadata. - /// \arg meta The DTMI object to check. - /// \arg cat Metadata category to check. - /// \arg elem Element name to check. - bool Meta_Has(DTSC::DTMI & meta, std::string cat, std::string elem){ - if (meta.getContentP(cat) == 0){return false;} - if (meta.getContentP(cat)->getContentP(elem) == 0){return false;} - return true; - } - /// Reads FLV from STDIN, outputs DTSC to STDOUT. int FLV2DTSC() { FLV::Tag FLV_in; // Temporary storage for incoming FLV data. - AMF::Object meta_in; // Temporary storage for incoming metadata. DTSC::DTMI meta_out; // Storage for outgoing DTMI header data. DTSC::DTMI pack_out; // Storage for outgoing DTMI data. std::stringstream prebuffer; // Temporary buffer before sending real data @@ -78,6 +28,8 @@ namespace Converters{ while (!feof(stdin)){ if (FLV_in.FileLoader(stdin)){ + pack_out = FLV_in.toDTSC(meta_out); + if (pack_out.isEmpty()){continue;} if (!sending){ counter++; if (counter > 10){ @@ -89,152 +41,9 @@ namespace Converters{ prebuffer.str(""); std::cerr << "Buffer done, starting real-time output..." << std::endl; } - } - if (FLV_in.data[0] == 0x12){ - meta_in = AMF::parse((unsigned char*)FLV_in.data+11, FLV_in.len-15); - if (meta_in.getContentP(0) && (meta_in.getContentP(0)->StrValue() == "onMetaData") && meta_in.getContentP(1)){ - AMF::Object * tmp = meta_in.getContentP(1); - if (tmp->getContentP("videocodecid")){ - switch ((unsigned int)tmp->getContentP("videocodecid")->NumValue()){ - case 2: Meta_Put(meta_out, "video", "codec", "H263"); break; - case 4: Meta_Put(meta_out, "video", "codec", "VP6"); break; - case 7: Meta_Put(meta_out, "video", "codec", "H264"); break; - default: Meta_Put(meta_out, "video", "codec", "?"); break; - } - } - if (tmp->getContentP("audiocodecid")){ - switch ((unsigned int)tmp->getContentP("audiocodecid")->NumValue()){ - case 2: Meta_Put(meta_out, "audio", "codec", "MP3"); break; - case 10: Meta_Put(meta_out, "audio", "codec", "AAC"); break; - default: Meta_Put(meta_out, "audio", "codec", "?"); break; - } - } - if (tmp->getContentP("width")){ - Meta_Put(meta_out, "video", "width", tmp->getContentP("width")->NumValue()); - } - if (tmp->getContentP("height")){ - Meta_Put(meta_out, "video", "height", tmp->getContentP("height")->NumValue()); - } - if (tmp->getContentP("framerate")){ - Meta_Put(meta_out, "video", "fpks", tmp->getContentP("framerate")->NumValue()*1000); - } - if (tmp->getContentP("videodatarate")){ - Meta_Put(meta_out, "video", "bps", (tmp->getContentP("videodatarate")->NumValue()*1024)/8); - } - if (tmp->getContentP("audiodatarate")){ - Meta_Put(meta_out, "audio", "bps", (tmp->getContentP("audiodatarate")->NumValue()*1024)/8); - } - if (tmp->getContentP("audiosamplerate")){ - Meta_Put(meta_out, "audio", "rate", tmp->getContentP("audiosamplerate")->NumValue()); - } - if (tmp->getContentP("audiosamplesize")){ - Meta_Put(meta_out, "audio", "size", tmp->getContentP("audiosamplesize")->NumValue()); - } - if (tmp->getContentP("stereo")){ - if (tmp->getContentP("stereo")->NumValue() == 1){ - Meta_Put(meta_out, "audio", "channels", 2); - }else{ - Meta_Put(meta_out, "audio", "channels", 1); - } - } - } - } - if (FLV_in.data[0] == 0x08){ - char audiodata = FLV_in.data[11]; - if (FLV_in.needsInitData() && FLV_in.isInitData()){ - if ((audiodata & 0xF0) == 0xA0){ - Meta_Put(meta_out, "audio", "init", std::string((char*)FLV_in.data+13, (size_t)FLV_in.len-17)); - }else{ - Meta_Put(meta_out, "audio", "init", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16)); - } - continue;//skip rest of parsing, get next tag. - } - pack_out = DTSC::DTMI("audio", DTSC::DTMI_ROOT); - pack_out.addContent(DTSC::DTMI("datatype", "audio")); - pack_out.addContent(DTSC::DTMI("time", FLV_in.tagTime())); - if (!Meta_Has(meta_out, "audio", "codec")){ - switch (audiodata & 0xF0){ - case 0x20: Meta_Put(meta_out, "audio", "codec", "MP3"); break; - case 0xA0: Meta_Put(meta_out, "audio", "codec", "AAC"); break; - default: Meta_Put(meta_out, "audio", "codec", "?"); break; - } - } - if (!Meta_Has(meta_out, "audio", "rate")){ - switch (audiodata & 0x0C){ - case 0x0: Meta_Put(meta_out, "audio", "rate", 5512); break; - case 0x4: Meta_Put(meta_out, "audio", "rate", 11025); break; - case 0x8: Meta_Put(meta_out, "audio", "rate", 22050); break; - case 0xC: Meta_Put(meta_out, "audio", "rate", 44100); break; - } - } - if (!Meta_Has(meta_out, "audio", "size")){ - switch (audiodata & 0x02){ - case 0x0: Meta_Put(meta_out, "audio", "size", 8); break; - case 0x2: Meta_Put(meta_out, "audio", "size", 16); break; - } - } - if (!Meta_Has(meta_out, "audio", "channels")){ - switch (audiodata & 0x01){ - case 0x0: Meta_Put(meta_out, "audio", "channels", 1); break; - case 0x1: Meta_Put(meta_out, "audio", "channels", 2); break; - } - } - if ((audiodata & 0xF0) == 0xA0){ - pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+13, (size_t)FLV_in.len-17))); - }else{ - pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16))); - } - if (sending){ - std::cout << pack_out.Pack(true); - }else{ - prebuffer << pack_out.Pack(true); - } - } - if (FLV_in.data[0] == 0x09){ - char videodata = FLV_in.data[11]; - if (FLV_in.needsInitData() && FLV_in.isInitData()){ - if ((videodata & 0x0F) == 7){ - Meta_Put(meta_out, "video", "init", std::string((char*)FLV_in.data+16, (size_t)FLV_in.len-20)); - }else{ - Meta_Put(meta_out, "video", "init", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16)); - } - continue;//skip rest of parsing, get next tag. - } - if (!Meta_Has(meta_out, "video", "codec")){ - switch (videodata & 0x0F){ - case 2: Meta_Put(meta_out, "video", "codec", "H263"); break; - case 4: Meta_Put(meta_out, "video", "codec", "VP6"); break; - case 7: Meta_Put(meta_out, "video", "codec", "H264"); break; - default: Meta_Put(meta_out, "video", "codec", "?"); break; - } - } - pack_out = DTSC::DTMI("video", DTSC::DTMI_ROOT); - pack_out.addContent(DTSC::DTMI("datatype", "video")); - switch (videodata & 0xF0){ - case 0x10: pack_out.addContent(DTSC::DTMI("keyframe", 1)); break; - case 0x20: pack_out.addContent(DTSC::DTMI("interframe", 1)); break; - case 0x30: pack_out.addContent(DTSC::DTMI("disposableframe", 1)); break; - case 0x40: pack_out.addContent(DTSC::DTMI("keyframe", 1)); break; - case 0x50: continue; break;//the video info byte we just throw away - useless to us... - } - pack_out.addContent(DTSC::DTMI("time", FLV_in.tagTime())); - if ((videodata & 0x0F) == 7){ - switch (FLV_in.data[12]){ - case 1: pack_out.addContent(DTSC::DTMI("nalu", 1)); break; - case 2: pack_out.addContent(DTSC::DTMI("nalu_end", 1)); break; - } - int offset = (FLV_in.data[13] << 16) + (FLV_in.data[14] << 8) + FLV_in.data[15]; - offset = (offset << 8) >> 8; - pack_out.addContent(DTSC::DTMI("offset", offset)); - pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+16, (size_t)FLV_in.len-20))); - }else{ - pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16))); - } - if (sending){ - std::cout << pack_out.Pack(true); - }else{ - prebuffer << pack_out.Pack(true); - } + prebuffer << pack_out.Pack(true); + }else{ + std::cout << pack_out.Pack(true); } } } diff --git a/util/dtsc.cpp b/util/dtsc.cpp index fd0b6a7e..26601a5d 100644 --- a/util/dtsc.cpp +++ b/util/dtsc.cpp @@ -198,6 +198,13 @@ const char * DTSC::DTMI::Str(){return strval.c_str();}; /// If this object is not a container type, this function will always return 0. int DTSC::DTMI::hasContent(){return contents.size();}; +/// Returns true if this DTSC::DTMI value is non-default. +/// Non-default means it is either not a root element or has content. +bool DTSC::DTMI::isEmpty(){ + if (myType != DTMI_ROOT){return false;} + return (hasContent() == 0); +}; + /// Adds an DTSC::DTMI to this object. Works for all types, but only makes sense for container types. /// This function resets DTMI::packed to an empty string, forcing a repack on the next call to DTMI::Pack. /// If the indice name already exists, replaces the indice. @@ -213,9 +220,12 @@ void DTSC::DTMI::addContent(DTSC::DTMI 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. +/// Returns null pointer if no object is held at this indice. /// \param i The indice of the object in this container. -DTSC::DTMI* DTSC::DTMI::getContentP(int i){return &contents.at(i);}; +DTSC::DTMI* DTSC::DTMI::getContentP(int i){ + if (contents.size() <= (unsigned int)i){return 0;} + 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. diff --git a/util/dtsc.h b/util/dtsc.h index 138e3ab4..428a445e 100644 --- a/util/dtsc.h +++ b/util/dtsc.h @@ -62,6 +62,7 @@ namespace DTSC{ std::string & StrValue(); const char * Str(); int hasContent(); + bool isEmpty(); void addContent(DTMI c); DTMI* getContentP(int i); DTMI getContent(int i); diff --git a/util/flv_tag.cpp b/util/flv_tag.cpp index bba87754..919772f0 100644 --- a/util/flv_tag.cpp +++ b/util/flv_tag.cpp @@ -771,3 +771,178 @@ bool FLV::Tag::FileLoader(FILE * f){ fcntl(fileno(f), F_SETFL, preflags); return false; }//FLV_GetPacket + +DTSC::DTMI FLV::Tag::toDTSC(DTSC::DTMI & metadata){ + DTSC::DTMI pack_out; // Storage for outgoing DTMI data. + + if (data[0] == 0x12){ + AMF::Object meta_in = AMF::parse((unsigned char*)data+11, len-15); + if (meta_in.getContentP(0) && (meta_in.getContentP(0)->StrValue() == "onMetaData") && meta_in.getContentP(1)){ + AMF::Object * tmp = meta_in.getContentP(1); + if (tmp->getContentP("videocodecid")){ + switch ((unsigned int)tmp->getContentP("videocodecid")->NumValue()){ + case 2: Meta_Put(metadata, "video", "codec", "H263"); break; + case 4: Meta_Put(metadata, "video", "codec", "VP6"); break; + case 7: Meta_Put(metadata, "video", "codec", "H264"); break; + default: Meta_Put(metadata, "video", "codec", "?"); break; + } + } + if (tmp->getContentP("audiocodecid")){ + switch ((unsigned int)tmp->getContentP("audiocodecid")->NumValue()){ + case 2: Meta_Put(metadata, "audio", "codec", "MP3"); break; + case 10: Meta_Put(metadata, "audio", "codec", "AAC"); break; + default: Meta_Put(metadata, "audio", "codec", "?"); break; + } + } + if (tmp->getContentP("width")){ + Meta_Put(metadata, "video", "width", tmp->getContentP("width")->NumValue()); + } + if (tmp->getContentP("height")){ + Meta_Put(metadata, "video", "height", tmp->getContentP("height")->NumValue()); + } + if (tmp->getContentP("framerate")){ + Meta_Put(metadata, "video", "fpks", tmp->getContentP("framerate")->NumValue()*1000); + } + if (tmp->getContentP("videodatarate")){ + Meta_Put(metadata, "video", "bps", (tmp->getContentP("videodatarate")->NumValue()*1024)/8); + } + if (tmp->getContentP("audiodatarate")){ + Meta_Put(metadata, "audio", "bps", (tmp->getContentP("audiodatarate")->NumValue()*1024)/8); + } + if (tmp->getContentP("audiosamplerate")){ + Meta_Put(metadata, "audio", "rate", tmp->getContentP("audiosamplerate")->NumValue()); + } + if (tmp->getContentP("audiosamplesize")){ + Meta_Put(metadata, "audio", "size", tmp->getContentP("audiosamplesize")->NumValue()); + } + if (tmp->getContentP("stereo")){ + if (tmp->getContentP("stereo")->NumValue() == 1){ + Meta_Put(metadata, "audio", "channels", 2); + }else{ + Meta_Put(metadata, "audio", "channels", 1); + } + } + } + return pack_out;//empty + } + if (data[0] == 0x08){ + char audiodata = data[11]; + if (needsInitData() && isInitData()){ + if ((audiodata & 0xF0) == 0xA0){ + Meta_Put(metadata, "audio", "init", std::string((char*)data+13, (size_t)len-17)); + }else{ + Meta_Put(metadata, "audio", "init", std::string((char*)data+12, (size_t)len-16)); + } + return pack_out;//skip rest of parsing, get next tag. + } + pack_out = DTSC::DTMI("audio", DTSC::DTMI_ROOT); + pack_out.addContent(DTSC::DTMI("datatype", "audio")); + pack_out.addContent(DTSC::DTMI("time", tagTime())); + if (!Meta_Has(metadata, "audio", "codec")){ + switch (audiodata & 0xF0){ + case 0x20: Meta_Put(metadata, "audio", "codec", "MP3"); break; + case 0xA0: Meta_Put(metadata, "audio", "codec", "AAC"); break; + default: Meta_Put(metadata, "audio", "codec", "?"); break; + } + } + if (!Meta_Has(metadata, "audio", "rate")){ + switch (audiodata & 0x0C){ + case 0x0: Meta_Put(metadata, "audio", "rate", 5512); break; + case 0x4: Meta_Put(metadata, "audio", "rate", 11025); break; + case 0x8: Meta_Put(metadata, "audio", "rate", 22050); break; + case 0xC: Meta_Put(metadata, "audio", "rate", 44100); break; + } + } + if (!Meta_Has(metadata, "audio", "size")){ + switch (audiodata & 0x02){ + case 0x0: Meta_Put(metadata, "audio", "size", 8); break; + case 0x2: Meta_Put(metadata, "audio", "size", 16); break; + } + } + if (!Meta_Has(metadata, "audio", "channels")){ + switch (audiodata & 0x01){ + case 0x0: Meta_Put(metadata, "audio", "channels", 1); break; + case 0x1: Meta_Put(metadata, "audio", "channels", 2); break; + } + } + if ((audiodata & 0xF0) == 0xA0){ + pack_out.addContent(DTSC::DTMI("data", std::string((char*)data+13, (size_t)len-17))); + }else{ + pack_out.addContent(DTSC::DTMI("data", std::string((char*)data+12, (size_t)len-16))); + } + return pack_out; + } + if (data[0] == 0x09){ + char videodata = data[11]; + if (needsInitData() && isInitData()){ + if ((videodata & 0x0F) == 7){ + Meta_Put(metadata, "video", "init", std::string((char*)data+16, (size_t)len-20)); + }else{ + Meta_Put(metadata, "video", "init", std::string((char*)data+12, (size_t)len-16)); + } + return pack_out;//skip rest of parsing, get next tag. + } + if (!Meta_Has(metadata, "video", "codec")){ + switch (videodata & 0x0F){ + case 2: Meta_Put(metadata, "video", "codec", "H263"); break; + case 4: Meta_Put(metadata, "video", "codec", "VP6"); break; + case 7: Meta_Put(metadata, "video", "codec", "H264"); break; + default: Meta_Put(metadata, "video", "codec", "?"); break; + } + } + pack_out = DTSC::DTMI("video", DTSC::DTMI_ROOT); + pack_out.addContent(DTSC::DTMI("datatype", "video")); + switch (videodata & 0xF0){ + case 0x10: pack_out.addContent(DTSC::DTMI("keyframe", 1)); break; + case 0x20: pack_out.addContent(DTSC::DTMI("interframe", 1)); break; + case 0x30: pack_out.addContent(DTSC::DTMI("disposableframe", 1)); break; + case 0x40: pack_out.addContent(DTSC::DTMI("keyframe", 1)); break; + case 0x50: return DTSC::DTMI(); break;//the video info byte we just throw away - useless to us... + } + pack_out.addContent(DTSC::DTMI("time", tagTime())); + if ((videodata & 0x0F) == 7){ + switch (data[12]){ + case 1: pack_out.addContent(DTSC::DTMI("nalu", 1)); break; + case 2: pack_out.addContent(DTSC::DTMI("nalu_end", 1)); break; + } + int offset = (data[13] << 16) + (data[14] << 8) + data[15]; + offset = (offset << 8) >> 8; + pack_out.addContent(DTSC::DTMI("offset", offset)); + pack_out.addContent(DTSC::DTMI("data", std::string((char*)data+16, (size_t)len-20))); + }else{ + pack_out.addContent(DTSC::DTMI("data", std::string((char*)data+12, (size_t)len-16))); + } + return pack_out; + } + return pack_out;//should never get here +}//FLV::Tag::toDTSC + +/// Inserts std::string type metadata into the passed DTMI object. +/// \arg meta The DTMI object to put the metadata into. +/// \arg cat Metadata category to insert into. +/// \arg elem Element name to put into the category. +/// \arg val Value to put into the element name. +void FLV::Tag::Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, std::string val){ + if (meta.getContentP(cat) == 0){meta.addContent(DTSC::DTMI(cat));} + meta.getContentP(cat)->addContent(DTSC::DTMI(elem, val)); +} + +/// Inserts uint64_t type metadata into the passed DTMI object. +/// \arg meta The DTMI object to put the metadata into. +/// \arg cat Metadata category to insert into. +/// \arg elem Element name to put into the category. +/// \arg val Value to put into the element name. +void FLV::Tag::Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, uint64_t val){ + if (meta.getContentP(cat) == 0){meta.addContent(DTSC::DTMI(cat));} + meta.getContentP(cat)->addContent(DTSC::DTMI(elem, val)); +} + +/// Returns true if the named category and elementname are available in the metadata. +/// \arg meta The DTMI object to check. +/// \arg cat Metadata category to check. +/// \arg elem Element name to check. +bool FLV::Tag::Meta_Has(DTSC::DTMI & meta, std::string cat, std::string elem){ + if (meta.getContentP(cat) == 0){return false;} + if (meta.getContentP(cat)->getContentP(elem) == 0){return false;} + return true; +} diff --git a/util/flv_tag.h b/util/flv_tag.h index 6f1f7da7..6848995c 100644 --- a/util/flv_tag.h +++ b/util/flv_tag.h @@ -43,6 +43,7 @@ namespace FLV { bool DTSCVideoInit(DTSC::Stream & S); bool DTSCAudioInit(DTSC::Stream & S); bool DTSCMetaInit(DTSC::Stream & S); + DTSC::DTMI toDTSC(DTSC::DTMI & metadata); bool MemLoader(char * D, unsigned int S, unsigned int & P); bool SockLoader(int sock); bool SockLoader(Socket::Connection sock); @@ -56,6 +57,10 @@ namespace FLV { 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, Socket::Connection & sock); bool FileReadUntil(char * buffer, unsigned int count, unsigned int & sofar, FILE * f); + //DTSC writer helpers + void Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, std::string val); + void Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, uint64_t val); + bool Meta_Has(DTSC::DTMI & meta, std::string cat, std::string elem); };//Tag };//FLV namespace From 9ae274b0c19df4a7486e45ec9a25793f4abc883d Mon Sep 17 00:00:00 2001 From: Thulinma Date: Wed, 18 Apr 2012 11:23:33 +0200 Subject: [PATCH 17/24] Fixed a minor ordering bug in FLV2DTSC, made Connector_RTMP fully functional in DTSC environment (push support). --- Connector_RTMP/main.cpp | 63 ++++++++++++++++++++--------------------- tools/FLV2DTSC/main.cpp | 9 +++--- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index d6eb2769..94a737a2 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "../util/socket.h" #include "../util/flv_tag.h" #include "../util/amf.h" @@ -137,6 +138,12 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){ /// Tries to get and parse one RTMP chunk at a time. void Connector_RTMP::parseChunk(std::string & inbuffer){ + //for DTSC conversion + static DTSC::DTMI meta_out; + static std::stringstream prebuffer; // Temporary buffer before sending real data + static bool sending = false; + static unsigned int counter = 0; + //for chunk parsing static RTMPStream::Chunk next; FLV::Tag F; static AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER); @@ -209,32 +216,33 @@ void Connector_RTMP::parseChunk(std::string & inbuffer){ 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: - F.ChunkLoader(next); + case 8://audio data + case 9://video data + case 18://meta data if (SS.connected()){ - #if DEBUG >= 4 - fprintf(stderr, "A"); - #endif - /// \TODO Convert to DTSC properly. - SS.write(std::string(F.data, F.len)); + F.ChunkLoader(next); + DTSC::DTMI pack_out = F.toDTSC(meta_out); + if (!pack_out.isEmpty()){ + if (!sending){ + counter++; + if (counter > 8){ + sending = true; + meta_out.Pack(true);//pack metadata + meta_out.packed.replace(0, 4, DTSC::Magic_Header);//prepare proper header + SS.write(meta_out.packed);//write header/metadata + SS.write(prebuffer.str());//write buffer + prebuffer.str("");//clear buffer + SS.write(pack_out.Pack(true));//simply write + }else{ + prebuffer << pack_out.Pack(true);//buffer + } + }else{ + SS.write(pack_out.Pack(true));//simple write + } + } }else{ #if DEBUG >= 4 - fprintf(stderr, "Received useless audio data\n"); - #endif - Socket.close(); - } - break; - case 9: - F.ChunkLoader(next); - if (SS.connected()){ - #if DEBUG >= 4 - fprintf(stderr, "V"); - #endif - /// \TODO Convert to DTSC properly. - SS.write(std::string(F.data, F.len)); - }else{ - #if DEBUG >= 4 - fprintf(stderr, "Received useless video data\n"); + fprintf(stderr, "Received useless media data\n"); #endif Socket.close(); } @@ -268,15 +276,6 @@ void Connector_RTMP::parseChunk(std::string & inbuffer){ parseAMFCommand(amfdata, 17, next.msg_stream_id); }//parsing AMF0-style } break; - case 18: - #if DEBUG >= 4 - fprintf(stderr, "Received AFM0 data message (metadata)\n"); - #endif - F.ChunkLoader(next); - if (SS.connected()){ - SS.write(std::string(F.data, F.len)); - } - break; case 19: #if DEBUG >= 4 fprintf(stderr, "Received AFM0 shared object\n"); diff --git a/tools/FLV2DTSC/main.cpp b/tools/FLV2DTSC/main.cpp index 6ae6516c..e1ad8b55 100644 --- a/tools/FLV2DTSC/main.cpp +++ b/tools/FLV2DTSC/main.cpp @@ -32,7 +32,7 @@ namespace Converters{ if (pack_out.isEmpty()){continue;} if (!sending){ counter++; - if (counter > 10){ + if (counter > 8){ sending = true; meta_out.Pack(true); meta_out.packed.replace(0, 4, DTSC::Magic_Header); @@ -40,11 +40,12 @@ namespace Converters{ std::cout << prebuffer.rdbuf(); prebuffer.str(""); std::cerr << "Buffer done, starting real-time output..." << std::endl; + }else{ + prebuffer << pack_out.Pack(true);//buffer + continue;//don't also write } - prebuffer << pack_out.Pack(true); - }else{ - std::cout << pack_out.Pack(true); } + std::cout << pack_out.Pack(true);//simply write } } From f527bc5d451128fba09b7ed6d429b566ac63ab22 Mon Sep 17 00:00:00 2001 From: Lekensteyn Date: Sat, 21 Apr 2012 16:56:38 +0200 Subject: [PATCH 18/24] Fix compile error on -lpthread --- Buffer/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Buffer/Makefile b/Buffer/Makefile index 89e4f902..4557dc18 100644 --- a/Buffer/Makefile +++ b/Buffer/Makefile @@ -13,9 +13,9 @@ LIBS = -lpthread .PHONY: clean default default: $(OUT) .cpp.o: - $(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@ + $(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ $(LIBS) $(OUT): $(OBJ) - $(CC) $(LIBS) -o $(OUT) $(OBJ) + $(CC) -o $(OUT) $(OBJ) $(LIBS) clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ install: $(OUT) From 51c760eef7118c8e14bbff2a03f916effe57d0ee Mon Sep 17 00:00:00 2001 From: Lekensteyn Date: Sun, 22 Apr 2012 15:37:00 +0200 Subject: [PATCH 19/24] Use coreutils' install command, support DESTDIR --- Buffer/Makefile | 3 ++- Connector_HTTP/Makefile | 3 ++- Connector_RAW/Makefile | 3 ++- Connector_RTMP/Makefile | 3 ++- DDV_Controller/Makefile | 3 +++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Buffer/Makefile b/Buffer/Makefile index 4557dc18..4e3c80f3 100644 --- a/Buffer/Makefile +++ b/Buffer/Makefile @@ -5,6 +5,7 @@ INCLUDES = DEBUG = 4 OPTIMIZE = -g CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) +INSTALL = install CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar @@ -19,5 +20,5 @@ $(OUT): $(OBJ) clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ install: $(OUT) - cp -f ./$(OUT) /usr/bin/ + $(INSTALL) -D ./$(OUT) $(DESTDIR)/usr/bin/$(OUT) diff --git a/Connector_HTTP/Makefile b/Connector_HTTP/Makefile index 7ccbfc72..75a62ea6 100644 --- a/Connector_HTTP/Makefile +++ b/Connector_HTTP/Makefile @@ -6,6 +6,7 @@ DEBUG = 4 OPTIMIZE = -g CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) -DVERSION=$(VERSION) VERSION = `git describe --tags` +INSTALL = install CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar @@ -20,7 +21,7 @@ $(OUT): $(OBJ) clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ install: $(OUT) - cp -f ./$(OUT) /usr/bin/ + $(INSTALL) -D ./$(OUT) $(DESTDIR)/usr/bin/$(OUT) cversion: rm -rf ../util/config.o diff --git a/Connector_RAW/Makefile b/Connector_RAW/Makefile index c6b3ec59..a19ba413 100644 --- a/Connector_RAW/Makefile +++ b/Connector_RAW/Makefile @@ -5,6 +5,7 @@ INCLUDES = DEBUG = 4 OPTIMIZE = -g CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) +INSTALL = install CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar @@ -19,5 +20,5 @@ $(OUT): $(OBJ) clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ install: $(OUT) - cp -f ./$(OUT) /usr/bin/ + $(INSTALL) -D ./$(OUT) $(DESTDIR)/usr/bin/$(OUT) diff --git a/Connector_RTMP/Makefile b/Connector_RTMP/Makefile index 0a337458..162b22f8 100644 --- a/Connector_RTMP/Makefile +++ b/Connector_RTMP/Makefile @@ -6,6 +6,7 @@ STATIC = DEBUG = 4 OPTIMIZE = -g CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) -DVERSION=$(VERSION) +INSTALL = install CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar @@ -21,7 +22,7 @@ $(OUT): $(OBJ) clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ install: $(OUT) - cp -f ./$(OUT) /usr/bin/ + $(INSTALL) -D ./$(OUT) $(DESTDIR)/usr/bin/$(OUT) cversion: rm -rf ../util/config.o diff --git a/DDV_Controller/Makefile b/DDV_Controller/Makefile index 2782d63f..1ef9613c 100644 --- a/DDV_Controller/Makefile +++ b/DDV_Controller/Makefile @@ -9,6 +9,7 @@ COMPILED_USERNAME = testuser COMPILED_PASSWORD = 179ad45c6ce2cb97cf1029e212046e81 #COMPILED_PASSWORD = testpass CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) -DCOMPILED_USERNAME=$(COMPILED_USERNAME) -DCOMPILED_PASSWORD=$(COMPILED_PASSWORD) -DVERSION=$(VERSION) +INSTALL = install CC = $(CROSS)g++ LD = $(CROSS)ld AR = $(CROSS)ar @@ -24,4 +25,6 @@ clean: rm -rf $(OBJ) $(OUT) Makefile.bak *~ cversion: rm -rf ../util/config.o +install: $(OUT) + $(INSTALL) -D ./$(OUT) $(DESTDIR)/usr/bin/$(OUT) From 2427c9eb85b18fc3b62ded53d6e0cee28bfa3aca Mon Sep 17 00:00:00 2001 From: Erik Zandvliet Date: Mon, 23 Apr 2012 18:59:58 +0200 Subject: [PATCH 20/24] Small Update --- .gitignore | 1 + Connector_TS/Makefile | 2 +- Connector_TS/main.cpp | 2 +- tools/TS_Analyser/main.cpp | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 51f16f99..36ac5158 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ HTTP_Box_Parser/Box_Parser gearbox/CPP/Client/Gearbox_Client *.ts +/nbproject/private/ \ No newline at end of file diff --git a/Connector_TS/Makefile b/Connector_TS/Makefile index 267fcece..889e385b 100644 --- a/Connector_TS/Makefile +++ b/Connector_TS/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/socket.cpp ../util/dtsc.cpp ../util/util.cpp +SRC = main.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/dtsc.cpp ../util/config.cpp ../util/base64.cpp OBJ = $(SRC:.cpp=.o) OUT = DDV_Conn_TS INCLUDES = diff --git a/Connector_TS/main.cpp b/Connector_TS/main.cpp index a296aba5..c2d4baea 100644 --- a/Connector_TS/main.cpp +++ b/Connector_TS/main.cpp @@ -335,7 +335,7 @@ int TS_Handler( Socket::Connection conn ) { case 0: break;//not ready yet default: ss.spool(); - if ( stream.parsePacket( conn.Received() ) ) { + if ( stream.parsePacket( ss.Received() ) ) { if( stream.lastType() == DTSC::VIDEO ) { fprintf(stderr, "Video contains NALU\n" ); SendPAT( conn ); diff --git a/tools/TS_Analyser/main.cpp b/tools/TS_Analyser/main.cpp index fc33aa80..a635ca53 100644 --- a/tools/TS_Analyser/main.cpp +++ b/tools/TS_Analyser/main.cpp @@ -347,7 +347,7 @@ void print_pmt( program_mapping_table PMT, bool Pointer_Field = false, std::stri printf( "%s\t\tReserved\t\t%d\n", offset.c_str(), PMT.Entries[i].Reserved_2 ); printf( "%s\t\tES Info Length\t\t%d\n", offset.c_str(), PMT.Entries[i].ES_Info_Length ); } - printf( "%s\tCRC 32\t\t%X\n", offset.c_str(), PMT.CRC_32 ); + printf( "%s\tCRC 32\t\t%8X\n", offset.c_str(), PMT.CRC_32 ); } /// Fills an AF structure with the right data From 6c588e51fc241bac4493a8a1861d8c15b76907f3 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Fri, 27 Apr 2012 21:07:26 +0200 Subject: [PATCH 21/24] Stable multi-user buffer and re-enabled push support. Renamed DDV_->Mist. Closes #25 --- Buffer/Makefile | 10 +- Buffer/main.cpp | 169 +++++++----------- Buffer/stats.cpp | 68 ++++---- Buffer/stats.h | 16 ++ Buffer/stream.cpp | 216 ++++++++++++++++++++++++ Buffer/stream.h | 69 ++++++++ Buffer/user.cpp | 169 ++++++++---------- Buffer/user.h | 41 +++++ Connector_HTTP/Makefile | 8 +- Connector_HTTP/main.cpp | 26 +-- Connector_RAW/Makefile | 8 +- Connector_RTMP/Makefile | 8 +- Connector_RTMP/main.cpp | 23 +-- {DDV_Controller => Controller}/Makefile | 6 +- {DDV_Controller => Controller}/main.cpp | 0 Makefile | 18 +- util/config.cpp | 61 +------ util/config.h | 24 +-- util/server_setup.cpp | 8 - util/socket.cpp | 41 +++++ util/socket.h | 6 + 21 files changed, 613 insertions(+), 382 deletions(-) create mode 100644 Buffer/stats.h create mode 100644 Buffer/stream.cpp create mode 100644 Buffer/stream.h create mode 100644 Buffer/user.h rename {DDV_Controller => Controller}/Makefile (88%) rename {DDV_Controller => Controller}/main.cpp (100%) diff --git a/Buffer/Makefile b/Buffer/Makefile index 89e4f902..58ee5b2f 100644 --- a/Buffer/Makefile +++ b/Buffer/Makefile @@ -1,6 +1,6 @@ -SRC = main.cpp ../util/json.cpp ../util/socket.cpp ../util/dtsc.cpp ../util/tinythread.cpp +SRC = main.cpp ../util/json.cpp ../util/socket.cpp ../util/dtsc.cpp ../util/tinythread.cpp user.cpp stats.cpp stream.cpp OBJ = $(SRC:.cpp=.o) -OUT = DDV_Buffer +OUT = MistBuffer INCLUDES = DEBUG = 4 OPTIMIZE = -g @@ -15,9 +15,9 @@ default: $(OUT) .cpp.o: $(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@ $(OUT): $(OBJ) - $(CC) $(LIBS) -o $(OUT) $(OBJ) + $(CC) $(LIBS) -o ../bin/$(OUT) $(OBJ) clean: - rm -rf $(OBJ) $(OUT) Makefile.bak *~ + rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~ install: $(OUT) - cp -f ./$(OUT) /usr/bin/ + cp -f ../bin/$(OUT) /usr/bin/ diff --git a/Buffer/main.cpp b/Buffer/main.cpp index a458cbd1..eb60ec18 100644 --- a/Buffer/main.cpp +++ b/Buffer/main.cpp @@ -12,27 +12,14 @@ #include #include #include -#include "../util/dtsc.h" //DTSC support -#include "../util/socket.h" //Socket lib -#include "../util/json.h" -#include "../util/tinythread.h" +#include "stream.h" /// Holds all code unique to the Buffer. namespace Buffer{ - class user;//forward declaration - JSON::Value Storage; ///< Global storage of data. - DTSC::Stream * Strm = 0; - std::string waiting_ip = ""; ///< IP address for media push. - Socket::Connection ip_input; ///< Connection used for media push. - tthread::mutex stats_mutex; ///< Mutex for stats modifications. - tthread::mutex transfer_mutex; ///< Mutex for data transfers. - tthread::mutex socket_mutex; ///< Mutex for user deletion/work. - bool buffer_running = true; ///< Set to false when shutting down. - std::vector users; ///< All connected users. - std::vector::iterator usersIt; ///< Iterator for all connected users. - std::string name; ///< Name for this buffer. - tthread::condition_variable moreData; ///< Triggered when more data becomes available. + volatile bool buffer_running = true; ///< Set to false when shutting down. + Stream * thisStream = 0; + Socket::Server SS; ///< The server socket. /// Gets the current system time in milliseconds. unsigned int getNowMS(){ @@ -50,40 +37,18 @@ namespace Buffer{ default: return; break; } } -} -#include "stats.cpp" -#include "user.cpp" - -namespace Buffer{ void handleStats(void * empty){ if (empty != 0){return;} - Socket::Connection StatsSocket = Socket::Connection("/tmp/ddv_statistics", true); + Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true); while (buffer_running){ usleep(1000000); //sleep one second - unsigned int now = time(0); - unsigned int tot_up = 0, tot_down = 0, tot_count = 0; - stats_mutex.lock(); - if (users.size() > 0){ - for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ - tot_down += usersIt->curr_down; - tot_up += usersIt->curr_up; - tot_count++; - } - } - Storage["totals"]["down"] = tot_down; - Storage["totals"]["up"] = tot_up; - Storage["totals"]["count"] = tot_count; - Storage["totals"]["now"] = now; - Storage["totals"]["buffer"] = name; if (!StatsSocket.connected()){ - StatsSocket = Socket::Connection("/tmp/ddv_statistics", true); + StatsSocket = Socket::Connection("/tmp/mist/statistics", true); } if (StatsSocket.connected()){ - StatsSocket.write(Storage.toString()+"\n\n"); - Storage["log"].null(); + StatsSocket.write(Stream::get()->getStats()+"\n\n"); } - stats_mutex.unlock(); } } @@ -91,8 +56,8 @@ namespace Buffer{ user * usr = (user*)v_usr; std::cerr << "Thread launched for user " << usr->MyStr << ", socket number " << usr->S.getSocket() << std::endl; - usr->myRing = Strm->getRing(); - if (!usr->S.write(Strm->outHeader())){ + usr->myRing = thisStream->getRing(); + if (!usr->S.write(thisStream->getHeader())){ usr->Disconnect("failed to receive the header!"); return; } @@ -108,10 +73,9 @@ namespace Buffer{ if (usr->inbuffer != ""){ if (usr->inbuffer[0] == 'P'){ std::cout << "Push attempt from IP " << usr->inbuffer.substr(2) << std::endl; - if (usr->inbuffer.substr(2) == waiting_ip){ - if (!ip_input.connected()){ + if (thisStream->checkWaitingIP(usr->inbuffer.substr(2))){ + if (thisStream->setInput(usr->S)){ std::cout << "Push accepted!" << std::endl; - ip_input = usr->S; usr->S = Socket::Connection(-1); return; }else{ @@ -122,38 +86,23 @@ namespace Buffer{ } } if (usr->inbuffer[0] == 'S'){ - stats_mutex.lock(); usr->tmpStats = Stats(usr->inbuffer.substr(2)); unsigned int secs = usr->tmpStats.conntime - usr->lastStats.conntime; if (secs < 1){secs = 1;} usr->curr_up = (usr->tmpStats.up - usr->lastStats.up) / secs; usr->curr_down = (usr->tmpStats.down - usr->lastStats.down) / secs; usr->lastStats = usr->tmpStats; - Storage["curr"][usr->MyStr]["connector"] = usr->tmpStats.connector; - Storage["curr"][usr->MyStr]["up"] = usr->tmpStats.up; - Storage["curr"][usr->MyStr]["down"] = usr->tmpStats.down; - Storage["curr"][usr->MyStr]["conntime"] = usr->tmpStats.conntime; - Storage["curr"][usr->MyStr]["host"] = usr->tmpStats.host; - Storage["curr"][usr->MyStr]["start"] = (unsigned int) time(0) - usr->tmpStats.conntime; - stats_mutex.unlock(); + thisStream->saveStats(usr->MyStr, usr->tmpStats); } } } usr->Send(); } - stats_mutex.lock(); - if (users.size() > 0){ - for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ - if (!(*usersIt).S.connected()){ - users.erase(usersIt); - break; - } - } - } - stats_mutex.unlock(); + thisStream->cleanUsers(); std::cerr << "User " << usr->MyStr << " disconnected, socket number " << usr->S.getSocket() << std::endl; } + /// Loop reading DTSC data from stdin and processing it at the correct speed. void handleStdin(void * empty){ if (empty != 0){return;} unsigned int lastPacketTime = 0;//time in MS last packet was parsed @@ -167,28 +116,49 @@ namespace Buffer{ while (std::cin.good() && buffer_running){ //slow down packet receiving to real-time now = getNowMS(); - if ((now - lastPacketTime > currPacketTime - prevPacketTime) || (currPacketTime <= prevPacketTime)){ + if ((now - lastPacketTime >= currPacketTime - prevPacketTime) || (currPacketTime <= prevPacketTime)){ std::cin.read(charBuffer, 1024*10); charCount = std::cin.gcount(); inBuffer.append(charBuffer, charCount); - transfer_mutex.lock(); - if (Strm->parsePacket(inBuffer)){ - Strm->outPacket(0); + thisStream->getWriteLock(); + if (thisStream->getStream()->parsePacket(inBuffer)){ + thisStream->getStream()->outPacket(0); lastPacketTime = now; prevPacketTime = currPacketTime; - currPacketTime = Strm->getTime(); - moreData.notify_all(); + currPacketTime = thisStream->getStream()->getTime(); } - transfer_mutex.unlock(); + thisStream->dropWriteLock(); }else{ - if (((currPacketTime - prevPacketTime) - (now - lastPacketTime)) > 1000){ - usleep(1000000); + if (((currPacketTime - prevPacketTime) - (now - lastPacketTime)) > 999){ + usleep(999000); }else{ - usleep(((currPacketTime - prevPacketTime) - (now - lastPacketTime)) * 1000); + usleep(((currPacketTime - prevPacketTime) - (now - lastPacketTime)) * 999); } } } buffer_running = false; + SS.close(); + } + + /// Loop reading DTSC data from an IP push address. + /// No changes to the speed are made. + void handlePushin(void * empty){ + if (empty != 0){return;} + std::string inBuffer; + while (buffer_running){ + if (thisStream->getIPInput().connected()){ + if (thisStream->getIPInput().iread(inBuffer)){ + thisStream->getWriteLock(); + if (thisStream->getStream()->parsePacket(inBuffer)){ + thisStream->getStream()->outPacket(0); + } + thisStream->dropWriteLock(); + } + }else{ + usleep(1000000); + } + } + SS.close(); } /// Starts a loop, waiting for connections to send data to. @@ -206,62 +176,49 @@ namespace Buffer{ std::cout << "usage: " << argv[0] << " streamName [awaiting_IP]" << std::endl; return 1; } - name = argv[1]; + std::string name = argv[1]; bool ip_waiting = false; + std::string waiting_ip; if (argc >= 4){ waiting_ip += argv[2]; ip_waiting = true; } - std::string shared_socket = "/tmp/shared_socket_"; - shared_socket += name; - Socket::Server SS(shared_socket, false); - Strm = new DTSC::Stream(5); + SS = Socket::makeStream(name); + thisStream = Stream::get(); + thisStream->setName(name); + if (ip_waiting){ + thisStream->setWaitingIP(waiting_ip); + } Socket::Connection incoming; Socket::Connection std_input(fileno(stdin)); - Storage["log"].null(); - Storage["curr"].null(); - Storage["totals"].null(); - - //tthread::thread StatsThread = tthread::thread(handleStats, 0); + tthread::thread StatsThread = tthread::thread(handleStats, 0); tthread::thread * StdinThread = 0; if (!ip_waiting){ StdinThread = new tthread::thread(handleStdin, 0); + }else{ + StdinThread = new tthread::thread(handlePushin, 0); } - while (buffer_running){ + while (buffer_running && SS.connected()){ //check for new connections, accept them if there are any //starts a thread for every accepted connection incoming = SS.accept(false); if (incoming.connected()){ - stats_mutex.lock(); - users.push_back(incoming); - user * usr_ptr = &(users.back()); - stats_mutex.unlock(); + user * usr_ptr = new user(incoming); + thisStream->addUser(usr_ptr); usr_ptr->Thread = new tthread::thread(handleUser, (void *)usr_ptr); } }//main loop // disconnect listener - /// \todo Deal with EOF more nicely - doesn't send the end of the stream to all users! buffer_running = false; - std::cout << "Buffer shutting down" << std::endl; + std::cout << "End of input file - buffer shutting down" << std::endl; SS.close(); - //StatsThread.join(); - if (StdinThread){StdinThread->join();} - - if (users.size() > 0){ - stats_mutex.lock(); - for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ - if ((*usersIt).S.connected()){ - (*usersIt).Disconnect("Terminating..."); - } - } - stats_mutex.unlock(); - } - - delete Strm; + StatsThread.join(); + StdinThread->join(); + delete thisStream; return 0; } diff --git a/Buffer/stats.cpp b/Buffer/stats.cpp index 25d244d8..db93db65 100644 --- a/Buffer/stats.cpp +++ b/Buffer/stats.cpp @@ -1,40 +1,32 @@ +#include "stats.h" +#include //for atoi() -namespace Buffer{ - /// Converts a stats line to up, down, host, connector and conntime values. - class Stats{ - public: - unsigned int up; - unsigned int down; - std::string host; - std::string connector; - unsigned int conntime; - Stats(){ - up = 0; - down = 0; - conntime = 0; - } - Stats(std::string s){ - size_t f = s.find(' '); - if (f != std::string::npos){ - host = s.substr(0, f); - s.erase(0, f+1); - } - f = s.find(' '); - if (f != std::string::npos){ - connector = s.substr(0, f); - s.erase(0, f+1); - } - f = s.find(' '); - if (f != std::string::npos){ - conntime = atoi(s.substr(0, f).c_str()); - s.erase(0, f+1); - } - f = s.find(' '); - if (f != std::string::npos){ - up = atoi(s.substr(0, f).c_str()); - s.erase(0, f+1); - down = atoi(s.c_str()); - } - } - }; +Buffer::Stats::Stats(){ + up = 0; + down = 0; + conntime = 0; +} + +Buffer::Stats::Stats(std::string s){ + size_t f = s.find(' '); + if (f != std::string::npos){ + host = s.substr(0, f); + s.erase(0, f+1); + } + f = s.find(' '); + if (f != std::string::npos){ + connector = s.substr(0, f); + s.erase(0, f+1); + } + f = s.find(' '); + if (f != std::string::npos){ + conntime = atoi(s.substr(0, f).c_str()); + s.erase(0, f+1); + } + f = s.find(' '); + if (f != std::string::npos){ + up = atoi(s.substr(0, f).c_str()); + s.erase(0, f+1); + down = atoi(s.c_str()); + } } diff --git a/Buffer/stats.h b/Buffer/stats.h new file mode 100644 index 00000000..38a31c29 --- /dev/null +++ b/Buffer/stats.h @@ -0,0 +1,16 @@ +#pragma once +#include + +namespace Buffer{ + /// Converts a stats line to up, down, host, connector and conntime values. + class Stats{ + public: + unsigned int up; + unsigned int down; + std::string host; + std::string connector; + unsigned int conntime; + Stats(); + Stats(std::string s); + }; +} diff --git a/Buffer/stream.cpp b/Buffer/stream.cpp new file mode 100644 index 00000000..7f1a282a --- /dev/null +++ b/Buffer/stream.cpp @@ -0,0 +1,216 @@ +#include "stream.h" + +/// Stores the globally equal reference. +Buffer::Stream * Buffer::Stream::ref = 0; + +/// Returns a globally equal reference to this class. +Buffer::Stream * Buffer::Stream::get(){ + static tthread::mutex creator; + if (ref == 0){ + //prevent creating two at the same time + creator.lock(); + if (ref == 0){ref = new Stream();} + creator.unlock(); + } + return ref; +} + +/// Creates a new DTSC::Stream object, private function so only one instance can exist. +Buffer::Stream::Stream(){ + Strm = new DTSC::Stream(5); +} + +/// Do cleanup on delete. +Buffer::Stream::~Stream(){ + delete Strm; + while (users.size() > 0){ + stats_mutex.lock(); + for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ + if ((**usersIt).S.connected()){ + if ((**usersIt).myRing->waiting){ + (**usersIt).S.close(); + printf("Closing user %s\n", (**usersIt).MyStr.c_str()); + } + } + } + stats_mutex.unlock(); + moreData.notify_all(); + cleanUsers(); + } +} + +/// Calculate and return the current statistics in JSON format. +std::string Buffer::Stream::getStats(){ + unsigned int now = time(0); + unsigned int tot_up = 0, tot_down = 0, tot_count = 0; + stats_mutex.lock(); + if (users.size() > 0){ + for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ + tot_down += (**usersIt).curr_down; + tot_up += (**usersIt).curr_up; + tot_count++; + } + } + Storage["totals"]["down"] = tot_down; + Storage["totals"]["up"] = tot_up; + Storage["totals"]["count"] = tot_count; + Storage["totals"]["now"] = now; + Storage["totals"]["buffer"] = name; + std::string ret = Storage.toString(); + Storage["log"].null(); + stats_mutex.unlock(); + return ret; +} + +/// Get a new DTSC::Ring object for a user. +DTSC::Ring * Buffer::Stream::getRing(){ + return Strm->getRing(); +} + +/// Drop a DTSC::Ring object. +void Buffer::Stream::dropRing(DTSC::Ring * ring){ + Strm->dropRing(ring); +} + +/// Get the (constant) header data of this stream. +std::string & Buffer::Stream::getHeader(){ + return Strm->outHeader(); +} + +/// Set the IP address to accept push data from. +void Buffer::Stream::setWaitingIP(std::string ip){ + waiting_ip = ip; +} + +/// Check if this is the IP address to accept push data from. +bool Buffer::Stream::checkWaitingIP(std::string ip){ + if (ip == waiting_ip || ip == "::ffff:"+waiting_ip){ + return true; + }else{ + return false; + } +} + +/// Sets the current socket for push data. +bool Buffer::Stream::setInput(Socket::Connection S){ + if (ip_input.connected()){ + return false; + }else{ + ip_input = S; + return true; + } +} + +/// Gets the current socket for push data. +Socket::Connection & Buffer::Stream::getIPInput(){ + return ip_input; +} + + +/// Stores intermediate statistics. +void Buffer::Stream::saveStats(std::string username, Stats & stats){ + stats_mutex.lock(); + Storage["curr"][username]["connector"] = stats.connector; + Storage["curr"][username]["up"] = stats.up; + Storage["curr"][username]["down"] = stats.down; + Storage["curr"][username]["conntime"] = stats.conntime; + Storage["curr"][username]["host"] = stats.host; + Storage["curr"][username]["start"] = (unsigned int) time(0) - stats.conntime; + stats_mutex.unlock(); +} + +/// Stores final statistics. +void Buffer::Stream::clearStats(std::string username, Stats & stats, std::string reason){ + stats_mutex.lock(); + Storage["curr"].removeMember(username); + Storage["log"][username]["connector"] = stats.connector; + Storage["log"][username]["up"] = stats.up; + Storage["log"][username]["down"] = stats.down; + Storage["log"][username]["conntime"] = stats.conntime; + Storage["log"][username]["host"] = stats.host; + Storage["log"][username]["start"] = (unsigned int)time(0) - stats.conntime; + std::cout << "Disconnected user " << username << ": " << reason << ". " << stats.connector << " transferred " << stats.up << " up and " << stats.down << " down in " << stats.conntime << " seconds to " << stats.host << std::endl; + stats_mutex.unlock(); + cleanUsers(); +} + +/// Cleans up broken connections +void Buffer::Stream::cleanUsers(){ + bool repeat = false; + stats_mutex.lock(); + do{ + repeat = false; + if (users.size() > 0){ + for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ + if ((**usersIt).Thread == 0 && !(**usersIt).S.connected()){ + delete *usersIt; + users.erase(usersIt); + repeat = true; + break; + } + } + } + }while(repeat); + stats_mutex.unlock(); +} + +/// Blocks until writing is safe. +void Buffer::Stream::getWriteLock(){ + rw_mutex.lock(); + writers++; + while (writers != 1 && readers != 0){ + rw_change.wait(rw_mutex); + } + rw_mutex.unlock(); +} + +/// Drops a previously gotten write lock. +void Buffer::Stream::dropWriteLock(){ + rw_mutex.lock(); + writers--; + rw_mutex.unlock(); + rw_change.notify_all(); + moreData.notify_all(); +} + +/// Blocks until reading is safe. +void Buffer::Stream::getReadLock(){ + rw_mutex.lock(); + while (writers > 0){ + rw_change.wait(rw_mutex); + } + readers++; + rw_mutex.unlock(); +} + +/// Drops a previously gotten read lock. +void Buffer::Stream::dropReadLock(){ + rw_mutex.lock(); + readers--; + rw_mutex.unlock(); + rw_change.notify_all(); +} + +/// Retrieves a reference to the DTSC::Stream +DTSC::Stream * Buffer::Stream::getStream(){ + return Strm; +} + +/// Sets the buffer name. +void Buffer::Stream::setName(std::string n){ + name = n; +} + +/// Add a user to the userlist. +void Buffer::Stream::addUser(user * new_user){ + stats_mutex.lock(); + users.push_back(new_user); + stats_mutex.unlock(); +} + +/// Blocks the thread until new data is available. +void Buffer::Stream::waitForData(){ + stats_mutex.lock(); + moreData.wait(stats_mutex); + stats_mutex.unlock(); +} diff --git a/Buffer/stream.h b/Buffer/stream.h new file mode 100644 index 00000000..e8153db0 --- /dev/null +++ b/Buffer/stream.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include "../util/tinythread.h" +#include "../util/json.h" +#include "user.h" + +namespace Buffer{ + class Stream{ + public: + /// Get a reference to this Stream object. + static Stream * get(); + /// Get the current statistics in JSON format. + std::string getStats(); + /// Get a new DTSC::Ring object for a user. + DTSC::Ring * getRing(); + /// Drop a DTSC::Ring object. + void dropRing(DTSC::Ring * ring); + /// Get the (constant) header data of this stream. + std::string & getHeader(); + /// Set the IP address to accept push data from. + void setWaitingIP(std::string ip); + /// Check if this is the IP address to accept push data from. + bool checkWaitingIP(std::string ip); + /// Sets the current socket for push data. + bool setInput(Socket::Connection S); + /// Gets the current socket for push data. + Socket::Connection & getIPInput(); + /// Stores intermediate statistics. + void saveStats(std::string username, Stats & stats); + /// Stores final statistics. + void clearStats(std::string username, Stats & stats, std::string reason); + /// Cleans up broken connections + void cleanUsers(); + /// Blocks until writing is safe. + void getWriteLock(); + /// Drops a previously gotten write lock. + void dropWriteLock(); + /// Blocks until reading is safe. + void getReadLock(); + /// Drops a previously gotten read lock. + void dropReadLock(); + /// Retrieves a reference to the DTSC::Stream + DTSC::Stream * getStream(); + /// Sets the buffer name. + void setName(std::string n); + /// Add a user to the userlist. + void addUser(user * new_user); + /// Blocks the thread until new data is available. + void waitForData(); + /// Cleanup function + ~Stream(); + private: + volatile int readers;///< Current count of active readers; + volatile int writers;///< Current count of waiting/active writers. + tthread::mutex rw_mutex; ///< Mutex for read/write locking. + tthread::condition_variable rw_change; ///< Triggered when reader/writer count changes. + static Stream * ref; + Stream(); + JSON::Value Storage; ///< Global storage of data. + DTSC::Stream * Strm; + std::string waiting_ip; ///< IP address for media push. + Socket::Connection ip_input; ///< Connection used for media push. + tthread::mutex stats_mutex; ///< Mutex for stats/users modifications. + std::vector users; ///< All connected users. + std::vector::iterator usersIt; ///< Iterator for all connected users. + std::string name; ///< Name for this buffer. + tthread::condition_variable moreData; ///< Triggered when more data becomes available. + }; +}; diff --git a/Buffer/user.cpp b/Buffer/user.cpp index 3fe1f065..d0b79898 100644 --- a/Buffer/user.cpp +++ b/Buffer/user.cpp @@ -1,97 +1,76 @@ -namespace Buffer{ - /// Holds connected users. - /// Keeps track of what buffer users are using and the connection status. - class user{ - public: - tthread::thread * Thread; ///< Holds the thread dealing with this user. - DTSC::Ring * myRing; ///< Ring of the buffer for this user. - int MyNum; ///< User ID of this user. - std::string MyStr; ///< User ID of this user as a string. - std::string inbuffer; ///< Used to buffer input data. - int currsend; ///< Current amount of bytes sent. - Stats lastStats; ///< Holds last known stats for this connection. - Stats tmpStats; ///< Holds temporary stats for this connection. - unsigned int curr_up; ///< Holds the current estimated transfer speed up. - unsigned int curr_down; ///< Holds the current estimated transfer speed down. - bool gotproperaudio; ///< Whether the user received proper audio yet. - void * lastpointer; ///< Pointer to data part of current buffer. - static int UserCount; ///< Global user counter. - Socket::Connection S; ///< Connection to user - /// Creates a new user from a newly connected socket. - /// Also prints "User connected" text to stdout. - user(Socket::Connection fd){ - S = fd; - MyNum = UserCount++; - std::stringstream st; - st << MyNum; - MyStr = st.str(); - curr_up = 0; - curr_down = 0; - currsend = 0; - myRing = 0; - Thread = 0; - std::cout << "User " << MyNum << " connected" << std::endl; - }//constructor - /// Drops held DTSC::Ring class, if one is held. - ~user(){ - Strm->dropRing(myRing); - }//destructor - /// 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();} - if (Thread != 0){ - if (Thread->joinable()){Thread->join();} - Thread = 0; - } - tthread::lock_guard lock(stats_mutex); - Storage["curr"].removeMember(MyStr); - Storage["log"][MyStr]["connector"] = lastStats.connector; - Storage["log"][MyStr]["up"] = lastStats.up; - Storage["log"][MyStr]["down"] = lastStats.down; - Storage["log"][MyStr]["conntime"] = lastStats.conntime; - Storage["log"][MyStr]["host"] = lastStats.host; - Storage["log"][MyStr]["start"] = (unsigned int)time(0) - lastStats.conntime; - std::cout << "Disconnected user " << MyStr << ": " << reason << ". " << lastStats.connector << " transferred " << lastStats.up << " up and " << lastStats.down << " down in " << lastStats.conntime << " seconds to " << lastStats.host << 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(const char * ptr, int len){ - int r = S.iwrite(ptr+currsend, len-currsend); - if (r <= 0){ - if (errno == EWOULDBLOCK){return false;} - Disconnect(S.getError()); - return false; - } - currsend += r; - return (currsend == len); - }//doSend - /// Try to send data to this user. Disconnects if any problems occur. - void Send(){ - if (!myRing){return;}//no ring! - if (!S.connected()){return;}//cancel if not connected - if (myRing->waiting){ - tthread::lock_guard guard(transfer_mutex); - moreData.wait(transfer_mutex); - return; - }//still waiting for next buffer? +#include "user.h" +#include "stream.h" +#include - if (myRing->starved){ - //if corrupt data, warn and get new DTSC::Ring - std::cout << "Warning: User was send corrupt video data and send to the next keyframe!" << std::endl; - Strm->dropRing(myRing); - myRing = Strm->getRing(); - return; - } +int Buffer::user::UserCount = 0; - //try to complete a send - if (doSend(Strm->outPacket(myRing->b).c_str(), Strm->outPacket(myRing->b).length())){ - //switch to next buffer - currsend = 0; - if (myRing->b <= 0){myRing->waiting = true; return;}//no next buffer? go in waiting mode. - myRing->b--; - }//completed a send - }//send - }; - int user::UserCount = 0; -} +/// Creates a new user from a newly connected socket. +/// Also prints "User connected" text to stdout. +Buffer::user::user(Socket::Connection fd){ + S = fd; + MyNum = UserCount++; + std::stringstream st; + st << MyNum; + MyStr = st.str(); + curr_up = 0; + curr_down = 0; + currsend = 0; + myRing = 0; + Thread = 0; + std::cout << "User " << MyNum << " connected" << std::endl; +}//constructor + +/// Drops held DTSC::Ring class, if one is held. +Buffer::user::~user(){ + Stream::get()->dropRing(myRing); +}//destructor + +/// Disconnects the current user. Doesn't do anything if already disconnected. +/// Prints "Disconnected user" to stdout if disconnect took place. +void Buffer::user::Disconnect(std::string reason) { + if (S.connected()){S.close();} + if (Thread != 0){ + if (Thread->joinable()){Thread->join();} + Thread = 0; + } + Stream::get()->clearStats(MyStr, lastStats, reason); +}//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 Buffer::user::doSend(const char * ptr, int len){ + int r = S.iwrite(ptr+currsend, len-currsend); + if (r <= 0){ + if (errno == EWOULDBLOCK){return false;} + Disconnect(S.getError()); + return false; + } + currsend += r; + return (currsend == len); +}//doSend + +/// Try to send data to this user. Disconnects if any problems occur. +void Buffer::user::Send(){ + if (!myRing){return;}//no ring! + if (!S.connected()){return;}//cancel if not connected + if (myRing->waiting){ + Stream::get()->waitForData(); + return; + }//still waiting for next buffer? + if (myRing->starved){ + //if corrupt data, warn and get new DTSC::Ring + std::cout << "Warning: User was send corrupt video data and send to the next keyframe!" << std::endl; + Stream::get()->dropRing(myRing); + myRing = Stream::get()->getRing(); + return; + } + //try to complete a send + Stream::get()->getReadLock(); + if (doSend(Stream::get()->getStream()->outPacket(myRing->b).c_str(), Stream::get()->getStream()->outPacket(myRing->b).length())){ + //switch to next buffer + currsend = 0; + if (myRing->b <= 0){myRing->waiting = true; return;}//no next buffer? go in waiting mode. + myRing->b--; + }//completed a send + Stream::get()->dropReadLock(); +}//send diff --git a/Buffer/user.h b/Buffer/user.h new file mode 100644 index 00000000..b1cb6558 --- /dev/null +++ b/Buffer/user.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include "stats.h" +#include "../util/dtsc.h" +#include "../util/socket.h" +#include "../util/tinythread.h" + +namespace Buffer{ + /// Holds connected users. + /// Keeps track of what buffer users are using and the connection status. + class user{ + public: + tthread::thread * Thread; ///< Holds the thread dealing with this user. + DTSC::Ring * myRing; ///< Ring of the buffer for this user. + int MyNum; ///< User ID of this user. + std::string MyStr; ///< User ID of this user as a string. + std::string inbuffer; ///< Used to buffer input data. + int currsend; ///< Current amount of bytes sent. + Stats lastStats; ///< Holds last known stats for this connection. + Stats tmpStats; ///< Holds temporary stats for this connection. + unsigned int curr_up; ///< Holds the current estimated transfer speed up. + unsigned int curr_down; ///< Holds the current estimated transfer speed down. + bool gotproperaudio; ///< Whether the user received proper audio yet. + void * lastpointer; ///< Pointer to data part of current buffer. + static int UserCount; ///< Global user counter. + Socket::Connection S; ///< Connection to user + /// Creates a new user from a newly connected socket. + /// Also prints "User connected" text to stdout. + user(Socket::Connection fd); + /// Drops held DTSC::Ring class, if one is held. + ~user(); + /// 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); + /// 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(const char * ptr, int len); + /// Try to send data to this user. Disconnects if any problems occur. + void Send(); + }; +} diff --git a/Connector_HTTP/Makefile b/Connector_HTTP/Makefile index 7ccbfc72..0e0cf9d1 100644 --- a/Connector_HTTP/Makefile +++ b/Connector_HTTP/Makefile @@ -1,6 +1,6 @@ SRC = main.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/dtsc.cpp ../util/config.cpp ../util/base64.cpp OBJ = $(SRC:.cpp=.o) -OUT = DDV_Conn_HTTP +OUT = MistConnHTTP INCLUDES = DEBUG = 4 OPTIMIZE = -g @@ -16,11 +16,11 @@ default: cversion $(OUT) .cpp.o: $(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@ $(OUT): $(OBJ) - $(CC) $(LIBS) -o $(OUT) $(OBJ) + $(CC) $(LIBS) -o ../bin/$(OUT) $(OBJ) clean: - rm -rf $(OBJ) $(OUT) Makefile.bak *~ + rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~ install: $(OUT) - cp -f ./$(OUT) /usr/bin/ + cp -f ../bin/$(OUT) /usr/bin/ cversion: rm -rf ../util/config.o diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index 3962fe18..8ea07b08 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -182,7 +182,7 @@ namespace Connector_HTTP{ 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. + HTTP_S.SendResponse(conn, "200", "OK"); #if DEBUG >= 3 printf("Sending crossdomain.xml file\n"); #endif @@ -206,15 +206,7 @@ namespace Connector_HTTP{ 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) && *i != '_'){ - Movie.erase(i); - }else{ - *i=tolower(*i); - }//strip nonalphanumeric - } - streamname += Movie; + streamname = Movie; if( !Flash_ManifestSent ) { HTTP_S.Clean(); HTTP_S.SetHeader("Content-Type","text/xml"); @@ -227,22 +219,18 @@ namespace Connector_HTTP{ ready4data = true; }//FLASH handler if (handler == HANDLER_PROGRESSIVE){ - //in het geval progressive nemen we aan dat de URL de streamname is, met .flv erachter + //we assume the URL is the stream name with a 3 letter extension std::string extension = HTTP_R.url.substr(HTTP_R.url.size()-4); - 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) && *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 + streamname = HTTP_R.url.substr(0, HTTP_R.url.size()-4);//strip the extension + /// \todo VoD streams will need support for position reading from the URL parameters ready4data = true; }//PROGRESSIVE handler - HTTP_R.CleanForNext(); //maak schoon na verwerken voor eventuele volgende requests... + HTTP_R.CleanForNext(); //clean for any possinble next requests } if (ready4data){ if (!inited){ //we are ready, connect the socket! - ss = Socket::Connection(streamname); + ss = Socket::getStream(streamname); if (!ss.connected()){ #if DEBUG >= 1 fprintf(stderr, "Could not connect to server!\n"); diff --git a/Connector_RAW/Makefile b/Connector_RAW/Makefile index c6b3ec59..1e3aa1bc 100644 --- a/Connector_RAW/Makefile +++ b/Connector_RAW/Makefile @@ -1,6 +1,6 @@ SRC = main.cpp ../util/socket.cpp OBJ = $(SRC:.cpp=.o) -OUT = DDV_Conn_RAW +OUT = MistConnRAW INCLUDES = DEBUG = 4 OPTIMIZE = -g @@ -15,9 +15,9 @@ default: $(OUT) .cpp.o: $(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@ $(OUT): $(OBJ) - $(CC) $(LIBS) -o $(OUT) $(OBJ) + $(CC) $(LIBS) -o ../bin/$(OUT) $(OBJ) clean: - rm -rf $(OBJ) $(OUT) Makefile.bak *~ + rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~ install: $(OUT) - cp -f ./$(OUT) /usr/bin/ + cp -f ../bin/$(OUT) /usr/bin/ diff --git a/Connector_RTMP/Makefile b/Connector_RTMP/Makefile index 0a337458..7f6ab177 100644 --- a/Connector_RTMP/Makefile +++ b/Connector_RTMP/Makefile @@ -1,6 +1,6 @@ SRC = main.cpp ../util/socket.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/rtmpchunks.cpp ../util/crypto.cpp ../util/config.cpp ../util/dtsc.cpp OBJ = $(SRC:.cpp=.o) -OUT = DDV_Conn_RTMP +OUT = MistConnRTMP INCLUDES = STATIC = DEBUG = 4 @@ -17,11 +17,11 @@ default: cversion $(OUT) .cpp.o: $(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ $(OUT): $(OBJ) - $(CC) -o $(OUT) $(OBJ) $(STATIC) $(LIBS) + $(CC) -o ../bin/$(OUT) $(OBJ) $(STATIC) $(LIBS) clean: - rm -rf $(OBJ) $(OUT) Makefile.bak *~ + rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~ install: $(OUT) - cp -f ./$(OUT) /usr/bin/ + cp -f ../bin/$(OUT) /usr/bin/ cversion: rm -rf ../util/config.o diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 94a737a2..20c860db 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -27,7 +27,7 @@ namespace Connector_RTMP{ Socket::Connection Socket; ///< Socket connected to user Socket::Connection SS; ///< Socket connected to server - std::string streamname = "/tmp/shared_socket"; ///< Stream that will be opened + std::string streamname; ///< Stream that will be opened void parseChunk(std::string & buffer);///< Parses a single RTMP chunk. void sendCommand(AMF::Object & amfreply, int messagetype, int stream_id);///< Sends a RTMP command either in AMF or AMF3 mode. void parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id);///< Parses a single AMF command message. @@ -73,7 +73,7 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){ if (ready4data){ if (!inited){ //we are ready, connect the socket! - SS = Socket::Connection(streamname); + SS = Socket::getStream(streamname); if (!SS.connected()){ #if DEBUG >= 1 fprintf(stderr, "Could not connect to server!\n"); @@ -398,20 +398,7 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int if ((amfdata.getContentP(0)->StrValue() == "publish")){ if (amfdata.getContentP(3)){ streamname = amfdata.getContentP(3)->StrValue(); - for (std::string::iterator i=streamname.begin(); i != streamname.end(); ++i){ - if (*i == '?'){streamname.erase(i, streamname.end()); break;} - if (!isalpha(*i) && !isdigit(*i) && *i != '_'){ - streamname.erase(i); - --i; - }else{ - *i=tolower(*i); - } - } - streamname = "/tmp/shared_socket_" + streamname; - #if DEBUG >= 4 - fprintf(stderr, "Connecting to buffer %s...\n", streamname.c_str()); - #endif - SS = Socket::Connection(streamname); + SS = Socket::getStream(streamname); if (!SS.connected()){ #if DEBUG >= 1 fprintf(stderr, "Could not connect to server!\n"); @@ -468,10 +455,6 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int 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) && *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); diff --git a/DDV_Controller/Makefile b/Controller/Makefile similarity index 88% rename from DDV_Controller/Makefile rename to Controller/Makefile index 2782d63f..757666b2 100644 --- a/DDV_Controller/Makefile +++ b/Controller/Makefile @@ -1,6 +1,6 @@ SRC = main.cpp ../util/json.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/md5.cpp ../util/config.cpp ../util/procs.cpp ../util/base64.cpp ../util/auth.cpp OBJ = $(SRC:.cpp=.o) -OUT = DDV_Controller +OUT = MistController INCLUDES = DEBUG = 4 OPTIMIZE = -g @@ -19,9 +19,9 @@ default: cversion $(OUT) .cpp.o: $(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ $(OUT): $(OBJ) - $(CC) -o $(OUT) $(OBJ) $(LIBS) + $(CC) -o ../bin/$(OUT) $(OBJ) $(LIBS) clean: - rm -rf $(OBJ) $(OUT) Makefile.bak *~ + rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~ cversion: rm -rf ../util/config.o diff --git a/DDV_Controller/main.cpp b/Controller/main.cpp similarity index 100% rename from DDV_Controller/main.cpp rename to Controller/main.cpp diff --git a/Makefile b/Makefile index 98bf11e1..ad0baa78 100644 --- a/Makefile +++ b/Makefile @@ -1,39 +1,43 @@ default: client .PHONY: client client-debug client-clean clean release-install debug-install docs -client-debug: +prepare: + mkdir -p ./bin + cp -f *.html ./bin/ +client-debug: prepare cd Connector_HTTP; $(MAKE) cd Connector_RTMP; $(MAKE) cd Connector_RAW; $(MAKE) cd Buffer; $(MAKE) - cd DDV_Controller; $(MAKE) + cd Controller; $(MAKE) client: client-debug client-clean: cd Connector_HTTP; $(MAKE) clean cd Connector_RTMP; $(MAKE) clean cd Connector_RAW; $(MAKE) clean cd Buffer; $(MAKE) clean - cd DDV_Controller; $(MAKE) clean + cd Controller; $(MAKE) clean clean: client-clean -client-release: + rm -rf ./bin +client-release: prepare 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 - cd DDV_Controller; $(MAKE) DEBUG=0 OPTIMIZE=-O2 + cd Controller; $(MAKE) DEBUG=0 OPTIMIZE=-O2 release: client-release release-install: client-clean client-release cd Connector_RTMP; $(MAKE) install cd Connector_HTTP; $(MAKE) install cd Connector_RAW; $(MAKE) install cd Buffer; $(MAKE) install - cd DDV_Controller; $(MAKE) install + cd Controller; $(MAKE) install debug-install: client-clean client-debug cd Connector_RTMP; $(MAKE) install cd Connector_HTTP; $(MAKE) install cd Connector_RAW; $(MAKE) install cd Buffer; $(MAKE) install - cd DDV_Controller; $(MAKE) install + cd Controller; $(MAKE) install docs: doxygen ./Doxyfile > /dev/null diff --git a/util/config.cpp b/util/config.cpp index b426766c..b7533fc3 100644 --- a/util/config.cpp +++ b/util/config.cpp @@ -26,12 +26,7 @@ Util::Config::Config(){ listen_port = 4242; daemon_mode = true; interface = "0.0.0.0"; - configfile = "/etc/ddvtech.conf"; username = "root"; - ignore_daemon = false; - ignore_interface = false; - ignore_port = false; - ignore_user = false; } /// Parses commandline arguments. @@ -48,17 +43,15 @@ void Util::Config::parseArgs(int argc, char ** argv){ {"username",1,0,'u'}, {"no-daemon",0,0,'n'}, {"daemon",0,0,'d'}, - {"configfile",1,0,'c'}, {"version",0,0,'v'} }; while ((opt = getopt_long(argc, argv, optString, longOpts, 0)) != -1){ switch (opt){ - 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 'p': listen_port = atoi(optarg); break; + case 'i': interface = optarg; break; + case 'n': daemon_mode = false; break; + case 'd': daemon_mode = true; break; + case 'u': username = optarg; break; case 'v': printf("%s\n", TOSTRING(VERSION)); exit(1); @@ -67,16 +60,9 @@ void Util::Config::parseArgs(int argc, char ** argv){ case '?': std::string doingdaemon = "true"; if (!daemon_mode){doingdaemon = "false";} - if (confsection == ""){ - printf("Options: -h[elp], -?, -v[ersion], -n[odaemon], -d[aemon], -p[ort] VAL, -i[nterface] VAL, -u[sername] VAL\n"); - printf("Defaults:\n interface: %s\n port: %i\n daemon mode: %s\n username: %s\n", interface.c_str(), listen_port, doingdaemon.c_str(), username.c_str()); - }else{ - printf("Options: -h[elp], -?, -v[ersion], -n[odaemon], -d[aemon], -p[ort] VAL, -i[nterface] VAL, -c[onfigfile] VAL, -u[sername] VAL\n"); - printf("Defaults:\n interface: %s\n port: %i\n daemon mode: %s\n configfile: %s\n username: %s\n", interface.c_str(), listen_port, doingdaemon.c_str(), configfile.c_str(), username.c_str()); - 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", confsection.c_str()); - } + printf("Options: -h[elp], -?, -v[ersion], -n[odaemon], -d[aemon], -p[ort] VAL, -i[nterface] VAL, -u[sername] VAL\n"); + printf("Defaults:\n interface: %s\n port: %i\n daemon mode: %s\n username: %s\n", interface.c_str(), listen_port, doingdaemon.c_str(), username.c_str()); + printf("Username root means no change to UID, no matter what the UID is.\n"); printf("This is %s version %s\n", argv[0], TOSTRING(VERSION)); exit(1); break; @@ -84,37 +70,6 @@ void Util::Config::parseArgs(int argc, char ** argv){ }//commandline options parser } -/// Parses the configuration file at configfile, if it exists. -/// Assumes confsection is set. -void Util::Config::parseFile(){ - 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 == confsection){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 -} - /// Sets the current process' running user void Util::setUser(std::string username){ if (username != "root"){ diff --git a/util/config.h b/util/config.h index 5a0b0da9..acf3fb17 100644 --- a/util/config.h +++ b/util/config.h @@ -9,23 +9,15 @@ /// Contains utility code, not directly related to streaming media namespace Util{ - /// Deals with parsing configuration from files or commandline options. + /// Deals with parsing configuration from commandline options. class Config{ - private: - bool ignore_daemon; - bool ignore_interface; - bool ignore_port; - bool ignore_user; - public: - std::string confsection; - std::string configfile; - bool daemon_mode; - std::string interface; - int listen_port; - std::string username; - Config(); - void parseArgs(int argc, char ** argv); - void parseFile(); + public: + bool daemon_mode; + std::string interface; + int listen_port; + std::string username; + Config(); + void parseArgs(int argc, char ** argv); }; /// Will set the active user to the named username. diff --git a/util/server_setup.cpp b/util/server_setup.cpp index 47f015ac..8f8cfad4 100644 --- a/util/server_setup.cpp +++ b/util/server_setup.cpp @@ -15,12 +15,6 @@ #endif -#ifndef CONFIGSECT - /// Configuration file section for this server. - #define CONFIGSECT None - #error "No configuration file section was set!" -#endif - #include "socket.h" //Socket library #include "config.h" //utilities for config management #include @@ -83,10 +77,8 @@ int main(int argc, char ** argv){ //set and parse configuration Util::Config C; - C.confsection = TOSTRING(CONFIGSECT); C.listen_port = DEFAULT_PORT; C.parseArgs(argc, argv); - C.parseFile(); //setup a new server socket, for the correct interface and port server_socket = Socket::Server(C.listen_port, C.interface); diff --git a/util/socket.cpp b/util/socket.cpp index e751b097..603075ee 100644 --- a/util/socket.cpp +++ b/util/socket.cpp @@ -3,6 +3,7 @@ /// Written by Jaron Vietor in 2010 for DDVTech #include "socket.h" +#include #include #include #include @@ -647,3 +648,43 @@ bool Socket::Server::connected(){ /// Returns internal socket number. int Socket::Server::getSocket(){return sock;} + +/// Connect to a stream on the system. +/// Filters the streamname, removing invalid characters and +/// converting all letters to lowercase. +/// If a '?' character is found, everything following that character is deleted. +Socket::Connection Socket::getStream(std::string streamname){ + //strip anything that isn't numbers, digits or underscores + for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){ + if (*i == '?'){streamname.erase(i, streamname.end()); break;} + if (!isalpha(*i) && !isdigit(*i) && *i != '_'){ + streamname.erase(i); + }else{ + *i=tolower(*i); + } + } + return Socket::Connection("/tmp/mist/stream_"+streamname); +} + +/// Create a stream on the system. +/// Filters the streamname, removing invalid characters and +/// converting all letters to lowercase. +/// If a '?' character is found, everything following that character is deleted. +/// If the /tmp/ddvtech directory doesn't exist yet, this will create it. +Socket::Server Socket::makeStream(std::string streamname){ + //strip anything that isn't numbers, digits or underscores + for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){ + if (*i == '?'){streamname.erase(i, streamname.end()); break;} + if (!isalpha(*i) && !isdigit(*i) && *i != '_'){ + streamname.erase(i); + }else{ + *i=tolower(*i); + } + } + std::string loc = "/tmp/mist/stream_"+streamname; + //attempt to create the /tmp/mist directory if it doesn't exist already. + //ignore errors - we catch all problems in the Socket::Server creation already + mkdir("/tmp/mist", S_IRWXU | S_IRWXG | S_IRWXO); + //create and return the Socket::Server + return Socket::Server(loc); +} diff --git a/util/socket.h b/util/socket.h index 3a538286..503ec4e9 100644 --- a/util/socket.h +++ b/util/socket.h @@ -77,4 +77,10 @@ namespace Socket{ int getSocket(); ///< Returns internal socket number. }; + /// Connect to a stream on the system. + Connection getStream(std::string streamname); + + /// Create a stream on the system. + Server makeStream(std::string streamname); + }; From d81a99573c03dbc20b6611574e903202aaa0a7bc Mon Sep 17 00:00:00 2001 From: Thulinma Date: Fri, 27 Apr 2012 23:44:52 +0200 Subject: [PATCH 22/24] Updated controller for #24 and #25, closes #24. --- Controller/main.cpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/Controller/main.cpp b/Controller/main.cpp index f95d84cf..9fc46841 100644 --- a/Controller/main.cpp +++ b/Controller/main.cpp @@ -157,7 +157,7 @@ void CheckProtocols(JSON::Value & p){ if (connports["HTTP"] != tmp){Util::Procs::Stop("HTTP");} connports["HTTP"] = tmp; if (!Util::Procs::isActive("HTTP")){ - Util::Procs::Start("HTTP", std::string("DDV_Conn_HTTP -n -p ")+tmp); + Util::Procs::Start("HTTP", std::string("MistConnHTTP -n -p ")+tmp); } } if (jit->first == "RTMP"){ @@ -166,7 +166,7 @@ void CheckProtocols(JSON::Value & p){ if (connports["RTMP"] != tmp){Util::Procs::Stop("RTMP");} connports["RTMP"] = tmp; if (!Util::Procs::isActive("RTMP")){ - Util::Procs::Start("RTMP", std::string("DDV_Conn_RTMP -n -p ")+tmp); + Util::Procs::Start("RTMP", std::string("MistConnRTMP -n -p ")+tmp); } } } @@ -206,49 +206,66 @@ void startStream(std::string name, JSON::Value & data){ std::string cmd1, cmd2; if (URL.substr(0, 4) == "push"){ std::string pusher = URL.substr(7); - cmd2 = "DDV_Buffer 500 "+name+" "+pusher; + cmd2 = "MistBuffer 500 "+name+" "+pusher; Util::Procs::Start(name, cmd2); }else{ - cmd1 = "ffmpeg -re -async 2 -i "+URL+" "+preset+" -f flv -"; - cmd2 = "DDV_Buffer 500 "+name; + if (preset == ""){ + cmd1 = "cat "+URL; + }else{ + cmd1 = "ffmpeg -re -async 2 -i "+URL+" "+preset+" -f flv -"; + } + cmd2 = "MistBuffer 500 "+name; Util::Procs::Start(name, cmd1, cmd2); } } void CheckAllStreams(JSON::Value & data){ unsigned int currTime = time(0); + bool changed = false; for (JSON::ObjIter jit = data.ObjBegin(); jit != data.ObjEnd(); jit++){ if (!Util::Procs::isActive(jit->first)){ startStream(jit->first, jit->second); } if (currTime - lastBuffer[jit->first] > 5){ + if (jit->second["online"] != 0){changed = true;} jit->second["online"] = 0; }else{ + if (jit->second["online"] != 1){changed = true;} jit->second["online"] = 1; } } + if (changed){ + WriteFile("/tmp/mist/streamlist", out.toString()); + } } void CheckStreams(JSON::Value & in, JSON::Value & out){ + bool changed = false; for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){ if (out.isMember(jit->first)){ if (!streamsEqual(jit->second, out[jit->first])){ Log("STRM", std::string("Updated stream ")+jit->first); + changed = true Util::Procs::Stop(jit->first); startStream(jit->first, jit->second); } }else{ Log("STRM", std::string("New stream ")+jit->first); + changed = true; startStream(jit->first, jit->second); } } for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){ if (!in.isMember(jit->first)){ Log("STRM", std::string("Deleted stream ")+jit->first); + changed = true; Util::Procs::Stop(jit->first); } } out = in; + if (changed){ + WriteFile("/tmp/mist/streamlist", out.toString()); + } } int main(int argc, char ** argv){ @@ -269,7 +286,8 @@ int main(int argc, char ** argv){ time_t lastuplink = 0; time_t processchecker = 0; API_Socket = Socket::Server(C.listen_port, C.interface, true); - Socket::Server Stats_Socket = Socket::Server("/tmp/ddv_statistics", true); + mkdir("/tmp/mist", S_IRWXU | S_IRWXG | S_IRWXO);//attempt to create /tmp/mist/ - ignore failures + Socket::Server Stats_Socket = Socket::Server("/tmp/mist/statistics", true); Util::setUser(C.username); if (C.daemon_mode){ Util::Daemonize(); From 8722effe2f8012c00d19f15a57359d2dc0b8dc2f Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sat, 28 Apr 2012 00:51:21 +0200 Subject: [PATCH 23/24] Fixed Controller compiling, upped socket lib verbosity, fixed JSON::Value null -> string convert, fixed Makefile install command. --- Controller/main.cpp | 93 +++++++++++++++++++++++++++++++-------------- Makefile | 15 ++------ util/json.cpp | 6 ++- util/socket.cpp | 12 +++--- 4 files changed, 80 insertions(+), 46 deletions(-) diff --git a/Controller/main.cpp b/Controller/main.cpp index 9fc46841..8c074c7e 100644 --- a/Controller/main.cpp +++ b/Controller/main.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -146,32 +147,64 @@ void Authorize( JSON::Value & Request, JSON::Value & Response, ConnectedUser & c } void CheckProtocols(JSON::Value & p){ - static std::map connports; - bool seenHTTP = false; - bool seenRTMP = false; + static std::map current_connectors; + std::map new_connectors; + std::map::iterator iter; + std::string tmp; + JSON::Value counter = (long long int)0; + + //collect object type for (JSON::ObjIter jit = p.ObjBegin(); jit != p.ObjEnd(); jit++){ - if (jit->first == "HTTP"){ - tmp = (std::string)jit->second["port"]; - seenHTTP = true; - if (connports["HTTP"] != tmp){Util::Procs::Stop("HTTP");} - connports["HTTP"] = tmp; - if (!Util::Procs::isActive("HTTP")){ - Util::Procs::Start("HTTP", std::string("MistConnHTTP -n -p ")+tmp); - } + tmp = "MistConn"; + tmp += (std::string)jit->second["connector"]; + tmp += " -n -p "; + tmp += (std::string)jit->second["port"]; + if (jit->second.isMember("interface")){ + tmp += " -i "; + tmp += (std::string)jit->second["interface"]; } - if (jit->first == "RTMP"){ - tmp = (std::string)jit->second["port"]; - seenRTMP = true; - if (connports["RTMP"] != tmp){Util::Procs::Stop("RTMP");} - connports["RTMP"] = tmp; - if (!Util::Procs::isActive("RTMP")){ - Util::Procs::Start("RTMP", std::string("MistConnRTMP -n -p ")+tmp); - } + if (jit->second.isMember("username")){ + tmp += " -u "; + tmp += (std::string)jit->second["username"]; + } + counter = (long long int)counter + 1; + new_connectors[std::string("Conn")+(std::string)counter] = tmp; + } + //collect array type + for (JSON::ArrIter ait = p.ArrBegin(); ait != p.ArrEnd(); ait++){ + tmp = "MistConn"; + tmp += (std::string)(*ait)["connector"]; + tmp += " -n -p "; + tmp += (std::string)(*ait)["port"]; + if ((*ait).isMember("interface")){ + tmp += " -i "; + tmp += (std::string)(*ait)["interface"]; + } + if ((*ait).isMember("username")){ + tmp += " -u "; + tmp += (std::string)(*ait)["username"]; + } + counter = (long long int)counter + 1; + new_connectors[std::string("Conn")+(std::string)counter] = tmp; + } + + //shut down deleted/changed connectors + for (iter = current_connectors.begin(); iter != current_connectors.end(); iter++){ + if (new_connectors.count(iter->first) != 1 || new_connectors[iter->first] != iter->second){ + Util::Procs::Stop(iter->first); } } - if (!seenHTTP){Util::Procs::Stop("HTTP");} - if (!seenRTMP){Util::Procs::Stop("RTMP");} + + //start up new/changed connectors + for (iter = new_connectors.begin(); iter != new_connectors.end(); iter++){ + if (current_connectors.count(iter->first) != 1 || current_connectors[iter->first] != iter->second || !Util::Procs::isActive(iter->first)){ + Util::Procs::Start(iter->first, iter->second); + } + } + + //store new state + current_connectors = new_connectors; } void CheckConfig(JSON::Value & in, JSON::Value & out){ @@ -227,15 +260,15 @@ void CheckAllStreams(JSON::Value & data){ startStream(jit->first, jit->second); } if (currTime - lastBuffer[jit->first] > 5){ - if (jit->second["online"] != 0){changed = true;} + if ((long long int)jit->second["online"] != 0){changed = true;} jit->second["online"] = 0; }else{ - if (jit->second["online"] != 1){changed = true;} + if ((long long int)jit->second["online"] != 1){changed = true;} jit->second["online"] = 1; } } if (changed){ - WriteFile("/tmp/mist/streamlist", out.toString()); + WriteFile("/tmp/mist/streamlist", data.toString()); } } @@ -245,7 +278,7 @@ void CheckStreams(JSON::Value & in, JSON::Value & out){ if (out.isMember(jit->first)){ if (!streamsEqual(jit->second, out[jit->first])){ Log("STRM", std::string("Updated stream ")+jit->first); - changed = true + changed = true; Util::Procs::Stop(jit->first); startStream(jit->first, jit->second); } @@ -279,10 +312,15 @@ int main(int argc, char ** argv){ sigaction(SIGTERM, &new_action, NULL); sigaction(SIGPIPE, &new_action, NULL); + Storage = JSON::fromString(ReadFile("config.json")); Util::Config C; - C.confsection = "API"; + C.listen_port = (long long int)Storage["config"]["controller"]["port"]; + if (C.listen_port < 1){C.listen_port = 4242;} + C.interface = (std::string)Storage["config"]["controller"]["interface"]; + if (C.interface == ""){C.interface = "0.0.0.0";} + C.username = (std::string)Storage["config"]["controller"]["username"]; + if (C.username == ""){C.username = "root";} C.parseArgs(argc, argv); - C.parseFile(); time_t lastuplink = 0; time_t processchecker = 0; API_Socket = Socket::Server(C.listen_port, C.interface, true); @@ -299,7 +337,6 @@ int main(int argc, char ** argv){ JSON::Value Response; std::string jsonp; ConnectedUser * uplink = 0; - Storage = JSON::fromString(ReadFile("config.json")); while (API_Socket.connected()){ usleep(100000); //sleep for 100 ms - prevents 100% CPU time diff --git a/Makefile b/Makefile index ad0baa78..60ab6f4f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ default: client -.PHONY: client client-debug client-clean clean release-install debug-install docs +.PHONY: client client-debug client-clean clean release-install debug-install install docs prepare: mkdir -p ./bin @@ -27,17 +27,10 @@ client-release: prepare cd Controller; $(MAKE) DEBUG=0 OPTIMIZE=-O2 release: client-release release-install: client-clean client-release - cd Connector_RTMP; $(MAKE) install - cd Connector_HTTP; $(MAKE) install - cd Connector_RAW; $(MAKE) install - cd Buffer; $(MAKE) install - cd Controller; $(MAKE) install + cp ./bin/Mist* /usr/bin/ debug-install: client-clean client-debug - cd Connector_RTMP; $(MAKE) install - cd Connector_HTTP; $(MAKE) install - cd Connector_RAW; $(MAKE) install - cd Buffer; $(MAKE) install - cd Controller; $(MAKE) install + cp ./bin/Mist* /usr/bin/ +install: debug-install docs: doxygen ./Doxyfile > /dev/null diff --git a/util/json.cpp b/util/json.cpp index 7542ed37..d4b3efc4 100644 --- a/util/json.cpp +++ b/util/json.cpp @@ -254,7 +254,11 @@ JSON::Value::operator std::string(){ if (myType == STRING){ return strVal; }else{ - return toString(); + if (myType == EMPTY){ + return ""; + }else{ + return toString(); + } } } diff --git a/util/socket.cpp b/util/socket.cpp index 603075ee..4ddcfb81 100644 --- a/util/socket.cpp +++ b/util/socket.cpp @@ -454,7 +454,7 @@ Socket::Server::Server(int port, std::string hostname, bool nonblock){ sock = socket(AF_INET6, SOCK_STREAM, 0); if (sock < 0){ #if DEBUG >= 1 - fprintf(stderr, "Could not create socket! Error: %s\n", strerror(errno)); + fprintf(stderr, "Could not create socket %s:%i! Error: %s\n", hostname.c_str(), port, strerror(errno)); #endif return; } @@ -468,7 +468,7 @@ Socket::Server::Server(int port, std::string hostname, bool nonblock){ struct sockaddr_in6 addr; addr.sin6_family = AF_INET6; addr.sin6_port = htons(port);//set port - if (hostname == "0.0.0.0"){ + if (hostname == "0.0.0.0" || hostname.length() == 0){ addr.sin6_addr = in6addr_any; }else{ inet_pton(AF_INET6, hostname.c_str(), &addr.sin6_addr);//set interface, 0.0.0.0 (default) is all @@ -487,14 +487,14 @@ Socket::Server::Server(int port, std::string hostname, bool nonblock){ } }else{ #if DEBUG >= 1 - fprintf(stderr, "Binding failed, retrying as IPv4... (%s)\n", strerror(errno)); + fprintf(stderr, "Binding %s:%i failed, retrying as IPv4... (%s)\n", hostname.c_str(), port, strerror(errno)); #endif close(); } sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0){ #if DEBUG >= 1 - fprintf(stderr, "Could not create socket! Error: %s\n", strerror(errno)); + fprintf(stderr, "Could not create socket %s:%i! Error: %s\n", hostname.c_str(), port, strerror(errno)); #endif return; } @@ -508,7 +508,7 @@ Socket::Server::Server(int port, std::string hostname, bool nonblock){ struct sockaddr_in addr4; addr4.sin_family = AF_INET; addr4.sin_port = htons(port);//set port - if (hostname == "0.0.0.0"){ + if (hostname == "0.0.0.0" || hostname.length() == 0){ addr4.sin_addr.s_addr = INADDR_ANY; }else{ inet_pton(AF_INET, hostname.c_str(), &addr4.sin_addr);//set interface, 0.0.0.0 (default) is all @@ -527,7 +527,7 @@ Socket::Server::Server(int port, std::string hostname, bool nonblock){ } }else{ #if DEBUG >= 1 - fprintf(stderr, "IPv4 binding also failed, giving up. (%s)\n", strerror(errno)); + fprintf(stderr, "IPv4 binding %s:%i also failed, giving up. (%s)\n", hostname.c_str(), port, strerror(errno)); #endif close(); return; From ebc1b9f09339273276833f873423b360c28507b7 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sat, 28 Apr 2012 14:58:49 +0200 Subject: [PATCH 24/24] Include basic embed code generation in the HTTP connector. Closes #23, #24 and #25. --- Connector_HTTP/Makefile | 2 +- Connector_HTTP/main.cpp | 23 +++++++++++++++++++++++ Controller/main.cpp | 15 +++------------ util/json.cpp | 11 +++++++++++ util/json.h | 1 + 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/Connector_HTTP/Makefile b/Connector_HTTP/Makefile index 0e0cf9d1..3d30eb53 100644 --- a/Connector_HTTP/Makefile +++ b/Connector_HTTP/Makefile @@ -1,4 +1,4 @@ -SRC = main.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/dtsc.cpp ../util/config.cpp ../util/base64.cpp +SRC = main.cpp ../util/socket.cpp ../util/http_parser.cpp ../util/flv_tag.cpp ../util/amf.cpp ../util/dtsc.cpp ../util/config.cpp ../util/base64.cpp ../util/json.cpp OBJ = $(SRC:.cpp=.o) OUT = MistConnHTTP INCLUDES = diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index 8ea07b08..3767eecb 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -13,6 +13,7 @@ #include #include "../util/socket.h" #include "../util/http_parser.h" +#include "../util/json.h" #include "../util/dtsc.h" #include "../util/flv_tag.h" #include "../util/MP4/interface.cpp" @@ -187,6 +188,28 @@ namespace Connector_HTTP{ printf("Sending crossdomain.xml file\n"); #endif } + if (HTTP_R.url.substr(0, 7) == "/embed_" && HTTP_R.url.substr(HTTP_R.url.length() - 3, 3) == ".js"){ + streamname = HTTP_R.url.substr(7, HTTP_R.url.length() - 10); + JSON::Value ServConf = JSON::fromFile("/tmp/mist/streamlist"); + std::string response; + handler = HANDLER_NONE; + HTTP_S.Clean(); + HTTP_S.SetHeader("Content-Type", "application/javascript"); + response = "// Generating embed code for stream " + streamname + "\n\n"; + if (ServConf["streams"].isMember(streamname)){ + std::string streamurl = "http://" + HTTP_S.GetHeader("Host") + "/" + streamname + ".flv"; + response += "// Stream URL: " + streamurl + "\n\n"; + response += "document.write('');\n"; + }else{ + response += "// Stream not available at this server.\nalert(\"This stream is currently not available at this server.\\\\nPlease try again later!\");"; + } + response += ""; + HTTP_S.SetBody(response); + HTTP_S.SendResponse(conn, "200", "OK"); + #if DEBUG >= 3 + printf("Sending embed code for %s\n", streamname.c_str()); + #endif + } if (handler == HANDLER_FLASH){ if (HTTP_R.url.find("f4m") == std::string::npos){ Movie = HTTP_R.url.substr(1); diff --git a/Controller/main.cpp b/Controller/main.cpp index 8c074c7e..40d114eb 100644 --- a/Controller/main.cpp +++ b/Controller/main.cpp @@ -72,15 +72,6 @@ void WriteFile( std::string Filename, std::string contents ) { File.close( ); } -std::string ReadFile( std::string Filename ) { - std::string Result; - std::ifstream File; - File.open( Filename.c_str( ) ); - while( File.good( ) ) { Result += File.get( ); } - File.close( ); - return Result; -} - class ConnectedUser{ public: std::string writebuffer; @@ -268,7 +259,7 @@ void CheckAllStreams(JSON::Value & data){ } } if (changed){ - WriteFile("/tmp/mist/streamlist", data.toString()); + WriteFile("/tmp/mist/streamlist", Storage.toString()); } } @@ -297,7 +288,7 @@ void CheckStreams(JSON::Value & in, JSON::Value & out){ } out = in; if (changed){ - WriteFile("/tmp/mist/streamlist", out.toString()); + WriteFile("/tmp/mist/streamlist", Storage.toString()); } } @@ -312,7 +303,7 @@ int main(int argc, char ** argv){ sigaction(SIGTERM, &new_action, NULL); sigaction(SIGPIPE, &new_action, NULL); - Storage = JSON::fromString(ReadFile("config.json")); + Storage = JSON::fromFile("config.json"); Util::Config C; C.listen_port = (long long int)Storage["config"]["controller"]["port"]; if (C.listen_port < 1){C.listen_port = 4242;} diff --git a/util/json.cpp b/util/json.cpp index d4b3efc4..e8e12641 100644 --- a/util/json.cpp +++ b/util/json.cpp @@ -2,6 +2,7 @@ #include "json.h" #include +#include int JSON::Value::c2hex(int c){ if (c >= '0' && c <= '9') return c - '0'; @@ -423,3 +424,13 @@ JSON::Value JSON::fromString(std::string json){ std::istringstream is(json); return JSON::Value(is); } + +/// Converts a file to a JSON::Value. +JSON::Value JSON::fromFile(std::string filename){ + std::string Result; + std::ifstream File; + File.open(filename.c_str()); + while (File.good()){Result += File.get();} + File.close( ); + return fromString(Result); +} diff --git a/util/json.h b/util/json.h index 8de01e11..047178cd 100644 --- a/util/json.h +++ b/util/json.h @@ -69,5 +69,6 @@ namespace JSON{ }; Value fromString(std::string json); + Value fromFile(std::string filename); };