Convert to autotools build system for cleanness (part1).

This commit is contained in:
Thulinma 2012-05-08 19:19:42 +02:00
parent c123111e70
commit ad410a2e79
75 changed files with 63 additions and 3314 deletions

15
lib/Makefile.am Normal file
View file

@ -0,0 +1,15 @@
noinst_LIBRARIES=libamf.a libauth.a libbase64.a libconfig.a libcrypto.a libdtsc.a libflv_tag.a libhttp_parser.a libjson.a libmd5.a libprocs.a librtmpchunks.a libsocket.a libtinythread.a
libamf_a_SOURCES=amf.h amf.cpp
libauth_a_SOURCES=auth.h auth.cpp
libbase64_a_SOURCES=base64.h base64.cpp
libconfig_a_SOURCES=config.h config.cpp
libcrypto_a_SOURCES=crypto.h crypto.cpp
libdtsc_a_SOURCES=dtsc.h dtsc.cpp
libflv_tag_a_SOURCES=flv_tag.h flv_tag.cpp
libhttp_parser_a_SOURCES=http_parser.h http_parser.cpp
libjson_a_SOURCES=json.h json.cpp
libmd5_a_SOURCES=md5.h md5.cpp
libprocs_a_SOURCES=procs.h procs.cpp
librtmpchunks_a_SOURCES=rtmpchunks.h rtmpchunks.cpp
libsocket_a_SOURCES=socket.h socket.cpp
libtinythread_a_SOURCES=tinythread.h tinythread.cpp

969
lib/amf.cpp Normal file
View file

@ -0,0 +1,969 @@
/// \file amf.cpp
/// Holds all code for the AMF namespace.
#include "amf.h"
#include <sstream>
#include <cstdio> //needed for stderr only
/// Returns the std::string Indice for the current object, if available.
/// Returns an empty string if no indice exists.
std::string AMF::Object::Indice(){return myIndice;};
/// Returns the AMF::obj0type AMF0 object type for this object.
AMF::obj0type AMF::Object::GetType(){return myType;};
/// Returns the numeric value of this object, if available.
/// If this object holds no numeric value, 0 is returned.
double AMF::Object::NumValue(){return numval;};
/// Returns the std::string value of this object, if available.
/// If this object holds no string value, an empty string is returned.
std::string AMF::Object::StrValue(){return strval;};
/// Returns the C-string value of this object, if available.
/// If this object holds no string value, an empty C-string is returned.
const char * AMF::Object::Str(){return strval.c_str();};
/// Returns a count of the amount of objects this object currently holds.
/// If this object is not a container type, this function will always return 0.
int AMF::Object::hasContent(){return contents.size();};
/// Adds an AMF::Object to this object. Works for all types, but only makes sense for container types.
void AMF::Object::addContent(AMF::Object c){contents.push_back(c);};
/// Returns a pointer to the object held at indice i.
/// Returns AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
/// \param i The indice of the object in this container.
AMF::Object* AMF::Object::getContentP(int i){return &contents.at(i);};
/// Returns a copy of the object held at indice i.
/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
/// \param i The indice of the object in this container.
AMF::Object AMF::Object::getContent(int i){return contents.at(i);};
/// Returns a pointer to the object held at indice s.
/// Returns NULL if no object is held at this indice.
/// \param s The indice of the object in this container.
AMF::Object* AMF::Object::getContentP(std::string s){
for (std::vector<AMF::Object>::iterator it = contents.begin(); it != contents.end(); it++){
if (it->Indice() == s){return &(*it);}
}
return 0;
};
/// Returns a copy of the object held at indice s.
/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
/// \param s The indice of the object in this container.
AMF::Object AMF::Object::getContent(std::string s){
for (std::vector<AMF::Object>::iterator it = contents.begin(); it != contents.end(); it++){
if (it->Indice() == s){return *it;}
}
return AMF::Object("error", AMF0_DDV_CONTAINER);
};
/// Default constructor.
/// Simply fills the data with AMF::Object("error", AMF0_DDV_CONTAINER)
AMF::Object::Object(){
*this = AMF::Object("error", AMF0_DDV_CONTAINER);
};//default constructor
/// Constructor for numeric objects.
/// The object type is by default AMF::AMF0_NUMBER, but this can be forced to a different value.
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
/// \param val The numeric value of this object. Numeric AMF0 objects only support double-type values.
/// \param setType The object type to force this object to.
AMF::Object::Object(std::string indice, double val, AMF::obj0type setType){//num type initializer
myIndice = indice;
myType = setType;
strval = "";
numval = val;
};
/// Constructor for string objects.
/// The object type is by default AMF::AMF0_STRING, but this can be forced to a different value.
/// There is no need to manually change the type to AMF::AMF0_LONGSTRING, this will be done automatically.
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
/// \param val The string value of this object.
/// \param setType The object type to force this object to.
AMF::Object::Object(std::string indice, std::string val, AMF::obj0type setType){//str type initializer
myIndice = indice;
myType = setType;
strval = val;
numval = 0;
};
/// Constructor for container objects.
/// The object type is by default AMF::AMF0_OBJECT, but this can be forced to a different value.
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
/// \param setType The object type to force this object to.
AMF::Object::Object(std::string indice, AMF::obj0type setType){//object type initializer
myIndice = indice;
myType = setType;
strval = "";
numval = 0;
};
/// Prints the contents of this object to std::cerr.
/// If this object contains other objects, it will call itself recursively
/// and print all nested content in a nice human-readable format.
std::string AMF::Object::Print(std::string indent){
std::stringstream st;
st << indent;
// print my type
switch (myType){
case AMF::AMF0_NUMBER: st << "Number"; break;
case AMF::AMF0_BOOL: st << "Bool"; break;
case AMF::AMF0_STRING://short string
case AMF::AMF0_LONGSTRING: st << "String"; break;
case AMF::AMF0_OBJECT: st << "Object"; break;
case AMF::AMF0_MOVIECLIP: st << "MovieClip"; break;
case AMF::AMF0_NULL: st << "Null"; break;
case AMF::AMF0_UNDEFINED: st << "Undefined"; break;
case AMF::AMF0_REFERENCE: st << "Reference"; break;
case AMF::AMF0_ECMA_ARRAY: st << "ECMA Array"; break;
case AMF::AMF0_OBJ_END: st << "Object end"; break;
case AMF::AMF0_STRICT_ARRAY: st << "Strict Array"; break;
case AMF::AMF0_DATE: st << "Date"; break;
case AMF::AMF0_UNSUPPORTED: st << "Unsupported"; break;
case AMF::AMF0_RECORDSET: st << "Recordset"; break;
case AMF::AMF0_XMLDOC: st << "XML Document"; break;
case AMF::AMF0_TYPED_OBJ: st << "Typed Object"; break;
case AMF::AMF0_UPGRADE: st << "Upgrade to AMF3"; break;
case AMF::AMF0_DDV_CONTAINER: st << "DDVTech Container"; break;
}
// print my string indice, if available
st << " " << myIndice << " ";
// print my numeric or string contents
switch (myType){
case AMF::AMF0_NUMBER: case AMF::AMF0_BOOL: case AMF::AMF0_REFERENCE: case AMF::AMF0_DATE: st << numval; break;
case AMF::AMF0_STRING: case AMF::AMF0_LONGSTRING: case AMF::AMF0_XMLDOC: case AMF::AMF0_TYPED_OBJ: st << strval; break;
default: break;//we don't care about the rest, and don't want a compiler warning...
}
st << std::endl;
// if I hold other objects, print those too, recursively.
if (contents.size() > 0){
for (std::vector<AMF::Object>::iterator it = contents.begin(); it != contents.end(); it++){st << it->Print(indent+" ");}
}
return st.str();
};//print
/// Packs the AMF object to a std::string for transfer over the network.
/// If the object is a container type, this function will call itself recursively and contain all contents.
/// Tip: When sending multiple AMF objects in one go, put them in a single AMF::AMF0_DDV_CONTAINER for easy transfer.
std::string AMF::Object::Pack(){
std::string r = "";
//check for string/longstring conversion
if ((myType == AMF::AMF0_STRING) && (strval.size() > 0xFFFF)){myType = AMF::AMF0_LONGSTRING;}
//skip output of DDV container types, they do not exist. Only output their contents.
if (myType != AMF::AMF0_DDV_CONTAINER){r += myType;}
//output the properly formatted AMF0 data stream for this object's contents.
switch (myType){
case AMF::AMF0_NUMBER:
r += *(((char*)&numval)+7); r += *(((char*)&numval)+6);
r += *(((char*)&numval)+5); r += *(((char*)&numval)+4);
r += *(((char*)&numval)+3); r += *(((char*)&numval)+2);
r += *(((char*)&numval)+1); r += *(((char*)&numval));
break;
case AMF::AMF0_DATE:
r += *(((char*)&numval)+7); r += *(((char*)&numval)+6);
r += *(((char*)&numval)+5); r += *(((char*)&numval)+4);
r += *(((char*)&numval)+3); r += *(((char*)&numval)+2);
r += *(((char*)&numval)+1); r += *(((char*)&numval));
r += (char)0;//timezone always 0
r += (char)0;//timezone always 0
break;
case AMF::AMF0_BOOL:
r += (char)numval;
break;
case AMF::AMF0_STRING:
r += strval.size() / 256;
r += strval.size() % 256;
r += strval;
break;
case AMF::AMF0_LONGSTRING:
case AMF::AMF0_XMLDOC://is always a longstring
r += strval.size() / (256*256*256);
r += strval.size() / (256*256);
r += strval.size() / 256;
r += strval.size() % 256;
r += strval;
break;
case AMF::AMF0_TYPED_OBJ:
r += Indice().size() / 256;
r += Indice().size() % 256;
r += Indice();
//is an object, with the classname first
case AMF::AMF0_OBJECT:
if (contents.size() > 0){
for (std::vector<AMF::Object>::iterator it = contents.begin(); it != contents.end(); it++){
r += it->Indice().size() / 256;
r += it->Indice().size() % 256;
r += it->Indice();
r += it->Pack();
}
}
r += (char)0; r += (char)0; r += (char)9;
break;
case AMF::AMF0_MOVIECLIP:
case AMF::AMF0_OBJ_END:
case AMF::AMF0_UPGRADE:
case AMF::AMF0_NULL:
case AMF::AMF0_UNDEFINED:
case AMF::AMF0_RECORDSET:
case AMF::AMF0_UNSUPPORTED:
//no data to add
break;
case AMF::AMF0_REFERENCE:
r += (char)((int)numval / 256);
r += (char)((int)numval % 256);
break;
case AMF::AMF0_ECMA_ARRAY:{
int arrlen = 0;
if (contents.size() > 0){
arrlen = contents.size();
r += arrlen / (256*256*256); r += arrlen / (256*256); r += arrlen / 256; r += arrlen % 256;
for (std::vector<AMF::Object>::iterator it = contents.begin(); it != contents.end(); it++){
r += it->Indice().size() / 256;
r += it->Indice().size() % 256;
r += it->Indice();
r += it->Pack();
}
}else{
r += (char)0; r += (char)0; r += (char)0; r += (char)0;
}
r += (char)0; r += (char)0; r += (char)9;
} break;
case AMF::AMF0_STRICT_ARRAY:{
int arrlen = 0;
if (contents.size() > 0){
arrlen = contents.size();
r += arrlen / (256*256*256); r += arrlen / (256*256); r += arrlen / 256; r += arrlen % 256;
for (std::vector<AMF::Object>::iterator it = contents.begin(); it != contents.end(); it++){
r += it->Pack();
}
}else{
r += (char)0; r += (char)0; r += (char)0; r += (char)0;
}
} break;
case AMF::AMF0_DDV_CONTAINER://only send contents
if (contents.size() > 0){
for (std::vector<AMF::Object>::iterator it = contents.begin(); it != contents.end(); it++){
r += it->Pack();
}
}
break;
}
return r;
};//pack
/// Parses a single AMF0 type - used recursively by the AMF::parse() functions.
/// This function updates i every call with the new position in the data.
/// \param data The raw data to parse.
/// \param len The size of the raw data.
/// \param i Current parsing position in the raw data.
/// \param name Indice name for any new object created.
/// \returns A single AMF::Object, parsed from the raw data.
AMF::Object AMF::parseOne(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name){
std::string tmpstr;
unsigned int tmpi = 0;
unsigned char tmpdbl[8];
#if DEBUG >= 10
fprintf(stderr, "Note: AMF type %hhx found. %i bytes left\n", data[i], len-i);
#endif
switch (data[i]){
case AMF::AMF0_NUMBER:
tmpdbl[7] = data[i+1];
tmpdbl[6] = data[i+2];
tmpdbl[5] = data[i+3];
tmpdbl[4] = data[i+4];
tmpdbl[3] = data[i+5];
tmpdbl[2] = data[i+6];
tmpdbl[1] = data[i+7];
tmpdbl[0] = data[i+8];
i+=9;//skip 8(a double)+1 forwards
return AMF::Object(name, *(double*)tmpdbl, AMF::AMF0_NUMBER);
break;
case AMF::AMF0_DATE:
tmpdbl[7] = data[i+1];
tmpdbl[6] = data[i+2];
tmpdbl[5] = data[i+3];
tmpdbl[4] = data[i+4];
tmpdbl[3] = data[i+5];
tmpdbl[2] = data[i+6];
tmpdbl[1] = data[i+7];
tmpdbl[0] = data[i+8];
i+=11;//skip 8(a double)+1+timezone(2) forwards
return AMF::Object(name, *(double*)tmpdbl, AMF::AMF0_DATE);
break;
case AMF::AMF0_BOOL:
i+=2;//skip bool+1 forwards
if (data[i-1] == 0){
return AMF::Object(name, (double)0, AMF::AMF0_BOOL);
}else{
return AMF::Object(name, (double)1, AMF::AMF0_BOOL);
}
break;
case AMF::AMF0_REFERENCE:
tmpi = data[i+1]*256+data[i+2];//get the ref number value as a double
i+=3;//skip ref+1 forwards
return AMF::Object(name, (double)tmpi, AMF::AMF0_REFERENCE);
break;
case AMF::AMF0_XMLDOC:
tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to UTF-8-long length
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char *)data+i+5, (size_t)tmpi);//add the string data
i += tmpi + 5;//skip length+size+1 forwards
return AMF::Object(name, tmpstr, AMF::AMF0_XMLDOC);
break;
case AMF::AMF0_LONGSTRING:
tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to UTF-8-long length
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char *)data+i+5, (size_t)tmpi);//add the string data
i += tmpi + 5;//skip length+size+1 forwards
return AMF::Object(name, tmpstr, AMF::AMF0_LONGSTRING);
break;
case AMF::AMF0_STRING:
tmpi = data[i+1]*256+data[i+2];//set tmpi to UTF-8 length
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char *)data+i+3, (size_t)tmpi);//add the string data
i += tmpi + 3;//skip length+size+1 forwards
return AMF::Object(name, tmpstr, AMF::AMF0_STRING);
break;
case AMF::AMF0_NULL:
case AMF::AMF0_UNDEFINED:
case AMF::AMF0_UNSUPPORTED:
++i;
return AMF::Object(name, (double)0, (AMF::obj0type)data[i-1]);
break;
case AMF::AMF0_OBJECT:{
++i;
AMF::Object ret(name, AMF::AMF0_OBJECT);
while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x000009)
tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char*)data+i+2, (size_t)tmpi);//add the string data
i += tmpi + 2;//skip length+size forwards
ret.addContent(AMF::parseOne(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr
}
i += 3;//skip 0x000009
return ret;
} break;
case AMF::AMF0_TYPED_OBJ:{
++i;
tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char*)data+i+2, (size_t)tmpi);//add the string data
AMF::Object ret(tmpstr, AMF::AMF0_TYPED_OBJ);//the object is not named "name" but tmpstr
while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x000009)
tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char*)data+i+2, (size_t)tmpi);//add the string data
i += tmpi + 2;//skip length+size forwards
ret.addContent(AMF::parseOne(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr
}
i += 3;//skip 0x000009
return ret;
} break;
case AMF::AMF0_ECMA_ARRAY:{
++i;
AMF::Object ret(name, AMF::AMF0_ECMA_ARRAY);
i += 4;//ignore the array length, we re-calculate it
while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x000009)
tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char*)data+i+2, (size_t)tmpi);//add the string data
i += tmpi + 2;//skip length+size forwards
ret.addContent(AMF::parseOne(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr
}
i += 3;//skip 0x000009
return ret;
} break;
case AMF::AMF0_STRICT_ARRAY:{
AMF::Object ret(name, AMF::AMF0_STRICT_ARRAY);
tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to array length
i += 5;//skip size+1 forwards
while (tmpi > 0){//while not done parsing array
ret.addContent(AMF::parseOne(data, len, i, "arrVal"));//add content, recursively parsed, updating i
--tmpi;
}
return ret;
} break;
}
#if DEBUG >= 2
fprintf(stderr, "Error: Unimplemented AMF type %hhx - returning.\n", data[i]);
#endif
return AMF::Object("error", AMF::AMF0_DDV_CONTAINER);
}//parseOne
/// Parses a C-string to a valid AMF::Object.
/// This function will find all AMF objects in the string and return
/// them all packed in a single AMF::AMF0_DDV_CONTAINER AMF::Object.
AMF::Object AMF::parse(const unsigned char * data, unsigned int len){
AMF::Object ret("returned", AMF::AMF0_DDV_CONTAINER);//container type
unsigned int i = 0, j = 0;
while (i < len){
ret.addContent(AMF::parseOne(data, len, i, ""));
if (i > j){j = i;}else{return ret;}
}
return ret;
}//parse
/// Parses a std::string to a valid AMF::Object.
/// This function will find all AMF objects in the string and return
/// them all packed in a single AMF::AMF0_DDV_CONTAINER AMF::Object.
AMF::Object AMF::parse(std::string data){
return AMF::parse((const unsigned char*)data.c_str(), data.size());
}//parse
/// Returns the std::string Indice for the current object, if available.
/// Returns an empty string if no indice exists.
std::string AMF::Object3::Indice(){return myIndice;};
/// Returns the AMF::obj0type AMF0 object type for this object.
AMF::obj3type AMF::Object3::GetType(){return myType;};
/// Returns the double value of this object, if available.
/// If this object holds no double value, 0 is returned.
double AMF::Object3::DblValue(){return dblval;};
/// Returns the integer value of this object, if available.
/// If this object holds no integer value, 0 is returned.
int AMF::Object3::IntValue(){return intval;};
/// Returns the std::string value of this object, if available.
/// If this object holds no string value, an empty string is returned.
std::string AMF::Object3::StrValue(){return strval;};
/// Returns the C-string value of this object, if available.
/// If this object holds no string value, an empty C-string is returned.
const char * AMF::Object3::Str(){return strval.c_str();};
/// Returns a count of the amount of objects this object currently holds.
/// If this object is not a container type, this function will always return 0.
int AMF::Object3::hasContent(){return contents.size();};
/// Adds an AMF::Object to this object. Works for all types, but only makes sense for container types.
void AMF::Object3::addContent(AMF::Object3 c){contents.push_back(c);};
/// Returns a pointer to the object held at indice i.
/// Returns AMF::AMF3_DDV_CONTAINER of indice "error" if no object is held at this indice.
/// \param i The indice of the object in this container.
AMF::Object3* AMF::Object3::getContentP(int i){return &contents.at(i);};
/// Returns a copy of the object held at indice i.
/// Returns a AMF::AMF3_DDV_CONTAINER of indice "error" if no object is held at this indice.
/// \param i The indice of the object in this container.
AMF::Object3 AMF::Object3::getContent(int i){return contents.at(i);};
/// Returns a pointer to the object held at indice s.
/// Returns NULL if no object is held at this indice.
/// \param s The indice of the object in this container.
AMF::Object3* AMF::Object3::getContentP(std::string s){
for (std::vector<AMF::Object3>::iterator it = contents.begin(); it != contents.end(); it++){
if (it->Indice() == s){return &(*it);}
}
return 0;
};
/// Returns a copy of the object held at indice s.
/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
/// \param s The indice of the object in this container.
AMF::Object3 AMF::Object3::getContent(std::string s){
for (std::vector<AMF::Object3>::iterator it = contents.begin(); it != contents.end(); it++){
if (it->Indice() == s){return *it;}
}
return AMF::Object3("error", AMF3_DDV_CONTAINER);
};
/// Default constructor.
/// Simply fills the data with AMF::Object3("error", AMF3_DDV_CONTAINER)
AMF::Object3::Object3(){
*this = AMF::Object3("error", AMF3_DDV_CONTAINER);
};//default constructor
/// Constructor for double objects.
/// The object type is by default AMF::AMF3_DOUBLE, but this can be forced to a different value.
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
/// \param val The numeric value of this object. Double AMF3 objects only support double-type values.
/// \param setType The object type to force this object to.
AMF::Object3::Object3(std::string indice, double val, AMF::obj3type setType){//num type initializer
myIndice = indice;
myType = setType;
strval = "";
dblval = val;
intval = 0;
};
/// Constructor for integer objects.
/// The object type is by default AMF::AMF3_INTEGER, but this can be forced to a different value.
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
/// \param val The numeric value of this object. Integer AMF3 objects only support integer-type values.
/// \param setType The object type to force this object to.
AMF::Object3::Object3(std::string indice, int val, AMF::obj3type setType){//num type initializer
myIndice = indice;
myType = setType;
strval = "";
dblval = val;
intval = 0;
};
/// Constructor for string objects.
/// The object type is by default AMF::AMF0_STRING, but this can be forced to a different value.
/// There is no need to manually change the type to AMF::AMF0_LONGSTRING, this will be done automatically.
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
/// \param val The string value of this object.
/// \param setType The object type to force this object to.
AMF::Object3::Object3(std::string indice, std::string val, AMF::obj3type setType){//str type initializer
myIndice = indice;
myType = setType;
strval = val;
dblval = 0;
intval = 0;
};
/// Constructor for container objects.
/// The object type is by default AMF::AMF0_OBJECT, but this can be forced to a different value.
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
/// \param setType The object type to force this object to.
AMF::Object3::Object3(std::string indice, AMF::obj3type setType){//object type initializer
myIndice = indice;
myType = setType;
strval = "";
dblval = 0;
intval = 0;
};
/// Prints the contents of this object to std::cerr.
/// If this object contains other objects, it will call itself recursively
/// and print all nested content in a nice human-readable format.
void AMF::Object3::Print(std::string indent){
std::cerr << indent;
// print my type
switch (myType){
case AMF::AMF3_UNDEFINED: std::cerr << "Undefined"; break;
case AMF::AMF3_NULL: std::cerr << "Null"; break;
case AMF::AMF3_FALSE: std::cerr << "False"; break;
case AMF::AMF3_TRUE: std::cerr << "True"; break;
case AMF::AMF3_INTEGER: std::cerr << "Integer"; break;
case AMF::AMF3_DOUBLE: std::cerr << "Double"; break;
case AMF::AMF3_STRING: std::cerr << "String"; break;
case AMF::AMF3_XMLDOC: std::cerr << "XML Doc"; break;
case AMF::AMF3_DATE: std::cerr << "Date"; break;
case AMF::AMF3_ARRAY: std::cerr << "Array"; break;
case AMF::AMF3_OBJECT: std::cerr << "Object"; break;
case AMF::AMF3_XML: std::cerr << "XML"; break;
case AMF::AMF3_BYTES: std::cerr << "ByteArray"; break;
case AMF::AMF3_DDV_CONTAINER: std::cerr << "DDVTech Container"; break;
}
// print my string indice, if available
std::cerr << " " << myIndice << " ";
// print my numeric or string contents
switch (myType){
case AMF::AMF3_INTEGER: std::cerr << intval; break;
case AMF::AMF3_DOUBLE: std::cerr << dblval; break;
case AMF::AMF3_STRING: case AMF::AMF3_XMLDOC: case AMF::AMF3_XML: case AMF::AMF3_BYTES:
if (intval > 0){
std::cerr << "REF" << intval;
}else{
std::cerr << strval;
}
break;
case AMF::AMF3_DATE:
if (intval > 0){
std::cerr << "REF" << intval;
}else{
std::cerr << dblval;
}
break;
case AMF::AMF3_ARRAY: case AMF::AMF3_OBJECT:
if (intval > 0){
std::cerr << "REF" << intval;
}
break;
default: break;//we don't care about the rest, and don't want a compiler warning...
}
std::cerr << std::endl;
// if I hold other objects, print those too, recursively.
if (contents.size() > 0){
for (std::vector<AMF::Object3>::iterator it = contents.begin(); it != contents.end(); it++){it->Print(indent+" ");}
}
};//print
/// Packs the AMF object to a std::string for transfer over the network.
/// If the object is a container type, this function will call itself recursively and contain all contents.
/// Tip: When sending multiple AMF objects in one go, put them in a single AMF::AMF0_DDV_CONTAINER for easy transfer.
std::string AMF::Object3::Pack(){
std::string r = "";
return r;
};//pack
/// Parses a single AMF3 type - used recursively by the AMF::parse3() functions.
/// This function updates i every call with the new position in the data.
/// \param data The raw data to parse.
/// \param len The size of the raw data.
/// \param i Current parsing position in the raw data.
/// \param name Indice name for any new object created.
/// \returns A single AMF::Object3, parsed from the raw data.
AMF::Object3 AMF::parseOne3(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name){
std::string tmpstr;
unsigned int tmpi = 0;
unsigned int arrsize = 0;
unsigned char tmpdbl[8];
#if DEBUG >= 10
fprintf(stderr, "Note: AMF3 type %hhx found. %i bytes left\n", data[i], len-i);
#endif
switch (data[i]){
case AMF::AMF3_UNDEFINED:
case AMF::AMF3_NULL:
case AMF::AMF3_FALSE:
case AMF::AMF3_TRUE:
++i;
return AMF::Object3(name, (AMF::obj3type)data[i-1]);
break;
case AMF::AMF3_INTEGER:
if (data[i+1] < 0x80){
tmpi = data[i+1];
i+=2;
}else{
tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up.
if (data[i+2] < 0x80){
tmpi |= data[i+2];
i+=3;
}else{
tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up.
if (data[i+3] < 0x80){
tmpi |= data[i+3];
i+=4;
}else{
tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up.
tmpi |= data[i+4];
i+=5;
}
}
}
tmpi = (tmpi << 3) >> 3;//fix sign bit
return AMF::Object3(name, (int)tmpi, AMF::AMF3_INTEGER);
break;
case AMF::AMF3_DOUBLE:
tmpdbl[7] = data[i+1];
tmpdbl[6] = data[i+2];
tmpdbl[5] = data[i+3];
tmpdbl[4] = data[i+4];
tmpdbl[3] = data[i+5];
tmpdbl[2] = data[i+6];
tmpdbl[1] = data[i+7];
tmpdbl[0] = data[i+8];
i+=9;//skip 8(a double)+1 forwards
return AMF::Object3(name, *(double*)tmpdbl, AMF::AMF3_DOUBLE);
break;
case AMF::AMF3_STRING:
if (data[i+1] < 0x80){
tmpi = data[i+1];
i+=2;
}else{
tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up.
if (data[i+2] < 0x80){
tmpi |= data[i+2];
i+=3;
}else{
tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up.
if (data[i+3] < 0x80){
tmpi |= data[i+3];
i+=4;
}else{
tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up.
tmpi |= data[i+4];
i+=5;
}
}
}
tmpi = (tmpi << 3) >> 3;//fix sign bit
if ((tmpi & 1) == 0){
return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_STRING);//reference type
}
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char *)data+i, (size_t)(tmpi >> 1));//add the string data
i += (tmpi >> 1);//skip length+size+1 forwards
return AMF::Object3(name, tmpstr, AMF::AMF3_STRING);//normal type
break;
case AMF::AMF3_XMLDOC:
if (data[i+1] < 0x80){
tmpi = data[i+1];
i+=2;
}else{
tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up.
if (data[i+2] < 0x80){
tmpi |= data[i+2];
i+=3;
}else{
tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up.
if (data[i+3] < 0x80){
tmpi |= data[i+3];
i+=4;
}else{
tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up.
tmpi |= data[i+4];
i+=5;
}
}
}
tmpi = (tmpi << 3) >> 3;//fix sign bit
if ((tmpi & 1) == 0){
return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_XMLDOC);//reference type
}
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char *)data+i, (size_t)(tmpi >> 1));//add the string data
i += (tmpi >> 1);//skip length+size+1 forwards
return AMF::Object3(name, tmpstr, AMF::AMF3_XMLDOC);//normal type
break;
case AMF::AMF3_XML:
if (data[i+1] < 0x80){
tmpi = data[i+1];
i+=2;
}else{
tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up.
if (data[i+2] < 0x80){
tmpi |= data[i+2];
i+=3;
}else{
tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up.
if (data[i+3] < 0x80){
tmpi |= data[i+3];
i+=4;
}else{
tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up.
tmpi |= data[i+4];
i+=5;
}
}
}
tmpi = (tmpi << 3) >> 3;//fix sign bit
if ((tmpi & 1) == 0){
return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_XML);//reference type
}
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char *)data+i, (size_t)(tmpi >> 1));//add the string data
i += (tmpi >> 1);//skip length+size+1 forwards
return AMF::Object3(name, tmpstr, AMF::AMF3_XML);//normal type
break;
case AMF::AMF3_BYTES:
if (data[i+1] < 0x80){
tmpi = data[i+1];
i+=2;
}else{
tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up.
if (data[i+2] < 0x80){
tmpi |= data[i+2];
i+=3;
}else{
tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up.
if (data[i+3] < 0x80){
tmpi |= data[i+3];
i+=4;
}else{
tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up.
tmpi |= data[i+4];
tmpi = (tmpi << 3) >> 3;//fix sign bit
i+=5;
}
}
}
if ((tmpi & 1) == 0){
return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_BYTES);//reference type
}
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char *)data+i, (size_t)(tmpi >> 1));//add the string data
i += (tmpi >> 1);//skip length+size+1 forwards
return AMF::Object3(name, tmpstr, AMF::AMF3_BYTES);//normal type
break;
case AMF::AMF3_DATE:
if (data[i+1] < 0x80){
tmpi = data[i+1];
i+=2;
}else{
tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up.
if (data[i+2] < 0x80){
tmpi |= data[i+2];
i+=3;
}else{
tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up.
if (data[i+3] < 0x80){
tmpi |= data[i+3];
i+=4;
}else{
tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up.
tmpi |= data[i+4];
i+=5;
}
}
}
tmpi = (tmpi << 3) >> 3;//fix sign bit
if ((tmpi & 1) == 0){
return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_DATE);//reference type
}
tmpdbl[7] = data[i];
tmpdbl[6] = data[i+1];
tmpdbl[5] = data[i+2];
tmpdbl[4] = data[i+3];
tmpdbl[3] = data[i+4];
tmpdbl[2] = data[i+5];
tmpdbl[1] = data[i+6];
tmpdbl[0] = data[i+7];
i += 8;//skip a double forwards
return AMF::Object3(name, *(double*)tmpdbl, AMF::AMF3_DATE);
break;
case AMF::AMF3_ARRAY:{
if (data[i+1] < 0x80){
tmpi = data[i+1];
i+=2;
}else{
tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up.
if (data[i+2] < 0x80){
tmpi |= data[i+2];
i+=3;
}else{
tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up.
if (data[i+3] < 0x80){
tmpi |= data[i+3];
i+=4;
}else{
tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up.
tmpi |= data[i+4];
i+=5;
}
}
}
tmpi = (tmpi << 3) >> 3;//fix sign bit
if ((tmpi & 1) == 0){
return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_ARRAY);//reference type
}
AMF::Object3 ret(name, AMF::AMF3_ARRAY);
arrsize = tmpi >> 1;
do{
if (data[i+1] < 0x80){
tmpi = data[i+1];
i+=2;
}else{
tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up.
if (data[i+2] < 0x80){
tmpi |= data[i+2];
i+=3;
}else{
tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up.
if (data[i+3] < 0x80){
tmpi |= data[i+3];
i+=4;
}else{
tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up.
tmpi |= data[i+4];
i+=5;
}
}
}
tmpi = (tmpi << 3) >> 4;//fix sign bit, ignore references for now...
/// \todo Fix references?
if (tmpi > 0){
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char*)data+i, (size_t)tmpi);//add the string data
ret.addContent(AMF::parseOne3(data, len, i, tmpstr));//add content, recursively parsed, updating i
}
}while(tmpi > 0);
while (arrsize > 0){//while not done parsing array
ret.addContent(AMF::parseOne3(data, len, i, "arrVal"));//add content, recursively parsed, updating i
--arrsize;
}
return ret;
} break;
case AMF::AMF3_OBJECT:{
if (data[i+1] < 0x80){
tmpi = data[i+1];
i+=2;
}else{
tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up.
if (data[i+2] < 0x80){
tmpi |= data[i+2];
i+=3;
}else{
tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up.
if (data[i+3] < 0x80){
tmpi |= data[i+3];
i+=4;
}else{
tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up.
tmpi |= data[i+4];
i+=5;
}
}
}
tmpi = (tmpi << 3) >> 3;//fix sign bit
if ((tmpi & 1) == 0){
return AMF::Object3(name, (int)((tmpi >> 1) + 1), AMF::AMF3_OBJECT);//reference type
}
AMF::Object3 ret(name, AMF::AMF3_OBJECT);
bool isdynamic = false;
if ((tmpi & 2) == 0){//traits by reference, skip for now
/// \todo Implement traits by reference. Or references in general, of course...
}else{
isdynamic = ((tmpi & 8) == 8);
arrsize = tmpi >> 4;//count of sealed members
/// \todo Read in arrsize sealed member names, then arrsize sealed members.
}
if (isdynamic){
do{
if (data[i+1] < 0x80){
tmpi = data[i+1];
i+=2;
}else{
tmpi = (data[i+1] & 0x7F) << 7;//strip the upper bit, shift 7 up.
if (data[i+2] < 0x80){
tmpi |= data[i+2];
i+=3;
}else{
tmpi = (tmpi | (data[i+2] & 0x7F)) << 7;//strip the upper bit, shift 7 up.
if (data[i+3] < 0x80){
tmpi |= data[i+3];
i+=4;
}else{
tmpi = (tmpi | (data[i+3] & 0x7F)) << 8;//strip the upper bit, shift 7 up.
tmpi |= data[i+4];
i+=5;
}
}
}
tmpi = (tmpi << 3) >> 4;//fix sign bit, ignore references for now...
/// \todo Fix references?
if (tmpi > 0){
tmpstr.clear();//clean tmpstr, just to be sure
tmpstr.append((const char*)data+i, (size_t)tmpi);//add the string data
ret.addContent(AMF::parseOne3(data, len, i, tmpstr));//add content, recursively parsed, updating i
}
}while(tmpi > 0);//keep reading dynamic values until empty string
}//dynamic types
return ret;
} break;
}
#if DEBUG >= 2
fprintf(stderr, "Error: Unimplemented AMF3 type %hhx - returning.\n", data[i]);
#endif
return AMF::Object3("error", AMF::AMF3_DDV_CONTAINER);
}//parseOne
/// Parses a C-string to a valid AMF::Object3.
/// This function will find all AMF3 objects in the string and return
/// them all packed in a single AMF::AMF3_DDV_CONTAINER AMF::Object3.
AMF::Object3 AMF::parse3(const unsigned char * data, unsigned int len){
AMF::Object3 ret("returned", AMF::AMF3_DDV_CONTAINER);//container type
unsigned int i = 0, j = 0;
while (i < len){
ret.addContent(AMF::parseOne3(data, len, i, ""));
if (i > j){j = i;}else{return ret;}
}
return ret;
}//parse
/// Parses a std::string to a valid AMF::Object3.
/// This function will find all AMF3 objects in the string and return
/// them all packed in a single AMF::AMF3_DDV_CONTAINER AMF::Object3.
AMF::Object3 AMF::parse3(std::string data){
return AMF::parse3((const unsigned char*)data.c_str(), data.size());
}//parse

129
lib/amf.h Normal file
View file

@ -0,0 +1,129 @@
/// \file amf.h
/// Holds all headers for the AMF namespace.
#pragma once
#include <vector>
#include <iostream>
//#include <string.h>
#include <string>
/// Holds all AMF parsing and creation related functions and classes.
namespace AMF{
/// Enumerates all possible AMF0 types, adding a special DDVTECH container type for ease of use.
enum obj0type {
AMF0_NUMBER = 0x00,
AMF0_BOOL = 0x01,
AMF0_STRING = 0x02,
AMF0_OBJECT = 0x03,
AMF0_MOVIECLIP = 0x04,
AMF0_NULL = 0x05,
AMF0_UNDEFINED = 0x06,
AMF0_REFERENCE = 0x07,
AMF0_ECMA_ARRAY = 0x08,
AMF0_OBJ_END = 0x09,
AMF0_STRICT_ARRAY = 0x0A,
AMF0_DATE = 0x0B,
AMF0_LONGSTRING = 0x0C,
AMF0_UNSUPPORTED = 0x0D,
AMF0_RECORDSET = 0x0E,
AMF0_XMLDOC = 0x0F,
AMF0_TYPED_OBJ = 0x10,
AMF0_UPGRADE = 0x11,
AMF0_DDV_CONTAINER = 0xFF
};
/// Enumerates all possible AMF3 types, adding a special DDVTECH container type for ease of use.
enum obj3type {
AMF3_UNDEFINED = 0x00,
AMF3_NULL = 0x01,
AMF3_FALSE = 0x02,
AMF3_TRUE = 0x03,
AMF3_INTEGER = 0x04,
AMF3_DOUBLE = 0x05,
AMF3_STRING = 0x06,
AMF3_XMLDOC = 0x07,
AMF3_DATE = 0x08,
AMF3_ARRAY = 0x09,
AMF3_OBJECT = 0x0A,
AMF3_XML = 0x0B,
AMF3_BYTES = 0x0C,
AMF3_DDV_CONTAINER = 0xFF
};
/// Recursive class that holds AMF0 objects.
/// It supports all AMF0 types (defined in AMF::obj0type), adding support for a special DDVTECH container type.
class Object {
public:
std::string Indice();
obj0type GetType();
double NumValue();
std::string StrValue();
const char * Str();
int hasContent();
void addContent(AMF::Object c);
Object* getContentP(int i);
Object getContent(int i);
Object* getContentP(std::string s);
Object getContent(std::string s);
Object();
Object(std::string indice, double val, obj0type setType = AMF0_NUMBER);
Object(std::string indice, std::string val, obj0type setType = AMF0_STRING);
Object(std::string indice, obj0type setType = AMF0_OBJECT);
std::string Print(std::string indent = "");
std::string Pack();
protected:
std::string myIndice; ///< Holds this objects indice, if any.
obj0type myType; ///< Holds this objects AMF0 type.
std::string strval; ///< Holds this objects string value, if any.
double numval; ///< Holds this objects numeric value, if any.
std::vector<Object> contents; ///< Holds this objects contents, if any (for container types).
};//AMFType
/// Parses a C-string to a valid AMF::Object.
Object parse(const unsigned char * data, unsigned int len);
/// Parses a std::string to a valid AMF::Object.
Object parse(std::string data);
/// Parses a single AMF0 type - used recursively by the AMF::parse() functions.
Object parseOne(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name);
/// Recursive class that holds AMF3 objects.
/// It supports all AMF3 types (defined in AMF::obj3type), adding support for a special DDVTECH container type.
class Object3 {
public:
std::string Indice();
obj3type GetType();
double DblValue();
int IntValue();
std::string StrValue();
const char * Str();
int hasContent();
void addContent(AMF::Object3 c);
Object3* getContentP(int i);
Object3 getContent(int i);
Object3* getContentP(std::string s);
Object3 getContent(std::string s);
Object3();
Object3(std::string indice, int val, obj3type setType = AMF3_INTEGER);
Object3(std::string indice, double val, obj3type setType = AMF3_DOUBLE);
Object3(std::string indice, std::string val, obj3type setType = AMF3_STRING);
Object3(std::string indice, obj3type setType = AMF3_OBJECT);
void Print(std::string indent = "");
std::string Pack();
protected:
std::string myIndice; ///< Holds this objects indice, if any.
obj3type myType; ///< Holds this objects AMF0 type.
std::string strval; ///< Holds this objects string value, if any.
double dblval; ///< Holds this objects double value, if any.
int intval; ///< Holds this objects int value, if any.
std::vector<Object3> contents; ///< Holds this objects contents, if any (for container types).
};//AMFType
/// Parses a C-string to a valid AMF::Object3.
Object3 parse3(const unsigned char * data, unsigned int len);
/// Parses a std::string to a valid AMF::Object3.
Object3 parse3(std::string data);
/// Parses a single AMF3 type - used recursively by the AMF::parse3() functions.
Object3 parseOne3(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name);
};//AMF namespace

45
lib/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
lib/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
lib/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
lib/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
lib/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(PACKAGE_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(PACKAGE_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
lib/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();
};

509
lib/crypto.cpp Normal file
View file

@ -0,0 +1,509 @@
/// \file crypto.cpp
/// Holds all code needed for RTMP cryptography.
#define STR(x) (((std::string)(x)).c_str())
#include "crypto.h"
#define P768 \
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
"E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF"
#define P1024 \
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" \
"FFFFFFFFFFFFFFFF"
#define Q1024 \
"7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68" \
"948127044533E63A0105DF531D89CD9128A5043CC71A026E" \
"F7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122" \
"F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6" \
"F71C35FDAD44CFD2D74F9208BE258FF324943328F67329C0" \
"FFFFFFFFFFFFFFFF"
#define P1536 \
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
"670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"
#define P2048 \
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
"15728E5A8AACAA68FFFFFFFFFFFFFFFF"
#define P3072 \
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
"43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"
#define P4096 \
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \
"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \
"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \
"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \
"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \
"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" \
"FFFFFFFFFFFFFFFF"
#define P6144 \
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \
"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \
"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \
"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \
"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \
"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" \
"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" \
"F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" \
"179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" \
"DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" \
"5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" \
"D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" \
"23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" \
"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" \
"06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" \
"DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" \
"12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF"
#define P8192 \
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \
"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \
"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \
"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \
"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \
"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \
"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \
"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \
"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \
"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" \
"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" \
"F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" \
"179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" \
"DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" \
"5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" \
"D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" \
"23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" \
"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" \
"06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" \
"DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" \
"12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" \
"38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" \
"741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" \
"3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" \
"22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" \
"4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" \
"062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" \
"4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" \
"B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" \
"4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" \
"9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" \
"60C980DD98EDD3DFFFFFFFFFFFFFFFFF"
uint8_t genuineFMSKey[] = {
0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20,
0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,
0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69,
0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001
0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,
0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,
0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,
0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae
}; // 68
uint8_t genuineFPKey[] = {
0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,
0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,
0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,
0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8,
0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57,
0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
}; // 62
void replace(std::string &target, std::string search, std::string replacement) {
if (search == replacement)
return;
if (search == "")
return;
std::string::size_type i = std::string::npos;
while ((i = target.find(search)) != std::string::npos) {
target.replace(i, search.length(), replacement);
}
}
DHWrapper::DHWrapper(int32_t bitsCount) {
_bitsCount = bitsCount;
_pDH = NULL;
_pSharedKey = NULL;
_sharedKeyLength = 0;
_peerPublickey = NULL;
}
DHWrapper::~DHWrapper() {
Cleanup();
}
bool DHWrapper::Initialize() {
Cleanup();
//1. Create the DH
_pDH = DH_new();
if (_pDH == NULL) {
Cleanup();
return false;
}
//2. Create his internal p and g
_pDH->p = BN_new();
if (_pDH->p == NULL) {
Cleanup();
return false;
}
_pDH->g = BN_new();
if (_pDH->g == NULL) {
Cleanup();
return false;
}
//3. initialize p, g and key length
if (BN_hex2bn(&_pDH->p, P1024) == 0) {
Cleanup();
return false;
}
if (BN_set_word(_pDH->g, 2) != 1) {
Cleanup();
return false;
}
//4. Set the key length
_pDH->length = _bitsCount;
//5. Generate private and public key
if (DH_generate_key(_pDH) != 1) {
Cleanup();
return false;
}
return true;
}
bool DHWrapper::CopyPublicKey(uint8_t *pDst, int32_t dstLength) {
if (_pDH == NULL) {
return false;
}
return CopyKey(_pDH->pub_key, pDst, dstLength);
}
bool DHWrapper::CopyPrivateKey(uint8_t *pDst, int32_t dstLength) {
if (_pDH == NULL) {
return false;
}
return CopyKey(_pDH->priv_key, pDst, dstLength);
}
bool DHWrapper::CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length) {
if (_pDH == NULL) {
return false;
}
if (_sharedKeyLength != 0 || _pSharedKey != NULL) {
return false;
}
_sharedKeyLength = DH_size(_pDH);
if (_sharedKeyLength <= 0 || _sharedKeyLength > 1024) {
return false;
}
_pSharedKey = new uint8_t[_sharedKeyLength];
_peerPublickey = BN_bin2bn(pPeerPublicKey, length, 0);
if (_peerPublickey == NULL) {
return false;
}
if (DH_compute_key(_pSharedKey, _peerPublickey, _pDH) != _sharedKeyLength) {
return false;
}
return true;
}
bool DHWrapper::CopySharedKey(uint8_t *pDst, int32_t dstLength) {
if (_pDH == NULL) {
return false;
}
if (dstLength != _sharedKeyLength) {
return false;
}
memcpy(pDst, _pSharedKey, _sharedKeyLength);
return true;
}
void DHWrapper::Cleanup() {
if (_pDH != NULL) {
if (_pDH->p != NULL) {
BN_free(_pDH->p);
_pDH->p = NULL;
}
if (_pDH->g != NULL) {
BN_free(_pDH->g);
_pDH->g = NULL;
}
DH_free(_pDH);
_pDH = NULL;
}
if (_pSharedKey != NULL) {
delete[] _pSharedKey;
_pSharedKey = NULL;
}
_sharedKeyLength = 0;
if (_peerPublickey != NULL) {
BN_free(_peerPublickey);
_peerPublickey = NULL;
}
}
bool DHWrapper::CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength) {
int32_t keySize = BN_num_bytes(pNum);
if ((keySize <= 0) || (dstLength <= 0) || (keySize > dstLength)) {
return false;
}
if (BN_bn2bin(pNum, pDst) != keySize) {
return false;
}
return true;
}
void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut) {
uint8_t digest[SHA256_DIGEST_LENGTH];
unsigned int digestLen = 0;
HMAC_CTX ctx;
HMAC_CTX_init(&ctx);
HMAC_Init_ex(&ctx, secretKey, 128, EVP_sha256(), 0);
HMAC_Update(&ctx, pubKeyIn, 128);
HMAC_Final(&ctx, digest, &digestLen);
HMAC_CTX_cleanup(&ctx);
RC4_set_key(rc4keyOut, 16, digest);
HMAC_CTX_init(&ctx);
HMAC_Init_ex(&ctx, secretKey, 128, EVP_sha256(), 0);
HMAC_Update(&ctx, pubKeyOut, 128);
HMAC_Final(&ctx, digest, &digestLen);
HMAC_CTX_cleanup(&ctx);
RC4_set_key(rc4keyIn, 16, digest);
}
std::string md5(std::string source, bool textResult) {
EVP_MD_CTX mdctx;
unsigned char md_value[EVP_MAX_MD_SIZE];
unsigned int md_len;
EVP_DigestInit(&mdctx, EVP_md5());
EVP_DigestUpdate(&mdctx, STR(source), source.length());
EVP_DigestFinal_ex(&mdctx, md_value, &md_len);
EVP_MD_CTX_cleanup(&mdctx);
if (textResult) {
std::string result = "";
char tmp[3];
for (uint32_t i = 0; i < md_len; i++) {
sprintf(tmp, "%02x", md_value[i]);
result += tmp;
}
return result;
} else {
return std::string((char *) md_value, md_len);
}
}
std::string b64(std::string source) {
return b64((uint8_t *) STR(source), source.size());
}
std::string b64(uint8_t *pBuffer, uint32_t length) {
BIO *bmem;
BIO *b64;
BUF_MEM *bptr;
b64 = BIO_new(BIO_f_base64());
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_write(b64, pBuffer, length);
std::string result = "";
if (BIO_flush(b64) == 1) {
BIO_get_mem_ptr(b64, &bptr);
result = std::string(bptr->data, bptr->length);
}
BIO_free_all(b64);
replace(result, "\n", "");
replace(result, "\r", "");
return result;
}
std::string unb64(std::string source) {
return unb64((uint8_t *)STR(source),source.length());
}
std::string unb64(uint8_t *pBuffer, uint32_t length){
// create a memory buffer containing base64 encoded data
//BIO* bmem = BIO_new_mem_buf((void*) STR(source), source.length());
BIO* bmem = BIO_new_mem_buf((void *)pBuffer, length);
// push a Base64 filter so that reading from buffer decodes it
BIO *bioCmd = BIO_new(BIO_f_base64());
// we don't want newlines
BIO_set_flags(bioCmd, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_push(bioCmd, bmem);
char *pOut = new char[length];
int finalLen = BIO_read(bmem, (void*) pOut, length);
BIO_free_all(bmem);
std::string result(pOut, finalLen);
delete[] pOut;
return result;
}
void HMACsha256(const void *pData, uint32_t dataLength, const void *pKey, uint32_t keyLength, void *pResult) {
unsigned int digestLen;
HMAC_CTX ctx;
HMAC_CTX_init(&ctx);
HMAC_Init_ex(&ctx, (unsigned char*) pKey, keyLength, EVP_sha256(), NULL);
HMAC_Update(&ctx, (unsigned char *) pData, dataLength);
HMAC_Final(&ctx, (unsigned char *) pResult, &digestLen);
HMAC_CTX_cleanup(&ctx);
}
uint32_t GetDigestOffset0(uint8_t *pBuffer) {
uint32_t offset = pBuffer[8] + pBuffer[9] + pBuffer[10] + pBuffer[11];
return (offset % 728) + 12;
}
uint32_t GetDigestOffset1(uint8_t *pBuffer) {
uint32_t offset = pBuffer[772] + pBuffer[773] + pBuffer[774] + pBuffer[775];
return (offset % 728) + 776;
}
uint32_t GetDigestOffset(uint8_t *pBuffer, uint8_t scheme){
if (scheme == 0){return GetDigestOffset0(pBuffer);}else{return GetDigestOffset1(pBuffer);}
}
uint32_t GetDHOffset0(uint8_t *pBuffer) {
uint32_t offset = pBuffer[1532] + pBuffer[1533] + pBuffer[1534] + pBuffer[1535];
return (offset % 632) + 772;
}
uint32_t GetDHOffset1(uint8_t *pBuffer) {
uint32_t offset = pBuffer[768] + pBuffer[769] + pBuffer[770] + pBuffer[771];
return (offset % 632) + 8;
}
uint32_t GetDHOffset(uint8_t *pBuffer, uint8_t scheme){
if (scheme == 0){return GetDHOffset0(pBuffer);}else{return GetDHOffset1(pBuffer);}
}
bool ValidateClientScheme(uint8_t * pBuffer, uint8_t scheme) {
uint32_t clientDigestOffset = GetDigestOffset(pBuffer, scheme);
uint8_t *pTempBuffer = new uint8_t[1536 - 32];
memcpy(pTempBuffer, pBuffer, clientDigestOffset);
memcpy(pTempBuffer + clientDigestOffset, pBuffer + clientDigestOffset + 32, 1536 - clientDigestOffset - 32);
uint8_t *pTempHash = new uint8_t[512];
HMACsha256(pTempBuffer, 1536 - 32, genuineFPKey, 30, pTempHash);
bool result = (memcmp(pBuffer+clientDigestOffset, pTempHash, 32) == 0);
#if DEBUG >= 4
fprintf(stderr, "Client scheme validation %hhi %s\n", scheme, result?"success":"failed");
#endif
delete[] pTempBuffer;
delete[] pTempHash;
return result;
}

56
lib/crypto.h Normal file
View file

@ -0,0 +1,56 @@
/// \file crypto.h
/// Holds all headers needed for RTMP cryptography functions.
#pragma once
#include <stdint.h>
#include <string>
#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/rc4.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/hmac.h>
class DHWrapper {
private:
int32_t _bitsCount;
DH *_pDH;
uint8_t *_pSharedKey;
int32_t _sharedKeyLength;
BIGNUM *_peerPublickey;
public:
DHWrapper(int32_t bitsCount);
virtual ~DHWrapper();
bool Initialize();
bool CopyPublicKey(uint8_t *pDst, int32_t dstLength);
bool CopyPrivateKey(uint8_t *pDst, int32_t dstLength);
bool CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length);
bool CopySharedKey(uint8_t *pDst, int32_t dstLength);
private:
void Cleanup();
bool CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength);
};
void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut);
std::string md5(std::string source, bool textResult);
std::string b64(std::string source);
std::string b64(uint8_t *pBuffer, uint32_t length);
std::string unb64(std::string source);
std::string unb64(uint8_t *pBuffer, uint32_t length);
void HMACsha256(const void *pData, uint32_t dataLength, const void *pKey, uint32_t keyLength, void *pResult);
uint32_t GetDigestOffset0(uint8_t *pBuffer);
uint32_t GetDigestOffset1(uint8_t *pBuffer);
uint32_t GetDigestOffset(uint8_t *pBuffer, uint8_t scheme);
uint32_t GetDHOffset0(uint8_t *pBuffer);
uint32_t GetDHOffset1(uint8_t *pBuffer);
uint32_t GetDHOffset(uint8_t *pBuffer, uint8_t scheme);
extern uint8_t genuineFMSKey[];
bool ValidateClientScheme(uint8_t * pBuffer, uint8_t scheme);

467
lib/dtsc.cpp Normal file
View file

@ -0,0 +1,467 @@
/// \file dtsc.cpp
/// Holds all code for DDVTECH Stream Container parsing/generation.
#include "dtsc.h"
#include <string.h> //for memcmp
#include <arpa/inet.h> //for htonl/ntohl
#include <stdio.h> //for fprint, stderr
char DTSC::Magic_Header[] = "DTSC";
char DTSC::Magic_Packet[] = "DTPD";
/// Initializes a DTSC::Stream with only one packet buffer.
DTSC::Stream::Stream(){
datapointer = 0;
buffercount = 1;
}
/// Initializes a DTSC::Stream with a minimum of rbuffers packet buffers.
/// The actual buffer count may not at all times be the requested amount.
DTSC::Stream::Stream(unsigned int rbuffers){
datapointer = 0;
if (rbuffers < 1){rbuffers = 1;}
buffercount = rbuffers;
}
/// Returns the time in milliseconds of the last received packet.
/// This is _not_ the time this packet was received, only the stored time.
unsigned int DTSC::Stream::getTime(){
return buffers.front().getContentP("time")->NumValue();
}
/// Attempts to parse a packet from the given std::string buffer.
/// Returns true if successful, removing the parsed part from the buffer string.
/// Returns false if invalid or not enough data is in the buffer.
/// \arg buffer The std::string buffer to attempt to parse.
bool DTSC::Stream::parsePacket(std::string & buffer){
uint32_t len;
if (buffer.length() > 8){
if (memcmp(buffer.c_str(), DTSC::Magic_Header, 4) == 0){
len = ntohl(((uint32_t *)buffer.c_str())[1]);
if (buffer.length() < len+8){return false;}
metadata = DTSC::parseDTMI((unsigned char*)buffer.c_str() + 8, len);
buffer.erase(0, len+8);
return false;
}
if (memcmp(buffer.c_str(), DTSC::Magic_Packet, 4) == 0){
len = ntohl(((uint32_t *)buffer.c_str())[1]);
if (buffer.length() < len+8){return false;}
buffers.push_front(DTSC::DTMI("empty", DTMI_ROOT));
buffers.front() = DTSC::parseDTMI((unsigned char*)buffer.c_str() + 8, len);
datapointertype = INVALID;
if (buffers.front().getContentP("data")){
datapointer = &(buffers.front().getContentP("data")->StrValue());
if (buffers.front().getContentP("datatype")){
std::string tmp = buffers.front().getContentP("datatype")->StrValue();
if (tmp == "video"){datapointertype = VIDEO;}
if (tmp == "audio"){datapointertype = AUDIO;}
if (tmp == "meta"){datapointertype = META;}
}
}else{
datapointer = 0;
}
buffer.erase(0, len+8);
while (buffers.size() > buffercount){buffers.pop_back();}
advanceRings();
return true;
}
#if DEBUG >= 2
std::cerr << "Error: Invalid DTMI data: " << buffer.substr(0, 4) << std::endl;
#endif
buffer.erase(0, 1);
}
return false;
}
/// Returns a direct pointer to the data attribute of the last received packet, if available.
/// Returns NULL if no valid pointer or packet is available.
std::string & DTSC::Stream::lastData(){
return *datapointer;
}
/// Returns the packed in this buffer number.
/// \arg num Buffer number.
DTSC::DTMI & DTSC::Stream::getPacket(unsigned int num){
return buffers[num];
}
/// Returns the type of the last received packet.
DTSC::datatype DTSC::Stream::lastType(){
return datapointertype;
}
/// Returns true if the current stream contains at least one video track.
bool DTSC::Stream::hasVideo(){
return (metadata.getContentP("video") != 0);
}
/// Returns true if the current stream contains at least one audio track.
bool DTSC::Stream::hasAudio(){
return (metadata.getContentP("audio") != 0);
}
/// Returns a packed DTSC packet, ready to sent over the network.
std::string & DTSC::Stream::outPacket(unsigned int num){
static std::string emptystring;
if (num >= buffers.size()) return emptystring;
buffers[num].Pack(true);
return buffers[num].packed;
}
/// Returns a packed DTSC header, ready to sent over the network.
std::string & DTSC::Stream::outHeader(){
if ((metadata.packed.length() < 4) || !metadata.netpacked){
metadata.Pack(true);
metadata.packed.replace(0, 4, Magic_Header);
}
return metadata.packed;
}
/// advances all given out and internal Ring classes to point to the new buffer, after one has been added.
/// Also updates the internal keyframes ring, as well as marking rings as starved if they are.
/// Unsets waiting rings, updating them with their new buffer number.
void DTSC::Stream::advanceRings(){
std::deque<DTSC::Ring>::iterator dit;
std::set<DTSC::Ring *>::iterator sit;
for (sit = rings.begin(); sit != rings.end(); sit++){
(*sit)->b++;
if ((*sit)->waiting){(*sit)->waiting = false; (*sit)->b = 0;}
if ((*sit)->starved || ((*sit)->b >= buffers.size())){(*sit)->starved = true; (*sit)->b = 0;}
}
for (dit = keyframes.begin(); dit != keyframes.end(); dit++){
dit->b++;
if (dit->b >= buffers.size()){keyframes.erase(dit); break;}
}
if ((lastType() == VIDEO) && (buffers.front().getContentP("keyframe"))){
keyframes.push_front(DTSC::Ring(0));
}
//increase buffer size if no keyframes available
if ((buffercount > 1) && (keyframes.size() < 1)){buffercount++;}
}
/// Constructs a new Ring, at the given buffer position.
/// \arg v Position for buffer.
DTSC::Ring::Ring(unsigned int v){
b = v;
waiting = false;
starved = false;
}
/// Requests a new Ring, which will be created and added to the internal Ring list.
/// This Ring will be kept updated so it always points to valid data or has the starved boolean set.
/// Don't forget to call dropRing() for all requested Ring classes that are no longer neccessary!
DTSC::Ring * DTSC::Stream::getRing(){
DTSC::Ring * tmp;
if (keyframes.size() == 0){
tmp = new DTSC::Ring(0);
}else{
tmp = new DTSC::Ring(keyframes[0].b);
}
rings.insert(tmp);
return tmp;
}
/// Deletes a given out Ring class from memory and internal Ring list.
/// Checks for NULL pointers and invalid pointers, silently discarding them.
void DTSC::Stream::dropRing(DTSC::Ring * ptr){
if (rings.find(ptr) != rings.end()){
rings.erase(ptr);
delete ptr;
}
}
/// Properly cleans up the object for erasing.
/// Drops all Ring classes that have been given out.
DTSC::Stream::~Stream(){
std::set<DTSC::Ring *>::iterator sit;
for (sit = rings.begin(); sit != rings.end(); sit++){delete (*sit);}
}
/// Returns the std::string Indice for the current object, if available.
/// Returns an empty string if no indice exists.
std::string DTSC::DTMI::Indice(){return myIndice;};
/// Returns the DTSC::DTMItype AMF0 object type for this object.
DTSC::DTMItype DTSC::DTMI::GetType(){return myType;};
/// Returns the numeric value of this object, if available.
/// If this object holds no numeric value, 0 is returned.
uint64_t & DTSC::DTMI::NumValue(){return numval;};
/// Returns the std::string value of this object, if available.
/// If this object holds no string value, an empty string is returned.
std::string & DTSC::DTMI::StrValue(){return strval;};
/// Returns the C-string value of this object, if available.
/// If this object holds no string value, an empty C-string is returned.
const char * DTSC::DTMI::Str(){return strval.c_str();};
/// Returns a count of the amount of objects this object currently holds.
/// If this object is not a container type, this function will always return 0.
int DTSC::DTMI::hasContent(){return contents.size();};
/// Returns true if this DTSC::DTMI value is non-default.
/// Non-default means it is either not a root element or has content.
bool DTSC::DTMI::isEmpty(){
if (myType != DTMI_ROOT){return false;}
return (hasContent() == 0);
};
/// Adds an DTSC::DTMI to this object. Works for all types, but only makes sense for container types.
/// This function resets DTMI::packed to an empty string, forcing a repack on the next call to DTMI::Pack.
/// If the indice name already exists, replaces the indice.
void DTSC::DTMI::addContent(DTSC::DTMI c){
std::vector<DTMI>::iterator it;
for (it = contents.begin(); it != contents.end(); it++){
if (it->Indice() == c.Indice()){
contents.erase(it);
break;
}
}
contents.push_back(c); packed = "";
};
/// Returns a pointer to the object held at indice i.
/// Returns null pointer if no object is held at this indice.
/// \param i The indice of the object in this container.
DTSC::DTMI* DTSC::DTMI::getContentP(int i){
if (contents.size() <= (unsigned int)i){return 0;}
return &contents.at(i);
};
/// Returns a copy of the object held at indice i.
/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
/// \param i The indice of the object in this container.
DTSC::DTMI DTSC::DTMI::getContent(int i){return contents.at(i);};
/// Returns a pointer to the object held at indice s.
/// Returns NULL if no object is held at this indice.
/// \param s The indice of the object in this container.
DTSC::DTMI* DTSC::DTMI::getContentP(std::string s){
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){
if (it->Indice() == s){return &(*it);}
}
return 0;
};
/// Returns a copy of the object held at indice s.
/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice.
/// \param s The indice of the object in this container.
DTSC::DTMI DTSC::DTMI::getContent(std::string s){
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){
if (it->Indice() == s){return *it;}
}
return DTSC::DTMI("error", DTMI_ROOT);
};
/// Default constructor.
/// Simply fills the data with DTSC::DTMI("error", AMF0_DDV_CONTAINER)
DTSC::DTMI::DTMI(){
*this = DTSC::DTMI("error", DTMI_ROOT);
};//default constructor
/// Constructor for numeric objects.
/// The object type is by default DTMItype::DTMI_INT, but this can be forced to a different value.
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
/// \param val The numeric value of this object. Numeric objects only support uint64_t values.
/// \param setType The object type to force this object to.
DTSC::DTMI::DTMI(std::string indice, uint64_t val, DTSC::DTMItype setType){//num type initializer
myIndice = indice;
myType = setType;
strval = "";
numval = val;
};
/// Constructor for string objects.
/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic.
/// \param val The string value of this object.
/// \param setType The object type to force this object to.
DTSC::DTMI::DTMI(std::string indice, std::string val, DTSC::DTMItype setType){//str type initializer
myIndice = indice;
myType = setType;
strval = val;
numval = 0;
};
/// Constructor for container objects.
/// \param indice The string indice of this object in its container, or empty string if none.
/// \param setType The object type to force this object to.
DTSC::DTMI::DTMI(std::string indice, DTSC::DTMItype setType){//object type initializer
myIndice = indice;
myType = setType;
strval = "";
numval = 0;
};
/// Prints the contents of this object to std::cerr.
/// If this object contains other objects, it will call itself recursively
/// and print all nested content in a nice human-readable format.
void DTSC::DTMI::Print(std::string indent){
std::cerr << indent;
// print my type
switch (myType){
case DTMI_INT: std::cerr << "Integer"; break;
case DTMI_STRING: std::cerr << "String"; break;
case DTMI_OBJECT: std::cerr << "Object"; break;
case DTMI_OBJ_END: std::cerr << "Object end"; break;
case DTMI_ROOT: std::cerr << "Root Node"; break;
}
// print my string indice, if available
std::cerr << " " << myIndice << " ";
// print my numeric or string contents
switch (myType){
case DTMI_INT: std::cerr << numval; break;
case DTMI_STRING:
if (strval.length() > 200 || ((strval.length() > 1) && ( (strval[0] < 'A') || (strval[0] > 'z') ) )){
std::cerr << strval.length() << " bytes of data";
}else{
std::cerr << strval;
}
break;
default: break;//we don't care about the rest, and don't want a compiler warning...
}
std::cerr << std::endl;
// if I hold other objects, print those too, recursively.
if (contents.size() > 0){
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){it->Print(indent+" ");}
}
};//print
/// Packs the DTMI to a std::string for transfer over the network.
/// If a packed version already exists, does not regenerate it.
/// If the object is a container type, this function will call itself recursively and contain all contents.
/// \arg netpack If true, will pack as a full DTMI packet, if false only as the contents without header.
std::string DTSC::DTMI::Pack(bool netpack){
if (packed != ""){
if (netpacked == netpack){return packed;}
if (netpacked){
packed.erase(0, 8);
}else{
unsigned int size = htonl(packed.length());
packed.insert(0, (char*)&size, 4);
packed.insert(0, Magic_Packet);
}
netpacked = !netpacked;
return packed;
}
std::string r = "";
r += myType;
//output the properly formatted data stream for this object's contents.
switch (myType){
case DTMI_INT:
r += *(((char*)&numval)+7); r += *(((char*)&numval)+6);
r += *(((char*)&numval)+5); r += *(((char*)&numval)+4);
r += *(((char*)&numval)+3); r += *(((char*)&numval)+2);
r += *(((char*)&numval)+1); r += *(((char*)&numval));
break;
case DTMI_STRING:
r += strval.size() / (256*256*256);
r += strval.size() / (256*256);
r += strval.size() / 256;
r += strval.size() % 256;
r += strval;
break;
case DTMI_OBJECT:
case DTMI_ROOT:
if (contents.size() > 0){
for (std::vector<DTSC::DTMI>::iterator it = contents.begin(); it != contents.end(); it++){
r += it->Indice().size() / 256;
r += it->Indice().size() % 256;
r += it->Indice();
r += it->Pack();
}
}
r += (char)0x0; r += (char)0x0; r += (char)0xEE;
break;
case DTMI_OBJ_END:
break;
}
packed = r;
netpacked = netpack;
if (netpacked){
unsigned int size = htonl(packed.length());
packed.insert(0, (char*)&size, 4);
packed.insert(0, Magic_Packet);
}
return packed;
};//pack
/// Parses a single AMF0 type - used recursively by the AMF::parse() functions.
/// This function updates i every call with the new position in the data.
/// \param data The raw data to parse.
/// \param len The size of the raw data.
/// \param i Current parsing position in the raw data.
/// \param name Indice name for any new object created.
/// \returns A single DTSC::DTMI, parsed from the raw data.
DTSC::DTMI DTSC::parseOneDTMI(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name){
unsigned int tmpi = 0;
unsigned char tmpdbl[8];
#if DEBUG >= 10
fprintf(stderr, "Note: AMF type %hhx found. %i bytes left\n", data[i], len-i);
#endif
switch (data[i]){
case DTMI_INT:
tmpdbl[7] = data[i+1];
tmpdbl[6] = data[i+2];
tmpdbl[5] = data[i+3];
tmpdbl[4] = data[i+4];
tmpdbl[3] = data[i+5];
tmpdbl[2] = data[i+6];
tmpdbl[1] = data[i+7];
tmpdbl[0] = data[i+8];
i+=9;//skip 8(an uint64_t)+1 forwards
return DTSC::DTMI(name, *(uint64_t*)tmpdbl, DTMI_INT);
break;
case DTMI_STRING:{
tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to UTF-8-long length
std::string tmpstr = std::string((const char *)data+i+5, (size_t)tmpi);//set the string data
i += tmpi + 5;//skip length+size+1 forwards
return DTSC::DTMI(name, tmpstr, DTMI_STRING);
} break;
case DTMI_ROOT:{
++i;
DTSC::DTMI ret(name, DTMI_ROOT);
while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x0000EE)
tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length
std::string tmpstr = std::string((const char *)data+i+2, (size_t)tmpi);//set the string data
i += tmpi + 2;//skip length+size forwards
ret.addContent(parseOneDTMI(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr
}
i += 3;//skip 0x0000EE
return ret;
} break;
case DTMI_OBJECT:{
++i;
DTSC::DTMI ret(name, DTMI_OBJECT);
while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x0000EE)
tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length
std::string tmpstr = std::string((const char *)data+i+2, (size_t)tmpi);//set the string data
i += tmpi + 2;//skip length+size forwards
ret.addContent(parseOneDTMI(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr
}
i += 3;//skip 0x0000EE
return ret;
} break;
}
#if DEBUG >= 2
fprintf(stderr, "Error: Unimplemented DTMI type %hhx - returning.\n", data[i]);
#endif
return DTSC::DTMI("error", DTMI_ROOT);
}//parseOne
/// Parses a C-string to a valid DTSC::DTMI.
/// This function will find one DTMI object in the string and return it.
DTSC::DTMI DTSC::parseDTMI(const unsigned char * data, unsigned int len){
DTSC::DTMI ret;//container type
unsigned int i = 0;
ret = parseOneDTMI(data, len, i, "");
ret.packed = std::string((char*)data, (size_t)len);
ret.netpacked = false;
return ret;
}//parse
/// Parses a std::string to a valid DTSC::DTMI.
/// This function will find one DTMI object in the string and return it.
DTSC::DTMI DTSC::parseDTMI(std::string data){
return parseDTMI((const unsigned char*)data.c_str(), data.size());
}//parse

144
lib/dtsc.h Normal file
View file

@ -0,0 +1,144 @@
/// \file dtsc.h
/// Holds all headers for DDVTECH Stream Container parsing/generation.
#pragma once
#include <vector>
#include <iostream>
#include <stdint.h> //for uint64_t
#include <string>
#include <deque>
#include <set>
/// Holds all DDVTECH Stream Container classes and parsers.
///Video:
/// - codec (string: H264, H263, VP6)
/// - width (int, pixels)
/// - height (int, pixels)
/// - fpks (int, frames per kilosecond (FPS * 1000))
/// - bps (int, bytes per second)
/// - init (string, init data)
///
///Audio:
/// - codec (string: AAC, MP3)
/// - rate (int, Hz)
/// - size (int, bitsize)
/// - bps (int, bytes per second)
/// - channels (int, channelcount)
/// - init (string, init data)
///
///All packets:
/// - datatype (string: audio, video, meta (unused))
/// - data (string: data)
/// - time (int: ms into video)
///
///Video packets:
/// - keyframe (int, if set, is a seekable keyframe)
/// - interframe (int, if set, is a non-seekable interframe)
/// - disposableframe (int, if set, is a disposable interframe)
///
///H264 video packets:
/// - nalu (int, if set, is a nalu)
/// - nalu_end (int, if set, is a end-of-sequence)
/// - offset (int, unsigned version of signed int! Holds the ms offset between timestamp and proper display time for B-frames)
namespace DTSC{
/// Enumerates all possible DTMI types.
enum DTMItype {
DTMI_INT = 0x01, ///< Unsigned 64-bit integer.
DTMI_STRING = 0x02, ///< String, equivalent to the AMF longstring type.
DTMI_OBJECT = 0xE0, ///< Object, equivalent to the AMF object type.
DTMI_OBJ_END = 0xEE, ///< End of object marker.
DTMI_ROOT = 0xFF ///< Root node for all DTMI data.
};
/// Recursive class that holds DDVTECH MediaInfo.
class DTMI {
public:
std::string Indice();
DTMItype GetType();
uint64_t & NumValue();
std::string & StrValue();
const char * Str();
int hasContent();
bool isEmpty();
void addContent(DTMI c);
DTMI* getContentP(int i);
DTMI getContent(int i);
DTMI* getContentP(std::string s);
DTMI getContent(std::string s);
DTMI();
DTMI(std::string indice, uint64_t val, DTMItype setType = DTMI_INT);
DTMI(std::string indice, std::string val, DTMItype setType = DTMI_STRING);
DTMI(std::string indice, DTMItype setType = DTMI_OBJECT);
void Print(std::string indent = "");
std::string Pack(bool netpack = false);
bool netpacked;
std::string packed;
protected:
std::string myIndice; ///< Holds this objects indice, if any.
DTMItype myType; ///< Holds this objects AMF0 type.
std::string strval; ///< Holds this objects string value, if any.
uint64_t numval; ///< Holds this objects numeric value, if any.
std::vector<DTMI> contents; ///< Holds this objects contents, if any (for container types).
};//AMFType
/// Parses a C-string to a valid DTSC::DTMI.
DTMI parseDTMI(const unsigned char * data, unsigned int len);
/// Parses a std::string to a valid DTSC::DTMI.
DTMI parseDTMI(std::string data);
/// Parses a single DTMI type - used recursively by the DTSC::parseDTMI() functions.
DTMI parseOneDTMI(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name);
/// This enum holds all possible datatypes for DTSC packets.
enum datatype {
AUDIO, ///< Stream Audio data
VIDEO, ///< Stream Video data
META, ///< Stream Metadata
INVALID ///< Anything else or no data available.
};
extern char Magic_Header[]; ///< The magic bytes for a DTSC header
extern char Magic_Packet[]; ///< The magic bytes for a DTSC packet
/// A part from the DTSC::Stream ringbuffer.
/// Holds information about a buffer that will stay consistent
class Ring {
public:
Ring(unsigned int v);
volatile unsigned int b; ///< Holds current number of buffer. May and is intended to change unexpectedly!
volatile bool waiting; ///< If true, this Ring is currently waiting for a buffer fill.
volatile bool starved; ///< If true, this Ring can no longer receive valid data.
};
/// Holds temporary data for a DTSC stream and provides functions to utilize it.
/// Optionally also acts as a ring buffer of a certain requested size.
/// If ring buffering mode is enabled, it will automatically grow in size to always contain at least one keyframe.
class Stream {
public:
Stream();
~Stream();
Stream(unsigned int buffers);
DTSC::DTMI metadata;
DTSC::DTMI & getPacket(unsigned int num = 0);
datatype lastType();
std::string & lastData();
bool hasVideo();
bool hasAudio();
bool parsePacket(std::string & buffer);
std::string & outPacket(unsigned int num);
std::string & outHeader();
Ring * getRing();
unsigned int getTime();
void dropRing(Ring * ptr);
private:
std::deque<DTSC::DTMI> buffers;
std::set<DTSC::Ring *> rings;
std::deque<DTSC::Ring> keyframes;
void advanceRings();
std::string * datapointer;
datatype datapointertype;
unsigned int buffercount;
};
};

948
lib/flv_tag.cpp Normal file
View file

@ -0,0 +1,948 @@
/// \file flv_tag.cpp
/// Holds all code for the FLV namespace.
#include "flv_tag.h"
#include "amf.h"
#include "rtmpchunks.h"
#include <stdio.h> //for Tag::FileLoader
#include <unistd.h> //for Tag::FileLoader
#include <fcntl.h> //for Tag::FileLoader
#include <stdlib.h> //malloc
#include <string.h> //memcpy
#include <sstream>
/// Holds the last FLV header parsed.
/// Defaults to a audio+video header on FLV version 0x01 if no header received yet.
char FLV::Header[13] = {'F', 'L', 'V', 0x01, 0x05, 0, 0, 0, 0x09, 0, 0, 0, 0};
bool FLV::Parse_Error = false; ///< This variable is set to true if a problem is encountered while parsing the FLV.
std::string FLV::Error_Str = "";
/// Checks a FLV Header for validness. Returns true if the header is valid, false
/// if the header is not. Not valid can mean:
/// - Not starting with the string "FLV".
/// - The DataOffset is not 9 bytes.
/// - The PreviousTagSize is not 0 bytes.
///
/// Note that we see PreviousTagSize as part of the FLV header, not part of the tag header!
bool FLV::check_header(char * header){
if (header[0] != 'F') return false;
if (header[1] != 'L') return false;
if (header[2] != 'V') return false;
if (header[5] != 0) return false;
if (header[6] != 0) return false;
if (header[7] != 0) return false;
if (header[8] != 0x09) return false;
if (header[9] != 0) return false;
if (header[10] != 0) return false;
if (header[11] != 0) return false;
if (header[12] != 0) return false;
return true;
}//FLV::check_header
/// Checks the first 3 bytes for the string "FLV". Implementing a basic FLV header check,
/// returning true if it is, false if not.
bool FLV::is_header(char * header){
if (header[0] != 'F') return false;
if (header[1] != 'L') return false;
if (header[2] != 'V') return false;
return true;
}//FLV::is_header
/// True if this media type requires init data.
/// Will always return false if the tag type is not 0x08 or 0x09.
/// Returns true for H263, AVC (H264), AAC.
/// \todo Check if MP3 does or does not require init data...
bool FLV::Tag::needsInitData(){
switch (data[0]){
case 0x09:
switch (data[11] & 0x0F){
case 2: return true; break;//H263 requires init data
case 7: return true; break;//AVC requires init data
default: return false; break;//other formats do not
}
break;
case 0x08:
switch (data[11] & 0xF0){
case 0x20: return false; break;//MP3 does not...? Unsure.
case 0xA0: return true; break;//AAC requires init data
case 0xE0: return false; break;//MP38kHz does not...?
default: return false; break;//other formats do not
}
break;
}
return false;//only audio/video can require init data
}
/// True if current tag is init data for this media type.
bool FLV::Tag::isInitData(){
switch (data[0]){
case 0x09:
switch (data[11] & 0xF0){
case 0x50: return true; break;
}
if ((data[11] & 0x0F) == 7){
switch (data[12]){
case 0: return true; break;
}
}
break;
case 0x08:
if ((data[12] == 0) && ((data[11] & 0xF0) == 0xA0)){
return true;
}
break;
}
return false;
}
/// Returns a std::string describing the tag in detail.
/// The string includes information about whether the tag is
/// audio, video or metadata, what encoding is used, and the details
/// of the encoding itself.
std::string FLV::Tag::tagType(){
std::stringstream R;
R << len << " bytes of ";
switch (data[0]){
case 0x09:
switch (data[11] & 0x0F){
case 1: R << "JPEG"; break;
case 2: R << "H263"; break;
case 3: R << "ScreenVideo1"; break;
case 4: R << "VP6"; break;
case 5: R << "VP6Alpha"; break;
case 6: R << "ScreenVideo2"; break;
case 7: R << "H264"; break;
default: R << "unknown"; break;
}
R << " video ";
switch (data[11] & 0xF0){
case 0x10: R << "keyframe"; break;
case 0x20: R << "iframe"; break;
case 0x30: R << "disposableiframe"; break;
case 0x40: R << "generatedkeyframe"; break;
case 0x50: R << "videoinfo"; break;
}
if ((data[11] & 0x0F) == 7){
switch (data[12]){
case 0: R << " header"; break;
case 1: R << " NALU"; break;
case 2: R << " endofsequence"; break;
}
}
break;
case 0x08:
switch (data[11] & 0xF0){
case 0x00: R << "linear PCM PE"; break;
case 0x10: R << "ADPCM"; break;
case 0x20: R << "MP3"; break;
case 0x30: R << "linear PCM LE"; break;
case 0x40: R << "Nelly16kHz"; break;
case 0x50: R << "Nelly8kHz"; break;
case 0x60: R << "Nelly"; break;
case 0x70: R << "G711A-law"; break;
case 0x80: R << "G711mu-law"; break;
case 0x90: R << "reserved"; break;
case 0xA0: R << "AAC"; break;
case 0xB0: R << "Speex"; break;
case 0xE0: R << "MP38kHz"; break;
case 0xF0: R << "DeviceSpecific"; break;
default: R << "unknown"; break;
}
switch (data[11] & 0x0C){
case 0x0: R << " 5.5kHz"; break;
case 0x4: R << " 11kHz"; break;
case 0x8: R << " 22kHz"; break;
case 0xC: R << " 44kHz"; break;
}
switch (data[11] & 0x02){
case 0: R << " 8bit"; break;
case 2: R << " 16bit"; break;
}
switch (data[11] & 0x01){
case 0: R << " mono"; break;
case 1: R << " stereo"; break;
}
R << " audio";
if ((data[12] == 0) && ((data[11] & 0xF0) == 0xA0)){
R << " initdata";
}
break;
case 0x12:{
R << "(meta)data: ";
AMF::Object metadata = AMF::parse((unsigned char*)data+11, len-15);
R << metadata.Print();
break;
}
default:
R << "unknown";
break;
}
return R.str();
}//FLV::Tag::tagtype
/// Returns the 32-bit timestamp of this tag.
unsigned int FLV::Tag::tagTime(){
return (data[4] << 16) + (data[5] << 8) + data[6] + (data[7] << 24);
}//tagTime getter
/// Sets the 32-bit timestamp of this tag.
void FLV::Tag::tagTime(unsigned int T){
data[4] = ((T >> 16) & 0xFF);
data[5] = ((T >> 8) & 0xFF);
data[6] = (T & 0xFF);
data[7] = ((T >> 24) & 0xFF);
}//tagTime setter
/// Constructor for a new, empty, tag.
/// The buffer length is initialized to 0, and later automatically
/// increased if neccesary.
FLV::Tag::Tag(){
len = 0; buf = 0; data = 0; isKeyframe = false; done = true; sofar = 0;
}//empty constructor
/// Copy constructor, copies the contents of an existing tag.
/// The buffer length is initialized to the actual size of the tag
/// that is being copied, and later automaticallt increased if
/// neccesary.
FLV::Tag::Tag(const Tag& O){
done = true;
sofar = 0;
buf = O.len;
len = buf;
if (len > 0){
data = (char*)malloc(len);
memcpy(data, O.data, len);
}else{
data = 0;
}
isKeyframe = O.isKeyframe;
}//copy constructor
/// Copy constructor from a RTMP chunk.
/// Copies the contents of a RTMP chunk into a valid FLV tag.
/// Exactly the same as making a chunk by through the default (empty) constructor
/// and then calling FLV::Tag::ChunkLoader with the chunk as argument.
FLV::Tag::Tag(const RTMPStream::Chunk& O){
len = 0; buf = 0; data = 0; isKeyframe = false; done = true; sofar = 0;
ChunkLoader(O);
}
/// Assignment operator - works exactly like the copy constructor.
/// This operator checks for self-assignment.
FLV::Tag & FLV::Tag::operator= (const FLV::Tag& O){
if (this != &O){//no self-assignment
len = O.len;
if (len > 0){
if (!data){
data = (char*)malloc(len);
buf = len;
}else{
if (buf < len){
data = (char*)realloc(data, len);
buf = len;
}
}
memcpy(data, O.data, len);
}
isKeyframe = O.isKeyframe;
}
return *this;
}//assignment operator
/// FLV loader function from DTSC.
/// Takes the DTSC data and makes it into FLV.
bool FLV::Tag::DTSCLoader(DTSC::Stream & S){
switch (S.lastType()){
case DTSC::VIDEO:
len = S.lastData().length() + 16;
if (S.metadata.getContentP("video") && S.metadata.getContentP("video")->getContentP("codec")){
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){len += 4;}
}
break;
case DTSC::AUDIO:
len = S.lastData().length() + 16;
if (S.metadata.getContentP("audio") && S.metadata.getContentP("audio")->getContentP("codec")){
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){len += 1;}
}
break;
case DTSC::META:
len = S.lastData().length() + 15;
break;
default://ignore all other types (there are currently no other types...)
break;
}
if (len > 0){
if (!data){
data = (char*)malloc(len);
buf = len;
}else{
if (buf < len){
data = (char*)realloc(data, len);
buf = len;
}
}
switch (S.lastType()){
case DTSC::VIDEO:
if ((unsigned int)len == S.lastData().length() + 16){
memcpy(data+12, S.lastData().c_str(), S.lastData().length());
}else{
memcpy(data+16, S.lastData().c_str(), S.lastData().length());
if (S.getPacket().getContentP("nalu")){data[12] = 1;}else{data[12] = 2;}
int offset = S.getPacket().getContentP("offset")->NumValue();
data[13] = (offset >> 16) & 0xFF;
data[14] = (offset >> 8) & 0XFF;
data[15] = offset & 0xFF;
}
data[11] = 0;
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){data[11] += 7;}
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H263"){data[11] += 2;}
if (S.getPacket().getContentP("keyframe")){data[11] += 0x10;}
if (S.getPacket().getContentP("interframe")){data[11] += 0x20;}
if (S.getPacket().getContentP("disposableframe")){data[11] += 0x30;}
break;
case DTSC::AUDIO:{
if ((unsigned int)len == S.lastData().length() + 16){
memcpy(data+12, S.lastData().c_str(), S.lastData().length());
}else{
memcpy(data+13, S.lastData().c_str(), S.lastData().length());
data[12] = 1;//raw AAC data, not sequence header
}
data[11] = 0;
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){data[11] += 0xA0;}
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){data[11] += 0x20;}
unsigned int datarate = S.metadata.getContentP("audio")->getContentP("rate")->NumValue();
if (datarate >= 44100){
data[11] += 0x0C;
}else if(datarate >= 22050){
data[11] += 0x08;
}else if(datarate >= 11025){
data[11] += 0x04;
}
if (S.metadata.getContentP("audio")->getContentP("size")->NumValue() == 16){data[11] += 0x02;}
if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;}
break;
}
case DTSC::META:
memcpy(data+11, S.lastData().c_str(), S.lastData().length());
break;
default: break;
}
}
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[1] = ((len-15) >> 16) & 0xFF;
data[2] = ((len-15) >> 8) & 0xFF;
data[3] = (len-15) & 0xFF;
data[8] = 0;
data[9] = 0;
data[10] = 0;
tagTime(S.getPacket().getContentP("time")->NumValue());
return true;
}
/// Helper function that properly sets the tag length from the internal len variable.
void FLV::Tag::setLen(){
int len4 = len - 4;
int i = len;
data[--i] = (len4) & 0xFF;
len4 >>= 8;
data[--i] = (len4) & 0xFF;
len4 >>= 8;
data[--i] = (len4) & 0xFF;
len4 >>= 8;
data[--i] = (len4) & 0xFF;
}
/// FLV Video init data loader function from DTSC.
/// Takes the DTSC Video init data and makes it into FLV.
/// Assumes init data is available - so check before calling!
bool FLV::Tag::DTSCVideoInit(DTSC::Stream & S){
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){
len = S.metadata.getContentP("video")->getContentP("init")->StrValue().length() + 20;
}
if (len > 0){
if (!data){
data = (char*)malloc(len);
buf = len;
}else{
if (buf < len){
data = (char*)realloc(data, len);
buf = len;
}
}
memcpy(data+16, S.metadata.getContentP("video")->getContentP("init")->StrValue().c_str(), len-20);
data[12] = 0;//H264 sequence header
data[13] = 0;
data[14] = 0;
data[15] = 0;
data[11] = 0x17;//H264 keyframe (0x07 & 0x10)
}
setLen();
data[0] = 0x09;
data[1] = ((len-15) >> 16) & 0xFF;
data[2] = ((len-15) >> 8) & 0xFF;
data[3] = (len-15) & 0xFF;
data[8] = 0;
data[9] = 0;
data[10] = 0;
tagTime(0);
return true;
}
/// FLV Audio init data loader function from DTSC.
/// Takes the DTSC Audio init data and makes it into FLV.
/// Assumes init data is available - so check before calling!
bool FLV::Tag::DTSCAudioInit(DTSC::Stream & S){
len = 0;
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){
len = S.metadata.getContentP("audio")->getContentP("init")->StrValue().length() + 17;
}
if (len > 0){
if (!data){
data = (char*)malloc(len);
buf = len;
}else{
if (buf < len){
data = (char*)realloc(data, len);
buf = len;
}
}
memcpy(data+13, S.metadata.getContentP("audio")->getContentP("init")->StrValue().c_str(), len-17);
data[12] = 0;//AAC sequence header
data[11] = 0;
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){data[11] += 0xA0;}
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){data[11] += 0x20;}
unsigned int datarate = S.metadata.getContentP("audio")->getContentP("rate")->NumValue();
if (datarate >= 44100){
data[11] += 0x0C;
}else if(datarate >= 22050){
data[11] += 0x08;
}else if(datarate >= 11025){
data[11] += 0x04;
}
if (S.metadata.getContentP("audio")->getContentP("size")->NumValue() == 16){data[11] += 0x02;}
if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;}
}
setLen();
data[0] = 0x08;
data[1] = ((len-15) >> 16) & 0xFF;
data[2] = ((len-15) >> 8) & 0xFF;
data[3] = (len-15) & 0xFF;
data[8] = 0;
data[9] = 0;
data[10] = 0;
tagTime(0);
return true;
}
/// FLV metadata loader function from DTSC.
/// Takes the DTSC metadata and makes it into FLV.
/// Assumes metadata is available - so check before calling!
bool FLV::Tag::DTSCMetaInit(DTSC::Stream & S){
AMF::Object amfdata("root", AMF::AMF0_DDV_CONTAINER);
amfdata.addContent(AMF::Object("", "onMetaData"));
amfdata.addContent(AMF::Object("", AMF::AMF0_ECMA_ARRAY));
if (S.metadata.getContentP("video")){
amfdata.getContentP(1)->addContent(AMF::Object("hasVideo", 1, AMF::AMF0_BOOL));
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 7, AMF::AMF0_NUMBER));
}
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "VP6"){
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 4, AMF::AMF0_NUMBER));
}
if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H263"){
amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 2, AMF::AMF0_NUMBER));
}
if (S.metadata.getContentP("video")->getContentP("width")){
amfdata.getContentP(1)->addContent(AMF::Object("width", S.metadata.getContentP("video")->getContentP("width")->NumValue(), AMF::AMF0_NUMBER));
}
if (S.metadata.getContentP("video")->getContentP("height")){
amfdata.getContentP(1)->addContent(AMF::Object("height", S.metadata.getContentP("video")->getContentP("height")->NumValue(), AMF::AMF0_NUMBER));
}
if (S.metadata.getContentP("video")->getContentP("fpks")){
amfdata.getContentP(1)->addContent(AMF::Object("framerate", (double)S.metadata.getContentP("video")->getContentP("fpks")->NumValue() / 1000.0, AMF::AMF0_NUMBER));
}
if (S.metadata.getContentP("video")->getContentP("bps")){
amfdata.getContentP(1)->addContent(AMF::Object("videodatarate", ((double)S.metadata.getContentP("video")->getContentP("bps")->NumValue() * 8.0) / 1024.0, AMF::AMF0_NUMBER));
}
}
if (S.metadata.getContentP("audio")){
amfdata.getContentP(1)->addContent(AMF::Object("hasAudio", 1, AMF::AMF0_BOOL));
amfdata.getContentP(1)->addContent(AMF::Object("audiodelay", 0, AMF::AMF0_NUMBER));
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){
amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", 10, AMF::AMF0_NUMBER));
}
if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){
amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", 2, AMF::AMF0_NUMBER));
}
if (S.metadata.getContentP("audio")->getContentP("channels")){
if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){
amfdata.getContentP(1)->addContent(AMF::Object("stereo", 1, AMF::AMF0_BOOL));
}else{
amfdata.getContentP(1)->addContent(AMF::Object("stereo", 0, AMF::AMF0_BOOL));
}
}
if (S.metadata.getContentP("audio")->getContentP("rate")){
amfdata.getContentP(1)->addContent(AMF::Object("audiosamplerate", S.metadata.getContentP("audio")->getContentP("rate")->NumValue(), AMF::AMF0_NUMBER));
}
if (S.metadata.getContentP("audio")->getContentP("size")){
amfdata.getContentP(1)->addContent(AMF::Object("audiosamplesize", S.metadata.getContentP("audio")->getContentP("size")->NumValue(), AMF::AMF0_NUMBER));
}
if (S.metadata.getContentP("audio")->getContentP("bps")){
amfdata.getContentP(1)->addContent(AMF::Object("audiodatarate", ((double)S.metadata.getContentP("audio")->getContentP("bps")->NumValue() * 8.0) / 1024.0, AMF::AMF0_NUMBER));
}
}
std::string tmp = amfdata.Pack();
len = tmp.length() + 15;
if (len > 0){
if (!data){
data = (char*)malloc(len);
buf = len;
}else{
if (buf < len){
data = (char*)realloc(data, len);
buf = len;
}
}
memcpy(data+11, tmp.c_str(), len-15);
}
setLen();
data[0] = 0x12;
data[1] = ((len-15) >> 16) & 0xFF;
data[2] = ((len-15) >> 8) & 0xFF;
data[3] = (len-15) & 0xFF;
data[8] = 0;
data[9] = 0;
data[10] = 0;
tagTime(0);
return true;
}
/// FLV loader function from chunk.
/// Copies the contents and wraps it in a FLV header.
bool FLV::Tag::ChunkLoader(const RTMPStream::Chunk& O){
len = O.len + 15;
if (len > 0){
if (!data){
data = (char*)malloc(len);
buf = len;
}else{
if (buf < len){
data = (char*)realloc(data, len);
buf = len;
}
}
memcpy(data+11, &(O.data[0]), O.len);
}
setLen();
data[0] = O.msg_type_id;
data[3] = O.len & 0xFF;
data[2] = (O.len >> 8) & 0xFF;
data[1] = (O.len >> 16) & 0xFF;
tagTime(O.timestamp);
return true;
}
/// Helper function for FLV::MemLoader.
/// This function will try to read count bytes from data buffer D into buffer.
/// This function should be called repeatedly until true.
/// P and sofar are not the same value, because D may not start with the current tag.
/// \param buffer The target buffer.
/// \param count Amount of bytes to read.
/// \param sofar Current amount read.
/// \param D The location of the data buffer.
/// \param S The size of the data buffer.
/// \param P The current position in the data buffer. Will be updated to reflect new position.
/// \return True if count bytes are read succesfully, false otherwise.
bool FLV::Tag::MemReadUntil(char * buffer, unsigned int count, unsigned int & sofar, char * D, unsigned int S, unsigned int & P){
if (sofar >= count){return true;}
int r = 0;
if (P+(count-sofar) > S){r = S-P;}else{r = count-sofar;}
memcpy(buffer+sofar, D+P, r);
P += r;
sofar += r;
if (sofar >= count){return true;}
return false;
}//Tag::MemReadUntil
/// Try to load a tag from a data buffer in memory.
/// This is a stateful function - if fed incorrect data, it will most likely never return true again!
/// While this function returns false, the Tag might not contain valid data.
/// \param D The location of the data buffer.
/// \param S The size of the data buffer.
/// \param P The current position in the data buffer. Will be updated to reflect new position.
/// \return True if a whole tag is succesfully read, false otherwise.
bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){
if (buf < 15){data = (char*)realloc(data, 15); buf = 15;}
if (done){
//read a header
if (MemReadUntil(data, 11, sofar, D, S, P)){
//if its a correct FLV header, throw away and read tag header
if (FLV::is_header(data)){
if (MemReadUntil(data, 13, sofar, D, S, P)){
if (FLV::check_header(data)){
sofar = 0;
memcpy(FLV::Header, data, 13);
}else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;}
}
}else{
//if a tag header, calculate length and read tag body
len = data[3] + 15;
len += (data[2] << 8);
len += (data[1] << 16);
if (buf < len){data = (char*)realloc(data, len); buf = len;}
if (data[0] > 0x12){
data[0] += 32;
FLV::Parse_Error = true;
Error_Str = "Invalid Tag received (";
Error_Str += data[0];
Error_Str += ").";
return false;
}
done = false;
}
}
}else{
//read tag body
if (MemReadUntil(data, len, sofar, D, S, P)){
//calculate keyframeness, next time read header again, return true
if ((data[0] == 0x09) && (((data[11] & 0xf0) >> 4) == 1)){isKeyframe = true;}else{isKeyframe = false;}
done = true;
sofar = 0;
return true;
}
}
return false;
}//Tag::MemLoader
/// Helper function for FLV::SockLoader.
/// This function will try to read count bytes from socket sock into buffer.
/// This function should be called repeatedly until true.
/// \param buffer The target buffer.
/// \param count Amount of bytes to read.
/// \param sofar Current amount read.
/// \param sock Socket to read from.
/// \return True if count bytes are read succesfully, false otherwise.
bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & sofar, Socket::Connection & sock){
if (sofar >= count){return true;}
int r = 0;
r = sock.iread(buffer + sofar,count-sofar);
sofar += r;
if (sofar >= count){return true;}
return false;
}//Tag::SockReadUntil
/// Try to load a tag from a socket.
/// This is a stateful function - if fed incorrect data, it will most likely never return true again!
/// While this function returns false, the Tag might not contain valid data.
/// \param sock The socket to read from.
/// \return True if a whole tag is succesfully read, false otherwise.
bool FLV::Tag::SockLoader(Socket::Connection sock){
if (buf < 15){data = (char*)realloc(data, 15); buf = 15;}
if (done){
if (SockReadUntil(data, 11, sofar, sock)){
//if its a correct FLV header, throw away and read tag header
if (FLV::is_header(data)){
if (SockReadUntil(data, 13, sofar, sock)){
if (FLV::check_header(data)){
sofar = 0;
memcpy(FLV::Header, data, 13);
}else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;}
}
}else{
//if a tag header, calculate length and read tag body
len = data[3] + 15;
len += (data[2] << 8);
len += (data[1] << 16);
if (buf < len){data = (char*)realloc(data, len); buf = len;}
if (data[0] > 0x12){
data[0] += 32;
FLV::Parse_Error = true;
Error_Str = "Invalid Tag received (";
Error_Str += data[0];
Error_Str += ").";
return false;
}
done = false;
}
}
}else{
//read tag body
if (SockReadUntil(data, len, sofar, sock)){
//calculate keyframeness, next time read header again, return true
if ((data[0] == 0x09) && (((data[11] & 0xf0) >> 4) == 1)){isKeyframe = true;}else{isKeyframe = false;}
done = true;
sofar = 0;
return true;
}
}
return false;
}//Tag::SockLoader
/// Try to load a tag from a socket.
/// This is a stateful function - if fed incorrect data, it will most likely never return true again!
/// While this function returns false, the Tag might not contain valid data.
/// \param sock The socket to read from.
/// \return True if a whole tag is succesfully read, false otherwise.
bool FLV::Tag::SockLoader(int sock){
return SockLoader(Socket::Connection(sock));
}//Tag::SockLoader
/// Helper function for FLV::FileLoader.
/// This function will try to read count bytes from file f into buffer.
/// This function should be called repeatedly until true.
/// \param buffer The target buffer.
/// \param count Amount of bytes to read.
/// \param sofar Current amount read.
/// \param f File to read from.
/// \return True if count bytes are read succesfully, false otherwise.
bool FLV::Tag::FileReadUntil(char * buffer, unsigned int count, unsigned int & sofar, FILE * f){
if (sofar >= count){return true;}
int r = 0;
r = fread(buffer + sofar,1,count-sofar,f);
if (r < 0){FLV::Parse_Error = true; Error_Str = "File reading error."; return false;}
sofar += r;
if (sofar >= count){return true;}
return false;
}
/// Try to load a tag from a file.
/// This is a stateful function - if fed incorrect data, it will most likely never return true again!
/// While this function returns false, the Tag might not contain valid data.
/// \param f The file to read from.
/// \return True if a whole tag is succesfully read, false otherwise.
bool FLV::Tag::FileLoader(FILE * f){
int preflags = fcntl(fileno(f), F_GETFL, 0);
int postflags = preflags | O_NONBLOCK;
fcntl(fileno(f), F_SETFL, postflags);
if (buf < 15){data = (char*)realloc(data, 15); buf = 15;}
if (done){
//read a header
if (FileReadUntil(data, 11, sofar, f)){
//if its a correct FLV header, throw away and read tag header
if (FLV::is_header(data)){
if (FileReadUntil(data, 13, sofar, f)){
if (FLV::check_header(data)){
sofar = 0;
memcpy(FLV::Header, data, 13);
}else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;}
}
}else{
//if a tag header, calculate length and read tag body
len = data[3] + 15;
len += (data[2] << 8);
len += (data[1] << 16);
if (buf < len){data = (char*)realloc(data, len); buf = len;}
if (data[0] > 0x12){
data[0] += 32;
FLV::Parse_Error = true;
Error_Str = "Invalid Tag received (";
Error_Str += data[0];
Error_Str += ").";
return false;
}
done = false;
}
}
}else{
//read tag body
if (FileReadUntil(data, len, sofar, f)){
//calculate keyframeness, next time read header again, return true
if ((data[0] == 0x09) && (((data[11] & 0xf0) >> 4) == 1)){isKeyframe = true;}else{isKeyframe = false;}
done = true;
sofar = 0;
fcntl(fileno(f), F_SETFL, preflags);
return true;
}
}
fcntl(fileno(f), F_SETFL, preflags);
return false;
}//FLV_GetPacket
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;
}

66
lib/flv_tag.h Normal file
View file

@ -0,0 +1,66 @@
/// \file flv_tag.h
/// Holds all headers for the FLV namespace.
#pragma once
#include "socket.h"
#include "dtsc.h"
#include <string>
//forward declaration of RTMPStream::Chunk to avoid circular dependencies.
namespace RTMPStream{
class Chunk;
};
/// This namespace holds all FLV-parsing related functionality.
namespace FLV {
//variables
extern char Header[13]; ///< Holds the last FLV header parsed.
extern bool Parse_Error; ///< This variable is set to true if a problem is encountered while parsing the FLV.
extern std::string Error_Str; ///< This variable is set if a problem is encountered while parsing the FLV.
//functions
bool check_header(char * header); ///< Checks a FLV Header for validness.
bool is_header(char * header); ///< Checks the first 3 bytes for the string "FLV".
/// This class is used to hold, work with and get information about a single FLV tag.
class Tag {
public:
int len; ///< Actual length of tag.
bool isKeyframe; ///< True if current tag is a video keyframe.
char * data; ///< Pointer to tag buffer.
bool needsInitData(); ///< True if this media type requires init data.
bool isInitData(); ///< True if current tag is init data for this media type.
std::string tagType(); ///< Returns a std::string describing the tag in detail.
unsigned int tagTime(); ///< Returns the 32-bit timestamp of this tag.
void tagTime(unsigned int T); ///< Sets the 32-bit timestamp of this tag.
Tag(); ///< Constructor for a new, empty, tag.
Tag(const Tag& O); ///< Copy constructor, copies the contents of an existing tag.
Tag & operator= (const Tag& O); ///< Assignment operator - works exactly like the copy constructor.
Tag(const RTMPStream::Chunk& O); ///<Copy constructor from a RTMP chunk.
//loader functions
bool ChunkLoader(const RTMPStream::Chunk& O);
bool DTSCLoader(DTSC::Stream & S);
bool DTSCVideoInit(DTSC::Stream & S);
bool DTSCAudioInit(DTSC::Stream & S);
bool DTSCMetaInit(DTSC::Stream & S);
DTSC::DTMI toDTSC(DTSC::DTMI & metadata);
bool MemLoader(char * D, unsigned int S, unsigned int & P);
bool SockLoader(int sock);
bool SockLoader(Socket::Connection sock);
bool FileLoader(FILE * f);
protected:
int buf; ///< Maximum length of buffer space.
bool done; ///< Body reading done?
unsigned int sofar; ///< How many bytes are read sofar?
void setLen();
//loader helper functions
bool MemReadUntil(char * buffer, unsigned int count, unsigned int & sofar, char * D, unsigned int S, unsigned int & P);
bool SockReadUntil(char * buffer, unsigned int count, unsigned int & sofar, Socket::Connection & sock);
bool FileReadUntil(char * buffer, unsigned int count, unsigned int & sofar, FILE * f);
//DTSC writer helpers
void Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, std::string val);
void Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, uint64_t val);
bool Meta_Has(DTSC::DTMI & meta, std::string cat, std::string elem);
};//Tag
};//FLV namespace

324
lib/http_parser.cpp Normal file
View file

@ -0,0 +1,324 @@
/// \file http_parser.cpp
/// Holds all code for the HTTP namespace.
#include "http_parser.h"
/// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing.
/// All this constructor does is call HTTP::Parser::Clean().
HTTP::Parser::Parser(){Clean();}
/// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing usage.
void HTTP::Parser::Clean(){
seenHeaders = false;
seenReq = false;
method = "GET";
url = "/";
protocol = "HTTP/1.1";
body.clear();
length = 0;
headers.clear();
vars.clear();
}
/// Re-initializes the HTTP::Parser, leaving the internal data buffer alone, then tries to parse a new request or response.
/// Does the same as HTTP::Parser::Clean(), then returns HTTP::Parser::parse().
bool HTTP::Parser::CleanForNext(){
Clean();
return parse();
}
/// Returns a string containing a valid HTTP 1.0 or 1.1 request, ready for sending.
/// The request is build from internal variables set before this call is made.
/// To be precise, method, url, protocol, headers and body are used.
/// \return A string containing a valid HTTP 1.0 or 1.1 request, ready for sending.
std::string HTTP::Parser::BuildRequest(){
/// \todo Include GET/POST variable parsing?
std::map<std::string, std::string>::iterator it;
std::string tmp = method+" "+url+" "+protocol+"\n";
for (it=headers.begin(); it != headers.end(); it++){
tmp += (*it).first + ": " + (*it).second + "\n";
}
tmp += "\n" + body + "\n";
return tmp;
}
/// Returns a string containing a valid HTTP 1.0 or 1.1 response, ready for sending.
/// The response is partly build from internal variables set before this call is made.
/// To be precise, protocol, headers and body are used.
/// \param code The HTTP response code. Usually you want 200.
/// \param message The HTTP response message. Usually you want "OK".
/// \return A string containing a valid HTTP 1.0 or 1.1 response, ready for sending.
std::string HTTP::Parser::BuildResponse(std::string code, std::string message){
/// \todo Include GET/POST variable parsing?
std::map<std::string, std::string>::iterator it;
std::string tmp = protocol+" "+code+" "+message+"\n";
for (it=headers.begin(); it != headers.end(); it++){
tmp += (*it).first + ": " + (*it).second + "\n";
}
tmp += "\n";
tmp += body;
return tmp;
}
/// Trims any whitespace at the front or back of the string.
/// Used when getting/setting headers.
/// \param s The string to trim. The string itself will be changed, not returned.
void HTTP::Parser::Trim(std::string & s){
size_t startpos = s.find_first_not_of(" \t");
size_t endpos = s.find_last_not_of(" \t");
if ((std::string::npos == startpos) || (std::string::npos == endpos)){s = "";}else{s = s.substr(startpos, endpos-startpos+1);}
}
/// Function that sets the body of a response or request, along with the correct Content-Length header.
/// \param s The string to set the body to.
void HTTP::Parser::SetBody(std::string s){
body = s;
SetHeader("Content-Length", s.length());
}
/// Function that sets the body of a response or request, along with the correct Content-Length header.
/// \param buffer The buffer data to set the body to.
/// \param len Length of the buffer data.
void HTTP::Parser::SetBody(char * buffer, int len){
body = "";
body.append(buffer, len);
SetHeader("Content-Length", len);
}
/// Returns header i, if set.
std::string HTTP::Parser::GetHeader(std::string i){return headers[i];}
/// Returns POST variable i, if set.
std::string HTTP::Parser::GetVar(std::string i){return vars[i];}
/// Sets header i to string value v.
void HTTP::Parser::SetHeader(std::string i, std::string v){
Trim(i);
Trim(v);
headers[i] = v;
}
/// Sets header i to integer value v.
void HTTP::Parser::SetHeader(std::string i, int v){
Trim(i);
char val[128];
sprintf(val, "%i", v);
headers[i] = val;
}
/// Sets POST variable i to string value v.
void HTTP::Parser::SetVar(std::string i, std::string v){
Trim(i);
Trim(v);
//only set if there is actually a key
if(!i.empty()){
vars[i] = v;
}
}
/// Attempt to read a whole HTTP request or response from Socket::Connection.
/// \param sock The socket to use.
/// \param nonblock When true, will not block even if the socket is blocking.
/// \return True of a whole request or response was read, false otherwise.
bool HTTP::Parser::Read(Socket::Connection & sock, bool nonblock){
if (nonblock && (sock.ready() < 1)){return parse();}
sock.read(HTTPbuffer);
return parse();
}//HTTPReader::ReadSocket
/// Reads a full set of HTTP responses/requests from file F.
/// \return Always false. Use HTTP::Parser::CleanForNext() to parse the contents of the file.
bool HTTP::Parser::Read(FILE * F){
//returned true als hele http packet gelezen is
int b = 1;
char buffer[500];
while (b > 0){
b = fread(buffer, 1, 500, F);
HTTPbuffer.append(buffer, b);
}
return false;
}//HTTPReader::ReadSocket
/// Attempt to read a whole HTTP response or request from the internal data buffer.
/// If succesful, fills its own fields with the proper data and removes the response/request
/// from the internal data buffer.
/// \return True on success, false otherwise.
bool HTTP::Parser::parse(){
size_t f;
std::string tmpA, tmpB, tmpC;
while (HTTPbuffer != ""){
if (!seenHeaders){
f = HTTPbuffer.find('\n');
if (f == std::string::npos) return false;
tmpA = HTTPbuffer.substr(0, f);
HTTPbuffer.erase(0, f+1);
while (tmpA.find('\r') != std::string::npos){tmpA.erase(tmpA.find('\r'));}
if (!seenReq){
seenReq = true;
f = tmpA.find(' ');
if (f != std::string::npos){method = tmpA.substr(0, f); tmpA.erase(0, f+1);}
f = tmpA.find(' ');
if (f != std::string::npos){url = tmpA.substr(0, f); tmpA.erase(0, f+1);}
f = tmpA.find(' ');
if (f != std::string::npos){protocol = tmpA.substr(0, f); tmpA.erase(0, f+1);}
if (url.find('?') != std::string::npos){
parseVars(url.substr(url.find('?')+1)); //parse GET variables
}
}else{
if (tmpA.size() == 0){
seenHeaders = true;
if (GetHeader("Content-Length") != ""){length = atoi(GetHeader("Content-Length").c_str());}
}else{
f = tmpA.find(':');
if (f == std::string::npos) continue;
tmpB = tmpA.substr(0, f);
tmpC = tmpA.substr(f+1);
SetHeader(tmpB, tmpC);
}
}
}
if (seenHeaders){
if (length > 0){
if (HTTPbuffer.length() >= length){
body = HTTPbuffer.substr(0, length);
parseVars(body); //parse POST variables
HTTPbuffer.erase(0, length);
return true;
}else{
return false;
}
}else{
return true;
}
}
}
return false; //we should never get here...
}//HTTPReader::parse
/// Sends data as response to conn.
/// The response is automatically first build using HTTP::Parser::BuildResponse().
/// \param conn The Socket::Connection to send the response over.
/// \param code The HTTP response code. Usually you want 200.
/// \param message The HTTP response message. Usually you want "OK".
void HTTP::Parser::SendResponse(Socket::Connection & conn, std::string code, std::string message){
std::string tmp = BuildResponse(code, message);
conn.write(tmp);
}
#include <iostream>
/// Parses GET or POST-style variable data.
/// Saves to internal variable structure using HTTP::Parser::SetVar.
void HTTP::Parser::parseVars(std::string data){
std::string varname;
std::string varval;
// position where a part start (e.g. after &)
size_t pos = 0;
while (pos < data.length()){
size_t nextpos = data.find('&', pos);
if (nextpos == std::string::npos){
nextpos = data.length();
}
size_t eq_pos = data.find('=', pos);
if (eq_pos < nextpos){
// there is a key and value
varname = data.substr(pos, eq_pos - pos);
varval = data.substr(eq_pos + 1, nextpos - eq_pos - 1);
}else{
// no value, only a key
varname = data.substr(pos, nextpos - pos);
varval.clear();
}
SetVar(urlunescape(varname), urlunescape(varval));
if (nextpos == std::string::npos){
// in case the string is gigantic
break;
}
// erase &
pos = nextpos + 1;
}
}
/// Sends data as HTTP/1.1 bodypart to conn.
/// HTTP/1.1 chunked encoding is automatically applied if needed.
/// \param conn The Socket::Connection to send the part over.
/// \param buffer The buffer to send.
/// \param len The length of the buffer.
void HTTP::Parser::SendBodyPart(Socket::Connection & conn, char * buffer, int len){
std::string tmp;
tmp.append(buffer, len);
SendBodyPart(conn, tmp);
}
/// Sends data as HTTP/1.1 bodypart to conn.
/// HTTP/1.1 chunked encoding is automatically applied if needed.
/// \param conn The Socket::Connection to send the part over.
/// \param bodypart The data to send.
void HTTP::Parser::SendBodyPart(Socket::Connection & conn, std::string bodypart){
if (protocol == "HTTP/1.1"){
static char len[10];
int sizelen;
sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size());
conn.write(len, sizelen);
conn.write(bodypart);
conn.write(len+sizelen-2, 2);
}else{
conn.write(bodypart);
}
}
/// Unescapes URLencoded std::string data.
std::string HTTP::Parser::urlunescape(const std::string & in){
std::string out;
for (unsigned int i = 0; i < in.length(); ++i){
if (in[i] == '%'){
char tmp = 0;
++i;
if (i < in.length()){
tmp = unhex(in[i]) << 4;
}
++i;
if (i < in.length()){
tmp += unhex(in[i]);
}
out += tmp;
} else {
if (in[i] == '+'){out += ' ';}else{out += in[i];}
}
}
return out;
}
/// Helper function for urlunescape.
/// Takes a single char input and outputs its integer hex value.
int HTTP::Parser::unhex(char c){
return( c >= '0' && c <= '9' ? c - '0' : c >= 'A' && c <= 'F' ? c - 'A' + 10 : c - 'a' + 10 );
}
/// URLencodes std::string data.
std::string HTTP::Parser::urlencode(const std::string &c){
std::string escaped="";
int max = c.length();
for(int i=0; i<max; i++){
if (('0'<=c[i] && c[i]<='9') || ('a'<=c[i] && c[i]<='z') || ('A'<=c[i] && c[i]<='Z') || (c[i]=='~' || c[i]=='!' || c[i]=='*' || c[i]=='(' || c[i]==')' || c[i]=='\'')){
escaped.append( &c[i], 1);
}else{
escaped.append("%");
escaped.append(hex(c[i]));
}
}
return escaped;
}
/// Helper function for urlescape.
/// Encodes a character as two hex digits.
std::string HTTP::Parser::hex(char dec){
char dig1 = (dec&0xF0)>>4;
char dig2 = (dec&0x0F);
if (dig1<= 9) dig1+=48;
if (10<= dig1 && dig1<=15) dig1+=97-10;
if (dig2<= 9) dig2+=48;
if (10<= dig2 && dig2<=15) dig2+=97-10;
std::string r;
r.append(&dig1, 1);
r.append(&dig2, 1);
return r;
}

52
lib/http_parser.h Normal file
View file

@ -0,0 +1,52 @@
/// \file http_parser.h
/// Holds all headers for the HTTP namespace.
#pragma once
#include <map>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include "socket.h"
/// Holds all HTTP processing related code.
namespace HTTP{
/// Simple class for reading and writing HTTP 1.0 and 1.1.
class Parser{
public:
Parser();
bool Read(Socket::Connection & sock, bool nonblock = true);
bool Read(FILE * F);
std::string GetHeader(std::string i);
std::string GetVar(std::string i);
void SetHeader(std::string i, std::string v);
void SetHeader(std::string i, int v);
void SetVar(std::string i, std::string v);
void SetBody(std::string s);
void SetBody(char * buffer, int len);
std::string BuildRequest();
std::string BuildResponse(std::string code, std::string message);
void SendResponse(Socket::Connection & conn, std::string code, std::string message);
void SendBodyPart(Socket::Connection & conn, char * buffer, int len);
void SendBodyPart(Socket::Connection & conn, std::string bodypart);
void Clean();
bool CleanForNext();
static std::string urlunescape(const std::string & in);
static std::string urlencode(const std::string & in);
std::string body;
std::string method;
std::string url;
std::string protocol;
unsigned int length;
private:
bool seenHeaders;
bool seenReq;
bool parse();
void parseVars(std::string data);
std::string HTTPbuffer;
std::map<std::string, std::string> headers;
std::map<std::string, std::string> vars;
void Trim(std::string & s);
static int unhex(char c);
static std::string hex(char dec);
};//HTTP::Parser class
};//HTTP namespace

455
lib/json.cpp Normal file
View file

@ -0,0 +1,455 @@
/// \file json.cpp Holds all JSON-related code.
#include "json.h"
#include <sstream>
#include <fstream>
#include <stdlib.h>
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;
}
/// Skips an std::istream forward until any of the following characters is seen: ,]}
void JSON::Value::skipToEnd(std::istream & fromstream){
while (fromstream.good()){
char peek = fromstream.peek();
if (peek == ','){return;}
if (peek == ']'){return;}
if (peek == '}'){return;}
peek = fromstream.get();
}
}
/// 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_array = false;
while (fromstream.good()){
int c = fromstream.peek();
switch (c){
case '{':
reading_object = 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){
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_array){
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':
skipToEnd(fromstream);
myType = BOOL;
intVal = 1;
return;
break;
case 'f':
case 'F':
skipToEnd(fromstream);
myType = BOOL;
intVal = 0;
return;
break;
case 'n':
case 'N':
skipToEnd(fromstream);
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 convertable.
JSON::Value::operator long long int(){
if (myType == INTEGER){
return intVal;
}
if (myType == STRING){
return atoll(strVal.c_str());
}
return 0;
}
/// 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 = "[";
if (arrVal.size() > 0){
for (ArrIter it = ArrBegin(); it != ArrEnd(); it++){
tmp += it->toString();
if (it + 1 != ArrEnd()){tmp += ",";}
}
}
tmp += "]";
return tmp;
break;
}
case OBJECT: {
std::string tmp2 = "{";
if (objVal.size() > 0){
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::ifstream File;
File.open(filename.c_str());
JSON::Value ret(File);
File.close();
return ret;
}

75
lib/json.h Normal file
View file

@ -0,0 +1,75 @@
/// \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);
static void skipToEnd(std::istream & fromstream);
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);
};

362
lib/md5.cpp Normal file
View file

@ -0,0 +1,362 @@
/* MD5
converted to C++ class by Frank Thilo (thilo@unix-ag.org)
for bzflag (http://www.bzflag.org)
based on:
md5.h and md5.c
reference implemantion of RFC 1321
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.
License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.
License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
*/
/* interface header */
#include "md5.h"
/* system implementation headers */
#include <stdio.h>
// Constants for MD5Transform routine.
#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21
///////////////////////////////////////////////
// F, G, H and I are basic MD5 functions.
inline MD5::uint4 MD5::F(uint4 x, uint4 y, uint4 z) {
return x&y | ~x&z;
}
inline MD5::uint4 MD5::G(uint4 x, uint4 y, uint4 z) {
return x&z | y&~z;
}
inline MD5::uint4 MD5::H(uint4 x, uint4 y, uint4 z) {
return x^y^z;
}
inline MD5::uint4 MD5::I(uint4 x, uint4 y, uint4 z) {
return y ^ (x | ~z);
}
// rotate_left rotates x left n bits.
inline MD5::uint4 MD5::rotate_left(uint4 x, int n) {
return (x << n) | (x >> (32-n));
}
// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
// Rotation is separate from addition to prevent recomputation.
inline void MD5::FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
a = rotate_left(a+ F(b,c,d) + x + ac, s) + b;
}
inline void MD5::GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
a = rotate_left(a + G(b,c,d) + x + ac, s) + b;
}
inline void MD5::HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
a = rotate_left(a + H(b,c,d) + x + ac, s) + b;
}
inline void MD5::II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {
a = rotate_left(a + I(b,c,d) + x + ac, s) + b;
}
//////////////////////////////////////////////
// default ctor, just initailize
MD5::MD5()
{
init();
}
//////////////////////////////////////////////
// nifty shortcut ctor, compute MD5 for string and finalize it right away
MD5::MD5(const std::string &text)
{
init();
update(text.c_str(), text.length());
finalize();
}
//////////////////////////////
void MD5::init()
{
finalized=false;
count[0] = 0;
count[1] = 0;
// load magic initialization constants.
state[0] = 0x67452301;
state[1] = 0xefcdab89;
state[2] = 0x98badcfe;
state[3] = 0x10325476;
}
//////////////////////////////
// decodes input (unsigned char) into output (uint4). Assumes len is a multiple of 4.
void MD5::decode(uint4 output[], const uint1 input[], size_type len)
{
for (unsigned int i = 0, j = 0; j < len; i++, j += 4)
output[i] = ((uint4)input[j]) | (((uint4)input[j+1]) << 8) |
(((uint4)input[j+2]) << 16) | (((uint4)input[j+3]) << 24);
}
//////////////////////////////
// encodes input (uint4) into output (unsigned char). Assumes len is
// a multiple of 4.
void MD5::encode(uint1 output[], const uint4 input[], size_type len)
{
for (size_type i = 0, j = 0; j < len; i++, j += 4) {
output[j] = input[i] & 0xff;
output[j+1] = (input[i] >> 8) & 0xff;
output[j+2] = (input[i] >> 16) & 0xff;
output[j+3] = (input[i] >> 24) & 0xff;
}
}
//////////////////////////////
// apply MD5 algo on a block
void MD5::transform(const uint1 block[blocksize])
{
uint4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
decode (x, block, blocksize);
/* Round 1 */
FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
/* Round 2 */
GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */
GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
/* Round 3 */
HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
/* Round 4 */
II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
// Zeroize sensitive information.
memset(x, 0, sizeof x);
}
//////////////////////////////
// MD5 block update operation. Continues an MD5 message-digest
// operation, processing another message block
void MD5::update(const unsigned char input[], size_type length)
{
// compute number of bytes mod 64
size_type index = count[0] / 8 % blocksize;
// Update number of bits
if ((count[0] += (length << 3)) < (length << 3))
count[1]++;
count[1] += (length >> 29);
// number of bytes we need to fill in buffer
size_type firstpart = 64 - index;
size_type i;
// transform as many times as possible.
if (length >= firstpart)
{
// fill buffer first, transform
memcpy(&buffer[index], input, firstpart);
transform(buffer);
// transform chunks of blocksize (64 bytes)
for (i = firstpart; i + blocksize <= length; i += blocksize)
transform(&input[i]);
index = 0;
}
else
i = 0;
// buffer remaining input
memcpy(&buffer[index], &input[i], length-i);
}
//////////////////////////////
// for convenience provide a verson with signed char
void MD5::update(const char input[], size_type length)
{
update((const unsigned char*)input, length);
}
//////////////////////////////
// MD5 finalization. Ends an MD5 message-digest operation, writing the
// the message digest and zeroizing the context.
MD5& MD5::finalize()
{
static unsigned char padding[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
if (!finalized) {
// Save number of bits
unsigned char bits[8];
encode(bits, count, 8);
// pad out to 56 mod 64.
size_type index = count[0] / 8 % 64;
size_type padLen = (index < 56) ? (56 - index) : (120 - index);
update(padding, padLen);
// Append length (before padding)
update(bits, 8);
// Store state in digest
encode(digest, state, 16);
// Zeroize sensitive information.
memset(buffer, 0, sizeof buffer);
memset(count, 0, sizeof count);
finalized=true;
}
return *this;
}
//////////////////////////////
// return hex representation of digest as string
std::string MD5::hexdigest() const
{
if (!finalized)
return "";
char buf[33];
for (int i=0; i<16; i++)
sprintf(buf+i*2, "%02x", digest[i]);
buf[32]=0;
return std::string(buf);
}
//////////////////////////////
std::ostream& operator<<(std::ostream& out, MD5 md5)
{
return out << md5.hexdigest();
}
//////////////////////////////
std::string md5(const std::string str)
{
MD5 md5 = MD5(str);
return md5.hexdigest();
}

93
lib/md5.h Normal file
View file

@ -0,0 +1,93 @@
/* MD5
converted to C++ class by Frank Thilo (thilo@unix-ag.org)
for bzflag (http://www.bzflag.org)
based on:
md5.h and md5.c
reference implementation of RFC 1321
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.
License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.
License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
*/
#ifndef BZF_MD5_H
#define BZF_MD5_H
#include <string>
#include <iostream>
#include <cstring>
// a small class for calculating MD5 hashes of strings or byte arrays
// it is not meant to be fast or secure
//
// usage: 1) feed it blocks of uchars with update()
// 2) finalize()
// 3) get hexdigest() string
// or
// MD5(std::string).hexdigest()
//
// assumes that char is 8 bit and int is 32 bit
class MD5
{
public:
typedef unsigned int size_type; // must be 32bit
MD5();
MD5(const std::string& text);
void update(const unsigned char *buf, size_type length);
void update(const char *buf, size_type length);
MD5& finalize();
std::string hexdigest() const;
friend std::ostream& operator<<(std::ostream&, MD5 md5);
private:
void init();
typedef unsigned char uint1; // 8bit
typedef unsigned int uint4; // 32bit
enum {blocksize = 64}; // VC6 won't eat a const static int here
void transform(const uint1 block[blocksize]);
static void decode(uint4 output[], const uint1 input[], size_type len);
static void encode(uint1 output[], const uint4 input[], size_type len);
bool finalized;
uint1 buffer[blocksize]; // bytes that didn't fit in last 64 byte chunk
uint4 count[2]; // 64bit counter for number of bits (lo, hi)
uint4 state[4]; // digest so far
uint1 digest[16]; // the result
// low level logic operations
static inline uint4 F(uint4 x, uint4 y, uint4 z);
static inline uint4 G(uint4 x, uint4 y, uint4 z);
static inline uint4 H(uint4 x, uint4 y, uint4 z);
static inline uint4 I(uint4 x, uint4 y, uint4 z);
static inline uint4 rotate_left(uint4 x, int n);
static inline void FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac);
static inline void GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac);
static inline void HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac);
static inline void II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac);
};
std::string md5(const std::string str);
#endif

361
lib/procs.cpp Normal file
View file

@ -0,0 +1,361 @@
/// \file procs.cpp
/// Contains generic functions for managing processes.
#include "procs.h"
#include <string.h>
#include <sys/types.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 <fcntl.h>
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
std::map<pid_t, std::string> Util::Procs::plist;
bool Util::Procs::handler_set = false;
/// Used internally to capture child signals and update plist.
void Util::Procs::childsig_handler(int signum){
if (signum != SIGCHLD){return;}
pid_t ret = wait(0);
#if DEBUG >= 1
std::string pname = plist[ret];
#endif
plist.erase(ret);
#if DEBUG >= 1
if (isActive(pname)){
std::cerr << "Process " << pname << " part-terminated." << std::endl;
Stop(pname);
}else{
std::cerr << "Process " << pname << " fully terminated." << std::endl;
}
#endif
}
/// Attempts to run the command cmd.
/// Replaces the current process - use after forking first!
/// This function will never return - it will either run the given
/// command or kill itself with return code 42.
void Util::Procs::runCmd(std::string & cmd){
//split cmd into arguments
//supports a maximum of 20 arguments
char * tmp = (char*)cmd.c_str();
char * tmp2 = 0;
char * args[21];
int i = 0;
tmp2 = strtok(tmp, " ");
args[0] = tmp2;
while (tmp2 != 0 && (i < 20)){
tmp2 = strtok(0, " ");
++i;
args[i] = tmp2;
}
if (i == 20){args[20] = 0;}
//execute the command
execvp(args[0], args);
#if DEBUG >= 1
std::cerr << "Error running \"" << cmd << "\": " << strerror(errno) << std::endl;
#endif
_exit(42);
}
/// Starts a new process if the name is not already active.
/// \return 0 if process was not started, process PID otherwise.
/// \arg name Name for this process - only used internally.
/// \arg cmd Commandline for this process.
pid_t Util::Procs::Start(std::string name, std::string cmd){
if (isActive(name)){return getPid(name);}
if (!handler_set){
struct sigaction new_action;
new_action.sa_handler = Util::Procs::childsig_handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGCHLD, &new_action, NULL);
handler_set = true;
}
pid_t ret = fork();
if (ret == 0){
runCmd(cmd);
}else{
if (ret > 0){
#if DEBUG >= 1
std::cerr << "Process " << name << " started, PID " << ret << ": " << cmd << std::endl;
#endif
plist.insert(std::pair<pid_t, std::string>(ret, name));
}else{
#if DEBUG >= 1
std::cerr << "Process " << name << " could not be started. fork() failed." << std::endl;
#endif
return 0;
}
}
return ret;
}
/// Starts two piped processes if the name is not already active.
/// \return 0 if process was not started, sub (sending) process PID otherwise.
/// \arg name Name for this process - only used internally.
/// \arg cmd Commandline for sub (sending) process.
/// \arg cmd2 Commandline for main (receiving) process.
pid_t Util::Procs::Start(std::string name, std::string cmd, std::string cmd2){
if (isActive(name)){return getPid(name);}
if (!handler_set){
struct sigaction new_action;
new_action.sa_handler = Util::Procs::childsig_handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGCHLD, &new_action, NULL);
handler_set = true;
}
int pfildes[2];
if (pipe(pfildes) == -1){
#if DEBUG >= 1
std::cerr << "Process " << name << " could not be started. Pipe creation failed." << std::endl;
#endif
return 0;
}
int devnull = open("/dev/null", O_RDWR);
pid_t ret = fork();
if (ret == 0){
close(pfildes[0]);
dup2(pfildes[1],STDOUT_FILENO);
close(pfildes[1]);
dup2(devnull, STDIN_FILENO);
dup2(devnull, STDERR_FILENO);
runCmd(cmd);
}else{
if (ret > 0){
plist.insert(std::pair<pid_t, std::string>(ret, name));
}else{
#if DEBUG >= 1
std::cerr << "Process " << name << " could not be started. fork() failed." << std::endl;
#endif
close(pfildes[1]);
close(pfildes[0]);
return 0;
}
}
pid_t ret2 = fork();
if (ret2 == 0){
close(pfildes[1]);
dup2(pfildes[0],STDIN_FILENO);
close(pfildes[0]);
dup2(devnull, STDOUT_FILENO);
dup2(devnull, STDERR_FILENO);
runCmd(cmd2);
}else{
if (ret2 > 0){
#if DEBUG >= 1
std::cerr << "Process " << name << " started, PIDs (" << ret << ", " << ret2 << "): " << cmd << " | " << cmd2 << std::endl;
#endif
plist.insert(std::pair<pid_t, std::string>(ret2, name));
}else{
#if DEBUG >= 1
std::cerr << "Process " << name << " could not be started. fork() failed." << std::endl;
#endif
Stop(name);
close(pfildes[1]);
close(pfildes[0]);
return 0;
}
}
close(pfildes[1]);
close(pfildes[0]);
return ret;
}
/// Starts three piped processes if the name is not already active.
/// \return 0 if process was not started, sub (sending) process PID otherwise.
/// \arg name Name for this process - only used internally.
/// \arg cmd Commandline for sub (sending) process.
/// \arg cmd2 Commandline for sub (middle) process.
/// \arg cmd3 Commandline for main (receiving) process.
pid_t Util::Procs::Start(std::string name, std::string cmd, std::string cmd2, std::string cmd3){
if (isActive(name)){return getPid(name);}
if (!handler_set){
struct sigaction new_action;
new_action.sa_handler = Util::Procs::childsig_handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGCHLD, &new_action, NULL);
handler_set = true;
}
int pfildes[2];
int pfildes2[2];
if (pipe(pfildes) == -1){
#if DEBUG >= 1
std::cerr << "Process " << name << " could not be started. Pipe creation failed." << std::endl;
#endif
return 0;
}
if (pipe(pfildes2) == -1){
#if DEBUG >= 1
std::cerr << "Process " << name << " could not be started. Pipe creation failed." << std::endl;
#endif
return 0;
}
int devnull = open("/dev/null", O_RDWR);
pid_t ret = fork();
if (ret == 0){
close(pfildes[0]);
dup2(pfildes[1],STDOUT_FILENO);
close(pfildes[1]);
dup2(devnull, STDIN_FILENO);
dup2(devnull, STDERR_FILENO);
close(pfildes2[1]);
close(pfildes2[0]);
runCmd(cmd);
}else{
if (ret > 0){
plist.insert(std::pair<pid_t, std::string>(ret, name));
}else{
#if DEBUG >= 1
std::cerr << "Process " << name << " could not be started. fork() failed." << std::endl;
#endif
close(pfildes[1]);
close(pfildes[0]);
close(pfildes2[1]);
close(pfildes2[0]);
return 0;
}
}
pid_t ret2 = fork();
if (ret2 == 0){
close(pfildes[1]);
close(pfildes2[0]);
dup2(pfildes[0],STDIN_FILENO);
close(pfildes[0]);
dup2(pfildes2[1],STDOUT_FILENO);
close(pfildes2[1]);
dup2(devnull, STDERR_FILENO);
runCmd(cmd2);
}else{
if (ret2 > 0){
#if DEBUG >= 1
std::cerr << "Process " << name << " started, PIDs (" << ret << ", " << ret2 << "): " << cmd << " | " << cmd2 << std::endl;
#endif
plist.insert(std::pair<pid_t, std::string>(ret2, name));
}else{
#if DEBUG >= 1
std::cerr << "Process " << name << " could not be started. fork() failed." << std::endl;
#endif
Stop(name);
close(pfildes[1]);
close(pfildes[0]);
close(pfildes2[1]);
close(pfildes2[0]);
return 0;
}
}
close(pfildes[1]);
close(pfildes[0]);
pid_t ret3 = fork();
if (ret3 == 0){
close(pfildes[1]);
close(pfildes[0]);
close(pfildes2[1]);
dup2(pfildes2[0],STDIN_FILENO);
close(pfildes2[0]);
dup2(devnull, STDOUT_FILENO);
dup2(devnull, STDERR_FILENO);
runCmd(cmd3);
}else{
if (ret3 > 0){
#if DEBUG >= 1
std::cerr << "Process " << name << " started, PIDs (" << ret << ", " << ret2 << ", " << ret3 << "): " << cmd << " | " << cmd2 << " | " << cmd3 << std::endl;
#endif
plist.insert(std::pair<pid_t, std::string>(ret3, name));
}else{
#if DEBUG >= 1
std::cerr << "Process " << name << " could not be started. fork() failed." << std::endl;
#endif
Stop(name);
close(pfildes[1]);
close(pfildes[0]);
close(pfildes2[1]);
close(pfildes2[0]);
return 0;
}
}
return ret3;
}
/// Stops the named process, if running.
/// \arg name (Internal) name of process to stop
void Util::Procs::Stop(std::string name){
int max = 5;
while (isActive(name)){
Stop(getPid(name));
max--;
if (max <= 0){return;}
}
}
/// Stops the process with this pid, if running.
/// \arg name The PID of the process to stop.
void Util::Procs::Stop(pid_t name){
if (isActive(name)){
kill(name, SIGTERM);
}
}
/// (Attempts to) stop all running child processes.
void Util::Procs::StopAll(){
std::map<pid_t, std::string>::iterator it;
for (it = plist.begin(); it != plist.end(); it++){
Stop((*it).first);
}
}
/// Returns the number of active child processes.
int Util::Procs::Count(){
return plist.size();
}
/// Returns true if a process by this name is currently active.
bool Util::Procs::isActive(std::string name){
std::map<pid_t, std::string>::iterator it;
for (it = plist.begin(); it != plist.end(); it++){
if ((*it).second == name){return true;}
}
return false;
}
/// Returns true if a process with this PID is currently active.
bool Util::Procs::isActive(pid_t name){
return (plist.count(name) == 1);
}
/// Gets PID for this named process, if active.
/// \return NULL if not active, process PID otherwise.
pid_t Util::Procs::getPid(std::string name){
std::map<pid_t, std::string>::iterator it;
for (it = plist.begin(); it != plist.end(); it++){
if ((*it).second == name){return (*it).first;}
}
return 0;
}
/// Gets name for this process PID, if active.
/// \return Empty string if not active, name otherwise.
std::string Util::Procs::getName(pid_t name){
if (plist.count(name) == 1){
return plist[name];
}
return "";
}

32
lib/procs.h Normal file
View file

@ -0,0 +1,32 @@
/// \file procs.h
/// Contains generic function headers for managing processes.
#include <unistd.h>
#include <string>
#include <map>
/// Contains utility code, not directly related to streaming media
namespace Util{
/// Deals with spawning, monitoring and stopping child processes
class Procs{
private:
static std::map<pid_t, std::string> plist; ///< Holds active processes
static bool handler_set; ///< If true, the sigchld handler has been setup.
static void childsig_handler(int signum);
static void runCmd(std::string & cmd);
public:
static pid_t Start(std::string name, std::string cmd);
static pid_t Start(std::string name, std::string cmd, std::string cmd2);
static pid_t Start(std::string name, std::string cmd, std::string cmd2, std::string cmd3);
static void Stop(std::string name);
static void Stop(pid_t name);
static void StopAll();
static int Count();
static bool isActive(std::string name);
static bool isActive(pid_t name);
static pid_t getPid(std::string name);
static std::string getName(pid_t name);
};
};

469
lib/rtmpchunks.cpp Normal file
View file

@ -0,0 +1,469 @@
/// \file rtmpchunks.cpp
/// Holds all code for the RTMPStream namespace.
#include "rtmpchunks.h"
#include "flv_tag.h"
#include "crypto.h"
char versionstring[] = "WWW.DDVTECH.COM "; ///< String that is repeated in the RTMP handshake
std::string RTMPStream::handshake_in; ///< Input for the handshake.
std::string RTMPStream::handshake_out;///< Output for the handshake.
/// Gets the current system time in milliseconds.
unsigned int RTMPStream::getNowMS(){
timeval t;
gettimeofday(&t, 0);
return t.tv_sec + t.tv_usec/1000;
}//RTMPStream::getNowMS
unsigned int RTMPStream::chunk_rec_max = 128;
unsigned int RTMPStream::chunk_snd_max = 128;
unsigned int RTMPStream::rec_window_size = 2500000;
unsigned int RTMPStream::snd_window_size = 2500000;
unsigned int RTMPStream::rec_window_at = 0;
unsigned int RTMPStream::snd_window_at = 0;
unsigned int RTMPStream::rec_cnt = 0;
unsigned int RTMPStream::snd_cnt = 0;
timeval RTMPStream::lastrec;
unsigned int RTMPStream::firsttime;
/// Holds the last sent chunk for every msg_id.
std::map<unsigned int, RTMPStream::Chunk> RTMPStream::Chunk::lastsend;
/// Holds the last received chunk for every msg_id.
std::map<unsigned int, RTMPStream::Chunk> RTMPStream::Chunk::lastrecv;
/// Packs up the chunk for sending over the network.
/// \warning Do not call if you are not actually sending the resulting data!
/// \returns A std::string ready to be sent.
std::string RTMPStream::Chunk::Pack(){
std::string output = "";
RTMPStream::Chunk prev = lastsend[cs_id];
unsigned int tmpi;
unsigned char chtype = 0x00;
//timestamp -= firsttime;
if ((prev.msg_type_id > 0) && (prev.cs_id == cs_id)){
if (msg_stream_id == prev.msg_stream_id){
chtype = 0x40;//do not send msg_stream_id
if (len == prev.len){
if (msg_type_id == prev.msg_type_id){
chtype = 0x80;//do not send len and msg_type_id
if (timestamp == prev.timestamp){
chtype = 0xC0;//do not send timestamp
}
}
}
}
//override - we always sent type 0x00 if the timestamp has decreased since last chunk in this channel
if (timestamp < prev.timestamp){chtype = 0x00;}
}
if (cs_id <= 63){
output += (unsigned char)(chtype | cs_id);
}else{
if (cs_id <= 255+64){
output += (unsigned char)(chtype | 0);
output += (unsigned char)(cs_id - 64);
}else{
output += (unsigned char)(chtype | 1);
output += (unsigned char)((cs_id - 64) % 256);
output += (unsigned char)((cs_id - 64) / 256);
}
}
unsigned int ntime = 0;
if (chtype != 0xC0){
//timestamp or timestamp diff
if (chtype == 0x00){
tmpi = timestamp;
}else{
tmpi = timestamp - prev.timestamp;
}
if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;}
output += (unsigned char)((tmpi >> 16) & 0xff);
output += (unsigned char)((tmpi >> 8) & 0xff);
output += (unsigned char)(tmpi & 0xff);
if (chtype != 0x80){
//len
tmpi = len;
output += (unsigned char)((tmpi >> 16) & 0xff);
output += (unsigned char)((tmpi >> 8) & 0xff);
output += (unsigned char)(tmpi & 0xff);
//msg type id
output += (unsigned char)msg_type_id;
if (chtype != 0x40){
//msg stream id
output += (unsigned char)(msg_stream_id % 256);
output += (unsigned char)(msg_stream_id / 256);
output += (unsigned char)(msg_stream_id / (256*256));
output += (unsigned char)(msg_stream_id / (256*256*256));
}
}
}
//support for 0x00ffffff timestamps
if (ntime){
output += (unsigned char)(ntime & 0xff);
output += (unsigned char)((ntime >> 8) & 0xff);
output += (unsigned char)((ntime >> 16) & 0xff);
output += (unsigned char)((ntime >> 24) & 0xff);
}
len_left = 0;
while (len_left < len){
tmpi = len - len_left;
if (tmpi > RTMPStream::chunk_snd_max){tmpi = RTMPStream::chunk_snd_max;}
output.append(data, len_left, tmpi);
len_left += tmpi;
if (len_left < len){
if (cs_id <= 63){
output += (unsigned char)(0xC0 + cs_id);
}else{
if (cs_id <= 255+64){
output += (unsigned char)(0xC0);
output += (unsigned char)(cs_id - 64);
}else{
output += (unsigned char)(0xC1);
output += (unsigned char)((cs_id - 64) % 256);
output += (unsigned char)((cs_id - 64) / 256);
}
}
}
}
lastsend[cs_id] = *this;
RTMPStream::snd_cnt += output.size();
return output;
}//SendChunk
/// Default contructor, creates an empty chunk with all values initialized to zero.
RTMPStream::Chunk::Chunk(){
cs_id = 0;
timestamp = 0;
len = 0;
real_len = 0;
len_left = 0;
msg_type_id = 0;
msg_stream_id = 0;
data = "";
}//constructor
/// Packs up a chunk with the given arguments as properties.
std::string RTMPStream::SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data){
RTMPStream::Chunk ch;
ch.cs_id = cs_id;
ch.timestamp = RTMPStream::getNowMS();
ch.len = data.size();
ch.real_len = data.size();
ch.len_left = 0;
ch.msg_type_id = msg_type_id;
ch.msg_stream_id = msg_stream_id;
ch.data = data;
return ch.Pack();
}//constructor
/// Packs up a chunk with media contents.
/// \param msg_type_id Type number of the media, as per FLV standard.
/// \param data Contents of the media data.
/// \param len Length of the media data, in bytes.
/// \param ts Timestamp of the media data, relative to current system time.
std::string RTMPStream::SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts){
RTMPStream::Chunk ch;
ch.cs_id = msg_type_id+42;
ch.timestamp = ts;
ch.len = len;
ch.real_len = len;
ch.len_left = 0;
ch.msg_type_id = msg_type_id;
ch.msg_stream_id = 1;
ch.data.append((char*)data, (size_t)len);
return ch.Pack();
}//SendMedia
/// Packs up a chunk with media contents.
/// \param tag FLV::Tag with media to send.
std::string RTMPStream::SendMedia(FLV::Tag & tag){
RTMPStream::Chunk ch;
ch.cs_id = ((unsigned char)tag.data[0]);
ch.timestamp = tag.tagTime();
ch.len = tag.len-15;
ch.real_len = tag.len-15;
ch.len_left = 0;
ch.msg_type_id = (unsigned char)tag.data[0];
ch.msg_stream_id = 1;
ch.data.append(tag.data+11, (size_t)(tag.len-15));
return ch.Pack();
}//SendMedia
/// Packs up a chunk for a control message with 1 argument.
std::string RTMPStream::SendCTL(unsigned char type, unsigned int data){
RTMPStream::Chunk ch;
ch.cs_id = 2;
ch.timestamp = RTMPStream::getNowMS();
ch.len = 4;
ch.real_len = 4;
ch.len_left = 0;
ch.msg_type_id = type;
ch.msg_stream_id = 0;
ch.data.resize(4);
*(int*)((char*)ch.data.c_str()) = htonl(data);
return ch.Pack();
}//SendCTL
/// Packs up a chunk for a control message with 2 arguments.
std::string RTMPStream::SendCTL(unsigned char type, unsigned int data, unsigned char data2){
RTMPStream::Chunk ch;
ch.cs_id = 2;
ch.timestamp = RTMPStream::getNowMS();
ch.len = 5;
ch.real_len = 5;
ch.len_left = 0;
ch.msg_type_id = type;
ch.msg_stream_id = 0;
ch.data.resize(5);
*(unsigned int*)((char*)ch.data.c_str()) = htonl(data);
ch.data[4] = data2;
return ch.Pack();
}//SendCTL
/// Packs up a chunk for a user control message with 1 argument.
std::string RTMPStream::SendUSR(unsigned char type, unsigned int data){
RTMPStream::Chunk ch;
ch.cs_id = 2;
ch.timestamp = RTMPStream::getNowMS();
ch.len = 6;
ch.real_len = 6;
ch.len_left = 0;
ch.msg_type_id = 4;
ch.msg_stream_id = 0;
ch.data.resize(6);
*(unsigned int*)(((char*)ch.data.c_str())+2) = htonl(data);
ch.data[0] = 0;
ch.data[1] = type;
return ch.Pack();
}//SendUSR
/// Packs up a chunk for a user control message with 2 arguments.
std::string RTMPStream::SendUSR(unsigned char type, unsigned int data, unsigned int data2){
RTMPStream::Chunk ch;
ch.cs_id = 2;
ch.timestamp = RTMPStream::getNowMS();
ch.len = 10;
ch.real_len = 10;
ch.len_left = 0;
ch.msg_type_id = 4;
ch.msg_stream_id = 0;
ch.data.resize(10);
*(unsigned int*)(((char*)ch.data.c_str())+2) = htonl(data);
*(unsigned int*)(((char*)ch.data.c_str())+6) = htonl(data2);
ch.data[0] = 0;
ch.data[1] = type;
return ch.Pack();
}//SendUSR
/// Parses the argument string into the current chunk.
/// Tries to read a whole chunk, if successful it will remove
/// the corresponding data from the input string.
/// If only part of a chunk is read, it will remove the part and call itself again.
/// This has the effect of only causing a "true" reponse in the case a *whole* chunk
/// is read, not just part of a chunk.
/// \param indata The input string to parse and update.
/// \warning This function will destroy the current data in this chunk!
/// \returns True if a whole chunk could be read, false otherwise.
bool RTMPStream::Chunk::Parse(std::string & indata){
gettimeofday(&RTMPStream::lastrec, 0);
unsigned int i = 0;
if (indata.size() < 1) return false;//need at least a byte
unsigned char chunktype = indata[i++];
//read the chunkstream ID properly
switch (chunktype & 0x3F){
case 0:
if (indata.size() < 2) return false;//need at least 2 bytes to continue
cs_id = indata[i++] + 64;
break;
case 1:
if (indata.size() < 3) return false;//need at least 3 bytes to continue
cs_id = indata[i++] + 64;
cs_id += indata[i++] * 256;
break;
default:
cs_id = chunktype & 0x3F;
break;
}
RTMPStream::Chunk prev = lastrecv[cs_id];
//process the rest of the header, for each chunk type
headertype = chunktype & 0xC0;
switch (headertype){
case 0x00:
if (indata.size() < i+11) return false; //can't read whole header
timestamp = indata[i++]*256*256;
timestamp += indata[i++]*256;
timestamp += indata[i++];
len = indata[i++]*256*256;
len += indata[i++]*256;
len += indata[i++];
len_left = 0;
msg_type_id = indata[i++];
msg_stream_id = indata[i++];
msg_stream_id += indata[i++]*256;
msg_stream_id += indata[i++]*256*256;
msg_stream_id += indata[i++]*256*256*256;
break;
case 0x40:
if (indata.size() < i+7) return false; //can't read whole header
if (prev.msg_type_id == 0){fprintf(stderr, "Warning: Header type 0x40 with no valid previous chunk!\n");}
timestamp = indata[i++]*256*256;
timestamp += indata[i++]*256;
timestamp += indata[i++];
if (timestamp != 0x00ffffff){timestamp += prev.timestamp;}
len = indata[i++]*256*256;
len += indata[i++]*256;
len += indata[i++];
len_left = 0;
msg_type_id = indata[i++];
msg_stream_id = prev.msg_stream_id;
break;
case 0x80:
if (indata.size() < i+3) return false; //can't read whole header
if (prev.msg_type_id == 0){fprintf(stderr, "Warning: Header type 0x80 with no valid previous chunk!\n");}
timestamp = indata[i++]*256*256;
timestamp += indata[i++]*256;
timestamp += indata[i++];
if (timestamp != 0x00ffffff){timestamp += prev.timestamp;}
len = prev.len;
len_left = prev.len_left;
msg_type_id = prev.msg_type_id;
msg_stream_id = prev.msg_stream_id;
break;
case 0xC0:
if (prev.msg_type_id == 0){fprintf(stderr, "Warning: Header type 0xC0 with no valid previous chunk!\n");}
timestamp = prev.timestamp;
len = prev.len;
len_left = prev.len_left;
msg_type_id = prev.msg_type_id;
msg_stream_id = prev.msg_stream_id;
break;
}
//calculate chunk length, real length, and length left till complete
if (len_left > 0){
real_len = len_left;
len_left -= real_len;
}else{
real_len = len;
}
if (real_len > RTMPStream::chunk_rec_max){
len_left += real_len - RTMPStream::chunk_rec_max;
real_len = RTMPStream::chunk_rec_max;
}
//read extended timestamp, if neccesary
if (timestamp == 0x00ffffff){
if (indata.size() < i+4) return false; //can't read whole header
timestamp = indata[i++]*256*256*256;
timestamp += indata[i++]*256*256;
timestamp += indata[i++]*256;
timestamp += indata[i++];
}
//read data if length > 0, and allocate it
if (real_len > 0){
if (prev.len_left > 0){
data = prev.data;
}else{
data = "";
}
if (indata.size() < i+real_len) return false;//can't read all data (yet)
data.append(indata, i, real_len);
indata = indata.substr(i+real_len);
lastrecv[cs_id] = *this;
RTMPStream::rec_cnt += i+real_len;
if (len_left == 0){
return true;
}else{
return Parse(indata);
}
}else{
data = "";
indata = indata.substr(i+real_len);
lastrecv[cs_id] = *this;
RTMPStream::rec_cnt += i+real_len;
return true;
}
}//Parse
/// Does the handshake. Expects handshake_in to be filled, and fills handshake_out.
/// After calling this function, don't forget to read and ignore 1536 extra bytes,
/// these are the handshake response and not interesting for us because we don't do client
/// verification.
bool RTMPStream::doHandshake(){
char Version;
//Read C0
Version = RTMPStream::handshake_in[0];
uint8_t * Client = (uint8_t *)RTMPStream::handshake_in.c_str() + 1;
RTMPStream::handshake_out.resize(3073);
uint8_t * Server = (uint8_t *)RTMPStream::handshake_out.c_str() + 1;
RTMPStream::rec_cnt += 1537;
//Build S1 Packet
*((uint32_t*)Server) = 0;//time zero
*(((uint32_t*)(Server+4))) = htonl(0x01020304);//version 1 2 3 4
for (int i = 8; i < 3072; ++i){Server[i] = versionstring[i%16];}//"random" data
bool encrypted = (Version == 6);
#if DEBUG >= 4
fprintf(stderr, "Handshake version is %hhi\n", Version);
#endif
uint8_t _validationScheme = 5;
if (ValidateClientScheme(Client, 0)) _validationScheme = 0;
if (ValidateClientScheme(Client, 1)) _validationScheme = 1;
#if DEBUG >= 4
fprintf(stderr, "Handshake type is %hhi, encryption is %s\n", _validationScheme, encrypted?"on":"off");
#endif
//FIRST 1536 bytes from server response
//compute DH key position
uint32_t serverDHOffset = GetDHOffset(Server, _validationScheme);
uint32_t clientDHOffset = GetDHOffset(Client, _validationScheme);
//generate DH key
DHWrapper dhWrapper(1024);
if (!dhWrapper.Initialize()) return false;
if (!dhWrapper.CreateSharedKey(Client + clientDHOffset, 128)) return false;
if (!dhWrapper.CopyPublicKey(Server + serverDHOffset, 128)) return false;
if (encrypted) {
uint8_t secretKey[128];
if (!dhWrapper.CopySharedKey(secretKey, sizeof (secretKey))) return false;
RC4_KEY _pKeyIn;
RC4_KEY _pKeyOut;
InitRC4Encryption(secretKey, (uint8_t*) & Client[clientDHOffset], (uint8_t*) & Server[serverDHOffset], &_pKeyIn, &_pKeyOut);
uint8_t data[1536];
RC4(&_pKeyIn, 1536, data, data);
RC4(&_pKeyOut, 1536, data, data);
}
//generate the digest
uint32_t serverDigestOffset = GetDigestOffset(Server, _validationScheme);
uint8_t *pTempBuffer = new uint8_t[1536 - 32];
memcpy(pTempBuffer, Server, serverDigestOffset);
memcpy(pTempBuffer + serverDigestOffset, Server + serverDigestOffset + 32, 1536 - serverDigestOffset - 32);
uint8_t *pTempHash = new uint8_t[512];
HMACsha256(pTempBuffer, 1536 - 32, genuineFMSKey, 36, pTempHash);
memcpy(Server + serverDigestOffset, pTempHash, 32);
delete[] pTempBuffer;
delete[] pTempHash;
//SECOND 1536 bytes from server response
uint32_t keyChallengeIndex = GetDigestOffset(Client, _validationScheme);
pTempHash = new uint8_t[512];
HMACsha256(Client + keyChallengeIndex, 32, genuineFMSKey, 68, pTempHash);
uint8_t *pLastHash = new uint8_t[512];
HMACsha256(Server + 1536, 1536 - 32, pTempHash, 32, pLastHash);
memcpy(Server + 1536 * 2 - 32, pLastHash, 32);
delete[] pTempHash;
delete[] pLastHash;
//DONE BUILDING THE RESPONSE ***//
Server[-1] = Version;
RTMPStream::snd_cnt += 3073;
return true;
}

71
lib/rtmpchunks.h Normal file
View file

@ -0,0 +1,71 @@
/// \file rtmpchunks.h
/// Holds all headers for the RTMPStream namespace.
#pragma once
#include <map>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <string>
#include <arpa/inet.h>
//forward declaration of FLV::Tag to avoid circular dependencies.
namespace FLV{
class Tag;
};
/// Contains all functions and classes needed for RTMP connections.
namespace RTMPStream{
/// Gets the current system time in milliseconds.
unsigned int getNowMS();
extern unsigned int chunk_rec_max; ///< Maximum size for a received chunk.
extern unsigned int chunk_snd_max; ///< Maximum size for a sent chunk.
extern unsigned int rec_window_size; ///< Window size for receiving.
extern unsigned int snd_window_size; ///< Window size for sending.
extern unsigned int rec_window_at; ///< Current position of the receiving window.
extern unsigned int snd_window_at; ///< Current position of the sending window.
extern unsigned int rec_cnt; ///< Counter for total data received, in bytes.
extern unsigned int snd_cnt; ///< Counter for total data sent, in bytes.
extern timeval lastrec; ///< Timestamp of last time data was received.
extern unsigned int firsttime; ///< Timestamp of first time a chunk was sent.
/// Holds a single RTMP chunk, either send or receive direction.
class Chunk{
public:
unsigned char headertype; ///< For input chunks, the type of header. This is calculated automatically for output chunks.
unsigned int cs_id; ///< ContentStream ID
unsigned int timestamp; ///< Timestamp of this chunk.
unsigned int len; ///< Length of the complete chunk.
unsigned int real_len; ///< Length of this particular part of it.
unsigned int len_left; ///< Length not yet received, out of complete chunk.
unsigned char msg_type_id; ///< Message Type ID
unsigned int msg_stream_id; ///< Message Stream ID
std::string data; ///< Payload of chunk.
Chunk();
bool Parse(std::string & data);
std::string Pack();
private:
static std::map<unsigned int, Chunk> lastsend;
static std::map<unsigned int, Chunk> lastrecv;
};//RTMPStream::Chunk
std::string SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data);
std::string SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts);
std::string SendMedia(FLV::Tag & tag);
std::string SendCTL(unsigned char type, unsigned int data);
std::string SendCTL(unsigned char type, unsigned int data, unsigned char data2);
std::string SendUSR(unsigned char type, unsigned int data);
std::string SendUSR(unsigned char type, unsigned int data, unsigned int data2);
/// This value should be set to the first 1537 bytes received.
extern std::string handshake_in;
/// This value is the handshake response that is to be sent out.
extern std::string handshake_out;
/// Does the handshake. Expects handshake_in to be filled, and fills handshake_out.
bool doHandshake();
};//RTMPStream namespace

702
lib/socket.cpp Normal file
View file

@ -0,0 +1,702 @@
/// \file socket.cpp
/// A handy Socket wrapper library.
/// Written by Jaron Vietor in 2010 for DDVTech
#include "socket.h"
#include <sys/stat.h>
#include <poll.h>
#include <netdb.h>
#include <sstream>
#ifdef __FreeBSD__
#include <netinet/in.h>
#endif
std::string uint2string(unsigned int i){
std::stringstream st;
st << i;
return st.str();
}
/// Create a new base socket. This is a basic constructor for converting any valid socket to a Socket::Connection.
/// \param sockNo Integer representing the socket to convert.
Socket::Connection::Connection(int sockNo){
sock = sockNo;
up = 0;
down = 0;
conntime = time(0);
Error = false;
Blocking = false;
}//Socket::Connection basic constructor
/// Create a new disconnected base socket. This is a basic constructor for placeholder purposes.
/// A socket created like this is always disconnected and should/could be overwritten at some point.
Socket::Connection::Connection(){
sock = -1;
up = 0;
down = 0;
conntime = time(0);
Error = false;
Blocking = false;
}//Socket::Connection basic constructor
/// Set this socket to be blocking (true) or nonblocking (false).
void Socket::Connection::setBlocking(bool blocking){
int flags = fcntl(sock, F_GETFL, 0);
if (!blocking){
flags |= O_NONBLOCK;
}else{
flags &= !O_NONBLOCK;
}
fcntl(sock, F_SETFL, flags);
}
/// Close connection. The internal socket is closed and then set to -1.
void Socket::Connection::close(){
#if DEBUG >= 6
fprintf(stderr, "Socket closed.\n");
#endif
shutdown(sock, SHUT_RDWR);
::close(sock);
sock = -1;
}//Socket::Connection::close
/// Returns internal socket number.
int Socket::Connection::getSocket(){return sock;}
/// Returns a string describing the last error that occured.
/// Simply calls strerror(errno) - not very reliable!
/// \todo Improve getError at some point to be more reliable and only report socket errors.
std::string Socket::Connection::getError(){return strerror(errno);}
/// Create a new Unix Socket. This socket will (try to) connect to the given address right away.
/// \param address String containing the location of the Unix socket to connect to.
/// \param nonblock Whether the socket should be nonblocking. False by default.
Socket::Connection::Connection(std::string address, bool nonblock){
sock = socket(PF_UNIX, SOCK_STREAM, 0);
if (sock < 0){
#if DEBUG >= 1
fprintf(stderr, "Could not create socket! Error: %s\n", strerror(errno));
#endif
return;
}
Error = false;
Blocking = false;
up = 0;
down = 0;
conntime = time(0);
sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, address.c_str(), address.size()+1);
int r = connect(sock, (sockaddr*)&addr, sizeof(addr));
if (r == 0){
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
}else{
#if DEBUG >= 1
fprintf(stderr, "Could not connect to %s! Error: %s\n", address.c_str(), strerror(errno));
#endif
close();
}
}//Socket::Connection Unix Contructor
/// Create a new TCP Socket. This socket will (try to) connect to the given host/port right away.
/// \param host String containing the hostname to connect to.
/// \param port String containing the port to connect to.
/// \param nonblock Whether the socket should be nonblocking.
Socket::Connection::Connection(std::string host, int port, bool nonblock){
struct addrinfo *result, *rp, hints;
Error = false;
Blocking = false;
up = 0;
down = 0;
conntime = time(0);
std::stringstream ss;
ss << port;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_protocol = 0;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
int s = getaddrinfo(host.c_str(), ss.str().c_str(), &hints, &result);
if (s != 0){
#if DEBUG >= 1
fprintf(stderr, "Could not connect to %s:%i! Error: %s\n", host.c_str(), port, gai_strerror(s));
#endif
close();
return;
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sock < 0){continue;}
if (connect(sock, rp->ai_addr, rp->ai_addrlen) == 0){break;}
::close(sock);
}
freeaddrinfo(result);
if (rp == 0){
#if DEBUG >= 1
fprintf(stderr, "Could not connect to %s! Error: %s\n", host.c_str(), strerror(errno));
#endif
close();
}else{
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
}
}//Socket::Connection TCP Contructor
/// Calls poll() on the socket, checking if data is available.
/// This function may return true even if there is no data, but never returns false when there is.
bool Socket::Connection::canRead(){
struct pollfd PFD;
PFD.fd = sock;
PFD.events = POLLIN;
PFD.revents = 0;
poll(&PFD, 1, 5);
return (PFD.revents & POLLIN) == POLLIN;
}
/// Calls poll() on the socket, checking if data can be written.
bool Socket::Connection::canWrite(){
struct pollfd PFD;
PFD.fd = sock;
PFD.events = POLLOUT;
PFD.revents = 0;
poll(&PFD, 1, 5);
return (PFD.revents & POLLOUT) == POLLOUT;
}
/// Returns the ready-state for this socket.
/// \returns 1 if data is waiting to be read, -1 if not connected, 0 otherwise.
signed int Socket::Connection::ready(){
if (sock < 0) return -1;
char tmp;
int preflags = fcntl(sock, F_GETFL, 0);
int postflags = preflags | O_NONBLOCK;
fcntl(sock, F_SETFL, postflags);
int r = recv(sock, &tmp, 1, MSG_PEEK);
fcntl(sock, F_SETFL, preflags);
if (r < 0){
if (errno == EAGAIN || errno == EWOULDBLOCK){
return 0;
}else{
#if DEBUG >= 2
fprintf(stderr, "Socket ready error! Error: %s\n", strerror(errno));
#endif
close();
return -1;
}
}
if (r == 0){
#if DEBUG >= 4
fprintf(stderr, "Socket ready error - socket is closed.\n");
#endif
close();
return -1;
}
return r;
}
/// Returns the connected-state for this socket.
/// Note that this function might be slightly behind the real situation.
/// The connection status is updated after every read/write attempt, when errors occur
/// and when the socket is closed manually.
/// \returns True if socket is connected, false otherwise.
bool Socket::Connection::connected(){
return (sock >= 0);
}
/// Returns total amount of bytes sent.
unsigned int Socket::Connection::dataUp(){
return up;
}
/// Returns total amount of bytes received.
unsigned int Socket::Connection::dataDown(){
return down;
}
/// Returns a std::string of stats, ended by a newline.
/// Requires the current connector name as an argument.
std::string Socket::Connection::getStats(std::string C){
return getHost() + " " + C + " " + uint2string(time(0) - conntime) + " " + uint2string(up) + " " + uint2string(down) + "\n";
}
/// Updates the downbuffer and upbuffer internal variables.
/// Returns true if new data was received, false otherwise.
bool Socket::Connection::spool(){
iwrite(upbuffer);
return iread(downbuffer);
}
/// Returns a reference to the download buffer.
std::string & Socket::Connection::Received(){
return downbuffer;
}
/// Appends data to the upbuffer.
void Socket::Connection::Send(std::string data){
upbuffer.append(data);
}
/// Writes data to socket. This function blocks if the socket is blocking and all data cannot be written right away.
/// If the socket is nonblocking and not all data can be written, this function sets internal variable Blocking to true
/// and returns false.
/// \param buffer Location of the buffer to write from.
/// \param len Amount of bytes to write.
/// \returns True if the whole write was succesfull, false otherwise.
bool Socket::Connection::write(const void * buffer, int len){
int sofar = 0;
if (sock < 0){return false;}
while (sofar != len){
int r = send(sock, (char*)buffer + sofar, len-sofar, 0);
if (r <= 0){
Error = true;
#if DEBUG >= 2
fprintf(stderr, "Could not write data! Error: %s\n", strerror(errno));
#endif
close();
up += sofar;
return false;
}else{
sofar += r;
}
}
up += sofar;
return true;
}//DDv::Socket::write
/// Reads data from socket. This function blocks if the socket is blocking and all data cannot be read right away.
/// If the socket is nonblocking and not all data can be read, this function sets internal variable Blocking to true
/// and returns false.
/// \param buffer Location of the buffer to read to.
/// \param len Amount of bytes to read.
/// \returns True if the whole read was succesfull, false otherwise.
bool Socket::Connection::read(const void * buffer, int len){
int sofar = 0;
if (sock < 0){return false;}
while (sofar != len){
int r = recv(sock, (char*)buffer + sofar, len-sofar, 0);
if (r < 0){
switch (errno){
case EWOULDBLOCK:
down += sofar;
return 0;
break;
default:
Error = true;
#if DEBUG >= 2
fprintf(stderr, "Could not read data! Error %i: %s\n", r, strerror(errno));
#endif
close();
down += sofar;
break;
}
return false;
}else{
if (r == 0){
Error = true;
#if DEBUG >= 2
fprintf(stderr, "Could not read data! Socket is closed.\n");
#endif
close();
down += sofar;
return false;
}
sofar += r;
}
}
down += sofar;
return true;
}//Socket::Connection::read
/// Read call that is compatible with file access syntax. This function simply calls the other read function.
bool Socket::Connection::read(const void * buffer, int width, int count){return read(buffer, width*count);}
/// Write call that is compatible with file access syntax. This function simply calls the other write function.
bool Socket::Connection::write(const void * buffer, int width, int count){return write(buffer, width*count);}
/// Write call that is compatible with std::string. This function simply calls the other write function.
bool Socket::Connection::write(const std::string data){return write(data.c_str(), data.size());}
/// Incremental write call. This function tries to write len bytes to the socket from the buffer,
/// returning the amount of bytes it actually wrote.
/// \param buffer Location of the buffer to write from.
/// \param len Amount of bytes to write.
/// \returns The amount of bytes actually written.
int Socket::Connection::iwrite(const void * buffer, int len){
if (sock < 0){return 0;}
int r = send(sock, buffer, len, 0);
if (r < 0){
switch (errno){
case EWOULDBLOCK: return 0; break;
default:
Error = true;
#if DEBUG >= 2
fprintf(stderr, "Could not iwrite data! Error: %s\n", strerror(errno));
#endif
close();
return 0;
break;
}
}
if (r == 0){
#if DEBUG >= 4
fprintf(stderr, "Could not iwrite data! Socket is closed.\n");
#endif
close();
}
up += r;
return r;
}//Socket::Connection::iwrite
/// Incremental read call. This function tries to read len bytes to the buffer from the socket,
/// returning the amount of bytes it actually read.
/// \param buffer Location of the buffer to read to.
/// \param len Amount of bytes to read.
/// \returns The amount of bytes actually read.
int Socket::Connection::iread(void * buffer, int len){
if (sock < 0){return 0;}
int r = recv(sock, buffer, len, 0);
if (r < 0){
switch (errno){
case EWOULDBLOCK: return 0; break;
default:
Error = true;
#if DEBUG >= 2
fprintf(stderr, "Could not iread data! Error: %s\n", strerror(errno));
#endif
close();
return 0;
break;
}
}
if (r == 0){
#if DEBUG >= 4
fprintf(stderr, "Could not iread data! Socket is closed.\n");
#endif
close();
}
down += r;
return r;
}//Socket::Connection::iread
/// Read call that is compatible with std::string.
/// Data is read using iread (which is nonblocking if the Socket::Connection itself is),
/// then appended to end of buffer. This functions reads at least one byte before returning.
/// \param buffer std::string to append data to.
/// \return True if new data arrived, false otherwise.
bool Socket::Connection::read(std::string & buffer){
char cbuffer[5000];
if (!read(cbuffer, 1)){return false;}
int num = iread(cbuffer+1, 4999);
if (num > 0){
buffer.append(cbuffer, num+1);
}else{
buffer.append(cbuffer, 1);
}
return true;
}//read
/// Read call that is compatible with std::string.
/// Data is read using iread (which is nonblocking if the Socket::Connection itself is),
/// then appended to end of buffer.
/// \param buffer std::string to append data to.
/// \return True if new data arrived, false otherwise.
bool Socket::Connection::iread(std::string & buffer){
char cbuffer[5000];
int num = iread(cbuffer, 5000);
if (num < 1){return false;}
buffer.append(cbuffer, num);
return true;
}//iread
/// Incremental write call that is compatible with std::string.
/// Data is written using iwrite (which is nonblocking if the Socket::Connection itself is),
/// then removed from front of buffer.
/// \param buffer std::string to remove data from.
/// \return True if more data was sent, false otherwise.
bool Socket::Connection::iwrite(std::string & buffer){
if (buffer.size() < 1){return false;}
int tmp = iwrite((void*)buffer.c_str(), buffer.size());
if (tmp < 1){return false;}
buffer = buffer.substr(tmp);
return true;
}//iwrite
/// Write call that is compatible with std::string.
/// Data is written using write (which is always blocking),
/// then removed from front of buffer.
/// \param buffer std::string to remove data from.
/// \return True if more data was sent, false otherwise.
bool Socket::Connection::swrite(std::string & buffer){
if (buffer.size() < 1){return false;}
bool tmp = write((void*)buffer.c_str(), buffer.size());
if (tmp){buffer = "";}
return tmp;
}//write
/// Gets hostname for connection, if available.
std::string Socket::Connection::getHost(){
return remotehost;
}
/// Create a new base Server. The socket is never connected, and a placeholder for later connections.
Socket::Server::Server(){
sock = -1;
}//Socket::Server base Constructor
/// Create a new TCP Server. The socket is immediately bound and set to listen.
/// A maximum of 100 connections will be accepted between accept() calls.
/// Any further connections coming in will be dropped.
/// \param port The TCP port to listen on
/// \param hostname (optional) The interface to bind to. The default is 0.0.0.0 (all interfaces).
/// \param nonblock (optional) Whether accept() calls will be nonblocking. Default is false (blocking).
Socket::Server::Server(int port, std::string hostname, bool nonblock){
sock = socket(AF_INET6, SOCK_STREAM, 0);
if (sock < 0){
#if DEBUG >= 1
fprintf(stderr, "Could not create socket %s:%i! Error: %s\n", hostname.c_str(), port, strerror(errno));
#endif
return;
}
int on = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
struct sockaddr_in6 addr;
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(port);//set port
if (hostname == "0.0.0.0" || hostname.length() == 0){
addr.sin6_addr = in6addr_any;
}else{
inet_pton(AF_INET6, hostname.c_str(), &addr.sin6_addr);//set interface, 0.0.0.0 (default) is all
}
int ret = bind(sock, (sockaddr*)&addr, sizeof(addr));//do the actual bind
if (ret == 0){
ret = listen(sock, 100);//start listening, backlog of 100 allowed
if (ret == 0){
return;
}else{
#if DEBUG >= 1
fprintf(stderr, "Listen failed! Error: %s\n", strerror(errno));
#endif
close();
return;
}
}else{
#if DEBUG >= 1
fprintf(stderr, "Binding %s:%i failed, retrying as IPv4... (%s)\n", hostname.c_str(), port, strerror(errno));
#endif
close();
}
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0){
#if DEBUG >= 1
fprintf(stderr, "Could not create socket %s:%i! Error: %s\n", hostname.c_str(), port, strerror(errno));
#endif
return;
}
on = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
struct sockaddr_in addr4;
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);//set port
if (hostname == "0.0.0.0" || hostname.length() == 0){
addr4.sin_addr.s_addr = INADDR_ANY;
}else{
inet_pton(AF_INET, hostname.c_str(), &addr4.sin_addr);//set interface, 0.0.0.0 (default) is all
}
ret = bind(sock, (sockaddr*)&addr4, sizeof(addr4));//do the actual bind
if (ret == 0){
ret = listen(sock, 100);//start listening, backlog of 100 allowed
if (ret == 0){
return;
}else{
#if DEBUG >= 1
fprintf(stderr, "Listen failed! Error: %s\n", strerror(errno));
#endif
close();
return;
}
}else{
#if DEBUG >= 1
fprintf(stderr, "IPv4 binding %s:%i also failed, giving up. (%s)\n", hostname.c_str(), port, strerror(errno));
#endif
close();
return;
}
}//Socket::Server TCP Constructor
/// Create a new Unix Server. The socket is immediately bound and set to listen.
/// A maximum of 100 connections will be accepted between accept() calls.
/// Any further connections coming in will be dropped.
/// The address used will first be unlinked - so it succeeds if the Unix socket already existed. Watch out for this behaviour - it will delete any file located at address!
/// \param address The location of the Unix socket to bind to.
/// \param nonblock (optional) Whether accept() calls will be nonblocking. Default is false (blocking).
Socket::Server::Server(std::string address, bool nonblock){
unlink(address.c_str());
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0){
#if DEBUG >= 1
fprintf(stderr, "Could not create socket! Error: %s\n", strerror(errno));
#endif
return;
}
if (nonblock){
int flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(sock, F_SETFL, flags);
}
sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, address.c_str(), address.size()+1);
int ret = bind(sock, (sockaddr*)&addr, sizeof(addr));
if (ret == 0){
ret = listen(sock, 100);//start listening, backlog of 100 allowed
if (ret == 0){
return;
}else{
#if DEBUG >= 1
fprintf(stderr, "Listen failed! Error: %s\n", strerror(errno));
#endif
close();
return;
}
}else{
#if DEBUG >= 1
fprintf(stderr, "Binding failed! Error: %s\n", strerror(errno));
#endif
close();
return;
}
}//Socket::Server Unix Constructor
/// Accept any waiting connections. If the Socket::Server is blocking, this function will block until there is an incoming connection.
/// If the Socket::Server is nonblocking, it might return a Socket::Connection that is not connected, so check for this.
/// \param nonblock (optional) Whether the newly connected socket should be nonblocking. Default is false (blocking).
/// \returns A Socket::Connection, which may or may not be connected, depending on settings and circumstances.
Socket::Connection Socket::Server::accept(bool nonblock){
if (sock < 0){return Socket::Connection(-1);}
struct sockaddr_in6 addrinfo;
socklen_t len = sizeof(addrinfo);
static char addrconv[INET6_ADDRSTRLEN];
int r = ::accept(sock, (sockaddr*)&addrinfo, &len);
//set the socket to be nonblocking, if requested.
//we could do this through accept4 with a flag, but that call is non-standard...
if ((r >= 0) && nonblock){
int flags = fcntl(r, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(r, F_SETFL, flags);
}
Socket::Connection tmp(r);
if (r < 0){
if ((errno != EWOULDBLOCK) && (errno != EAGAIN) && (errno != EINTR)){
#if DEBUG >= 1
fprintf(stderr, "Error during accept - closing server socket.\n");
#endif
close();
}
}else{
if (addrinfo.sin6_family == AF_INET6){
tmp.remotehost = inet_ntop(AF_INET6, &(addrinfo.sin6_addr), addrconv, INET6_ADDRSTRLEN);
#if DEBUG >= 6
fprintf(stderr,"IPv6 addr: %s\n", tmp.remotehost.c_str());
#endif
}
if (addrinfo.sin6_family == AF_INET){
tmp.remotehost = inet_ntop(AF_INET, &(((sockaddr_in*)&addrinfo)->sin_addr), addrconv, INET6_ADDRSTRLEN);
#if DEBUG >= 6
fprintf(stderr,"IPv4 addr: %s\n", tmp.remotehost.c_str());
#endif
}
if (addrinfo.sin6_family == AF_UNIX){
#if DEBUG >= 6
tmp.remotehost = ((sockaddr_un*)&addrinfo)->sun_path;
fprintf(stderr,"Unix socket, no address\n");
#endif
tmp.remotehost = "UNIX_SOCKET";
}
}
return tmp;
}
/// Close connection. The internal socket is closed and then set to -1.
void Socket::Server::close(){
#if DEBUG >= 6
fprintf(stderr, "ServerSocket closed.\n");
#endif
shutdown(sock, SHUT_RDWR);
::close(sock);
sock = -1;
}//Socket::Server::close
/// Returns the connected-state for this socket.
/// Note that this function might be slightly behind the real situation.
/// The connection status is updated after every accept attempt, when errors occur
/// and when the socket is closed manually.
/// \returns True if socket is connected, false otherwise.
bool Socket::Server::connected(){
return (sock >= 0);
}//Socket::Server::connected
/// Returns internal socket number.
int Socket::Server::getSocket(){return sock;}
/// Connect to a stream on the system.
/// Filters the streamname, removing invalid characters and
/// converting all letters to lowercase.
/// If a '?' character is found, everything following that character is deleted.
Socket::Connection Socket::getStream(std::string streamname){
//strip anything that isn't numbers, digits or underscores
for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){
if (*i == '?'){streamname.erase(i, streamname.end()); break;}
if (!isalpha(*i) && !isdigit(*i) && *i != '_'){
streamname.erase(i);
}else{
*i=tolower(*i);
}
}
return Socket::Connection("/tmp/mist/stream_"+streamname);
}
/// Create a stream on the system.
/// Filters the streamname, removing invalid characters and
/// converting all letters to lowercase.
/// If a '?' character is found, everything following that character is deleted.
/// If the /tmp/ddvtech directory doesn't exist yet, this will create it.
Socket::Server Socket::makeStream(std::string streamname){
//strip anything that isn't numbers, digits or underscores
for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){
if (*i == '?'){streamname.erase(i, streamname.end()); break;}
if (!isalpha(*i) && !isdigit(*i) && *i != '_'){
streamname.erase(i);
}else{
*i=tolower(*i);
}
}
std::string loc = "/tmp/mist/stream_"+streamname;
//attempt to create the /tmp/mist directory if it doesn't exist already.
//ignore errors - we catch all problems in the Socket::Server creation already
mkdir("/tmp/mist", S_IRWXU | S_IRWXG | S_IRWXO);
//create and return the Socket::Server
return Socket::Server(loc);
}

87
lib/socket.h Normal file
View file

@ -0,0 +1,87 @@
/// \file socket.h
/// A handy Socket wrapper library.
/// Written by Jaron Vietor in 2010 for DDVTech
#pragma once
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
///Holds Socket tools.
namespace Socket{
/// This class is for easy communicating through sockets, either TCP or Unix.
class Connection{
private:
int sock; ///< Internally saved socket number.
std::string remotehost; ///< Stores remote host address.
unsigned int up;
unsigned int down;
unsigned int conntime;
std::string downbuffer; ///< Stores temporary data coming in.
std::string upbuffer; ///< Stores temporary data going out.
public:
Connection(); ///< Create a new disconnected base socket.
Connection(int sockNo); ///< Create a new base socket.
Connection(std::string hostname, int port, bool nonblock); ///< Create a new TCP socket.
Connection(std::string adres, bool nonblock = false); ///< Create a new Unix Socket.
void setBlocking(bool blocking); ///< Set this socket to be blocking (true) or nonblocking (false).
bool canRead(); ///< Calls poll() on the socket, checking if data is available.
bool canWrite(); ///< Calls poll() on the socket, checking if data can be written.
signed int ready(); ///< Returns the ready-state for this socket.
bool connected(); ///< Returns the connected-state for this socket.
bool read(const void * buffer, int len); ///< Reads data from socket.
bool read(const void * buffer, int width, int count); ///< Read call that is compatible with file access syntax.
bool write(const void * buffer, int len); ///< Writes data to socket.
bool write(const void * buffer, int width, int count); ///< Write call that is compatible with file access syntax.
bool write(const std::string data); ///< Write call that is compatible with std::string.
int iwrite(const void * buffer, int len); ///< Incremental write call.
int iread(void * buffer, int len); ///< Incremental read call.
bool read(std::string & buffer); ///< Read call that is compatible with std::string.
bool swrite(std::string & buffer); ///< Write call that is compatible with std::string.
bool iread(std::string & buffer); ///< Incremental write call that is compatible with std::string.
bool iwrite(std::string & buffer); ///< Write call that is compatible with std::string.
bool spool(); ///< Updates the downbuffer and upbuffer internal variables.
std::string & Received(); ///< Returns a reference to the download buffer.
void Send(std::string data); ///< Appends data to the upbuffer.
void close(); ///< Close connection.
std::string getHost(); ///< Gets hostname for connection, if available.
int getSocket(); ///< Returns internal socket number.
std::string getError(); ///< Returns a string describing the last error that occured.
unsigned int dataUp(); ///< Returns total amount of bytes sent.
unsigned int dataDown(); ///< Returns total amount of bytes received.
std::string getStats(std::string C); ///< Returns a std::string of stats, ended by a newline.
friend class Server;
bool Error; ///< Set to true if a socket error happened.
bool Blocking; ///< Set to true if a socket is currently or wants to be blocking.
};
/// This class is for easily setting up listening socket, either TCP or Unix.
class Server{
private:
int sock; ///< Internally saved socket number.
public:
Server(); ///< Create a new base Server.
Server(int port, std::string hostname = "0.0.0.0", bool nonblock = false); ///< Create a new TCP Server.
Server(std::string adres, bool nonblock = false); ///< Create a new Unix Server.
Connection accept(bool nonblock = false); ///< Accept any waiting connections.
bool connected(); ///< Returns the connected-state for this socket.
void close(); ///< Close connection.
int getSocket(); ///< Returns internal socket number.
};
/// Connect to a stream on the system.
Connection getStream(std::string streamname);
/// Create a stream on the system.
Server makeStream(std::string streamname);
};

287
lib/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
lib/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_