Convert to autotools build system for cleanness (part1).
This commit is contained in:
parent
c123111e70
commit
ad410a2e79
75 changed files with 63 additions and 3314 deletions
15
lib/Makefile.am
Normal file
15
lib/Makefile.am
Normal 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
969
lib/amf.cpp
Normal 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
129
lib/amf.h
Normal 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
45
lib/auth.cpp
Normal 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
11
lib/auth.h
Normal 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
64
lib/base64.cpp
Normal 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
11
lib/base64.h
Normal 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
105
lib/config.cpp
Normal 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
29
lib/config.h
Normal 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
509
lib/crypto.cpp
Normal 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
56
lib/crypto.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/// \file crypto.h
|
||||
/// Holds all headers needed for RTMP cryptography functions.
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/dh.h>
|
||||
#include <openssl/rc4.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/hmac.h>
|
||||
|
||||
class DHWrapper {
|
||||
private:
|
||||
int32_t _bitsCount;
|
||||
DH *_pDH;
|
||||
uint8_t *_pSharedKey;
|
||||
int32_t _sharedKeyLength;
|
||||
BIGNUM *_peerPublickey;
|
||||
public:
|
||||
DHWrapper(int32_t bitsCount);
|
||||
virtual ~DHWrapper();
|
||||
|
||||
bool Initialize();
|
||||
bool CopyPublicKey(uint8_t *pDst, int32_t dstLength);
|
||||
bool CopyPrivateKey(uint8_t *pDst, int32_t dstLength);
|
||||
bool CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length);
|
||||
bool CopySharedKey(uint8_t *pDst, int32_t dstLength);
|
||||
private:
|
||||
void Cleanup();
|
||||
bool CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength);
|
||||
};
|
||||
|
||||
|
||||
void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut);
|
||||
std::string md5(std::string source, bool textResult);
|
||||
std::string b64(std::string source);
|
||||
std::string b64(uint8_t *pBuffer, uint32_t length);
|
||||
std::string unb64(std::string source);
|
||||
std::string unb64(uint8_t *pBuffer, uint32_t length);
|
||||
|
||||
void HMACsha256(const void *pData, uint32_t dataLength, const void *pKey, uint32_t keyLength, void *pResult);
|
||||
|
||||
uint32_t GetDigestOffset0(uint8_t *pBuffer);
|
||||
uint32_t GetDigestOffset1(uint8_t *pBuffer);
|
||||
uint32_t GetDigestOffset(uint8_t *pBuffer, uint8_t scheme);
|
||||
uint32_t GetDHOffset0(uint8_t *pBuffer);
|
||||
uint32_t GetDHOffset1(uint8_t *pBuffer);
|
||||
uint32_t GetDHOffset(uint8_t *pBuffer, uint8_t scheme);
|
||||
|
||||
extern uint8_t genuineFMSKey[];
|
||||
|
||||
bool ValidateClientScheme(uint8_t * pBuffer, uint8_t scheme);
|
467
lib/dtsc.cpp
Normal file
467
lib/dtsc.cpp
Normal 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
144
lib/dtsc.h
Normal 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
948
lib/flv_tag.cpp
Normal 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
66
lib/flv_tag.h
Normal 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
324
lib/http_parser.cpp
Normal 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
52
lib/http_parser.h
Normal 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
455
lib/json.cpp
Normal 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
75
lib/json.h
Normal 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
362
lib/md5.cpp
Normal 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
93
lib/md5.h
Normal 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
361
lib/procs.cpp
Normal 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
32
lib/procs.h
Normal 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
469
lib/rtmpchunks.cpp
Normal 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
71
lib/rtmpchunks.h
Normal 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
702
lib/socket.cpp
Normal 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
87
lib/socket.h
Normal 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
287
lib/tinythread.cpp
Normal 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
696
lib/tinythread.h
Normal 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_
|
Loading…
Add table
Add a link
Reference in a new issue