Merge branch 'DTSC'

Conflicts:
	server.html
This commit is contained in:
Thulinma 2012-04-28 15:04:06 +02:00
commit 45ecfdfe6b
66 changed files with 3906 additions and 8689 deletions

1
.gitignore vendored
View file

@ -19,3 +19,4 @@ HTTP_Box_Parser/Box_Parser
gearbox/CPP/Client/Gearbox_Client gearbox/CPP/Client/Gearbox_Client
*.ts *.ts
/nbproject/private/

View file

@ -1,6 +1,6 @@
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 ../util/tinythread.cpp user.cpp stats.cpp stream.cpp
OBJ = $(SRC:.cpp=.o) OBJ = $(SRC:.cpp=.o)
OUT = DDV_Buffer OUT = MistBuffer
INCLUDES = INCLUDES =
DEBUG = 4 DEBUG = 4
OPTIMIZE = -g OPTIMIZE = -g
@ -8,16 +8,16 @@ CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG)
CC = $(CROSS)g++ CC = $(CROSS)g++
LD = $(CROSS)ld LD = $(CROSS)ld
AR = $(CROSS)ar AR = $(CROSS)ar
LIBS = LIBS = -lpthread
.SUFFIXES: .cpp .SUFFIXES: .cpp
.PHONY: clean default .PHONY: clean default
default: $(OUT) default: $(OUT)
.cpp.o: .cpp.o:
$(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@ $(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@
$(OUT): $(OBJ) $(OUT): $(OBJ)
$(CC) $(LIBS) -o $(OUT) $(OBJ) $(CC) $(LIBS) -o ../bin/$(OUT) $(OBJ)
clean: clean:
rm -rf $(OBJ) $(OUT) Makefile.bak *~ rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~
install: $(OUT) install: $(OUT)
cp -f ./$(OUT) /usr/bin/ cp -f ../bin/$(OUT) /usr/bin/

View file

@ -11,178 +11,157 @@
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
#include <sstream> #include <sstream>
#include "../util/flv_tag.h" //FLV format parser #include <sys/time.h>
#include "../util/socket.h" //Socket lib #include "stream.h"
#include "../util/json/json.h"
/// Holds all code unique to the Buffer. /// Holds all code unique to the Buffer.
namespace Buffer{ namespace Buffer{
Json::Value Storage = Json::Value(Json::objectValue); ///< Global storage of data. 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(){
timeval t;
gettimeofday(&t, 0);
return t.tv_sec + t.tv_usec/1000;
}//getNowMS
///A simple signal handler that ignores all signals. ///A simple signal handler that ignores all signals.
void termination_handler (int signum){ void termination_handler (int signum){
switch (signum){ switch (signum){
case SIGKILL: buffer_running = false; break;
case SIGPIPE: return; break; case SIGPIPE: return; break;
default: return; break; default: return; break;
} }
} }
///holds FLV::Tag objects and their numbers void handleStats(void * empty){
struct buffer{ if (empty != 0){return;}
int number; Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
FLV::Tag FLV; while (buffer_running){
};//buffer usleep(1000000); //sleep one second
if (!StatsSocket.connected()){
/// Converts a stats line to up, down, host, connector and conntime values. StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
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){ if (StatsSocket.connected()){
size_t f = s.find(' '); StatsSocket.write(Stream::get()->getStats()+"\n\n");
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());
}
} }
}; }
}
/// Holds connected users. void handleUser(void * v_usr){
/// Keeps track of what buffer users are using and the connection status. user * usr = (user*)v_usr;
class user{ std::cerr << "Thread launched for user " << usr->MyStr << ", socket number " << usr->S.getSocket() << std::endl;
public:
int MyBuffer; ///< Index of currently used buffer.
int MyBuffer_num; ///< Number of currently used buffer.
int MyBuffer_len; ///< Length in bytes of currently used buffer.
int MyNum; ///< User ID of this user.
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();
gotproperaudio = false;
curr_up = 0;
curr_down = 0;
std::cout << "User " << MyNum << " connected" << std::endl;
}//constructor
/// Disconnects the current user. Doesn't do anything if already disconnected.
/// Prints "Disconnected user" to stdout if disconnect took place.
void Disconnect(std::string reason) {
if (S.connected()) {
S.close();
}
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(){
int r = S.iwrite((char*)lastpointer+currsend, MyBuffer_len-currsend);
if (r <= 0){
if (errno == EWOULDBLOCK){return false;}
Disconnect(S.getError());
return false;
}
currsend += r;
return (currsend == MyBuffer_len);
}//doSend
/// Try to send data to this user. Disconnects if any problems occur.
/// \param ringbuf Array of buffers (FLV:Tag with ID attached)
/// \param buffers Count of elements in ringbuf
void Send(buffer ** ringbuf, int buffers){
/// \todo For MP3: gotproperaudio - if false, only send if first byte is 0xFF and set to true
if (!S.connected()){return;}//cancel if not connected
//still waiting for next buffer? check it usr->myRing = thisStream->getRing();
if (MyBuffer_num < 0){ if (!usr->S.write(thisStream->getHeader())){
MyBuffer_num = ringbuf[MyBuffer]->number; usr->Disconnect("failed to receive the header!");
if (MyBuffer_num < 0){ return;
return; //still waiting? don't crash - wait longer. }
}else{
MyBuffer_len = ringbuf[MyBuffer]->FLV.len;
lastpointer = ringbuf[MyBuffer]->FLV.data;
}
}
//do check for buffer resizes while (usr->S.connected()){
if (lastpointer != ringbuf[MyBuffer]->FLV.data){ usleep(5000); //sleep 5ms
Disconnect("Buffer resize at wrong time... had to disconnect"); if (usr->S.canRead()){
return; usr->inbuffer.clear();
char charbuf;
while ((usr->S.iread(&charbuf, 1) == 1) && charbuf != '\n' ){
usr->inbuffer += charbuf;
} }
if (usr->inbuffer != ""){
//try to complete a send if (usr->inbuffer[0] == 'P'){
if (doSend()){ std::cout << "Push attempt from IP " << usr->inbuffer.substr(2) << std::endl;
//switch to next buffer if (thisStream->checkWaitingIP(usr->inbuffer.substr(2))){
if ((ringbuf[MyBuffer]->number != MyBuffer_num)){ if (thisStream->setInput(usr->S)){
//if corrupt data, warn and find keyframe std::cout << "Push accepted!" << std::endl;
std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl; usr->S = Socket::Connection(-1);
int nocrashcount = 0; return;
do{ }else{
MyBuffer++; usr->Disconnect("Push denied - push already in progress!");
nocrashcount++; }
MyBuffer %= buffers; }else{
}while(!ringbuf[MyBuffer]->FLV.isKeyframe && (nocrashcount < buffers)); usr->Disconnect("Push denied - invalid IP address!");
//if keyframe not available, try again later
if (nocrashcount >= buffers){
std::cout << "Warning: No keyframe found in buffers! Skipping search for now..." << std::endl;
return;
} }
}else{
MyBuffer++;
MyBuffer %= buffers;
} }
MyBuffer_num = -1; if (usr->inbuffer[0] == 'S'){
lastpointer = 0; usr->tmpStats = Stats(usr->inbuffer.substr(2));
currsend = 0; unsigned int secs = usr->tmpStats.conntime - usr->lastStats.conntime;
}//completed a send if (secs < 1){secs = 1;}
}//send usr->curr_up = (usr->tmpStats.up - usr->lastStats.up) / secs;
}; usr->curr_down = (usr->tmpStats.down - usr->lastStats.down) / secs;
int user::UserCount = 0; usr->lastStats = usr->tmpStats;
thisStream->saveStats(usr->MyStr, usr->tmpStats);
}
}
}
usr->Send();
}
thisStream->cleanUsers();
std::cerr << "User " << usr->MyStr << " disconnected, socket number " << usr->S.getSocket() << std::endl;
}
/// Starts a loop, waiting for connections to send video data to. /// 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
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);
thisStream->getWriteLock();
if (thisStream->getStream()->parsePacket(inBuffer)){
thisStream->getStream()->outPacket(0);
lastPacketTime = now;
prevPacketTime = currPacketTime;
currPacketTime = thisStream->getStream()->getTime();
}
thisStream->dropWriteLock();
}else{
if (((currPacketTime - prevPacketTime) - (now - lastPacketTime)) > 999){
usleep(999000);
}else{
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.
int Start(int argc, char ** argv) { int Start(int argc, char ** argv) {
//first make sure no segpipe signals will kill us //first make sure no segpipe signals will kill us
struct sigaction new_action; struct sigaction new_action;
@ -190,226 +169,56 @@ namespace Buffer{
sigemptyset (&new_action.sa_mask); sigemptyset (&new_action.sa_mask);
new_action.sa_flags = 0; new_action.sa_flags = 0;
sigaction (SIGPIPE, &new_action, NULL); sigaction (SIGPIPE, &new_action, NULL);
sigaction (SIGKILL, &new_action, NULL);
//then check and parse the commandline //then check and parse the commandline
if (argc < 3) { if (argc < 2) {
std::cout << "usage: " << argv[0] << " buffers_count streamname [awaiting_IP]" << std::endl; std::cout << "usage: " << argv[0] << " streamName [awaiting_IP]" << std::endl;
return 1; return 1;
} }
std::string waiting_ip = ""; std::string name = argv[1];
bool ip_waiting = false; bool ip_waiting = false;
Socket::Connection ip_input; std::string waiting_ip;
if (argc >= 4){ if (argc >= 4){
waiting_ip += argv[3]; waiting_ip += argv[2];
ip_waiting = true; ip_waiting = true;
} }
std::string shared_socket = "/tmp/shared_socket_";
shared_socket += argv[2];
Socket::Server SS(shared_socket, true); SS = Socket::makeStream(name);
FLV::Tag metadata; thisStream = Stream::get();
FLV::Tag video_init; thisStream->setName(name);
FLV::Tag audio_init; if (ip_waiting){
int buffers = atoi(argv[1]); thisStream->setWaitingIP(waiting_ip);
buffer ** ringbuf = (buffer**) calloc (buffers,sizeof(buffer*)); }
std::vector<user> users;
std::vector<user>::iterator usersIt;
for (int i = 0; i < buffers; ++i) ringbuf[i] = new buffer;
int current_buffer = 0;
int lastproper = 0;//last properly finished buffer number
unsigned int loopcount = 0;
unsigned int stattimer = 0;
Socket::Connection incoming; Socket::Connection incoming;
Socket::Connection std_input(fileno(stdin)); Socket::Connection std_input(fileno(stdin));
Socket::Connection StatsSocket = Socket::Connection("/tmp/ddv_statistics", true);
Storage["log"] = Json::Value(Json::objectValue); tthread::thread StatsThread = tthread::thread(handleStats, 0);
Storage["curr"] = Json::Value(Json::objectValue); tthread::thread * StdinThread = 0;
Storage["totals"] = Json::Value(Json::objectValue); if (!ip_waiting){
StdinThread = new tthread::thread(handleStdin, 0);
unsigned char packtype; }else{
bool gotVideoInfo = false; StdinThread = new tthread::thread(handlePushin, 0);
bool gotAudioInfo = false; }
bool gotData = false;
while((!feof(stdin) || ip_waiting) && !FLV::Parse_Error){
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[2];
if (!StatsSocket.connected()){
StatsSocket = Socket::Connection("/tmp/ddv_statistics", true);
}
if (StatsSocket.connected()){
StatsSocket.write(Storage.toStyledString()+"\n\n");
Storage["log"].clear();
}
}
//invalidate the current buffer
ringbuf[current_buffer]->number = -1;
if (
(!ip_waiting &&
(std_input.canRead()) && ringbuf[current_buffer]->FLV.FileLoader(stdin)
) || (ip_waiting && (ip_input.connected()) &&
ringbuf[current_buffer]->FLV.SockLoader(ip_input)
)
){
loopcount++;
packtype = ringbuf[current_buffer]->FLV.data[0];
//store metadata, if available
if (packtype == 0x12){
metadata = ringbuf[current_buffer]->FLV;
std::cout << "Received metadata!" << std::endl;
if (gotVideoInfo && gotAudioInfo){
FLV::Parse_Error = true;
std::cout << "... after proper video and audio? Cancelling broadcast!" << std::endl;
}
gotVideoInfo = false;
gotAudioInfo = false;
}
//store video init data, if available
if (!gotVideoInfo && ringbuf[current_buffer]->FLV.isKeyframe){
if ((ringbuf[current_buffer]->FLV.data[11] & 0x0f) == 7){//avc packet
if (ringbuf[current_buffer]->FLV.data[12] == 0){
ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero
video_init = ringbuf[current_buffer]->FLV;
gotVideoInfo = true;
std::cout << "Received video configuration!" << std::endl;
}
}else{gotVideoInfo = true;}//non-avc = no config...
}
//store audio init data, if available
if (!gotAudioInfo && (packtype == 0x08)){
if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 10){//aac packet
ringbuf[current_buffer]->FLV.tagTime(0);//timestamp to zero
audio_init = ringbuf[current_buffer]->FLV;
gotAudioInfo = true;
std::cout << "Received audio configuration!" << std::endl;
}else{gotAudioInfo = true;}//no aac = no config...
}
//on keyframe set possible start point
if (packtype == 0x09){
if (((ringbuf[current_buffer]->FLV.data[11] & 0xf0) >> 4) == 1){
lastproper = current_buffer;
}
}
if (loopcount > 5){gotData = true;}
//keep track of buffers
ringbuf[current_buffer]->number = loopcount;
current_buffer++;
current_buffer %= buffers;
}
while (buffer_running && SS.connected()){
//check for new connections, accept them if there are any //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()){ if (incoming.connected()){
users.push_back(incoming); user * usr_ptr = new user(incoming);
//send the FLV header thisStream->addUser(usr_ptr);
users.back().currsend = 0; usr_ptr->Thread = new tthread::thread(handleUser, (void *)usr_ptr);
users.back().MyBuffer = lastproper;
users.back().MyBuffer_num = -1;
/// \todo Do this more nicely?
if (gotData){
if (!users.back().S.write(FLV::Header, 13)){
users.back().Disconnect("failed to receive the header!");
}else{
if (metadata.len > 0){
if (!users.back().S.write(metadata.data, metadata.len)){
users.back().Disconnect("failed to receive metadata!");
}
}
if (audio_init.len > 0){
if (!users.back().S.write(audio_init.data, audio_init.len)){
users.back().Disconnect("failed to receive audio init!");
}
}
if (video_init.len > 0){
if (!users.back().S.write(video_init.data, video_init.len)){
users.back().Disconnect("failed to receive video init!");
}
}
}
}
}
//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 || tmp.substr(2) == "::ffff:"+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 ("+waiting_ip+"!="+tmp.substr(2)+")!");
}
}
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(ringbuf, buffers);
}
}
} }
}//main loop }//main loop
// disconnect listener // disconnect listener
if (FLV::Parse_Error){ buffer_running = false;
std::cout << "FLV parse error:" << FLV::Error_Str << std::endl; std::cout << "End of input file - buffer shutting down" << std::endl;
}else{
std::cout << "Reached EOF of input" << std::endl;
}
SS.close(); SS.close();
while (users.size() > 0){ StatsThread.join();
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){ StdinThread->join();
(*usersIt).Disconnect("Shutting down..."); delete thisStream;
if (!(*usersIt).S.connected()){users.erase(usersIt);break;}
}
}
return 0; return 0;
} }

32
Buffer/stats.cpp Normal file
View file

@ -0,0 +1,32 @@
#include "stats.h"
#include <stdlib.h> //for atoi()
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());
}
}

16
Buffer/stats.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include <string>
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);
};
}

216
Buffer/stream.cpp Normal file
View file

@ -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();
}

69
Buffer/stream.h Normal file
View file

@ -0,0 +1,69 @@
#pragma once
#include <string>
#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<user*> users; ///< All connected users.
std::vector<user*>::iterator usersIt; ///< Iterator for all connected users.
std::string name; ///< Name for this buffer.
tthread::condition_variable moreData; ///< Triggered when more data becomes available.
};
};

76
Buffer/user.cpp Normal file
View file

@ -0,0 +1,76 @@
#include "user.h"
#include "stream.h"
#include <sstream>
int Buffer::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

41
Buffer/user.h Normal file
View file

@ -0,0 +1,41 @@
#pragma once
#include <string>
#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();
};
}

View file

@ -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

View file

@ -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/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 ../util/json.cpp
OBJ = $(SRC:.cpp=.o) OBJ = $(SRC:.cpp=.o)
OUT = DDV_Conn_HTTP OUT = MistConnHTTP
INCLUDES = INCLUDES =
DEBUG = 4 DEBUG = 4
OPTIMIZE = -g OPTIMIZE = -g
@ -16,11 +16,11 @@ default: cversion $(OUT)
.cpp.o: .cpp.o:
$(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@ $(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@
$(OUT): $(OBJ) $(OUT): $(OBJ)
$(CC) $(LIBS) -o $(OUT) $(OBJ) $(CC) $(LIBS) -o ../bin/$(OUT) $(OBJ)
clean: clean:
rm -rf $(OBJ) $(OUT) Makefile.bak *~ rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~
install: $(OUT) install: $(OUT)
cp -f ./$(OUT) /usr/bin/ cp -f ../bin/$(OUT) /usr/bin/
cversion: cversion:
rm -rf ../util/util.o rm -rf ../util/config.o

View file

@ -13,79 +13,23 @@
#include <ctime> #include <ctime>
#include "../util/socket.h" #include "../util/socket.h"
#include "../util/http_parser.h" #include "../util/http_parser.h"
#include "../util/json.h"
#include "../util/dtsc.h" #include "../util/dtsc.h"
#include "../util/flv_tag.h" #include "../util/flv_tag.h"
#include "../util/MP4/interface.cpp" #include "../util/MP4/interface.cpp"
#include "../util/base64.h"
#include "../util/amf.h" #include "../util/amf.h"
/// Holds everything unique to HTTP Connector. /// Holds everything unique to HTTP Connector.
namespace Connector_HTTP{ namespace Connector_HTTP{
/// Defines the type of handler used to process this request. /// 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 std::queue<std::string> Flash_FragBuffer;///<Fragment buffer for F4V
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; DTSC::Stream Strm;///< Incoming stream buffer.
HTTP::Parser HTTP_R, HTTP_S;///<HTTP Receiver en HTTP Sender.
/// 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. /// Returns AMF-format metadata for Adobe HTTP Dynamic Streaming.
std::string GetMetaData( ) { std::string GetMetaData( ) {
@ -127,7 +71,7 @@ namespace Connector_HTTP{
}//getMetaData }//getMetaData
/// Returns a F4M-format manifest file for Adobe HTTP Dynamic Streaming. /// Returns a F4M-format manifest file for Adobe HTTP Dynamic Streaming.
std::string BuildManifest( std::string MetaData, std::string MovieId, int CurrentMediaTime ) { std::string BuildManifest(std::string MovieId) {
Interface * temp = new Interface; Interface * temp = new Interface;
std::string Result="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n"; std::string Result="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n";
Result += "<id>"; Result += "<id>";
@ -136,13 +80,13 @@ namespace Connector_HTTP{
Result += "<streamType>live</streamType>\n"; Result += "<streamType>live</streamType>\n";
Result += "<deliveryType>streaming</deliveryType>\n"; Result += "<deliveryType>streaming</deliveryType>\n";
Result += "<bootstrapInfo profile=\"named\" id=\"bootstrap1\">"; Result += "<bootstrapInfo profile=\"named\" id=\"bootstrap1\">";
Result += base64_encode(temp->GenerateLiveBootstrap(1)); Result += Base64::encode(temp->GenerateLiveBootstrap(1));
Result += "</bootstrapInfo>\n"; Result += "</bootstrapInfo>\n";
Result += "<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\""; Result += "<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"";
Result += MovieId; Result += MovieId;
Result += "/\">\n"; Result += "/\">\n";
Result += "<metadata>"; Result += "<metadata>";
Result += base64_encode(GetMetaData()); Result += Base64::encode(GetMetaData());
Result += "</metadata>\n"; Result += "</metadata>\n";
Result += "</media>\n"; Result += "</media>\n";
Result += "</manifest>\n"; Result += "</manifest>\n";
@ -161,7 +105,7 @@ namespace Connector_HTTP{
HTTP_S.SendResponse(conn, "200", "OK");//geen SetBody = unknown length! Dat willen we hier. 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 //HTTP_S.SendBodyPart(CONN_fd, FLVHeader, 13);//schrijf de FLV header
conn.write(FLV::Header, 13); conn.write(FLV::Header, 13);
FLV::Tag tmp; static FLV::Tag tmp;
tmp.DTSCMetaInit(Strm); tmp.DTSCMetaInit(Strm);
conn.write(tmp.data, tmp.len); conn.write(tmp.data, tmp.len);
if (Strm.metadata.getContentP("audio") && Strm.metadata.getContentP("audio")->getContentP("init")){ if (Strm.metadata.getContentP("audio") && Strm.metadata.getContentP("audio")->getContentP("init")){
@ -182,65 +126,28 @@ namespace Connector_HTTP{
} }
/// Handles Flash Dynamic HTTP streaming requests /// Handles Flash Dynamic HTTP streaming requests
void FlashDynamic(FLV::Tag & tag, HTTP::Parser HTTP_S, Socket::Connection & conn, DTSC::Stream & Strm){ void FlashDynamic(FLV::Tag & tag, DTSC::Stream & Strm){
static std::queue<std::string> Flash_FragBuffer;
static unsigned int Flash_StartTime = 0;
static std::string FlashBuf; static std::string FlashBuf;
static std::string FlashMeta; static FLV::Tag tmp;
static bool FlashFirstVideo = false; if (Strm.getPacket(0).getContentP("keyframe")){
static bool FlashFirstAudio = false; if (FlashBuf != ""){
static bool Flash_ManifestSent = false; Flash_FragBuffer.push(FlashBuf);
static int Flash_RequestPending = 0; #if DEBUG >= 4
if (tag.tagTime() > 0){ fprintf(stderr, "Received a fragment. Now %i in buffer.\n", (int)Flash_FragBuffer.size());
if (Flash_StartTime == 0){ #endif
Flash_StartTime = tag.tagTime(); }
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);
} }
@ -253,14 +160,14 @@ namespace Connector_HTTP{
std::string streamname; std::string streamname;
FLV::Tag tag;///< Temporary tag buffer. FLV::Tag tag;///< Temporary tag buffer.
std::string recBuffer = ""; 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 Movie = "";
std::string Quality = ""; std::string Quality = "";
int Segment = -1; int Segment = -1;
int ReqFragment = -1; int ReqFragment = -1;
int temp; int temp;
int Flash_RequestPending = 0;
bool Flash_ManifestSent = false;
unsigned int lastStats = 0; unsigned int lastStats = 0;
//int CurrentFragment = -1; later herbruiken? //int CurrentFragment = -1; later herbruiken?
@ -268,6 +175,7 @@ namespace Connector_HTTP{
//only parse input if available or not yet init'ed //only parse input if available or not yet init'ed
if (HTTP_R.Read(conn, ready4data)){ if (HTTP_R.Read(conn, ready4data)){
handler = HANDLER_PROGRESSIVE; 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("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.find("f4m") != std::string::npos){handler = HANDLER_FLASH;}
if (HTTP_R.url == "/crossdomain.xml"){ if (HTTP_R.url == "/crossdomain.xml"){
@ -275,11 +183,33 @@ namespace Connector_HTTP{
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type", "text/xml"); HTTP_S.SetHeader("Content-Type", "text/xml");
HTTP_S.SetBody("<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\"><cross-domain-policy><allow-access-from domain=\"*\" /><site-control permitted-cross-domain-policies=\"all\"/></cross-domain-policy>"); HTTP_S.SetBody("<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\"><cross-domain-policy><allow-access-from domain=\"*\" /><site-control permitted-cross-domain-policies=\"all\"/></cross-domain-policy>");
HTTP_S.SendResponse(conn, "200", "OK");//geen SetBody = unknown length! Dat willen we hier. HTTP_S.SendResponse(conn, "200", "OK");
#if DEBUG >= 3 #if DEBUG >= 3
printf("Sending crossdomain.xml file\n"); printf("Sending crossdomain.xml file\n");
#endif #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('<object width=\"600\" height=\"409\"><param name=\"movie\" value=\"http://fpdownload.adobe.com/strobe/FlashMediaPlayback.swf\"></param><param name=\"flashvars\" value=\"src="+HTTP::Parser::urlencode(streamurl)+"&controlBarMode=floating\"></param><param name=\"allowFullScreen\" value=\"true\"></param><param name=\"allowscriptaccess\" value=\"always\"></param><embed src=\"http://fpdownload.adobe.com/strobe/FlashMediaPlayback.swf\" type=\"application/x-shockwave-flash\" allowscriptaccess=\"always\" allowfullscreen=\"true\" width=\"600\" height=\"409\" flashvars=\"src="+HTTP::Parser::urlencode(streamurl)+"&controlBarMode=floating\"></embed></object>');\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 (handler == HANDLER_FLASH){
if (HTTP_R.url.find("f4m") == std::string::npos){ if (HTTP_R.url.find("f4m") == std::string::npos){
Movie = HTTP_R.url.substr(1); Movie = HTTP_R.url.substr(1);
@ -294,43 +224,36 @@ namespace Connector_HTTP{
printf( "URL: %s\n", HTTP_R.url.c_str()); 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); printf( "Movie: %s, Quality: %s, Seg %d Frag %d\n", Movie.c_str(), Quality.c_str(), Segment, ReqFragment);
#endif #endif
/// \todo Handle these requests properly...
/*
Flash_ManifestSent = true;//stop manifest from being sent multiple times
Flash_RequestPending++; Flash_RequestPending++;
*/
}else{ }else{
Movie = HTTP_R.url.substr(1); Movie = HTTP_R.url.substr(1);
Movie = Movie.substr(0,Movie.find("/")); Movie = Movie.substr(0,Movie.find("/"));
} }
streamname = "/tmp/shared_socket_"; streamname = Movie;
for (std::string::iterator i=Movie.end()-1; i>=Movie.begin(); --i){ if( !Flash_ManifestSent ) {
if (!isalpha(*i) && !isdigit(*i) && *i != '_'){ HTTP_S.Clean();
Movie.erase(i); HTTP_S.SetHeader("Content-Type","text/xml");
}else{ HTTP_S.SetHeader("Cache-Control","no-cache");
*i=tolower(*i); HTTP_S.SetBody(BuildManifest(Movie));
}//strip nonalphanumeric HTTP_S.SendResponse(conn, "200", "OK");
Flash_ManifestSent = true;//stop manifest from being sent multiple times
std::cout << "Sent manifest" << std::endl;
} }
streamname += Movie;
ready4data = true; ready4data = true;
}//FLASH handler }//FLASH handler
if (handler == HANDLER_PROGRESSIVE){ 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
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 streamname = HTTP_R.url.substr(0, HTTP_R.url.size()-4);//strip the extension
for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){ /// \todo VoD streams will need support for position reading from the URL parameters
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
ready4data = true; ready4data = true;
}//PROGRESSIVE handler }//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 (ready4data){
if (!inited){ if (!inited){
//we are ready, connect the socket! //we are ready, connect the socket!
ss = Socket::Connection(streamname); ss = Socket::getStream(streamname);
if (!ss.connected()){ if (!ss.connected()){
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Could not connect to server!\n"); fprintf(stderr, "Could not connect to server!\n");
@ -343,8 +266,6 @@ namespace Connector_HTTP{
#endif #endif
inited = true; inited = true;
} }
/// \todo Send pending flash requests...
/*
if ((Flash_RequestPending > 0) && !Flash_FragBuffer.empty()){ if ((Flash_RequestPending > 0) && !Flash_FragBuffer.empty()){
HTTP_S.Clean(); HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type","video/mp4"); HTTP_S.SetHeader("Content-Type","video/mp4");
@ -364,28 +285,17 @@ namespace Connector_HTTP{
ss.write(stat); ss.write(stat);
} }
} }
ss.canRead(); if (ss.canRead()){
switch (ss.ready()){ ss.spool();
case -1: if (Strm.parsePacket(ss.Received())){
conn.close(); tag.DTSCLoader(Strm);
#if DEBUG >= 1 if (handler == HANDLER_FLASH){
fprintf(stderr, "Source socket is disconnected.\n"); FlashDynamic(tag, Strm);
#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);
}
}
} }
break; if (handler == HANDLER_PROGRESSIVE){
Progressive(tag, HTTP_S, conn, Strm);
}
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
SRC = main.cpp ../util/socket.cpp SRC = main.cpp ../util/socket.cpp
OBJ = $(SRC:.cpp=.o) OBJ = $(SRC:.cpp=.o)
OUT = DDV_Conn_RAW OUT = MistConnRAW
INCLUDES = INCLUDES =
DEBUG = 4 DEBUG = 4
OPTIMIZE = -g OPTIMIZE = -g
@ -15,9 +15,9 @@ default: $(OUT)
.cpp.o: .cpp.o:
$(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@ $(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@
$(OUT): $(OBJ) $(OUT): $(OBJ)
$(CC) $(LIBS) -o $(OUT) $(OBJ) $(CC) $(LIBS) -o ../bin/$(OUT) $(OBJ)
clean: clean:
rm -rf $(OBJ) $(OUT) Makefile.bak *~ rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~
install: $(OUT) install: $(OUT)
cp -f ./$(OUT) /usr/bin/ cp -f ../bin/$(OUT) /usr/bin/

View file

@ -22,7 +22,7 @@ int main(int argc, char ** argv) {
} }
//transport ~50kb at a time //transport ~50kb at a time
//this is a nice tradeoff between CPU usage and speed //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);} while(std::cout.good() && S.read(buffer,50000)){std::cout.write(buffer,50000);}
S.close(); S.close();
return 0; return 0;

View file

@ -1,6 +1,6 @@
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/config.cpp ../util/dtsc.cpp
OBJ = $(SRC:.cpp=.o) OBJ = $(SRC:.cpp=.o)
OUT = DDV_Conn_RTMP OUT = MistConnRTMP
INCLUDES = INCLUDES =
STATIC = STATIC =
DEBUG = 4 DEBUG = 4
@ -17,11 +17,11 @@ default: cversion $(OUT)
.cpp.o: .cpp.o:
$(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ $(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@
$(OUT): $(OBJ) $(OUT): $(OBJ)
$(CC) -o $(OUT) $(OBJ) $(STATIC) $(LIBS) $(CC) -o ../bin/$(OUT) $(OBJ) $(STATIC) $(LIBS)
clean: clean:
rm -rf $(OBJ) $(OUT) Makefile.bak *~ rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~
install: $(OUT) install: $(OUT)
cp -f ./$(OUT) /usr/bin/ cp -f ../bin/$(OUT) /usr/bin/
cversion: cversion:
rm -rf ../util/util.o rm -rf ../util/config.o

View file

@ -10,6 +10,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <getopt.h> #include <getopt.h>
#include <sstream>
#include "../util/socket.h" #include "../util/socket.h"
#include "../util/flv_tag.h" #include "../util/flv_tag.h"
#include "../util/amf.h" #include "../util/amf.h"
@ -26,8 +27,10 @@ namespace Connector_RTMP{
Socket::Connection Socket; ///< Socket connected to user Socket::Connection Socket; ///< Socket connected to user
Socket::Connection SS; ///< Socket connected to server 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(); 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); int Connector_RTMP(Socket::Connection conn);
};//Connector_RTMP namespace; };//Connector_RTMP namespace;
@ -35,8 +38,9 @@ namespace Connector_RTMP{
/// Main Connector_RTMP function /// Main Connector_RTMP function
int Connector_RTMP::Connector_RTMP(Socket::Connection conn){ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
Socket = conn; Socket = conn;
FLV::Tag tag, viddata, auddata; FLV::Tag tag, init_tag;
bool viddone = false, auddone = false; DTSC::Stream Strm;
bool stream_inited = false;//true if init data for audio/video was sent
//first timestamp set //first timestamp set
RTMPStream::firsttime = RTMPStream::getNowMS(); RTMPStream::firsttime = RTMPStream::getNowMS();
@ -61,20 +65,15 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
unsigned int lastStats = 0; unsigned int lastStats = 0;
while (Socket.connected() && !FLV::Parse_Error){ while (Socket.connected()){
//only parse input if available or not yet init'ed sleep(10000);//sleep 10ms to prevent high CPU usage
//rightnow = getNowMS(); if (Socket.spool()){
if (Socket.canRead() || !ready4data){// || (snd_cnt - snd_window_at >= snd_window_size) parseChunk(Socket.Received());
switch (Socket.ready()){
case -1: break; //disconnected
case 0: break; //not ready yet
default: parseChunk(); break; //new data is waiting
}
} }
if (ready4data){ if (ready4data){
if (!inited){ if (!inited){
//we are ready, connect the socket! //we are ready, connect the socket!
SS = Socket::Connection(streamname); SS = Socket::getStream(streamname);
if (!SS.connected()){ if (!SS.connected()){
#if DEBUG >= 1 #if DEBUG >= 1
fprintf(stderr, "Could not connect to server!\n"); fprintf(stderr, "Could not connect to server!\n");
@ -95,54 +94,27 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
SS.write(stat); SS.write(stat);
} }
} }
SS.canRead(); if (SS.spool()){
switch (SS.ready()){ if (Strm.parsePacket(SS.Received())){
case -1: //sent init data if needed
#if DEBUG >= 1 if (!stream_inited){
fprintf(stderr, "Source socket is disconnected.\n"); if (Strm.metadata.getContentP("audio") && Strm.metadata.getContentP("audio")->getContentP("init")){
#endif init_tag.DTSCAudioInit(Strm);
Socket.close();//disconnect user Socket.write(RTMPStream::SendMedia(init_tag));
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 (viddone && auddone && justdone){ if (Strm.metadata.getContentP("video") && Strm.metadata.getContentP("video")->getContentP("init")){
if (viddata.len != 0){ init_tag.DTSCVideoInit(Strm);
Socket.write(RTMPStream::SendMedia(viddata)); Socket.write(RTMPStream::SendMedia(init_tag));
#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;
} }
//not gotten init yet? cancel this tag stream_inited = true;
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
} }
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 +137,19 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
}//Connector_RTMP }//Connector_RTMP
/// Tries to get and parse one RTMP chunk at a time. /// Tries to get and parse one RTMP chunk at a time.
void Connector_RTMP::parseChunk(){ 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; static RTMPStream::Chunk next;
static std::string inbuffer;
FLV::Tag F; FLV::Tag F;
static AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER); static AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER);
static AMF::Object amfelem("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 amf3data("empty", AMF::AMF3_DDV_CONTAINER);
static AMF::Object3 amf3elem("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)){ while (next.Parse(inbuffer)){
@ -240,30 +216,33 @@ void Connector_RTMP::parseChunk(){
RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str()); 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) Socket.write(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5)
break; break;
case 8: case 8://audio data
F.ChunkLoader(next); case 9://video data
case 18://meta data
if (SS.connected()){ if (SS.connected()){
#if DEBUG >= 4 F.ChunkLoader(next);
fprintf(stderr, "A"); DTSC::DTMI pack_out = F.toDTSC(meta_out);
#endif if (!pack_out.isEmpty()){
SS.write(std::string(F.data, F.len)); 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{ }else{
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received useless audio data\n"); fprintf(stderr, "Received useless media data\n");
#endif
Socket.close();
}
break;
case 9:
F.ChunkLoader(next);
if (SS.connected()){
#if DEBUG >= 4
fprintf(stderr, "V");
#endif
SS.write(std::string(F.data, F.len));
}else{
#if DEBUG >= 4
fprintf(stderr, "Received useless video data\n");
#endif #endif
Socket.close(); Socket.close();
} }
@ -279,7 +258,6 @@ void Connector_RTMP::parseChunk(){
#endif #endif
break; break;
case 17:{ case 17:{
bool parsed3 = false;
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received AFM3 command message\n"); fprintf(stderr, "Received AFM3 command message\n");
#endif #endif
@ -295,428 +273,17 @@ void Connector_RTMP::parseChunk(){
#endif #endif
next.data = next.data.substr(1); next.data = next.data.substr(1);
amfdata = AMF::parse(next.data); amfdata = AMF::parse(next.data);
#if DEBUG >= 4 parseAMFCommand(amfdata, 17, next.msg_stream_id);
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
}
}//parsing AMF0-style }//parsing AMF0-style
} break; } 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: case 19:
#if DEBUG >= 4 #if DEBUG >= 4
fprintf(stderr, "Received AFM0 shared object\n"); fprintf(stderr, "Received AFM0 shared object\n");
#endif #endif
break; break;
case 20:{//AMF0 command message case 20:{//AMF0 command message
bool parsed = false;
amfdata = AMF::parse(next.data); amfdata = AMF::parse(next.data);
#if DEBUG >= 4 parseAMFCommand(amfdata, 20, next.msg_stream_id);
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
}
} break; } break;
case 22: case 22:
#if DEBUG >= 4 #if DEBUG >= 4
@ -733,6 +300,206 @@ void Connector_RTMP::parseChunk(){
} }
}//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();
SS = Socket::getStream(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();
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 // Load main server setup file, default port 1935, handler is Connector_RTMP::Connector_RTMP
#define DEFAULT_PORT 1935 #define DEFAULT_PORT 1935

View file

@ -1,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) OBJ = $(SRC:.cpp=.o)
OUT = Connector_RTSP OUT = Connector_RTSP
INCLUDES = $(shell pkg-config --cflags jrtplib) INCLUDES = $(shell pkg-config --cflags jrtplib)

View file

@ -12,6 +12,7 @@
#include <unistd.h> #include <unistd.h>
#include <getopt.h> #include <getopt.h>
#include <iostream> #include <iostream>
#include <sstream>
#include <sys/time.h> #include <sys/time.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/types.h> #include <sys/types.h>
@ -54,6 +55,7 @@ int RTSP_Handler( Socket::Connection conn ) {
jrtplib::RTPSessionParams VideoParams; jrtplib::RTPSessionParams VideoParams;
jrtplib::RTPUDPv6TransmissionParams VideoTransParams; jrtplib::RTPUDPv6TransmissionParams VideoTransParams;
std::string PreviousRequest = ""; std::string PreviousRequest = "";
std::string streamname;
Socket::Connection ss(-1); Socket::Connection ss(-1);
HTTP::Parser HTTP_R, HTTP_S; HTTP::Parser HTTP_R, HTTP_S;
//Some clients appear to expect a single request per connection. Don't know which ones. //Some clients appear to expect a single request per connection. Don't know which ones.
@ -89,21 +91,28 @@ int RTSP_Handler( Socket::Connection conn ) {
/// \todo Add audio to SDP file. /// \todo Add audio to SDP file.
//This is just a dummy with data that was supposedly right for our teststream. //This is just a dummy with data that was supposedly right for our teststream.
//SDP Docs: http://tools.ietf.org/html/rfc4566 //SDP Docs: http://tools.ietf.org/html/rfc4566
//v=0 HTTP_S.SetBody( "v=0\r\n" //protocol version
//o=- 0 0 IN IP4 ddvtech.com "o=- 0 0 IN IP4 ddvtech.com\r\n" //originator and session identifier (5.2):
//s=Fifa Test //username sess-id sess-version nettype addrtype unicast-addr
//c=IN IP4 127.0.0.1 //"-": no concept of User IDs, nettype IN(ternet)
//t=0 0 //IP4: following address is a FQDN for IPv4
//a=recvonly "s=Fifa Test\r\n" //session name (5.3)
//m=video 0 RTP/AVP 98 //"c" - destination is specified in SETUP per rfc2326 C.1.7, set null as recommended
//a=control:rtsp://localhost/fifa/video "c=IN IP4 0.0.0.0\r\n" //connection information -- not required if included in all media
//a=rtpmap:98 H264/90000 //nettype addrtype connection-address
//a=fmtp:98 packetization-mode=0 "t=0 0\r\n" //time the session is active: start-time stop-time; "0 0"=permanent session
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"); "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:" + 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");
//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() ); fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "200", "OK" ).c_str() );
conn.write( HTTP_S.BuildResponse( "200", "OK" ) ); conn.write( HTTP_S.BuildResponse( "200", "OK" ) );
} }
} else if ( HTTP_R.method == "SETUP" ) { } 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"); std::string temp = HTTP_R.GetHeader("Transport");
//Extract the random UTP pair for video data ( RTP/RTCP) //Extract the random UTP pair for video data ( RTP/RTCP)
int ClientRTPLoc = temp.find( "client_port=" ) + 12; int ClientRTPLoc = temp.find( "client_port=" ) + 12;
@ -114,28 +123,66 @@ int RTSP_Handler( Socket::Connection conn ) {
fprintf( stderr, "RESPONSE:\n%s\n", HTTP_S.BuildResponse( "459", "Aggregate Operation Not Allowed" ).c_str() ); 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" ) ); conn.write( HTTP_S.BuildResponse( "459", "Aggregate Operation Not Allowed" ) );
} else { } 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( "CSeq", HTTP_R.GetHeader( "CSeq" ).c_str() );
HTTP_S.SetHeader( "Session", time(NULL) ); HTTP_S.SetHeader( "Session", time(NULL) );
/// \todo "Random" generation of server_ports
/// \todo Add support for audio /// \todo Add support for audio
// if( HTTP_R.url.find( "audio" ) != std::string::npos ) { // if( HTTP_R.url.find( "audio" ) != std::string::npos ) {
// HTTP_S.SetHeader( "Transport", HTTP_R.GetHeader( "Transport" ) + ";server_port=50002-50003" ); // HTTP_S.SetHeader( "Transport", HTTP_R.GetHeader( "Transport" ) + ";server_port=50002-50003" );
// } else { // } 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 //Stub data for testing purposes. This should now be extracted somehow from DTSC::DTMI
VideoParams.SetOwnTimestampUnit( ( 1.0 / 29.917 ) * 90000.0 ); VideoParams.SetOwnTimestampUnit( ( 1.0 / 29.917 ) * 90000.0 );
VideoParams.SetMaximumPacketSize( 10000 ); VideoParams.SetMaximumPacketSize( 10000 );
//pick the right port here
VideoTransParams.SetPortbase( 50000 );
//create a JRTPlib session //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 ) { 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 ); exit( -1 );
} else { } 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 /// \todo Connect with clients other than localhost
uint8_t localip[32]; uint8_t localip[32];
@ -147,7 +194,7 @@ int RTSP_Handler( Socket::Connection conn ) {
//add the destination address to the VideoSession //add the destination address to the VideoSession
VideoStatus = VideoSession.AddDestination(addr); VideoStatus = VideoSession.AddDestination(addr);
if (VideoStatus < 0) { if (VideoStatus < 0) {
std::cerr << jrtplib::RTPGetErrorString(VideoStatus) << std::endl; std::cerr << "Destination could not be set: " << jrtplib::RTPGetErrorString(VideoStatus) << std::endl;
exit(-1); exit(-1);
} else { } else {
std::cerr << "Destination Set\n"; std::cerr << "Destination Set\n";
@ -201,13 +248,34 @@ int RTSP_Handler( Socket::Connection conn ) {
} }
} }
if( PlayVideo ) { if( PlayVideo ) {
/// \todo Select correct source. This should become the DTSC::DTMI or the DTSC::Stream, whatever seems more natural. bool no_data_ignore = false;
std::string VideoBuf = ReadNALU( ); std::string VideoBuf;
if( 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. //videobuffer is empty, no more data.
jrtplib::RTPTime delay = jrtplib::RTPTime(10.0); jrtplib::RTPTime delay = jrtplib::RTPTime(10.0);
VideoSession.BYEDestroy(delay,"Out of data",11); VideoSession.BYEDestroy(delay,"Out of data",11);
conn.close(); conn.close();
std::cerr << "Buffer empty - closing connection" << std::endl;
} else { } else {
//Send a single NALU (H264 block) here. //Send a single NALU (H264 block) here.
VideoSession.SendPacket( VideoBuf.c_str(), VideoBuf.size(), 98, false, ( 1.0 / 29.917 ) * 90000 ); VideoSession.SendPacket( VideoBuf.c_str(), VideoBuf.size(), 98, false, ( 1.0 / 29.917 ) * 90000 );

View file

@ -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) OBJ = $(SRC:.cpp=.o)
OUT = DDV_Conn_TS OUT = DDV_Conn_TS
INCLUDES = INCLUDES =

View file

@ -335,7 +335,7 @@ int TS_Handler( Socket::Connection conn ) {
case 0: break;//not ready yet case 0: break;//not ready yet
default: default:
ss.spool(); ss.spool();
if ( stream.parsePacket( conn.Received() ) ) { if ( stream.parsePacket( ss.Received() ) ) {
if( stream.lastType() == DTSC::VIDEO ) { if( stream.lastType() == DTSC::VIDEO ) {
fprintf(stderr, "Video contains NALU\n" ); fprintf(stderr, "Video contains NALU\n" );
SendPAT( conn ); SendPAT( conn );

View file

@ -1,6 +1,6 @@
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) OBJ = $(SRC:.cpp=.o)
OUT = DDV_Controller OUT = MistController
INCLUDES = INCLUDES =
DEBUG = 4 DEBUG = 4
OPTIMIZE = -g OPTIMIZE = -g
@ -9,6 +9,7 @@ COMPILED_USERNAME = testuser
COMPILED_PASSWORD = 179ad45c6ce2cb97cf1029e212046e81 COMPILED_PASSWORD = 179ad45c6ce2cb97cf1029e212046e81
#COMPILED_PASSWORD = testpass #COMPILED_PASSWORD = testpass
CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) -DCOMPILED_USERNAME=$(COMPILED_USERNAME) -DCOMPILED_PASSWORD=$(COMPILED_PASSWORD) -DVERSION=$(VERSION) CCFLAGS = -Wall -Wextra -funsigned-char $(OPTIMIZE) -DDEBUG=$(DEBUG) -DCOMPILED_USERNAME=$(COMPILED_USERNAME) -DCOMPILED_PASSWORD=$(COMPILED_PASSWORD) -DVERSION=$(VERSION)
INSTALL = install
CC = $(CROSS)g++ CC = $(CROSS)g++
LD = $(CROSS)ld LD = $(CROSS)ld
AR = $(CROSS)ar AR = $(CROSS)ar
@ -19,9 +20,11 @@ default: cversion $(OUT)
.cpp.o: .cpp.o:
$(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@ $(CC) $(INCLUDES) $(CCFLAGS) -c $< -o $@
$(OUT): $(OBJ) $(OUT): $(OBJ)
$(CC) -o $(OUT) $(OBJ) $(LIBS) $(CC) -o ../bin/$(OUT) $(OBJ) $(LIBS)
clean: clean:
rm -rf $(OBJ) $(OUT) Makefile.bak *~ rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~
cversion: cversion:
rm -rf ../util/util.o rm -rf ../util/config.o
install: $(OUT)
$(INSTALL) -D ./$(OUT) $(DESTDIR)/usr/bin/$(OUT)

505
Controller/main.cpp Normal file
View file

@ -0,0 +1,505 @@
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <set>
#include <cstdlib>
#include <queue>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <climits>
#include <cstring>
#include <unistd.h>
#include <getopt.h>
#include <set>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <sstream>
#include "../util/socket.h"
#include "../util/http_parser.h"
#include "../util/md5.h"
#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<std::string, int> 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.
/// Disconnecting the server_socket will terminate the main listening loop
/// and cleanly shut down the process.
void signal_handler (int signum){
switch (signum){
case SIGINT:
#if DEBUG >= 1
fprintf(stderr, "Received SIGINT - closing server socket.\n");
#endif
break;
case SIGHUP:
#if DEBUG >= 1
fprintf(stderr, "Received SIGHUP - closing server socket.\n");
#endif
break;
case SIGTERM:
#if DEBUG >= 1
fprintf(stderr, "Received SIGTERM - closing server socket.\n");
#endif
break;
default: return; break;
}
API_Socket.close();
}//signal_handler
JSON::Value Storage; ///< Global storage of data.
void WriteFile( std::string Filename, std::string contents ) {
std::ofstream File;
File.open( Filename.c_str( ) );
File << contents << std::endl;
File.close( );
}
class ConnectedUser{
public:
std::string writebuffer;
Socket::Connection C;
HTTP::Parser H;
bool Authorized;
bool clientMode;
int logins;
std::string Username;
ConnectedUser(Socket::Connection c){
C = c;
H.Clean();
logins = 0;
Authorized = false;
clientMode = false;
}
};
void Log(std::string kind, std::string message){
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 ) {
time_t Time = time(0);
tm * TimeInfo = localtime(&Time);
std::stringstream Date;
std::string retval;
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"];
if (Storage["account"].isMember(UserID)){
if( md5( (std::string)Storage["account"][UserID]["password"] + Challenge ) == (std::string)Request["authorize"]["password"] ) {
Response["authorize"]["status"] = "OK";
conn.Username = UserID;
conn.Authorized = true;
return;
}
}
if (Storage["authorize"].isMember("key")){
UserID = "gearbox";
if (keychecker.PubKey_Check(Challenge, Storage["authorize"]["key"])){
Response["authorize"]["status"] = "OK";
conn.Username = UserID;
conn.Authorized = true;
return;
}
}
if (UserID != ""){
Log("AUTH", "Failed login attempt "+UserID+" @ "+conn.C.getHost());
}
conn.logins++;
}
conn.Username = "";
conn.Authorized = false;
Response["authorize"]["status"] = "CHALL";
Response["authorize"]["challenge"] = Challenge;
return;
}
void CheckProtocols(JSON::Value & p){
static std::map<std::string, std::string> current_connectors;
std::map<std::string, std::string> new_connectors;
std::map<std::string, std::string>::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++){
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->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);
}
}
//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){
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);
}
}
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"] != two["channel"]["URL"]){return false;}
if (one["preset"]["cmd"] != two["preset"]["cmd"]){return false;}
return true;
}
void startStream(std::string name, JSON::Value & data){
Log("BUFF", "(re)starting stream buffer "+name);
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);
cmd2 = "MistBuffer 500 "+name+" "+pusher;
Util::Procs::Start(name, cmd2);
}else{
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 ((long long int)jit->second["online"] != 0){changed = true;}
jit->second["online"] = 0;
}else{
if ((long long int)jit->second["online"] != 1){changed = true;}
jit->second["online"] = 1;
}
}
if (changed){
WriteFile("/tmp/mist/streamlist", Storage.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", Storage.toString());
}
}
int main(int argc, char ** argv){
//setup signal handler
struct sigaction new_action;
new_action.sa_handler = signal_handler;
sigemptyset (&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGINT, &new_action, NULL);
sigaction(SIGHUP, &new_action, NULL);
sigaction(SIGTERM, &new_action, NULL);
sigaction(SIGPIPE, &new_action, NULL);
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;}
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);
time_t lastuplink = 0;
time_t processchecker = 0;
API_Socket = Socket::Server(C.listen_port, C.interface, 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();
}
Socket::Connection Incoming;
std::vector< ConnectedUser > users;
std::vector<Socket::Connection> buffers;
JSON::Value Request;
JSON::Value Response;
std::string jsonp;
ConnectedUser * uplink = 0;
while (API_Socket.connected()){
usleep(100000); //sleep for 100 ms - prevents 100% CPU time
if (time(0) - processchecker > 10){
processchecker = time(0);
CheckProtocols(Storage["config"]["protocols"]);
CheckAllStreams(Storage["streams"]);
}
if (time(0) - lastuplink > UPLINK_INTERVAL){
lastuplink = time(0);
bool gotUplink = false;
if (users.size() > 0){
for( std::vector< ConnectedUser >::iterator it = users.end() - 1; it >= users.begin(); it--) {
if (!it->C.connected()){
it->C.close();
users.erase(it);
break;
}
if (it->clientMode){uplink = &*it; gotUplink = true;}
}
}
if (!gotUplink){
Incoming = Socket::Connection("gearbox.ddvtech.com", 4242, true);
if (Incoming.connected()){
users.push_back(Incoming);
users.back().clientMode = true;
uplink = &users.back();
gotUplink = true;
}
}
if (gotUplink){
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.toString()));
uplink->H.BuildRequest();
uplink->writebuffer += uplink->H.BuildResponse("200", "OK");
uplink->H.Clean();
//Log("UPLK", "Sending server data to uplink.");
}else{
Log("UPLK", "Could not connect to uplink.");
}
}
Incoming = API_Socket.accept();
if (Incoming.connected()){users.push_back(Incoming);}
Incoming = Stats_Socket.accept();
if (Incoming.connected()){buffers.push_back(Incoming);}
if (buffers.size() > 0){
for( std::vector< Socket::Connection >::iterator it = buffers.begin(); it != buffers.end(); it++) {
if (!it->connected()){
it->close();
buffers.erase(it);
break;
}
it->spool();
if (it->Received() != ""){
size_t newlines = it->Received().find("\n\n");
while (newlines != std::string::npos){
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);
}
}
it->Received().erase(0, newlines+2);
newlines = it->Received().find("\n\n");
}
}
}
}
if (users.size() > 0){
for( std::vector< ConnectedUser >::iterator it = users.begin(); it != users.end(); it++) {
if (!it->C.connected() || it->logins > 3){
it->C.close();
users.erase(it);
break;
}
if (it->writebuffer != ""){
it->C.iwrite(it->writebuffer);
}
if (it->H.Read(it->C)){
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).
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.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{
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");}
if (it->H.GetVar("jsonp") != ""){jsonp = it->H.GetVar("jsonp");}
it->H.Clean();
it->H.protocol = "HTTP/1.0";
it->H.SetHeader("Content-Type", "text/javascript");
if (jsonp == ""){
it->H.SetBody(Response.toString()+"\n\n");
}else{
it->H.SetBody(jsonp+"("+Response.toString()+");\n\n");
}
it->writebuffer += it->H.BuildResponse("200", "OK");
it->H.Clean();
}
}
}
}
}
Util::Procs::StopAll();
WriteFile("config.json", Storage.toString());
std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl;
return 0;
}

View file

@ -1,592 +0,0 @@
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <set>
#include <cstdlib>
#include <queue>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <climits>
#include <cstring>
#include <unistd.h>
#include <getopt.h>
#include <set>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <sstream>
#include "../util/socket.h"
#include "../util/http_parser.h"
#include "../util/md5.h"
#include "../util/json/json.h"
#include "../util/util.h"
#include <openssl/rsa.h>
#include <openssl/x509.h>
#define UPLINK_INTERVAL 30
Socket::Server API_Socket; ///< Main connection socket.
std::map<std::string, int> lastBuffer; ///< Last moment of contact with all buffers.
/// Basic signal handler. Disconnects the server_socket if it receives
/// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE.
/// Disconnecting the server_socket will terminate the main listening loop
/// and cleanly shut down the process.
void signal_handler (int signum){
switch (signum){
case SIGINT:
#if DEBUG >= 1
fprintf(stderr, "Received SIGINT - closing server socket.\n");
#endif
break;
case SIGHUP:
#if DEBUG >= 1
fprintf(stderr, "Received SIGHUP - closing server socket.\n");
#endif
break;
case SIGTERM:
#if DEBUG >= 1
fprintf(stderr, "Received SIGTERM - closing server socket.\n");
#endif
break;
default: return; break;
}
API_Socket.close();
}//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.
void WriteFile( std::string Filename, std::string contents ) {
std::ofstream File;
File.open( Filename.c_str( ) );
File << contents << std::endl;
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;
Socket::Connection C;
HTTP::Parser H;
bool Authorized;
bool clientMode;
int logins;
std::string Username;
ConnectedUser(Socket::Connection c){
C = c;
H.Clean();
logins = 0;
Authorized = false;
clientMode = false;
}
};
void Log(std::string kind, std::string message){
Json::Value m;
m.append((Json::Value::UInt)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 ) {
time_t Time = time(0);
tm * TimeInfo = localtime(&Time);
std::stringstream Date;
std::string retval;
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();
if (Storage["account"].isMember(UserID)){
if( md5( Storage["account"][UserID]["password"].asString() + Challenge ) == Request["authorize"]["password"].asString() ) {
Response["authorize"]["status"] = "OK";
conn.Username = UserID;
conn.Authorized = true;
return;
}
}
if (Storage["authorize"].isMember("key")){
UserID = "gearbox";
if (RSA_check(Challenge, Storage["authorize"]["key"].asString())){
Response["authorize"]["status"] = "OK";
conn.Username = UserID;
conn.Authorized = true;
return;
}
}
if (UserID != ""){
Log("AUTH", "Failed login attempt "+UserID+" @ "+conn.C.getHost());
}
conn.logins++;
}
conn.Username = "";
conn.Authorized = false;
Response["authorize"]["status"] = "CHALL";
Response["authorize"]["challenge"] = Challenge;
return;
}
void CheckProtocols(Json::Value & p){
static std::map<std::string, std::string> 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();
seenHTTP = true;
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);
}
}
if (jit.memberName() == std::string("RTMP")){
tmp = p[jit.memberName()]["port"].asString();
seenRTMP = true;
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);
}
}
}
if (!seenHTTP){Util::Procs::Stop("HTTP");}
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());
}
}
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());
}
}
}
}
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;}
return true;
}
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 cmd1, cmd2;
if (URL.substr(0, 4) == "push"){
std::string pusher = URL.substr(7);
cmd2 = "DDV_Buffer 500 "+name+" "+pusher;
Util::Procs::Start(name, cmd2);
}else{
cmd1 = "ffmpeg -re -async 2 -i "+URL+" "+preset+" -f flv -";
cmd2 = "DDV_Buffer 500 "+name;
Util::Procs::Start(name, cmd1, cmd2);
}
}
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()]);
}
if (currTime - lastBuffer[jit.memberName()] > 5){
data[jit.memberName()]["online"] = 0;
}else{
data[jit.memberName()]["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()]);
}
}
}
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());
}
}
}
out = in;
}
int main(int argc, char ** argv){
//setup signal handler
struct sigaction new_action;
new_action.sa_handler = signal_handler;
sigemptyset (&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGINT, &new_action, NULL);
sigaction(SIGHUP, &new_action, NULL);
sigaction(SIGTERM, &new_action, NULL);
sigaction(SIGPIPE, &new_action, NULL);
RSA_Load(); // Load GearBox public key
Util::Config C;
C.confsection = "API";
C.parseArgs(argc, argv);
C.parseFile();
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);
Util::setUser(C.username);
if (C.daemon_mode){
Util::Daemonize();
}
Socket::Connection Incoming;
std::vector< ConnectedUser > users;
std::vector<Socket::Connection> buffers;
Json::Value Request = Json::Value(Json::objectValue);
Json::Value Response = Json::Value(Json::objectValue);
Json::Reader JsonParse;
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);}
while (API_Socket.connected()){
usleep(100000); //sleep for 100 ms - prevents 100% CPU time
if (time(0) - processchecker > 10){
processchecker = time(0);
CheckProtocols(Storage["config"]["protocols"]);
CheckAllStreams(Storage["streams"]);
}
if (time(0) - lastuplink > UPLINK_INTERVAL){
lastuplink = time(0);
bool gotUplink = false;
if (users.size() > 0){
for( std::vector< ConnectedUser >::iterator it = users.end() - 1; it >= users.begin(); it--) {
if (!it->C.connected()){
it->C.close();
users.erase(it);
break;
}
if (it->clientMode){uplink = &*it; gotUplink = true;}
}
}
if (!gotUplink){
Incoming = Socket::Connection("gearbox.ddvtech.com", 4242, true);
if (Incoming.connected()){
users.push_back(Incoming);
users.back().clientMode = true;
uplink = &users.back();
gotUplink = true;
}
}
if (gotUplink){
Response.clear(); //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.BuildRequest();
uplink->writebuffer += uplink->H.BuildResponse("200", "OK");
uplink->H.Clean();
//Log("UPLK", "Sending server data to uplink.");
}else{
Log("UPLK", "Could not connect to uplink.");
}
}
Incoming = API_Socket.accept();
if (Incoming.connected()){users.push_back(Incoming);}
Incoming = Stats_Socket.accept();
if (Incoming.connected()){buffers.push_back(Incoming);}
if (buffers.size() > 0){
for( std::vector< Socket::Connection >::iterator it = buffers.begin(); it != buffers.end(); it++) {
if (!it->connected()){
it->close();
buffers.erase(it);
break;
}
it->spool();
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()]);
}
}
}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");
}
}
}
}
if (users.size() > 0){
for( std::vector< ConnectedUser >::iterator it = users.begin(); it != users.end(); it++) {
if (!it->C.connected() || it->logins > 3){
it->C.close();
users.erase(it);
break;
}
if (it->writebuffer != ""){
it->C.iwrite(it->writebuffer);
}
if (it->H.Read(it->C)){
Response.clear(); //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";
}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();
}
}
}
}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();
}
}
jsonp = "";
if (it->H.GetVar("callback") != ""){jsonp = it->H.GetVar("callback");}
if (it->H.GetVar("jsonp") != ""){jsonp = it->H.GetVar("jsonp");}
it->H.Clean();
it->H.protocol = "HTTP/1.0";
it->H.SetHeader("Content-Type", "text/javascript");
if (jsonp == ""){
it->H.SetBody(Response.toStyledString()+"\n\n");
}else{
it->H.SetBody(jsonp+"("+Response.toStyledString()+");\n\n");
}
it->writebuffer += it->H.BuildResponse("200", "OK");
it->H.Clean();
}
}
}
}
}
Util::Procs::StopAll();
WriteFile("config.json", Storage.toStyledString());
std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl;
return 0;
}

View file

@ -1,34 +1,36 @@
default: client 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
client-debug: prepare:
mkdir -p ./bin
cp -f *.html ./bin/
client-debug: prepare
cd Connector_HTTP; $(MAKE) cd Connector_HTTP; $(MAKE)
cd Connector_RTMP; $(MAKE) cd Connector_RTMP; $(MAKE)
cd Connector_RAW; $(MAKE) cd Connector_RAW; $(MAKE)
cd Buffer; $(MAKE) cd Buffer; $(MAKE)
cd Controller; $(MAKE)
client: client-debug client: client-debug
client-clean: client-clean:
cd Connector_HTTP; $(MAKE) clean cd Connector_HTTP; $(MAKE) clean
cd Connector_RTMP; $(MAKE) clean cd Connector_RTMP; $(MAKE) clean
cd Connector_RAW; $(MAKE) clean cd Connector_RAW; $(MAKE) clean
cd Buffer; $(MAKE) clean cd Buffer; $(MAKE) clean
cd Controller; $(MAKE) clean
clean: client-clean clean: client-clean
client-release: rm -rf ./bin
client-release: prepare
cd Connector_HTTP; $(MAKE) DEBUG=0 OPTIMIZE=-O2 cd Connector_HTTP; $(MAKE) DEBUG=0 OPTIMIZE=-O2
cd Connector_RTMP; $(MAKE) DEBUG=0 OPTIMIZE=-O2 cd Connector_RTMP; $(MAKE) DEBUG=0 OPTIMIZE=-O2
cd Connector_RAW; $(MAKE) DEBUG=0 OPTIMIZE=-O2 cd Connector_RAW; $(MAKE) DEBUG=0 OPTIMIZE=-O2
cd Buffer; $(MAKE) DEBUG=0 OPTIMIZE=-O2 cd Buffer; $(MAKE) DEBUG=0 OPTIMIZE=-O2
cd Controller; $(MAKE) DEBUG=0 OPTIMIZE=-O2
release: client-release release: client-release
release-install: client-clean client-release release-install: client-clean client-release
cd Connector_RTMP; $(MAKE) install cp ./bin/Mist* /usr/bin/
cd Connector_HTTP; $(MAKE) install
cd Connector_RAW; $(MAKE) install
cd Buffer; $(MAKE) install
debug-install: client-clean client-debug debug-install: client-clean client-debug
cd Connector_RTMP; $(MAKE) install cp ./bin/Mist* /usr/bin/
cd Connector_HTTP; $(MAKE) install install: debug-install
cd Connector_RAW; $(MAKE) install
cd Buffer; $(MAKE) install
docs: docs:
doxygen ./Doxyfile > /dev/null doxygen ./Doxyfile > /dev/null

23
tools/DTSC2FLV/Makefile Normal file
View file

@ -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/

64
tools/DTSC2FLV/main.cpp Normal file
View file

@ -0,0 +1,64 @@
/// \file DTSC2FLV/main.cpp
/// Contains the code that will transform any valid DTSC input into valid FLVs.
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#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

View file

@ -14,62 +14,12 @@
#include "../../util/dtsc.h" //DTSC support #include "../../util/dtsc.h" //DTSC support
#include "../../util/amf.h" //AMF 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. /// Holds all code that converts filetypes to DTSC.
namespace Converters{ 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. /// Reads FLV from STDIN, outputs DTSC to STDOUT.
int FLV2DTSC() { int FLV2DTSC() {
FLV::Tag FLV_in; // Temporary storage for incoming FLV data. 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 meta_out; // Storage for outgoing DTMI header data.
DTSC::DTMI pack_out; // Storage for outgoing DTMI data. DTSC::DTMI pack_out; // Storage for outgoing DTMI data.
std::stringstream prebuffer; // Temporary buffer before sending real data std::stringstream prebuffer; // Temporary buffer before sending real data
@ -78,9 +28,11 @@ namespace Converters{
while (!feof(stdin)){ while (!feof(stdin)){
if (FLV_in.FileLoader(stdin)){ if (FLV_in.FileLoader(stdin)){
pack_out = FLV_in.toDTSC(meta_out);
if (pack_out.isEmpty()){continue;}
if (!sending){ if (!sending){
counter++; counter++;
if (counter > 10){ if (counter > 8){
sending = true; sending = true;
meta_out.Pack(true); meta_out.Pack(true);
meta_out.packed.replace(0, 4, DTSC::Magic_Header); meta_out.packed.replace(0, 4, DTSC::Magic_Header);
@ -88,152 +40,12 @@ namespace Converters{
std::cout << prebuffer.rdbuf(); std::cout << prebuffer.rdbuf();
prebuffer.str(""); prebuffer.str("");
std::cerr << "Buffer done, starting real-time output..." << std::endl; 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{ }else{
pack_out.addContent(DTSC::DTMI("data", std::string((char*)FLV_in.data+12, (size_t)FLV_in.len-16))); prebuffer << pack_out.Pack(true);//buffer
} continue;//don't also write
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...
}
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("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{
prebuffer << pack_out.Pack(true);
} }
} }
std::cout << pack_out.Pack(true);//simply write
} }
} }

View file

@ -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/

View file

@ -0,0 +1,52 @@
/// \file DTSC_Analyser/main.cpp
/// Contains the code for the DTSC Analysing tool.
#include <fcntl.h>
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#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;
}

View file

@ -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\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\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 /// Fills an AF structure with the right data

View file

@ -2,6 +2,7 @@
/// Holds all code for the AMF namespace. /// Holds all code for the AMF namespace.
#include "amf.h" #include "amf.h"
#include <sstream>
#include <cstdio> //needed for stderr only #include <cstdio> //needed for stderr only
/// Returns the std::string Indice for the current object, if available. /// 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. /// Prints the contents of this object to std::cerr.
/// If this object contains other objects, it will call itself recursively /// If this object contains other objects, it will call itself recursively
/// and print all nested content in a nice human-readable format. /// and print all nested content in a nice human-readable format.
void AMF::Object::Print(std::string indent){ std::string AMF::Object::Print(std::string indent){
std::cerr << indent; std::stringstream st;
st << indent;
// print my type // print my type
switch (myType){ switch (myType){
case AMF::AMF0_NUMBER: std::cerr << "Number"; break; case AMF::AMF0_NUMBER: st << "Number"; break;
case AMF::AMF0_BOOL: std::cerr << "Bool"; break; case AMF::AMF0_BOOL: st << "Bool"; break;
case AMF::AMF0_STRING://short string case AMF::AMF0_STRING://short string
case AMF::AMF0_LONGSTRING: std::cerr << "String"; break; case AMF::AMF0_LONGSTRING: st << "String"; break;
case AMF::AMF0_OBJECT: std::cerr << "Object"; break; case AMF::AMF0_OBJECT: st << "Object"; break;
case AMF::AMF0_MOVIECLIP: std::cerr << "MovieClip"; break; case AMF::AMF0_MOVIECLIP: st << "MovieClip"; break;
case AMF::AMF0_NULL: std::cerr << "Null"; break; case AMF::AMF0_NULL: st << "Null"; break;
case AMF::AMF0_UNDEFINED: std::cerr << "Undefined"; break; case AMF::AMF0_UNDEFINED: st << "Undefined"; break;
case AMF::AMF0_REFERENCE: std::cerr << "Reference"; break; case AMF::AMF0_REFERENCE: st << "Reference"; break;
case AMF::AMF0_ECMA_ARRAY: std::cerr << "ECMA Array"; break; case AMF::AMF0_ECMA_ARRAY: st << "ECMA Array"; break;
case AMF::AMF0_OBJ_END: std::cerr << "Object end"; break; case AMF::AMF0_OBJ_END: st << "Object end"; break;
case AMF::AMF0_STRICT_ARRAY: std::cerr << "Strict Array"; break; case AMF::AMF0_STRICT_ARRAY: st << "Strict Array"; break;
case AMF::AMF0_DATE: std::cerr << "Date"; break; case AMF::AMF0_DATE: st << "Date"; break;
case AMF::AMF0_UNSUPPORTED: std::cerr << "Unsupported"; break; case AMF::AMF0_UNSUPPORTED: st << "Unsupported"; break;
case AMF::AMF0_RECORDSET: std::cerr << "Recordset"; break; case AMF::AMF0_RECORDSET: st << "Recordset"; break;
case AMF::AMF0_XMLDOC: std::cerr << "XML Document"; break; case AMF::AMF0_XMLDOC: st << "XML Document"; break;
case AMF::AMF0_TYPED_OBJ: std::cerr << "Typed Object"; break; case AMF::AMF0_TYPED_OBJ: st << "Typed Object"; break;
case AMF::AMF0_UPGRADE: std::cerr << "Upgrade to AMF3"; break; case AMF::AMF0_UPGRADE: st << "Upgrade to AMF3"; break;
case AMF::AMF0_DDV_CONTAINER: std::cerr << "DDVTech Container"; break; case AMF::AMF0_DDV_CONTAINER: st << "DDVTech Container"; break;
} }
// print my string indice, if available // print my string indice, if available
std::cerr << " " << myIndice << " "; st << " " << myIndice << " ";
// print my numeric or string contents // print my numeric or string contents
switch (myType){ 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_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: std::cerr << strval; 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... 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 I hold other objects, print those too, recursively.
if (contents.size() > 0){ if (contents.size() > 0){
for (std::vector<AMF::Object>::iterator it = contents.begin(); it != contents.end(); it++){it->Print(indent+" ");} for (std::vector<AMF::Object>::iterator it = contents.begin(); it != contents.end(); it++){st << it->Print(indent+" ");}
} }
return st.str();
};//print };//print
/// Packs the AMF object to a std::string for transfer over the network. /// Packs the AMF object to a std::string for transfer over the network.

View file

@ -70,7 +70,7 @@ namespace AMF{
Object(std::string indice, double val, obj0type setType = AMF0_NUMBER); 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, std::string val, obj0type setType = AMF0_STRING);
Object(std::string indice, obj0type setType = AMF0_OBJECT); Object(std::string indice, obj0type setType = AMF0_OBJECT);
void Print(std::string indent = ""); std::string Print(std::string indent = "");
std::string Pack(); std::string Pack();
protected: protected:
std::string myIndice; ///< Holds this objects indice, if any. std::string myIndice; ///< Holds this objects indice, if any.

45
util/auth.cpp Normal file
View file

@ -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);
}

11
util/auth.h Normal file
View file

@ -0,0 +1,11 @@
#include <string>
#include <openssl/rsa.h>
#include <openssl/x509.h>
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.
};

64
util/base64.cpp Normal file
View file

@ -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;
}

11
util/base64.h Normal file
View file

@ -0,0 +1,11 @@
#include <string>
/// 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);
};

105
util/config.cpp Normal file
View file

@ -0,0 +1,105 @@
/// \file config.cpp
/// Contains generic functions for managing configuration.
#include "config.h"
#include <string.h>
#include <signal.h>
#ifdef __FreeBSD__
#include <sys/wait.h>
#else
#include <wait.h>
#endif
#include <errno.h>
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include <fstream>
/// Creates a new configuration manager.
Util::Config::Config(){
listen_port = 4242;
daemon_mode = true;
interface = "0.0.0.0";
username = "root";
}
/// 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'},
{"version",0,0,'v'}
};
while ((opt = getopt_long(argc, argv, optString, longOpts, 0)) != -1){
switch (opt){
case 'p': listen_port = atoi(optarg); break;
case 'i': interface = optarg; break;
case 'n': daemon_mode = false; break;
case 'd': daemon_mode = true; break;
case 'u': username = optarg; break;
case 'v':
printf("%s\n", TOSTRING(VERSION));
exit(1);
break;
case 'h':
case '?':
std::string doingdaemon = "true";
if (!daemon_mode){doingdaemon = "false";}
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;
}
}//commandline options parser
}
/// 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);
}

29
util/config.h Normal file
View file

@ -0,0 +1,29 @@
/// \file config.h
/// Contains generic function headers for managing configuration.
#include <string>
#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 commandline options.
class Config{
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.
void setUser(std::string user);
/// Will turn the current process into a daemon.
void Daemonize();
};

View file

@ -23,6 +23,12 @@ DTSC::Stream::Stream(unsigned int rbuffers){
buffercount = 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. /// Attempts to parse a packet from the given std::string buffer.
/// Returns true if successful, removing the parsed part from the buffer string. /// Returns true if successful, removing the parsed part from the buffer string.
/// Returns false if invalid or not enough data is in the buffer. /// Returns false if invalid or not enough data is in the buffer.
@ -60,8 +66,9 @@ bool DTSC::Stream::parsePacket(std::string & buffer){
return true; return true;
} }
#if DEBUG >= 2 #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 #endif
buffer.erase(0, 1);
} }
return false; return false;
} }
@ -191,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. /// If this object is not a container type, this function will always return 0.
int DTSC::DTMI::hasContent(){return contents.size();}; 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. /// 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. /// 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. /// If the indice name already exists, replaces the indice.
@ -206,9 +220,12 @@ void DTSC::DTMI::addContent(DTSC::DTMI c){
}; };
/// Returns a pointer to the object held at indice i. /// 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. /// \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 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. /// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.

View file

@ -62,6 +62,7 @@ namespace DTSC{
std::string & StrValue(); std::string & StrValue();
const char * Str(); const char * Str();
int hasContent(); int hasContent();
bool isEmpty();
void addContent(DTMI c); void addContent(DTMI c);
DTMI* getContentP(int i); DTMI* getContentP(int i);
DTMI getContent(int i); DTMI getContent(int i);
@ -106,9 +107,9 @@ namespace DTSC{
class Ring { class Ring {
public: public:
Ring(unsigned int v); Ring(unsigned int v);
unsigned int b; ///< Holds current number of buffer. May and is intended to change unexpectedly! volatile 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. volatile 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 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. /// Holds temporary data for a DTSC stream and provides functions to utilize it.
@ -129,6 +130,7 @@ namespace DTSC{
std::string & outPacket(unsigned int num); std::string & outPacket(unsigned int num);
std::string & outHeader(); std::string & outHeader();
Ring * getRing(); Ring * getRing();
unsigned int getTime();
void dropRing(Ring * ptr); void dropRing(Ring * ptr);
private: private:
std::deque<DTSC::DTMI> buffers; std::deque<DTSC::DTMI> buffers;

View file

@ -9,6 +9,7 @@
#include <fcntl.h> //for Tag::FileLoader #include <fcntl.h> //for Tag::FileLoader
#include <stdlib.h> //malloc #include <stdlib.h> //malloc
#include <string.h> //memcpy #include <string.h> //memcpy
#include <sstream>
/// Holds the last FLV header parsed. /// Holds the last FLV header parsed.
/// Defaults to a audio+video header on FLV version 0x01 if no header received yet. /// 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 /// audio, video or metadata, what encoding is used, and the details
/// of the encoding itself. /// of the encoding itself.
std::string FLV::Tag::tagType(){ std::string FLV::Tag::tagType(){
std::string R = ""; std::stringstream R;
R << len << " bytes of ";
switch (data[0]){ switch (data[0]){
case 0x09: case 0x09:
switch (data[11] & 0x0F){ switch (data[11] & 0x0F){
case 1: R += "JPEG"; break; case 1: R << "JPEG"; break;
case 2: R += "H263"; break; case 2: R << "H263"; break;
case 3: R += "ScreenVideo1"; break; case 3: R << "ScreenVideo1"; break;
case 4: R += "VP6"; break; case 4: R << "VP6"; break;
case 5: R += "VP6Alpha"; break; case 5: R << "VP6Alpha"; break;
case 6: R += "ScreenVideo2"; break; case 6: R << "ScreenVideo2"; break;
case 7: R += "H264"; break; case 7: R << "H264"; break;
default: R += "unknown"; break; default: R << "unknown"; break;
} }
R += " video "; R << " video ";
switch (data[11] & 0xF0){ switch (data[11] & 0xF0){
case 0x10: R += "keyframe"; break; case 0x10: R << "keyframe"; break;
case 0x20: R += "iframe"; break; case 0x20: R << "iframe"; break;
case 0x30: R += "disposableiframe"; break; case 0x30: R << "disposableiframe"; break;
case 0x40: R += "generatedkeyframe"; break; case 0x40: R << "generatedkeyframe"; break;
case 0x50: R += "videoinfo"; break; case 0x50: R << "videoinfo"; break;
} }
if ((data[11] & 0x0F) == 7){ if ((data[11] & 0x0F) == 7){
switch (data[12]){ switch (data[12]){
case 0: R += " header"; break; case 0: R << " header"; break;
case 1: R += " NALU"; break; case 1: R << " NALU"; break;
case 2: R += " endofsequence"; break; case 2: R << " endofsequence"; break;
} }
} }
break; break;
case 0x08: case 0x08:
switch (data[11] & 0xF0){ switch (data[11] & 0xF0){
case 0x00: R += "linear PCM PE"; break; case 0x00: R << "linear PCM PE"; break;
case 0x10: R += "ADPCM"; break; case 0x10: R << "ADPCM"; break;
case 0x20: R += "MP3"; break; case 0x20: R << "MP3"; break;
case 0x30: R += "linear PCM LE"; break; case 0x30: R << "linear PCM LE"; break;
case 0x40: R += "Nelly16kHz"; break; case 0x40: R << "Nelly16kHz"; break;
case 0x50: R += "Nelly8kHz"; break; case 0x50: R << "Nelly8kHz"; break;
case 0x60: R += "Nelly"; break; case 0x60: R << "Nelly"; break;
case 0x70: R += "G711A-law"; break; case 0x70: R << "G711A-law"; break;
case 0x80: R += "G711mu-law"; break; case 0x80: R << "G711mu-law"; break;
case 0x90: R += "reserved"; break; case 0x90: R << "reserved"; break;
case 0xA0: R += "AAC"; break; case 0xA0: R << "AAC"; break;
case 0xB0: R += "Speex"; break; case 0xB0: R << "Speex"; break;
case 0xE0: R += "MP38kHz"; break; case 0xE0: R << "MP38kHz"; break;
case 0xF0: R += "DeviceSpecific"; break; case 0xF0: R << "DeviceSpecific"; break;
default: R += "unknown"; break; default: R << "unknown"; break;
} }
switch (data[11] & 0x0C){ switch (data[11] & 0x0C){
case 0x0: R += " 5.5kHz"; break; case 0x0: R << " 5.5kHz"; break;
case 0x4: R += " 11kHz"; break; case 0x4: R << " 11kHz"; break;
case 0x8: R += " 22kHz"; break; case 0x8: R << " 22kHz"; break;
case 0xC: R += " 44kHz"; break; case 0xC: R << " 44kHz"; break;
} }
switch (data[11] & 0x02){ switch (data[11] & 0x02){
case 0: R += " 8bit"; break; case 0: R << " 8bit"; break;
case 2: R += " 16bit"; break; case 2: R << " 16bit"; break;
} }
switch (data[11] & 0x01){ switch (data[11] & 0x01){
case 0: R += " mono"; break; case 0: R << " mono"; break;
case 1: R += " stereo"; break; case 1: R << " stereo"; break;
} }
R += " audio"; R << " audio";
if ((data[12] == 0) && ((data[11] & 0xF0) == 0xA0)){ if ((data[12] == 0) && ((data[11] & 0xF0) == 0xA0)){
R += " initdata"; R << " initdata";
} }
break; break;
case 0x12: case 0x12:{
R += "(meta)data"; R << "(meta)data: ";
AMF::Object metadata = AMF::parse((unsigned char*)data+11, len-15);
R << metadata.Print();
break; break;
}
default: default:
R += "unknown"; R << "unknown";
break; break;
} }
return R; return R.str();
}//FLV::Tag::tagtype }//FLV::Tag::tagtype
/// Returns the 32-bit timestamp of this tag. /// 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("interframe")){data[11] += 0x20;}
if (S.getPacket().getContentP("disposableframe")){data[11] += 0x30;} if (S.getPacket().getContentP("disposableframe")){data[11] += 0x30;}
break; break;
case DTSC::AUDIO: case DTSC::AUDIO:{
if ((unsigned int)len == S.lastData().length() + 16){ if ((unsigned int)len == S.lastData().length() + 16){
memcpy(data+12, S.lastData().c_str(), S.lastData().length()); memcpy(data+12, S.lastData().c_str(), S.lastData().length());
}else{ }else{
@ -307,12 +312,18 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){
data[11] = 0; data[11] = 0;
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){data[11] += 0xA0;} 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("codec")->StrValue() == "MP3"){data[11] += 0x20;}
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 11025){data[11] += 0x04;} unsigned int datarate = S.metadata.getContentP("audio")->getContentP("rate")->NumValue();
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 22050){data[11] += 0x08;} if (datarate >= 44100){
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 44100){data[11] += 0x0C;} 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("size")->NumValue() == 16){data[11] += 0x02;}
if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;} if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;}
break; break;
}
case DTSC::META: case DTSC::META:
memcpy(data+11, S.lastData().c_str(), S.lastData().length()); memcpy(data+11, S.lastData().c_str(), S.lastData().length());
break; break;
@ -329,6 +340,9 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){
data[1] = ((len-15) >> 16) & 0xFF; data[1] = ((len-15) >> 16) & 0xFF;
data[2] = ((len-15) >> 8) & 0xFF; data[2] = ((len-15) >> 8) & 0xFF;
data[3] = (len-15) & 0xFF; data[3] = (len-15) & 0xFF;
data[8] = 0;
data[9] = 0;
data[10] = 0;
tagTime(S.getPacket().getContentP("time")->NumValue()); tagTime(S.getPacket().getContentP("time")->NumValue());
return true; 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. /// Helper function that properly sets the tag length from the internal len variable.
void FLV::Tag::setLen(){ void FLV::Tag::setLen(){
int len4 = len - 4; int len4 = len - 4;
int i = len-1; int i = len;
data[--i] = (len4) & 0xFF; data[--i] = (len4) & 0xFF;
len4 >>= 8; len4 >>= 8;
data[--i] = (len4) & 0xFF; data[--i] = (len4) & 0xFF;
@ -375,6 +389,9 @@ bool FLV::Tag::DTSCVideoInit(DTSC::Stream & S){
data[1] = ((len-15) >> 16) & 0xFF; data[1] = ((len-15) >> 16) & 0xFF;
data[2] = ((len-15) >> 8) & 0xFF; data[2] = ((len-15) >> 8) & 0xFF;
data[3] = (len-15) & 0xFF; data[3] = (len-15) & 0xFF;
data[8] = 0;
data[9] = 0;
data[10] = 0;
tagTime(0); tagTime(0);
return true; return true;
} }
@ -402,23 +419,25 @@ bool FLV::Tag::DTSCAudioInit(DTSC::Stream & S){
data[11] = 0; data[11] = 0;
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){data[11] += 0xA0;} 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("codec")->StrValue() == "MP3"){data[11] += 0x20;}
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 11000){data[11] += 0x04;} unsigned int datarate = S.metadata.getContentP("audio")->getContentP("rate")->NumValue();
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 22000){data[11] += 0x08;} if (datarate >= 44100){
if (S.metadata.getContentP("audio")->getContentP("rate")->NumValue() == 44000){data[11] += 0x0C;} 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("size")->NumValue() == 16){data[11] += 0x02;}
if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;} if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;}
} }
setLen(); 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[0] = 0x08;
data[1] = ((len-15) >> 16) & 0xFF; data[1] = ((len-15) >> 16) & 0xFF;
data[2] = ((len-15) >> 8) & 0xFF; data[2] = ((len-15) >> 8) & 0xFF;
data[3] = (len-15) & 0xFF; data[3] = (len-15) & 0xFF;
data[8] = 0;
data[9] = 0;
data[10] = 0;
tagTime(0); tagTime(0);
return true; return true;
} }
@ -501,6 +520,9 @@ bool FLV::Tag::DTSCMetaInit(DTSC::Stream & S){
data[1] = ((len-15) >> 16) & 0xFF; data[1] = ((len-15) >> 16) & 0xFF;
data[2] = ((len-15) >> 8) & 0xFF; data[2] = ((len-15) >> 8) & 0xFF;
data[3] = (len-15) & 0xFF; data[3] = (len-15) & 0xFF;
data[8] = 0;
data[9] = 0;
data[10] = 0;
tagTime(0); tagTime(0);
return true; return true;
} }
@ -749,3 +771,178 @@ bool FLV::Tag::FileLoader(FILE * f){
fcntl(fileno(f), F_SETFL, preflags); fcntl(fileno(f), F_SETFL, preflags);
return false; return false;
}//FLV_GetPacket }//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;
}

View file

@ -43,6 +43,7 @@ namespace FLV {
bool DTSCVideoInit(DTSC::Stream & S); bool DTSCVideoInit(DTSC::Stream & S);
bool DTSCAudioInit(DTSC::Stream & S); bool DTSCAudioInit(DTSC::Stream & S);
bool DTSCMetaInit(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 MemLoader(char * D, unsigned int S, unsigned int & P);
bool SockLoader(int sock); bool SockLoader(int sock);
bool SockLoader(Socket::Connection 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 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 SockReadUntil(char * buffer, unsigned int count, unsigned int & sofar, Socket::Connection & sock);
bool FileReadUntil(char * buffer, unsigned int count, unsigned int & sofar, FILE * f); 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 };//Tag
};//FLV namespace };//FLV namespace

436
util/json.cpp Normal file
View file

@ -0,0 +1,436 @@
/// \file json.cpp Holds all JSON-related code.
#include "json.h"
#include <sstream>
#include <fstream>
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<std::string, Value>::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{
if (myType == EMPTY){
return "";
}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);
}
/// 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);
}

74
util/json.h Normal file
View file

@ -0,0 +1,74 @@
/// \file json.h Holds all JSON-related headers.
#include <string>
#include <deque>
#include <map>
#include <istream>
/// 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<std::string, Value>::iterator ObjIter;
typedef std::deque<Value>::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<Value> arrVal;
std::map<std::string, Value> 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);
Value fromFile(std::string filename);
};

View file

@ -1,19 +0,0 @@
#ifndef JSON_AUTOLINK_H_INCLUDED
# define JSON_AUTOLINK_H_INCLUDED
# include "config.h"
# ifdef JSON_IN_CPPTL
# include <cpptl/cpptl_autolink.h>
# 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

View file

@ -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 <cpptl/config.h>
# 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,125 +0,0 @@
#ifndef JSONCPP_BATCHALLOCATOR_H_INCLUDED
# define JSONCPP_BATCHALLOCATOR_H_INCLUDED
# include <stdlib.h>
# include <assert.h>
# 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<typename AllocatedType
,const unsigned int objectPerAllocation>
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<BatchInfo*>( 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

View file

@ -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<Value **>( newIndexes );
}
virtual void releaseArrayPageIndex( Value **indexes,
ValueInternalArray::PageIndex indexCount )
{
if ( indexes )
free( indexes );
}
virtual Value *allocateArrayPage()
{
return static_cast<Value *>( 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<Value **>( newIndexes );
}
virtual void releaseArrayPageIndex( Value **indexes,
ValueInternalArray::PageIndex indexCount )
{
if ( indexes )
free( indexes );
}
virtual Value *allocateArrayPage()
{
return static_cast<Value *>( pagesAllocator_.allocate() );
}
virtual void releaseArrayPage( Value *value )
{
if ( value )
pagesAllocator_.release( value );
}
private:
BatchAllocator<ValueInternalArray,1> arraysAllocator_;
BatchAllocator<Value,ValueInternalArray::itemsPerPage> 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<ValueInternalArray *>( this );
it.currentItemIndex_ = 0;
it.currentPageIndex_ = pages_;
}
void
ValueInternalArray::makeIterator( IteratorState &it, ArrayIndex index ) const
{
it.array_ = const_cast<ValueInternalArray *>( 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;
}

View file

@ -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<ValueInternalMap,1> mapsAllocator_;
BatchAllocator<ValueInternalLink,1> 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 &current->items_[index];
}
}
return 0;
}
Value *
ValueInternalMap::find( const char *key )
{
const ValueInternalMap *constThis = this;
return const_cast<Value *>( 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 = &current->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<ValueInternalMap *>( this );
it.bucketIndex_ = 0;
it.itemIndex_ = 0;
it.link_ = buckets_;
}
void
ValueInternalMap::makeEndIterator( IteratorState &it ) const
{
it.map_ = const_cast<ValueInternalMap *>( 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;
}

View file

@ -1,885 +0,0 @@
#include "reader.h"
#include "value.h"
#include <utility>
#include <cstdio>
#include <cassert>
#include <cstring>
#include <iostream>
#include <stdexcept>
#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<char>(cp);
}
else if (cp <= 0x7FF)
{
result.resize(2);
result[1] = static_cast<char>(0x80 | (0x3f & cp));
result[0] = static_cast<char>(0xC0 | (0x1f & (cp >> 6)));
}
else if (cp <= 0xFFFF)
{
result.resize(3);
result[2] = static_cast<char>(0x80 | (0x3f & cp));
result[1] = 0x80 | static_cast<char>((0x3f & (cp >> 6)));
result[0] = 0xE0 | static_cast<char>((0xf & (cp >> 12)));
}
else if (cp <= 0x10FFFF)
{
result.resize(4);
result[3] = static_cast<char>(0x80 | (0x3f & cp));
result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12)));
result[0] = static_cast<char>(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<char> begin(sin);
//std::istream_iterator<char> 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_ = &currentValue();
}
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 &current,
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 &current,
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

File diff suppressed because it is too large Load diff

View file

@ -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 &current )
: 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 &current )
: 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 &current )
: 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;
}

View file

@ -1,829 +0,0 @@
#include "writer.h"
#include <utility>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <sstream>
#include <iomanip>
#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 *&current )
{
*--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 </
// sequence.
// Should add a flag to allow this compatibility mode and prevent this
// sequence from occurring.
default:
if ( isControlCharacter( *c ) )
{
std::ostringstream oss;
oss << "\\u" << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << static_cast<int>(*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

View file

@ -1,196 +0,0 @@
#ifndef CPPTL_JSON_READER_H_INCLUDED
# define CPPTL_JSON_READER_H_INCLUDED
# include "features.h"
# include "value.h"
# include <deque>
# include <stack>
# include <string>
# include <iostream>
namespace Json {
/** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> 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 <a HREF="http://www.json.org">JSON</a> 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 <a HREF="http://www.json.org">JSON</a> 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<ErrorInfo> 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 &current,
Location end,
unsigned int &unicode );
bool decodeUnicodeEscapeSequence( Token &token,
Location &current,
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 &currentValue();
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<Value *> 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

File diff suppressed because it is too large Load diff

View file

@ -1,174 +0,0 @@
#ifndef JSON_WRITER_H_INCLUDED
# define JSON_WRITER_H_INCLUDED
# include "value.h"
# include <vector>
# include <string>
# include <iostream>
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 <a HREF="http://www.json.org">JSON</a> 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 <a HREF="http://www.json.org">JSON</a> 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 <a HREF="http://www.json.org">JSON</a> 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<std::string> ChildValues;
ChildValues childValues_;
std::string document_;
std::string indentString_;
int rightMargin_;
int indentSize_;
bool addChildValues_;
};
/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> 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 <a HREF="http://www.json.org">JSON</a> 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<std::string> 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

View file

@ -1,7 +1,7 @@
/// \file util.cpp /// \file procs.cpp
/// Contains generic functions for managing processes and configuration. /// Contains generic functions for managing processes.
#include "util.h" #include "procs.h"
#include <string.h> #include <string.h>
#include <sys/types.h> #include <sys/types.h>
#include <signal.h> #include <signal.h>
@ -16,37 +16,12 @@
#include <sys/types.h> #include <sys/types.h>
#include <fcntl.h> #include <fcntl.h>
#include <pwd.h> #include <pwd.h>
#include <getopt.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <fstream>
std::map<pid_t, std::string> Util::Procs::plist; std::map<pid_t, std::string> Util::Procs::plist;
bool Util::Procs::handler_set = false; 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. /// Used internally to capture child signals and update plist.
void Util::Procs::childsig_handler(int signum){ void Util::Procs::childsig_handler(int signum){
if (signum != SIGCHLD){return;} if (signum != SIGCHLD){return;}
@ -263,108 +238,3 @@ std::string Util::Procs::getName(pid_t name){
} }
return ""; 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);
}

View file

@ -1,13 +1,10 @@
/// \file util.h /// \file procs.h
/// Contains generic function headers for managing processes and configuration. /// Contains generic function headers for managing processes.
#include <unistd.h> #include <unistd.h>
#include <string> #include <string>
#include <map> #include <map>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
/// Contains utility code, not directly related to streaming media /// Contains utility code, not directly related to streaming media
namespace Util{ namespace Util{
@ -31,29 +28,4 @@ namespace Util{
static std::string getName(pid_t name); 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();
}; };

View file

@ -15,14 +15,8 @@
#endif #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 "socket.h" //Socket library
#include "util.h" //utilities for process spawning and config management #include "config.h" //utilities for config management
#include <signal.h> #include <signal.h>
#include <sys/types.h> #include <sys/types.h>
#include <pwd.h> #include <pwd.h>
@ -83,10 +77,8 @@ int main(int argc, char ** argv){
//set and parse configuration //set and parse configuration
Util::Config C; Util::Config C;
C.confsection = TOSTRING(CONFIGSECT);
C.listen_port = DEFAULT_PORT; C.listen_port = DEFAULT_PORT;
C.parseArgs(argc, argv); C.parseArgs(argc, argv);
C.parseFile();
//setup a new server socket, for the correct interface and port //setup a new server socket, for the correct interface and port
server_socket = Socket::Server(C.listen_port, C.interface); server_socket = Socket::Server(C.listen_port, C.interface);

View file

@ -3,6 +3,7 @@
/// Written by Jaron Vietor in 2010 for DDVTech /// Written by Jaron Vietor in 2010 for DDVTech
#include "socket.h" #include "socket.h"
#include <sys/stat.h>
#include <poll.h> #include <poll.h>
#include <netdb.h> #include <netdb.h>
#include <sstream> #include <sstream>
@ -222,9 +223,10 @@ std::string Socket::Connection::getStats(std::string C){
} }
/// Updates the downbuffer and upbuffer internal variables. /// Updates the downbuffer and upbuffer internal variables.
void Socket::Connection::spool(){ /// Returns true if new data was received, false otherwise.
iread(downbuffer); bool Socket::Connection::spool(){
iwrite(upbuffer); iwrite(upbuffer);
return iread(downbuffer);
} }
/// Returns a reference to the download buffer. /// Returns a reference to the download buffer.
@ -452,7 +454,7 @@ Socket::Server::Server(int port, std::string hostname, bool nonblock){
sock = socket(AF_INET6, SOCK_STREAM, 0); sock = socket(AF_INET6, SOCK_STREAM, 0);
if (sock < 0){ if (sock < 0){
#if DEBUG >= 1 #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 #endif
return; return;
} }
@ -466,7 +468,7 @@ Socket::Server::Server(int port, std::string hostname, bool nonblock){
struct sockaddr_in6 addr; struct sockaddr_in6 addr;
addr.sin6_family = AF_INET6; addr.sin6_family = AF_INET6;
addr.sin6_port = htons(port);//set port 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; addr.sin6_addr = in6addr_any;
}else{ }else{
inet_pton(AF_INET6, hostname.c_str(), &addr.sin6_addr);//set interface, 0.0.0.0 (default) is all inet_pton(AF_INET6, hostname.c_str(), &addr.sin6_addr);//set interface, 0.0.0.0 (default) is all
@ -485,14 +487,14 @@ Socket::Server::Server(int port, std::string hostname, bool nonblock){
} }
}else{ }else{
#if DEBUG >= 1 #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 #endif
close(); close();
} }
sock = socket(AF_INET, SOCK_STREAM, 0); sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0){ if (sock < 0){
#if DEBUG >= 1 #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 #endif
return; return;
} }
@ -506,7 +508,7 @@ Socket::Server::Server(int port, std::string hostname, bool nonblock){
struct sockaddr_in addr4; struct sockaddr_in addr4;
addr4.sin_family = AF_INET; addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);//set port 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; addr4.sin_addr.s_addr = INADDR_ANY;
}else{ }else{
inet_pton(AF_INET, hostname.c_str(), &addr4.sin_addr);//set interface, 0.0.0.0 (default) is all inet_pton(AF_INET, hostname.c_str(), &addr4.sin_addr);//set interface, 0.0.0.0 (default) is all
@ -525,7 +527,7 @@ Socket::Server::Server(int port, std::string hostname, bool nonblock){
} }
}else{ }else{
#if DEBUG >= 1 #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 #endif
close(); close();
return; return;
@ -646,3 +648,43 @@ bool Socket::Server::connected(){
/// Returns internal socket number. /// Returns internal socket number.
int Socket::Server::getSocket(){return sock;} 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);
}

View file

@ -48,7 +48,7 @@ namespace Socket{
bool swrite(std::string & buffer); ///< Write call that is compatible with std::string. 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 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. 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. std::string & Received(); ///< Returns a reference to the download buffer.
void Send(std::string data); ///< Appends data to the upbuffer. void Send(std::string data); ///< Appends data to the upbuffer.
void close(); ///< Close connection. void close(); ///< Close connection.
@ -77,4 +77,10 @@ namespace Socket{
int getSocket(); ///< Returns internal socket number. 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);
}; };

287
util/tinythread.cpp Normal file
View file

@ -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 <exception>
#include "tinythread.h"
#if defined(_TTHREAD_POSIX_)
#include <unistd.h>
#include <map>
#elif defined(_TTHREAD_WIN32_)
#include <process.h>
#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<pthread_t, unsigned long int> idMap;
static unsigned long int idCount(1);
lock_guard<mutex> 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<mutex> 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<mutex> 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
}
}

696
util/tinythread.h Normal file
View file

@ -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 <windows.h>
#else
#include <pthread.h>
#include <signal.h>
#include <sched.h>
#include <unistd.h>
#endif
// Generic includes
#include <ostream>
/// 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<mutex> guard(m);
/// ++ counter;
/// }
/// @endcode
template <class T>
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<mutex> guard(m);
/// while(count < targetCount)
/// cond.wait(m);
/// }
///
/// // Increment the counter, and notify waiting threads
/// void increment()
/// {
/// lock_guard<mutex> 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 <class _mutexT>
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:
/// <tt>void fun(void * arg)</tt>
/// @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 <ratio> - 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 _Rep, class _Period = ratio<1> > class duration {
private:
_Rep rep_;
public:
typedef _Rep rep;
typedef _Period period;
/// Construct a duration object with the given duration.
template <class _Rep2>
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 <class _Rep, class _Period> 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_