Restructuring so our libraries can go into a separate libmist project.
This commit is contained in:
parent
87f4c4723c
commit
af12c6a94e
50 changed files with 541 additions and 6816 deletions
|
@ -1,4 +1,4 @@
|
|||
SUBDIRS=lib src
|
||||
SUBDIRS=src
|
||||
EXTRA_DIST=server.html VERSION
|
||||
docs:
|
||||
doxygen ./Doxyfile > /dev/null
|
||||
|
|
|
@ -16,10 +16,12 @@ AC_PROG_CC
|
|||
|
||||
# Checks for libraries.
|
||||
AC_CHECK_LIB(ssl, RC4)
|
||||
PKG_CHECK_MODULES([MIST], [mist-1.0 >= 0.0.0])
|
||||
|
||||
# Checks for header files.
|
||||
AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/socket.h sys/time.h unistd.h])
|
||||
|
||||
|
||||
# Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_HEADER_STDBOOL
|
||||
AC_C_INLINE
|
||||
|
@ -43,7 +45,6 @@ CXXFLAGS="$CXXFLAGS -funsigned-char"
|
|||
AC_ARG_ENABLE([verbose], AC_HELP_STRING([--enable-verbose], [Compile with verbose messages]), CXXFLAGS="-DDEBUG=4 $CXXFLAGS")
|
||||
|
||||
AC_CONFIG_FILES([Makefile
|
||||
lib/Makefile
|
||||
src/converters/Makefile
|
||||
src/analysers/Makefile
|
||||
src/Makefile])
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
noinst_LTLIBRARIES=libamf.la libauth.la libbase64.la libconfig.la libkeycrypto.la libdtsc.la libflv_tag.la libhttp_parser.la libjson.la libprocs.la librtmpchunks.la libsocket.la libtinythread.la libmp4.la
|
||||
libamf_la_SOURCES=amf.h amf.cpp
|
||||
libauth_la_SOURCES=auth.h auth.cpp
|
||||
libauth_la_LIBADD=-lssl -lcrypto
|
||||
libbase64_la_SOURCES=base64.h base64.cpp
|
||||
libconfig_la_SOURCES=config.h config.cpp
|
||||
libkeycrypto_la_SOURCES=crypto.h crypto.cpp
|
||||
libkeycrypto_la_LIBADD=-lssl -lcrypto
|
||||
libdtsc_la_SOURCES=dtsc.h dtsc.cpp
|
||||
libflv_tag_la_SOURCES=flv_tag.h flv_tag.cpp
|
||||
libflv_tag_la_LIBADD=./libamf.la ./libsocket.la
|
||||
libhttp_parser_la_SOURCES=http_parser.h http_parser.cpp
|
||||
libjson_la_SOURCES=json.h json.cpp
|
||||
libprocs_la_SOURCES=procs.h procs.cpp
|
||||
librtmpchunks_la_SOURCES=rtmpchunks.h rtmpchunks.cpp
|
||||
librtmpchunks_la_LIBADD=./libflv_tag.la -lssl -lcrypto ./libkeycrypto.la
|
||||
libsocket_la_SOURCES=socket.h socket.cpp
|
||||
libtinythread_la_SOURCES=tinythread.h tinythread.cpp
|
||||
libtinythread_la_LIBADD=-lpthread
|
||||
libmp4_la_SOURCES=mp4.h mp4.cpp
|
969
lib/amf.cpp
969
lib/amf.cpp
|
@ -1,969 +0,0 @@
|
|||
/// \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
129
lib/amf.h
|
@ -1,129 +0,0 @@
|
|||
/// \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
45
lib/auth.cpp
|
@ -1,45 +0,0 @@
|
|||
#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
11
lib/auth.h
|
@ -1,11 +0,0 @@
|
|||
#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.
|
||||
};
|
|
@ -1,64 +0,0 @@
|
|||
#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 encoded_string 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
11
lib/base64.h
|
@ -1,11 +0,0 @@
|
|||
#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);
|
||||
};
|
106
lib/config.cpp
106
lib/config.cpp
|
@ -1,106 +0,0 @@
|
|||
/// \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'},
|
||||
{"nodaemon",0,0,'n'},
|
||||
{"daemon",0,0,'d'},
|
||||
{"version",0,0,'v'},
|
||||
0
|
||||
};
|
||||
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", 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], 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
29
lib/config.h
|
@ -1,29 +0,0 @@
|
|||
/// \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
509
lib/crypto.cpp
|
@ -1,509 +0,0 @@
|
|||
/// \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
56
lib/crypto.h
|
@ -1,56 +0,0 @@
|
|||
/// \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);
|
477
lib/dtsc.cpp
477
lib/dtsc.cpp
|
@ -1,477 +0,0 @@
|
|||
/// \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;
|
||||
static bool syncing = false;
|
||||
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();
|
||||
syncing = false;
|
||||
return true;
|
||||
}
|
||||
#if DEBUG >= 2
|
||||
if (!syncing){
|
||||
std::cerr << "Error: Invalid DTMI data detected - re-syncing" << std::endl;
|
||||
syncing = true;
|
||||
}
|
||||
#endif
|
||||
size_t magic_search = buffer.find(Magic_Packet);
|
||||
if (magic_search == std::string::npos){
|
||||
buffer.clear();
|
||||
}else{
|
||||
buffer.erase(0, magic_search);
|
||||
}
|
||||
}
|
||||
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
144
lib/dtsc.h
|
@ -1,144 +0,0 @@
|
|||
/// \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;
|
||||
};
|
||||
};
|
954
lib/flv_tag.cpp
954
lib/flv_tag.cpp
|
@ -1,954 +0,0 @@
|
|||
/// \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", (unsigned long long int)tmp->getContentP("width")->NumValue());
|
||||
}
|
||||
if (tmp->getContentP("height")){
|
||||
Meta_Put(metadata, "video", "height", (unsigned long long int)tmp->getContentP("height")->NumValue());
|
||||
}
|
||||
if (tmp->getContentP("framerate")){
|
||||
Meta_Put(metadata, "video", "fpks", (unsigned long long int)tmp->getContentP("framerate")->NumValue()*1000);
|
||||
}
|
||||
if (tmp->getContentP("videodatarate")){
|
||||
Meta_Put(metadata, "video", "bps", (unsigned long long int)(tmp->getContentP("videodatarate")->NumValue()*1024)/8);
|
||||
}
|
||||
if (tmp->getContentP("audiodatarate")){
|
||||
Meta_Put(metadata, "audio", "bps", (unsigned long long int)(tmp->getContentP("audiodatarate")->NumValue()*1024)/8);
|
||||
}
|
||||
if (tmp->getContentP("audiosamplerate")){
|
||||
Meta_Put(metadata, "audio", "rate", (unsigned long long int)tmp->getContentP("audiosamplerate")->NumValue());
|
||||
}
|
||||
if (tmp->getContentP("audiosamplesize")){
|
||||
Meta_Put(metadata, "audio", "size", (unsigned long long int)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){
|
||||
if (len < 18){return DTSC::DTMI();}
|
||||
pack_out.addContent(DTSC::DTMI("data", std::string((char*)data+13, (size_t)len-17)));
|
||||
}else{
|
||||
if (len < 17){return DTSC::DTMI();}
|
||||
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){
|
||||
if (len < 21){return DTSC::DTMI();}
|
||||
Meta_Put(metadata, "video", "init", std::string((char*)data+16, (size_t)len-20));
|
||||
}else{
|
||||
if (len < 17){return DTSC::DTMI();}
|
||||
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));
|
||||
if (len < 21){return DTSC::DTMI();}
|
||||
pack_out.addContent(DTSC::DTMI("data", std::string((char*)data+16, (size_t)len-20)));
|
||||
}else{
|
||||
if (len < 17){return DTSC::DTMI();}
|
||||
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;
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/// \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
|
|
@ -1,278 +0,0 @@
|
|||
/// \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();
|
||||
}
|
||||
|
||||
/// 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 a std::string buffer.
|
||||
/// If a whole request could be read, it is removed from the front of the given buffer.
|
||||
/// \param strbuf The buffer to read from.
|
||||
/// \return True if a whole request or response was read, false otherwise.
|
||||
bool HTTP::Parser::Read(std::string & strbuf){
|
||||
return parse(strbuf);
|
||||
}//HTTPReader::Read
|
||||
|
||||
/// Attempt to read a whole HTTP response or request from a data buffer.
|
||||
/// If succesful, fills its own fields with the proper data and removes the response/request
|
||||
/// from the data buffer.
|
||||
/// \param HTTPbuffer The data buffer to read from.
|
||||
/// \return True on success, false otherwise.
|
||||
bool HTTP::Parser::parse(std::string & HTTPbuffer){
|
||||
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
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a string to chunked format if protocol is HTTP/1.1 - does nothing otherwise.
|
||||
/// \param bodypart The data to convert - will be converted in-place.
|
||||
void HTTP::Parser::Chunkify(std::string & bodypart){
|
||||
if (protocol == "HTTP/1.1"){
|
||||
static char len[10];
|
||||
int sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size());
|
||||
//prepend the chunk size and \r\n
|
||||
bodypart.insert(0, len, sizelen);
|
||||
//append \r\n
|
||||
bodypart.append("\r\n", 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/// \file http_parser.h
|
||||
/// Holds all headers for the HTTP namespace.
|
||||
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.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(std::string & strbuf);
|
||||
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 Chunkify(std::string & bodypart);
|
||||
void Clean();
|
||||
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(std::string & HTTPbuffer);
|
||||
void parseVars(std::string data);
|
||||
std::string read_buffer;
|
||||
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
|
459
lib/json.cpp
459
lib/json.cpp
|
@ -1,459 +0,0 @@
|
|||
/// \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;
|
||||
Value tmp = JSON::Value(fromstream);
|
||||
if (tmp.myType != EMPTY){
|
||||
append(tmp);
|
||||
}
|
||||
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
75
lib/json.h
|
@ -1,75 +0,0 @@
|
|||
/// \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);
|
||||
|
||||
};
|
413
lib/mp4.cpp
413
lib/mp4.cpp
|
@ -1,413 +0,0 @@
|
|||
#include "mp4.h"
|
||||
#include <stdlib.h> //for malloc and free
|
||||
#include <string.h> //for memcpy
|
||||
#include <arpa/inet.h> //for htonl and friends
|
||||
|
||||
/// Contains all MP4 format related code.
|
||||
namespace MP4{
|
||||
|
||||
Box::Box() {
|
||||
Payload = (uint8_t *)malloc(8);
|
||||
PayloadSize = 0;
|
||||
}
|
||||
|
||||
Box::Box(uint32_t BoxType) {
|
||||
Payload = (uint8_t *)malloc(8);
|
||||
SetBoxType(BoxType);
|
||||
PayloadSize = 0;
|
||||
}
|
||||
|
||||
Box::Box(uint8_t * Content, uint32_t length) {
|
||||
PayloadSize = length-8;
|
||||
Payload = (uint8_t *)malloc(length);
|
||||
memcpy(Payload, Content, length);
|
||||
}
|
||||
|
||||
Box::~Box() {
|
||||
if (Payload) free(Payload);
|
||||
}
|
||||
|
||||
void Box::SetBoxType(uint32_t BoxType) {
|
||||
((unsigned int*)Payload)[1] = htonl(BoxType);
|
||||
}
|
||||
|
||||
uint32_t Box::GetBoxType() {
|
||||
return ntohl(((unsigned int*)Payload)[1]);
|
||||
}
|
||||
|
||||
void Box::SetPayload(uint32_t Size, uint8_t * Data, uint32_t Index) {
|
||||
if ( Index + Size > PayloadSize ) {
|
||||
PayloadSize = Index + Size;
|
||||
((unsigned int*)Payload)[0] = htonl(PayloadSize+8);
|
||||
Payload = (uint8_t *)realloc(Payload, PayloadSize + 8);
|
||||
}
|
||||
memcpy(Payload + 8 + Index, Data, Size);
|
||||
}
|
||||
|
||||
uint32_t Box::GetPayloadSize() {
|
||||
return PayloadSize;
|
||||
}
|
||||
|
||||
uint8_t * Box::GetPayload() {
|
||||
return Payload+8;
|
||||
}
|
||||
|
||||
uint8_t * Box::GetPayload(uint32_t Index, uint32_t & Size) {
|
||||
if(Index > PayloadSize) {Size = 0;}
|
||||
if(Index + Size > PayloadSize) { Size = PayloadSize - Index; }
|
||||
return Payload + 8 + Index;
|
||||
}
|
||||
|
||||
uint32_t Box::GetBoxedDataSize() {
|
||||
return ntohl(((unsigned int*)Payload)[0]);
|
||||
}
|
||||
|
||||
uint8_t * Box::GetBoxedData( ) {
|
||||
return Payload;
|
||||
}
|
||||
|
||||
|
||||
uint8_t * Box::uint32_to_uint8( uint32_t data ) {
|
||||
uint8_t * temp = new uint8_t[4];
|
||||
temp[0] = (data >> 24) & 0x000000FF;
|
||||
temp[1] = (data >> 16 ) & 0x000000FF;
|
||||
temp[2] = (data >> 8 ) & 0x000000FF;
|
||||
temp[3] = (data ) & 0x000000FF;
|
||||
return temp;
|
||||
}
|
||||
|
||||
uint8_t * Box::uint16_to_uint8( uint16_t data ) {
|
||||
uint8_t * temp = new uint8_t[2];
|
||||
temp[0] = (data >> 8) & 0x00FF;
|
||||
temp[1] = (data ) & 0x00FF;
|
||||
return temp;
|
||||
}
|
||||
|
||||
uint8_t * Box::uint8_to_uint8( uint8_t data ) {
|
||||
uint8_t * temp = new uint8_t[1];
|
||||
temp[0] = data;
|
||||
return temp;
|
||||
}
|
||||
|
||||
void Box::ResetPayload( ) {
|
||||
PayloadSize = 0;
|
||||
Payload = (uint8_t *)realloc(Payload, PayloadSize + 8);
|
||||
((unsigned int*)Payload)[0] = htonl(0);
|
||||
}
|
||||
|
||||
void ABST::SetBootstrapVersion( uint32_t Version ) {
|
||||
curBootstrapInfoVersion = Version;
|
||||
}
|
||||
|
||||
void ABST::SetProfile( uint8_t Profile ) {
|
||||
curProfile = Profile;
|
||||
}
|
||||
|
||||
void ABST::SetLive( bool Live ) {
|
||||
isLive = Live;
|
||||
}
|
||||
|
||||
void ABST::SetUpdate( bool Update ) {
|
||||
isUpdate = Update;
|
||||
}
|
||||
|
||||
void ABST::SetTimeScale( uint32_t Scale ) {
|
||||
curTimeScale = Scale;
|
||||
}
|
||||
|
||||
void ABST::SetMediaTime( uint32_t Time ) {
|
||||
curMediatime = Time;
|
||||
}
|
||||
|
||||
void ABST::SetSMPTE( uint32_t Smpte ) {
|
||||
curSMPTE = Smpte;
|
||||
}
|
||||
|
||||
void ABST::SetMovieIdentifier( std::string Identifier ) {
|
||||
curMovieIdentifier = Identifier;
|
||||
}
|
||||
|
||||
void ABST::SetDRM( std::string Drm ) {
|
||||
curDRM = Drm;
|
||||
}
|
||||
|
||||
void ABST::SetMetaData( std::string MetaData ) {
|
||||
curMetaData = MetaData;
|
||||
}
|
||||
|
||||
void ABST::AddServerEntry( std::string Url, uint32_t Offset ) {
|
||||
if(Offset >= Servers.size()) {
|
||||
Servers.resize(Offset+1);
|
||||
}
|
||||
Servers[Offset].ServerBaseUrl = Url;
|
||||
}
|
||||
|
||||
void ABST::AddQualityEntry( std::string Quality, uint32_t Offset ) {
|
||||
if(Offset >= Qualities.size()) {
|
||||
Qualities.resize(Offset+1);
|
||||
}
|
||||
Qualities[Offset].QualityModifier = Quality;
|
||||
}
|
||||
|
||||
void ABST::AddSegmentRunTable( Box * newSegment, uint32_t Offset ) {
|
||||
if( Offset >= SegmentRunTables.size() ) {
|
||||
SegmentRunTables.resize(Offset+1);
|
||||
}
|
||||
if( SegmentRunTables[Offset] ) {
|
||||
delete SegmentRunTables[Offset];
|
||||
}
|
||||
SegmentRunTables[Offset] = newSegment;
|
||||
}
|
||||
|
||||
void ABST::AddFragmentRunTable( Box * newFragment, uint32_t Offset ) {
|
||||
if( Offset >= FragmentRunTables.size() ) {
|
||||
FragmentRunTables.resize(Offset+1);
|
||||
}
|
||||
if( FragmentRunTables[Offset] ) {
|
||||
delete FragmentRunTables[Offset];
|
||||
}
|
||||
FragmentRunTables[Offset] = newFragment;
|
||||
}
|
||||
|
||||
void ABST::SetDefaults( ) {
|
||||
SetProfile( );
|
||||
SetLive( );
|
||||
SetUpdate( );
|
||||
SetTimeScale( );
|
||||
SetMediaTime( );
|
||||
SetSMPTE( );
|
||||
SetMovieIdentifier( );
|
||||
SetDRM( );
|
||||
SetMetaData( );
|
||||
SetVersion( );
|
||||
}
|
||||
|
||||
void ABST::SetVersion( bool NewVersion) {
|
||||
Version = NewVersion;
|
||||
}
|
||||
|
||||
void ABST::SetReserved( ) {
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(0));
|
||||
}
|
||||
|
||||
void ABST::WriteContent( ) {
|
||||
Box * current;
|
||||
std::string serializedServers = "";
|
||||
std::string serializedQualities = "";
|
||||
std::string serializedSegments = "";
|
||||
std::string serializedFragments = "";
|
||||
int SegmentAmount = 0;
|
||||
int FragmentAmount = 0;
|
||||
uint8_t * temp = new uint8_t[1];
|
||||
|
||||
ResetPayload( );
|
||||
SetReserved( );
|
||||
|
||||
for( uint32_t i = 0; i < Servers.size(); i++ ) {
|
||||
serializedServers.append(Servers[i].ServerBaseUrl.c_str());
|
||||
serializedServers += '\0';
|
||||
}
|
||||
for( uint32_t i = 0; i < Qualities.size(); i++ ) {
|
||||
serializedQualities.append(Qualities[i].QualityModifier.c_str());
|
||||
serializedQualities += '\0';
|
||||
}
|
||||
for( uint32_t i = 0; i < SegmentRunTables.size(); i++ ) {
|
||||
current=SegmentRunTables[i];
|
||||
if( current ) {
|
||||
SegmentAmount ++;
|
||||
serializedSegments.append((char*)current->GetBoxedData(),current->GetBoxedDataSize());
|
||||
}
|
||||
}
|
||||
for( uint32_t i = 0; i < FragmentRunTables.size(); i++ ) {
|
||||
current=FragmentRunTables[i];
|
||||
if( current ) {
|
||||
FragmentAmount ++;
|
||||
serializedFragments.append((char*)current->GetBoxedData(),current->GetBoxedDataSize());
|
||||
}
|
||||
}
|
||||
uint32_t OffsetServerEntryCount = 29 + curMovieIdentifier.size() + 1;
|
||||
uint32_t OffsetQualityEntryCount = OffsetServerEntryCount + 1 + serializedServers.size();
|
||||
uint32_t OffsetDrmData = OffsetQualityEntryCount + 1 + serializedQualities.size();
|
||||
uint32_t OffsetMetaData = OffsetDrmData + curDRM.size() + 1;
|
||||
uint32_t OffsetSegmentRuntableCount = OffsetMetaData + curMetaData.size() + 1;
|
||||
uint32_t OffsetFragmentRuntableCount = OffsetSegmentRuntableCount + 1 + serializedSegments.size();
|
||||
|
||||
temp[0] = 0 + ( curProfile << 6 ) + ( (uint8_t)isLive << 7 ) + ( (uint8_t)isUpdate << 7 );
|
||||
|
||||
SetPayload((uint32_t)serializedFragments.size(),(uint8_t*)serializedFragments.c_str(),OffsetFragmentRuntableCount+1);
|
||||
SetPayload((uint32_t)1,Box::uint8_to_uint8(FragmentAmount),OffsetFragmentRuntableCount);
|
||||
SetPayload((uint32_t)serializedSegments.size(),(uint8_t*)serializedSegments.c_str(),OffsetSegmentRuntableCount+1);
|
||||
SetPayload((uint32_t)1,Box::uint8_to_uint8(SegmentAmount),OffsetSegmentRuntableCount);
|
||||
SetPayload((uint32_t)curMetaData.size()+1,(uint8_t*)curMetaData.c_str(),OffsetMetaData);
|
||||
SetPayload((uint32_t)curDRM.size()+1,(uint8_t*)curDRM.c_str(),OffsetDrmData);
|
||||
SetPayload((uint32_t)serializedQualities.size(),(uint8_t*)serializedQualities.c_str(),OffsetQualityEntryCount+1);
|
||||
SetPayload((uint32_t)1,Box::uint8_to_uint8(Qualities.size()),OffsetQualityEntryCount);
|
||||
SetPayload((uint32_t)serializedServers.size(),(uint8_t*)serializedServers.c_str(),OffsetServerEntryCount+1);
|
||||
SetPayload((uint32_t)1,Box::uint8_to_uint8(Servers.size()),OffsetServerEntryCount);
|
||||
SetPayload((uint32_t)curMovieIdentifier.size()+1,(uint8_t*)curMovieIdentifier.c_str(),29);//+1 for \0-terminated string...
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(curSMPTE),25);
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(0),21);
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(curMediatime),17);
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(0),13);
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(curTimeScale),9);
|
||||
SetPayload((uint32_t)1,temp,8);
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(curBootstrapInfoVersion),4);
|
||||
}
|
||||
|
||||
void AFRT::SetUpdate( bool Update ) {
|
||||
isUpdate = Update;
|
||||
}
|
||||
|
||||
void AFRT::AddQualityEntry( std::string Quality, uint32_t Offset ) {
|
||||
if(Offset >= QualitySegmentUrlModifiers.size()) {
|
||||
QualitySegmentUrlModifiers.resize(Offset+1);
|
||||
}
|
||||
QualitySegmentUrlModifiers[Offset] = Quality;
|
||||
}
|
||||
|
||||
void AFRT::AddFragmentRunEntry( uint32_t FirstFragment, uint32_t FirstFragmentTimestamp, uint32_t FragmentsDuration, uint8_t Discontinuity, uint32_t Offset ) {
|
||||
if( Offset >= FragmentRunEntryTable.size() ) {
|
||||
FragmentRunEntryTable.resize(Offset+1);
|
||||
}
|
||||
FragmentRunEntryTable[Offset].FirstFragment = FirstFragment;
|
||||
FragmentRunEntryTable[Offset].FirstFragmentTimestamp = FirstFragmentTimestamp;
|
||||
FragmentRunEntryTable[Offset].FragmentDuration = FragmentsDuration;
|
||||
if( FragmentsDuration == 0) {
|
||||
FragmentRunEntryTable[Offset].DiscontinuityIndicator = Discontinuity;
|
||||
}
|
||||
}
|
||||
|
||||
void AFRT::SetDefaults( ) {
|
||||
SetUpdate( );
|
||||
SetTimeScale( );
|
||||
}
|
||||
|
||||
void AFRT::SetTimeScale( uint32_t Scale ) {
|
||||
curTimeScale = Scale;
|
||||
}
|
||||
|
||||
void AFRT::WriteContent( ) {
|
||||
std::string serializedQualities = "";
|
||||
std::string serializedFragmentEntries = "";
|
||||
ResetPayload( );
|
||||
|
||||
for( uint32_t i = 0; i < QualitySegmentUrlModifiers.size(); i++ ) {
|
||||
serializedQualities.append(QualitySegmentUrlModifiers[i].c_str());
|
||||
serializedQualities += '\0';
|
||||
}
|
||||
for( uint32_t i = 0; i < FragmentRunEntryTable.size(); i ++ ) {
|
||||
serializedFragmentEntries.append((char*)Box::uint32_to_uint8(FragmentRunEntryTable[i].FirstFragment),4);
|
||||
serializedFragmentEntries.append((char*)Box::uint32_to_uint8(0),4);
|
||||
serializedFragmentEntries.append((char*)Box::uint32_to_uint8(FragmentRunEntryTable[i].FirstFragmentTimestamp),4);
|
||||
serializedFragmentEntries.append((char*)Box::uint32_to_uint8(FragmentRunEntryTable[i].FragmentDuration),4);
|
||||
if(FragmentRunEntryTable[i].FragmentDuration == 0) {
|
||||
serializedFragmentEntries.append((char*)Box::uint8_to_uint8(FragmentRunEntryTable[i].DiscontinuityIndicator),1);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t OffsetFragmentRunEntryCount = 9 + serializedQualities.size();
|
||||
|
||||
SetPayload((uint32_t)serializedFragmentEntries.size(),(uint8_t*)serializedFragmentEntries.c_str(),OffsetFragmentRunEntryCount+4);
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(FragmentRunEntryTable.size()),OffsetFragmentRunEntryCount);
|
||||
SetPayload((uint32_t)serializedQualities.size(),(uint8_t*)serializedQualities.c_str(),9);
|
||||
SetPayload((uint32_t)1,Box::uint8_to_uint8(QualitySegmentUrlModifiers.size()),8);
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(curTimeScale),4);
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8((isUpdate ? 1 : 0)));
|
||||
}
|
||||
|
||||
void ASRT::SetUpdate( bool Update ) {
|
||||
isUpdate = Update;
|
||||
}
|
||||
|
||||
void ASRT::AddQualityEntry( std::string Quality, uint32_t Offset ) {
|
||||
if(Offset >= QualitySegmentUrlModifiers.size()) {
|
||||
QualitySegmentUrlModifiers.resize(Offset+1);
|
||||
}
|
||||
QualitySegmentUrlModifiers[Offset] = Quality;
|
||||
}
|
||||
|
||||
void ASRT::AddSegmentRunEntry( uint32_t FirstSegment, uint32_t FragmentsPerSegment, uint32_t Offset ) {
|
||||
if( Offset >= SegmentRunEntryTable.size() ) {
|
||||
SegmentRunEntryTable.resize(Offset+1);
|
||||
}
|
||||
SegmentRunEntryTable[Offset].FirstSegment = FirstSegment;
|
||||
SegmentRunEntryTable[Offset].FragmentsPerSegment = FragmentsPerSegment;
|
||||
}
|
||||
|
||||
void ASRT::SetVersion( bool NewVersion ) {
|
||||
Version = NewVersion;
|
||||
}
|
||||
|
||||
void ASRT::SetDefaults( ) {
|
||||
SetUpdate( );
|
||||
}
|
||||
|
||||
void ASRT::WriteContent( ) {
|
||||
std::string serializedQualities = "";
|
||||
ResetPayload( );
|
||||
|
||||
for( uint32_t i = 0; i < QualitySegmentUrlModifiers.size(); i++ ) {
|
||||
serializedQualities.append(QualitySegmentUrlModifiers[i].c_str());
|
||||
serializedQualities += '\0';
|
||||
}
|
||||
|
||||
uint32_t OffsetSegmentRunEntryCount = 5 + serializedQualities.size();
|
||||
|
||||
for( uint32_t i = 0; i < SegmentRunEntryTable.size(); i ++ ) {
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(SegmentRunEntryTable[i].FragmentsPerSegment),(8*i)+OffsetSegmentRunEntryCount+8);
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(SegmentRunEntryTable[i].FirstSegment),(8*i)+OffsetSegmentRunEntryCount+4);
|
||||
}
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8(SegmentRunEntryTable.size()),OffsetSegmentRunEntryCount);
|
||||
SetPayload((uint32_t)serializedQualities.size(),(uint8_t*)serializedQualities.c_str(),5);
|
||||
SetPayload((uint32_t)1,Box::uint8_to_uint8(QualitySegmentUrlModifiers.size()),4);
|
||||
SetPayload((uint32_t)4,Box::uint32_to_uint8((isUpdate ? 1 : 0)));
|
||||
}
|
||||
|
||||
std::string GenerateLiveBootstrap( uint32_t CurMediaTime ) {
|
||||
AFRT afrt;
|
||||
afrt.SetUpdate(false);
|
||||
afrt.SetTimeScale(1000);
|
||||
afrt.AddQualityEntry("");
|
||||
afrt.AddFragmentRunEntry(1, 0 , 4000); //FirstFragment, FirstFragmentTimestamp,Fragment Duration in milliseconds
|
||||
afrt.WriteContent();
|
||||
|
||||
ASRT asrt;
|
||||
asrt.SetUpdate(false);
|
||||
asrt.AddQualityEntry("");
|
||||
asrt.AddSegmentRunEntry(1, 199);//1 Segment, 199 Fragments
|
||||
asrt.WriteContent();
|
||||
|
||||
ABST abst;
|
||||
abst.AddFragmentRunTable(&afrt);
|
||||
abst.AddSegmentRunTable(&asrt);
|
||||
abst.SetBootstrapVersion(1);
|
||||
abst.SetProfile(0);
|
||||
abst.SetLive(true);
|
||||
abst.SetUpdate(false);
|
||||
abst.SetTimeScale(1000);
|
||||
abst.SetMediaTime(0xFFFFFFFF);
|
||||
abst.SetSMPTE(0);
|
||||
abst.SetMovieIdentifier("fifa");
|
||||
abst.SetDRM("");
|
||||
abst.SetMetaData("");
|
||||
abst.AddServerEntry("");
|
||||
abst.AddQualityEntry("");
|
||||
abst.WriteContent();
|
||||
|
||||
std::string Result;
|
||||
Result.append((char*)abst.GetBoxedData(), (int)abst.GetBoxedDataSize());
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::string mdatFold(std::string data){
|
||||
std::string Result;
|
||||
unsigned int t_int;
|
||||
t_int = htonl(data.size()+8);
|
||||
Result.append((char*)&t_int, 4);
|
||||
t_int = htonl(0x6D646174);
|
||||
Result.append((char*)&t_int, 4);
|
||||
Result.append(data);
|
||||
return Result;
|
||||
}
|
||||
|
||||
};
|
131
lib/mp4.h
131
lib/mp4.h
|
@ -1,131 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
/// Contains all MP4 format related code.
|
||||
namespace MP4{
|
||||
|
||||
class Box {
|
||||
public:
|
||||
Box();
|
||||
Box(uint32_t BoxType);
|
||||
Box(uint8_t * Content, uint32_t length);
|
||||
~Box();
|
||||
void SetBoxType(uint32_t BoxType);
|
||||
uint32_t GetBoxType();
|
||||
void SetPayload(uint32_t Size, uint8_t * Data, uint32_t Index = 0);
|
||||
uint32_t GetPayloadSize();
|
||||
uint8_t * GetPayload();
|
||||
uint8_t * GetPayload(uint32_t Index, uint32_t & Size);
|
||||
uint32_t GetBoxedDataSize();
|
||||
uint8_t * GetBoxedData( );
|
||||
static uint8_t * uint32_to_uint8( uint32_t data );
|
||||
static uint8_t * uint16_to_uint8( uint16_t data );
|
||||
static uint8_t * uint8_to_uint8( uint8_t data );
|
||||
void ResetPayload( );
|
||||
private:
|
||||
uint8_t * Payload;
|
||||
uint32_t PayloadSize;
|
||||
};//Box Class
|
||||
|
||||
struct abst_serverentry {
|
||||
std::string ServerBaseUrl;
|
||||
};//abst_serverentry
|
||||
|
||||
struct abst_qualityentry {
|
||||
std::string QualityModifier;
|
||||
};//abst_qualityentry
|
||||
|
||||
/// ABST Box class
|
||||
class ABST: public Box {
|
||||
public:
|
||||
ABST() : Box(0x61627374){};
|
||||
void SetBootstrapVersion( uint32_t Version = 1 );
|
||||
void SetProfile( uint8_t Profile = 0 );
|
||||
void SetLive( bool Live = true );
|
||||
void SetUpdate( bool Update = false );
|
||||
void SetTimeScale( uint32_t Scale = 1000 );
|
||||
void SetMediaTime( uint32_t Time = 0 );
|
||||
void SetSMPTE( uint32_t Smpte = 0 );
|
||||
void SetMovieIdentifier( std::string Identifier = "" );
|
||||
void SetDRM( std::string Drm = "" );
|
||||
void SetMetaData( std::string MetaData = "" );
|
||||
void AddServerEntry( std::string Url = "", uint32_t Offset = 0 );
|
||||
void AddQualityEntry( std::string Quality = "", uint32_t Offset = 0 );
|
||||
void AddSegmentRunTable( Box * newSegment, uint32_t Offset = 0 );
|
||||
void AddFragmentRunTable( Box * newFragment, uint32_t Offset = 0 );
|
||||
void SetVersion( bool NewVersion = 0 );
|
||||
void WriteContent( );
|
||||
private:
|
||||
void SetDefaults( );
|
||||
void SetReserved( );
|
||||
uint32_t curBootstrapInfoVersion;
|
||||
uint8_t curProfile;
|
||||
bool isLive;
|
||||
bool isUpdate;
|
||||
bool Version;
|
||||
uint32_t curTimeScale;
|
||||
uint32_t curMediatime;//write as uint64_t
|
||||
uint32_t curSMPTE;//write as uint64_t
|
||||
std::string curMovieIdentifier;
|
||||
std::string curDRM;
|
||||
std::string curMetaData;
|
||||
std::vector<abst_serverentry> Servers;
|
||||
std::vector<abst_qualityentry> Qualities;
|
||||
std::vector<Box *> SegmentRunTables;
|
||||
std::vector<Box *> FragmentRunTables;
|
||||
};//ABST Box
|
||||
|
||||
struct afrt_fragmentrunentry {
|
||||
uint32_t FirstFragment;
|
||||
uint32_t FirstFragmentTimestamp; //write as uint64_t
|
||||
uint32_t FragmentDuration;
|
||||
uint8_t DiscontinuityIndicator;//if FragmentDuration == 0
|
||||
};//afrt_fragmentrunentry
|
||||
|
||||
|
||||
/// AFRT Box class
|
||||
class AFRT : public Box {
|
||||
public:
|
||||
AFRT() : Box(0x61667274){};
|
||||
void SetUpdate( bool Update = false );
|
||||
void SetTimeScale( uint32_t Scale = 1000 );
|
||||
void AddQualityEntry( std::string Quality = "", uint32_t Offset = 0 );
|
||||
void AddFragmentRunEntry( uint32_t FirstFragment = 0, uint32_t FirstFragmentTimestamp = 0, uint32_t FragmentsDuration = 1, uint8_t Discontinuity = 0, uint32_t Offset = 0 );
|
||||
void WriteContent( );
|
||||
private:
|
||||
void SetDefaults( );
|
||||
bool isUpdate;
|
||||
uint32_t curTimeScale;
|
||||
std::vector<std::string> QualitySegmentUrlModifiers;
|
||||
std::vector<afrt_fragmentrunentry> FragmentRunEntryTable;
|
||||
};//AFRT Box
|
||||
|
||||
struct asrt_segmentrunentry {
|
||||
uint32_t FirstSegment;
|
||||
uint32_t FragmentsPerSegment;
|
||||
};//abst_qualityentry
|
||||
|
||||
/// ASRT Box class
|
||||
class ASRT : public Box {
|
||||
public:
|
||||
ASRT() : Box(0x61737274){};
|
||||
void SetUpdate( bool Update = false );
|
||||
void AddQualityEntry( std::string Quality = "", uint32_t Offset = 0 );
|
||||
void AddSegmentRunEntry( uint32_t FirstSegment = 0, uint32_t FragmentsPerSegment = 100, uint32_t Offset = 0 );
|
||||
void WriteContent( );
|
||||
void SetVersion( bool NewVersion = 0 );
|
||||
private:
|
||||
void SetDefaults( );
|
||||
bool isUpdate;
|
||||
bool Version;
|
||||
std::vector<std::string> QualitySegmentUrlModifiers;
|
||||
std::vector<asrt_segmentrunentry> SegmentRunEntryTable;
|
||||
Box * Container;
|
||||
};//ASRT Box
|
||||
|
||||
std::string GenerateLiveBootstrap( uint32_t CurMediaTime );
|
||||
std::string mdatFold(std::string data);
|
||||
|
||||
};
|
361
lib/procs.cpp
361
lib/procs.cpp
|
@ -1,361 +0,0 @@
|
|||
/// \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
32
lib/procs.h
|
@ -1,32 +0,0 @@
|
|||
/// \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);
|
||||
};
|
||||
|
||||
};
|
|
@ -1,467 +0,0 @@
|
|||
/// \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 * 1000 + 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;
|
||||
|
||||
/// 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;
|
||||
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;
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
/// \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.
|
||||
|
||||
/// 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
|
747
lib/socket.cpp
747
lib/socket.cpp
|
@ -1,747 +0,0 @@
|
|||
/// \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.
|
||||
/// If the connection is already closed, nothing happens.
|
||||
void Socket::Connection::close(){
|
||||
if (connected()){
|
||||
#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() const{
|
||||
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;
|
||||
}
|
||||
|
||||
/// Returns true if these sockets are the same socket.
|
||||
/// Does not check the internal stats - only the socket itself.
|
||||
bool Socket::Connection::operator== (const Connection &B) const{
|
||||
return sock == B.sock;
|
||||
}
|
||||
|
||||
/// Returns true if these sockets are not the same socket.
|
||||
/// Does not check the internal stats - only the socket itself.
|
||||
bool Socket::Connection::operator!= (const Connection &B) const{
|
||||
return sock != B.sock;
|
||||
}
|
||||
|
||||
/// Returns true if the socket is valid.
|
||||
/// Aliases for Socket::Connection::connected()
|
||||
Socket::Connection::operator bool() const{
|
||||
return connected();
|
||||
}
|
||||
|
||||
/// 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){
|
||||
if (!IPv6bind(port, hostname, nonblock) && !IPv4bind(port, hostname, nonblock)){
|
||||
fprintf(stderr, "Could not create socket %s:%i! Error: %s\n", hostname.c_str(), port, strerror(errno));
|
||||
sock = -1;
|
||||
}
|
||||
}//Socket::Server TCP Constructor
|
||||
|
||||
/// Attempt to bind an IPv6 socket.
|
||||
/// \param port The TCP port to listen on
|
||||
/// \param hostname The interface to bind to. The default is 0.0.0.0 (all interfaces).
|
||||
/// \param nonblock Whether accept() calls will be nonblocking. Default is false (blocking).
|
||||
/// \return True if successful, false otherwise.
|
||||
bool Socket::Server::IPv6bind(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 IPv6 socket %s:%i! Error: %s\n", hostname.c_str(), port, strerror(errno));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
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 true;
|
||||
}else{
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "IPv6 Listen failed! Error: %s\n", strerror(errno));
|
||||
#endif
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "IPv6 Binding %s:%i failed (%s)\n", hostname.c_str(), port, strerror(errno));
|
||||
#endif
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to bind an IPv4 socket.
|
||||
/// \param port The TCP port to listen on
|
||||
/// \param hostname The interface to bind to. The default is 0.0.0.0 (all interfaces).
|
||||
/// \param nonblock Whether accept() calls will be nonblocking. Default is false (blocking).
|
||||
/// \return True if successful, false otherwise.
|
||||
bool Socket::Server::IPv4bind(int port, std::string hostname, bool nonblock){
|
||||
sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock < 0){
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "Could not create IPv4 socket %s:%i! Error: %s\n", hostname.c_str(), port, strerror(errno));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
int on = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
|
||||
if (nonblock){
|
||||
int flags = fcntl(sock, F_GETFL, 0);
|
||||
flags |= O_NONBLOCK;
|
||||
fcntl(sock, F_SETFL, flags);
|
||||
}
|
||||
struct sockaddr_in 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
|
||||
}
|
||||
int 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 true;
|
||||
}else{
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "IPv4 Listen failed! Error: %s\n", strerror(errno));
|
||||
#endif
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "IPv4 binding %s:%i failed (%s)\n", hostname.c_str(), port, strerror(errno));
|
||||
#endif
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// If the connection is already closed, nothing happens.
|
||||
void Socket::Server::close(){
|
||||
if (connected()){
|
||||
#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() const{
|
||||
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);
|
||||
}
|
93
lib/socket.h
93
lib/socket.h
|
@ -1,93 +0,0 @@
|
|||
/// \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() const; ///< 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.
|
||||
//overloaded operators
|
||||
bool operator== (const Connection &B) const;
|
||||
bool operator!= (const Connection &B) const;
|
||||
operator bool() const;
|
||||
};
|
||||
|
||||
/// This class is for easily setting up listening socket, either TCP or Unix.
|
||||
class Server{
|
||||
private:
|
||||
int sock; ///< Internally saved socket number.
|
||||
bool IPv6bind(int port, std::string hostname, bool nonblock); ///< Attempt to bind an IPv6 socket
|
||||
bool IPv4bind(int port, std::string hostname, bool nonblock); ///< Attempt to bind an IPv4 socket
|
||||
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() const; ///< 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);
|
||||
|
||||
};
|
|
@ -1,12 +1,9 @@
|
|||
AM_CPPFLAGS = $(MIST_CFLAGS)
|
||||
LDADD = $(MIST_LIBS)
|
||||
SUBDIRS=converters analysers
|
||||
bin_PROGRAMS=MistBuffer MistController MistConnRAW MistConnRTMP MistConnHTTP
|
||||
MistBuffer_SOURCES=buffer.cpp buffer_user.h buffer_user.cpp buffer_stream.h buffer_stream.cpp ../VERSION
|
||||
MistBuffer_LDADD=../lib/libjson.la ../lib/libsocket.la ../lib/libdtsc.la ../lib/libtinythread.la
|
||||
MistBuffer_SOURCES=buffer.cpp buffer_user.h buffer_user.cpp buffer_stream.h buffer_stream.cpp tinythread.cpp tinythread.h ../VERSION
|
||||
MistController_SOURCES=controller.cpp ../VERSION
|
||||
MistController_LDADD=../lib/libjson.la ../lib/libsocket.la ../lib/libprocs.la ../lib/libconfig.la ../lib/libhttp_parser.la ../lib/libauth.la ../lib/libbase64.la
|
||||
MistConnRAW_SOURCES=conn_raw.cpp ../VERSION
|
||||
MistConnRAW_LDADD=../lib/libsocket.la
|
||||
MistConnRTMP_SOURCES=conn_rtmp.cpp server_setup.h ../VERSION
|
||||
MistConnRTMP_LDADD=../lib/libdtsc.la ../lib/librtmpchunks.la ../lib/libconfig.la
|
||||
MistConnHTTP_SOURCES=conn_http.cpp server_setup.h ../VERSION
|
||||
MistConnHTTP_LDADD=../lib/libdtsc.la ../lib/libflv_tag.la ../lib/libjson.la ../lib/libhttp_parser.la ../lib/libmp4.la ../lib/libprocs.la ../lib/libbase64.la ../lib/libconfig.la
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
AM_CPPFLAGS = $(MIST_CFLAGS)
|
||||
LDADD = $(MIST_LIBS)
|
||||
bin_PROGRAMS=MistAnalyserRTMP MistAnalyserFLV MistAnalyserDTSC MistAnalyserAMF
|
||||
MistAnalyserRTMP_SOURCES=rtmp_analyser.cpp
|
||||
MistAnalyserRTMP_LDADD=../../lib/librtmpchunks.la ../../lib/libdtsc.la ../../lib/libkeycrypto.la
|
||||
MistAnalyserFLV_SOURCES=flv_analyser.cpp
|
||||
MistAnalyserFLV_LDADD=../../lib/libflv_tag.la ../../lib/libdtsc.la
|
||||
MistAnalyserDTSC_SOURCES=dtsc_analyser.cpp
|
||||
MistAnalyserDTSC_LDADD=../../lib/libdtsc.la
|
||||
MistAnalyserAMF_SOURCES=amf_analyser.cpp
|
||||
MistAnalyserAMF_LDADD=../../lib/libamf.la
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include "../../lib/amf.h"
|
||||
#include <mist/amf.h>
|
||||
|
||||
/// Debugging tool for AMF data.
|
||||
/// Expects AMF data through stdin, outputs human-readable information to stderr.
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "../../lib/dtsc.h" //DTSC support
|
||||
#include <mist/dtsc.h> //DTSC support
|
||||
|
||||
/// Reads DTSC from stdin and outputs human-readable information to stderr.
|
||||
int main() {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "../../lib/flv_tag.h" //FLV support
|
||||
#include <mist/flv_tag.h> //FLV support
|
||||
|
||||
/// Reads FLV from stdin and outputs human-readable information to stderr.
|
||||
int main() {
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
#include <fstream>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "../../lib/flv_tag.h"
|
||||
#include "../../lib/amf.h"
|
||||
#include "../../lib/rtmpchunks.h"
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/amf.h>
|
||||
#include <mist/rtmpchunks.h>
|
||||
|
||||
int Detail = 0;
|
||||
#define DETAIL_RECONSTRUCT 1
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "../lib/tinythread.h"
|
||||
#include "../lib/json.h"
|
||||
#include <mist/json.h>
|
||||
#include "tinythread.h"
|
||||
#include "buffer_user.h"
|
||||
|
||||
namespace Buffer{
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "../lib/dtsc.h"
|
||||
#include "../lib/socket.h"
|
||||
#include "../lib/tinythread.h"
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/socket.h>
|
||||
#include "tinythread.h"
|
||||
|
||||
namespace Buffer{
|
||||
/// Converts a stats line to up, down, host, connector and conntime values.
|
||||
|
|
|
@ -11,14 +11,14 @@
|
|||
#include <sys/wait.h>
|
||||
#include <getopt.h>
|
||||
#include <ctime>
|
||||
#include "../lib/socket.h"
|
||||
#include "../lib/http_parser.h"
|
||||
#include "../lib/json.h"
|
||||
#include "../lib/dtsc.h"
|
||||
#include "../lib/flv_tag.h"
|
||||
#include "../lib/base64.h"
|
||||
#include "../lib/amf.h"
|
||||
#include "../lib/mp4.h"
|
||||
#include <mist/socket.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/base64.h>
|
||||
#include <mist/amf.h>
|
||||
#include <mist/mp4.h>
|
||||
|
||||
/// Holds everything unique to HTTP Connector.
|
||||
namespace Connector_HTTP{
|
||||
|
|
235
src/conn_http_dynamic.cpp
Normal file
235
src/conn_http_dynamic.cpp
Normal file
|
@ -0,0 +1,235 @@
|
|||
/// \file conn_http_dynamic.cpp
|
||||
/// Contains the main code for the HTTP Dynamic Connector
|
||||
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <getopt.h>
|
||||
#include <ctime>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/base64.h>
|
||||
#include <mist/amf.h>
|
||||
#include <mist/mp4.h>
|
||||
|
||||
/// Holds everything unique to HTTP Dynamic Connector.
|
||||
namespace Connector_HTTP_Dynamic{
|
||||
|
||||
/// Returns AMF-format metadata
|
||||
std::string GetMetaData( ) {
|
||||
/// \todo Make this actually do what it should - even though it seems to be ignored completely by all media players.
|
||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||
amfreply.addContent(AMF::Object("onMetaData",AMF::AMF0_STRING));
|
||||
amfreply.addContent(AMF::Object("",AMF::AMF0_ECMA_ARRAY));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("trackinfo", AMF::AMF0_STRICT_ARRAY));
|
||||
amfreply.getContentP(1)->getContentP(0)->addContent(AMF::Object("arrVal"));
|
||||
//amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMF::Object("timescale",(double)1000));
|
||||
//amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMF::Object("length",(double)59641700));
|
||||
amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMF::Object("language","eng"));
|
||||
amfreply.getContentP(1)->getContentP(0)->getContentP(0)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY));
|
||||
amfreply.getContentP(1)->getContentP(0)->getContentP(0)->getContentP(1)->addContent(AMF::Object("arrVal"));
|
||||
amfreply.getContentP(1)->getContentP(0)->getContentP(0)->getContentP(1)->getContentP(0)->addContent(AMF::Object("sampletype","avc1"));
|
||||
amfreply.getContentP(1)->getContentP(0)->addContent(AMF::Object("arrVal"));
|
||||
//amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMF::Object("timescale",(double)44100));
|
||||
//amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMF::Object("length",(double)28630000));
|
||||
amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMF::Object("language","eng"));
|
||||
amfreply.getContentP(1)->getContentP(0)->getContentP(1)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY));
|
||||
amfreply.getContentP(1)->getContentP(0)->getContentP(1)->getContentP(1)->addContent(AMF::Object("arrVal"));
|
||||
amfreply.getContentP(1)->getContentP(0)->getContentP(1)->getContentP(1)->getContentP(0)->addContent(AMF::Object("sampletype","mp4a"));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("audiochannels",(double)2));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("audiosamplerate",(double)44100));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("videoframerate",(double)25));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("aacaot",(double)2));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("avclevel",(double)12));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("avcprofile",(double)77));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("audiocodecid","mp4a"));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("videocodecid","avc1"));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("width",(double)1280));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("height",(double)720));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("frameWidth",(double)1280));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("frameHeight",(double)720));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("displayWidth",(double)1280));
|
||||
amfreply.getContentP(1)->addContent(AMF::Object("displayHeight",(double)720));
|
||||
//amfreply.getContentP(1)->addContent(AMF::Object("moovposition",(double)35506700));
|
||||
//amfreply.getContentP(1)->addContent(AMF::Object("duration",(double)596.458));
|
||||
return amfreply.Pack( );
|
||||
}//getMetaData
|
||||
|
||||
/// Returns a F4M-format manifest file
|
||||
std::string BuildManifest(std::string MovieId) {
|
||||
std::string Result="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
|
||||
"<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n" +
|
||||
"<id>" + MovieId + "</id>\n" +
|
||||
"<mimeType>video/mp4</mimeType>\n" +
|
||||
"<streamType>live</streamType>\n" +
|
||||
"<deliveryType>streaming</deliveryType>\n" +
|
||||
"<bootstrapInfo profile=\"named\" id=\"bootstrap1\">" + Base64::encode(MP4::GenerateLiveBootstrap(1)) + "</bootstrapInfo>\n" +
|
||||
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId + "/\">\n" +
|
||||
"<metadata>" + Base64::encode(GetMetaData()) + "</metadata>\n" +
|
||||
"</media>\n" +
|
||||
"</manifest>\n";
|
||||
return Result;
|
||||
}//BuildManifest
|
||||
|
||||
/// Main function for Connector_HTTP_Dynamic
|
||||
int Connector_HTTP_Dynamic(Socket::Connection conn){
|
||||
std::string FlashBuf;
|
||||
FLV::Tag tmp;//temporary tag, for init data
|
||||
|
||||
std::queue<std::string> Flash_FragBuffer;//Fragment buffer
|
||||
DTSC::Stream Strm;//Incoming stream buffer.
|
||||
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
|
||||
|
||||
bool ready4data = false;//Set to true when streaming is to begin.
|
||||
bool inited = false;
|
||||
Socket::Connection ss(-1);
|
||||
std::string streamname;
|
||||
FLV::Tag tag;///< Temporary tag buffer.
|
||||
std::string recBuffer = "";
|
||||
|
||||
std::string Movie;
|
||||
std::string Quality;
|
||||
int Segment = -1;
|
||||
int ReqFragment = -1;
|
||||
int temp;
|
||||
int Flash_RequestPending = 0;
|
||||
unsigned int lastStats = 0;
|
||||
conn.setBlocking(false);//do not block on conn.spool() when no data is available
|
||||
|
||||
while (conn.connected()){
|
||||
if (conn.spool()){
|
||||
if (HTTP_R.Read(conn.Received())){
|
||||
#if DEBUG >= 4
|
||||
std::cout << "Received request: " << HTTP_R.url << std::endl;
|
||||
#endif
|
||||
if (HTTP_R.url.find("f4m") == std::string::npos){
|
||||
Movie = HTTP_R.url.substr(1);
|
||||
Movie = Movie.substr(0,Movie.find("/"));
|
||||
Quality = HTTP_R.url.substr( HTTP_R.url.find("/",1)+1 );
|
||||
Quality = Quality.substr(0, Quality.find("Seg"));
|
||||
temp = HTTP_R.url.find("Seg") + 3;
|
||||
Segment = atoi( HTTP_R.url.substr(temp,HTTP_R.url.find("-",temp)-temp).c_str());
|
||||
temp = HTTP_R.url.find("Frag") + 4;
|
||||
ReqFragment = atoi( HTTP_R.url.substr(temp).c_str() );
|
||||
#if DEBUG >= 4
|
||||
printf( "Quality: %s, Seg %d Frag %d\n", Quality.c_str(), Segment, ReqFragment);
|
||||
#endif
|
||||
Flash_RequestPending++;
|
||||
}else{
|
||||
Movie = HTTP_R.url.substr(1);
|
||||
Movie = Movie.substr(0,Movie.find("/"));
|
||||
HTTP_S.Clean();
|
||||
HTTP_S.SetHeader("Content-Type","text/xml");
|
||||
HTTP_S.SetHeader("Cache-Control","no-cache");
|
||||
std::string manifest = BuildManifest(Movie);
|
||||
HTTP_S.SetBody(manifest);
|
||||
conn.Send(HTTP_S.BuildResponse("200", "OK"));
|
||||
#if DEBUG >= 3
|
||||
printf("Sent manifest\n");
|
||||
#endif
|
||||
}
|
||||
ready4data = true;
|
||||
HTTP_R.Clean(); //clean for any possible next requests
|
||||
}else{
|
||||
#if DEBUG >= 3
|
||||
fprintf(stderr, "Could not parse the following:\n%s\n", conn.Received().c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (ready4data){
|
||||
if (!inited){
|
||||
//we are ready, connect the socket!
|
||||
ss = Socket::getStream(streamname);
|
||||
if (!ss.connected()){
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "Could not connect to server!\n");
|
||||
#endif
|
||||
ss.close();
|
||||
HTTP_S.Clean();
|
||||
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
|
||||
conn.Send(HTTP_S.BuildResponse("404", "Not found"));
|
||||
ready4data = false;
|
||||
continue;
|
||||
}
|
||||
#if DEBUG >= 3
|
||||
fprintf(stderr, "Everything connected, starting to send video data...\n");
|
||||
#endif
|
||||
inited = true;
|
||||
}
|
||||
if ((Flash_RequestPending > 0) && !Flash_FragBuffer.empty()){
|
||||
HTTP_S.Clean();
|
||||
HTTP_S.SetHeader("Content-Type","video/mp4");
|
||||
HTTP_S.SetBody(MP4::mdatFold(Flash_FragBuffer.front()));
|
||||
Flash_FragBuffer.pop();
|
||||
conn.Send(HTTP_S.BuildResponse("200", "OK"));
|
||||
Flash_RequestPending--;
|
||||
#if DEBUG >= 3
|
||||
fprintf(stderr, "Sending a video fragment. %i left in buffer, %i requested\n", (int)Flash_FragBuffer.size(), Flash_RequestPending);
|
||||
#endif
|
||||
}
|
||||
unsigned int now = time(0);
|
||||
if (now != lastStats){
|
||||
lastStats = now;
|
||||
ss.Send("S "+conn.getStats("HTTP_Dynamic"));
|
||||
}
|
||||
if (ss.spool() || ss.Received() != ""){
|
||||
if (Strm.parsePacket(ss.Received())){
|
||||
tag.DTSCLoader(Strm);
|
||||
if (Strm.getPacket(0).getContentP("keyframe")){
|
||||
if (FlashBuf != ""){
|
||||
Flash_FragBuffer.push(FlashBuf);
|
||||
while (Flash_FragBuffer.size() > 2){
|
||||
Flash_FragBuffer.pop();
|
||||
}
|
||||
#if DEBUG >= 4
|
||||
fprintf(stderr, "Received a fragment. Now %i in buffer.\n", (int)Flash_FragBuffer.size());
|
||||
#endif
|
||||
}
|
||||
FlashBuf.clear();
|
||||
//fill buffer with init data, if needed.
|
||||
if (Strm.metadata.getContentP("audio") && Strm.metadata.getContentP("audio")->getContentP("init")){
|
||||
tmp.DTSCAudioInit(Strm);
|
||||
FlashBuf.append(tmp.data, tmp.len);
|
||||
}
|
||||
if (Strm.metadata.getContentP("video") && Strm.metadata.getContentP("video")->getContentP("init")){
|
||||
tmp.DTSCVideoInit(Strm);
|
||||
FlashBuf.append(tmp.data, tmp.len);
|
||||
}
|
||||
}
|
||||
FlashBuf.append(tag.data, tag.len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
conn.close();
|
||||
ss.close();
|
||||
#if DEBUG >= 1
|
||||
if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());}
|
||||
fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
|
||||
if (inited){
|
||||
fprintf(stderr, "Status was: inited\n");
|
||||
}else{
|
||||
if (ready4data){
|
||||
fprintf(stderr, "Status was: ready4data\n");
|
||||
}else{
|
||||
fprintf(stderr, "Status was: connected\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}//Connector_HTTP_Dynamic main function
|
||||
|
||||
};//Connector_HTTP_Dynamic namespace
|
||||
|
||||
// Load http setup file with the correct settings for this HTTP connector
|
||||
#define MAINHANDLER Connector_HTTP_Dynamic::Connector_HTTP_Dynamic
|
||||
#define CONNECTOR dynamic
|
||||
#include "server_setup_http.h"
|
145
src/conn_http_progressive.cpp
Normal file
145
src/conn_http_progressive.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
/// \file conn_http_progressive.cpp
|
||||
/// Contains the main code for the HTTP Progressive Connector
|
||||
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <getopt.h>
|
||||
#include <ctime>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/amf.h>
|
||||
|
||||
/// Holds everything unique to HTTP Progressive Connector.
|
||||
namespace Connector_HTTP_Progressive{
|
||||
|
||||
/// Main function for Connector_HTTP_Progressive
|
||||
int Connector_HTTP_Progressive(Socket::Connection conn){
|
||||
bool progressive_has_sent_header = false;
|
||||
bool ready4data = false;///< Set to true when streaming is to begin.
|
||||
DTSC::Stream Strm;///< Incoming stream buffer.
|
||||
HTTP::Parser HTTP_R, HTTP_S;///<HTTP Receiver en HTTP Sender.
|
||||
bool inited = false;
|
||||
Socket::Connection ss(-1);
|
||||
std::string streamname;
|
||||
FLV::Tag tag;///< Temporary tag buffer.
|
||||
std::string recBuffer = "";
|
||||
|
||||
std::string Movie;
|
||||
std::string Quality;
|
||||
int Segment = -1;
|
||||
int ReqFragment = -1;
|
||||
int temp;
|
||||
int Flash_RequestPending = 0;
|
||||
unsigned int lastStats = 0;
|
||||
conn.setBlocking(false);//do not block on conn.spool() when no data is available
|
||||
|
||||
while (conn.connected()){
|
||||
//only parse input if available or not yet init'ed
|
||||
if (conn.spool()){
|
||||
if (HTTP_R.Read(conn.Received())){
|
||||
#if DEBUG >= 4
|
||||
std::cout << "Received request: " << HTTP_R.url << std::endl;
|
||||
#endif
|
||||
//we assume the URL is the stream name with a 3 letter extension
|
||||
std::string extension = HTTP_R.url.substr(HTTP_R.url.size()-4);
|
||||
Movie = HTTP_R.url.substr(0, HTTP_R.url.size()-4);//strip the extension
|
||||
/// \todo VoD streams will need support for position reading from the URL parameters
|
||||
ready4data = true;
|
||||
HTTP_R.Clean(); //clean for any possible next requests
|
||||
}else{
|
||||
#if DEBUG >= 3
|
||||
fprintf(stderr, "Could not parse the following:\n%s\n", conn.Received().c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (ready4data){
|
||||
if (!inited){
|
||||
//we are ready, connect the socket!
|
||||
ss = Socket::getStream(streamname);
|
||||
if (!ss.connected()){
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "Could not connect to server!\n");
|
||||
#endif
|
||||
ss.close();
|
||||
HTTP_S.Clean();
|
||||
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
|
||||
conn.Send(HTTP_S.BuildResponse("404", "Not found"));
|
||||
ready4data = false;
|
||||
continue;
|
||||
}
|
||||
#if DEBUG >= 3
|
||||
fprintf(stderr, "Everything connected, starting to send video data...\n");
|
||||
#endif
|
||||
inited = true;
|
||||
}
|
||||
unsigned int now = time(0);
|
||||
if (now != lastStats){
|
||||
lastStats = now;
|
||||
ss.Send("S "+conn.getStats("HTTP_Progressive"));
|
||||
}
|
||||
if (ss.spool() || ss.Received() != ""){
|
||||
if (Strm.parsePacket(ss.Received())){
|
||||
tag.DTSCLoader(Strm);
|
||||
if (!progressive_has_sent_header){
|
||||
HTTP_S.Clean();//make sure no parts of old requests are left in any buffers
|
||||
HTTP_S.SetHeader("Content-Type", "video/x-flv");//Send the correct content-type for FLV files
|
||||
//HTTP_S.SetHeader("Transfer-Encoding", "chunked");
|
||||
HTTP_S.protocol = "HTTP/1.0";
|
||||
conn.Send(HTTP_S.BuildResponse("200", "OK"));//no SetBody = unknown length - this is intentional, we will stream the entire file
|
||||
conn.Send(std::string(FLV::Header, 13));//write FLV header
|
||||
static FLV::Tag tmp;
|
||||
//write metadata
|
||||
tmp.DTSCMetaInit(Strm);
|
||||
conn.Send(std::string(tmp.data, tmp.len));
|
||||
//write video init data, if needed
|
||||
if (Strm.metadata.getContentP("video") && Strm.metadata.getContentP("video")->getContentP("init")){
|
||||
tmp.DTSCVideoInit(Strm);
|
||||
conn.Send(std::string(tmp.data, tmp.len));
|
||||
}
|
||||
//write audio init data, if needed
|
||||
if (Strm.metadata.getContentP("audio") && Strm.metadata.getContentP("audio")->getContentP("init")){
|
||||
tmp.DTSCAudioInit(Strm);
|
||||
conn.Send(std::string(tmp.data, tmp.len));
|
||||
}
|
||||
progressive_has_sent_header = true;
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "Sent progressive FLV header\n");
|
||||
#endif
|
||||
}
|
||||
conn.Send(std::string(tag.data, tag.len));//write the tag contents
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
conn.close();
|
||||
ss.close();
|
||||
#if DEBUG >= 1
|
||||
if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());}
|
||||
fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
|
||||
if (inited){
|
||||
fprintf(stderr, "Status was: inited\n");
|
||||
}else{
|
||||
if (ready4data){
|
||||
fprintf(stderr, "Status was: ready4data\n");
|
||||
}else{
|
||||
fprintf(stderr, "Status was: connected\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}//Connector_HTTP main function
|
||||
|
||||
};//Connector_HTTP namespace
|
||||
|
||||
// Load http setup file with the correct settings for this HTTP connector
|
||||
#define MAINHANDLER Connector_HTTP_Progressive::Connector_HTTP_Progressive
|
||||
#define CONNECTOR progressive
|
||||
#include "server_setup_http.h"
|
|
@ -2,7 +2,7 @@
|
|||
/// Contains the main code for the RAW connector.
|
||||
|
||||
#include <iostream>
|
||||
#include "../lib/socket.h"
|
||||
#include <mist/socket.h>
|
||||
|
||||
/// Contains the main code for the RAW connector.
|
||||
/// Expects a single commandline argument telling it which stream to connect to,
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
#include <sys/wait.h>
|
||||
#include <getopt.h>
|
||||
#include <sstream>
|
||||
#include "../lib/socket.h"
|
||||
#include "../lib/flv_tag.h"
|
||||
#include "../lib/amf.h"
|
||||
#include "../lib/rtmpchunks.h"
|
||||
#include <mist/socket.h>
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/amf.h>
|
||||
#include <mist/rtmpchunks.h>
|
||||
|
||||
/// Holds all functions and data unique to the RTMP Connector
|
||||
namespace Connector_RTMP{
|
||||
|
|
|
@ -25,12 +25,12 @@
|
|||
#include <signal.h>
|
||||
#include <sstream>
|
||||
#include <openssl/md5.h>
|
||||
#include "../lib/socket.h"
|
||||
#include "../lib/http_parser.h"
|
||||
#include "../lib/json.h"
|
||||
#include "../lib/procs.h"
|
||||
#include "../lib/config.h"
|
||||
#include "../lib/auth.h"
|
||||
#include <mist/socket.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/procs.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/auth.h>
|
||||
|
||||
#define UPLINK_INTERVAL 30
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
AM_CPPFLAGS = $(MIST_CFLAGS)
|
||||
LDADD = $(MIST_LIBS)
|
||||
bin_PROGRAMS=MistDTSC2FLV MistFLV2DTSC
|
||||
MistDTSC2FLV_SOURCES=dtsc2flv.cpp
|
||||
MistDTSC2FLV_LDADD=../../lib/libdtsc.la ../../lib/libflv_tag.la
|
||||
MistFLV2DTSC_SOURCES=flv2dtsc.cpp
|
||||
MistFLV2DTSC_LDADD=../../lib/libdtsc.la ../../lib/libflv_tag.la
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "../../lib/flv_tag.h" //FLV support
|
||||
#include "../../lib/dtsc.h" //DTSC support
|
||||
#include "../../lib/amf.h" //AMF support
|
||||
#include <mist/flv_tag.h> //FLV support
|
||||
#include <mist/dtsc.h> //DTSC support
|
||||
#include <mist/amf.h> //AMF support
|
||||
|
||||
/// Holds all code that converts filetypes to/from DTSC.
|
||||
namespace Converters{
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "../../lib/flv_tag.h" //FLV support
|
||||
#include "../../lib/dtsc.h" //DTSC support
|
||||
#include "../../lib/amf.h" //AMF support
|
||||
#include <mist/flv_tag.h> //FLV support
|
||||
#include <mist/dtsc.h> //DTSC support
|
||||
#include <mist/amf.h> //AMF support
|
||||
|
||||
/// Holds all code that converts filetypes to/from to DTSC.
|
||||
namespace Converters{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/// \file server_setup.h
|
||||
/// Contains generic functions for setting up a DDVTECH Connector.
|
||||
/// Contains generic functions for setting up a Connector.
|
||||
|
||||
#ifndef MAINHANDLER
|
||||
/// Handler that is called for accepted incoming connections.
|
||||
|
@ -15,8 +15,8 @@
|
|||
#endif
|
||||
|
||||
|
||||
#include "../lib/socket.h" //Socket library
|
||||
#include "../lib/config.h" //utilities for config management
|
||||
#include <mist/socket.h> //Socket library
|
||||
#include <mist/config.h> //utilities for config management
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
|
|
112
src/server_setup_http.h
Normal file
112
src/server_setup_http.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
/// \file server_setup_http.h
|
||||
/// Contains generic functions for setting up a HTTP Connector.
|
||||
|
||||
#ifndef MAINHANDLER
|
||||
/// Handler that is called for accepted incoming connections.
|
||||
#define MAINHANDLER NoHandler
|
||||
#error "No handler was set!"
|
||||
#endif
|
||||
|
||||
#ifndef CONNECTOR
|
||||
/// Connector name for the socket.
|
||||
#define CONNECTOR NoConnector
|
||||
#error "No connector was set!"
|
||||
#endif
|
||||
|
||||
#include <mist/socket.h> //Socket library
|
||||
#include <mist/config.h> //utilities for config management
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#include <fstream>
|
||||
Socket::Server server_socket; ///< Placeholder for the server socket
|
||||
|
||||
/// Basic signal handler. Disconnects the server_socket if it receives
|
||||
/// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE.
|
||||
/// Disconnecting the server_socket will terminate the main listening loop
|
||||
/// and cleanly shut down the process.
|
||||
void signal_handler (int signum){
|
||||
switch (signum){
|
||||
case SIGINT:
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "Received SIGINT - closing server socket.\n");
|
||||
#endif
|
||||
break;
|
||||
case SIGHUP:
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "Received SIGHUP - closing server socket.\n");
|
||||
#endif
|
||||
break;
|
||||
case SIGTERM:
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "Received SIGTERM - closing server socket.\n");
|
||||
#endif
|
||||
break;
|
||||
case SIGCHLD:
|
||||
wait(0);
|
||||
return;
|
||||
break;
|
||||
default: return; break;
|
||||
}
|
||||
if (!server_socket.connected()) return;
|
||||
server_socket.close();
|
||||
}//signal_handler
|
||||
|
||||
/// Generic main entry point and loop for DDV HTTP-based Connectors.
|
||||
/// This sets up the proper termination handler, checks commandline options,
|
||||
/// parses config files and opens a listening socket.
|
||||
/// Any incoming connections will be accepted and start up the function #MAINHANDLER,
|
||||
/// which should be defined before including server_setup_http.cpp.
|
||||
/// The connector name is set by define #CONNECTOR.
|
||||
int main(int argc, char ** argv){
|
||||
Socket::Connection S;//placeholder for incoming connections
|
||||
|
||||
//setup signal handler
|
||||
struct sigaction new_action;
|
||||
new_action.sa_handler = signal_handler;
|
||||
sigemptyset (&new_action.sa_mask);
|
||||
new_action.sa_flags = 0;
|
||||
sigaction(SIGINT, &new_action, NULL);
|
||||
sigaction(SIGHUP, &new_action, NULL);
|
||||
sigaction(SIGTERM, &new_action, NULL);
|
||||
sigaction(SIGPIPE, &new_action, NULL);
|
||||
sigaction(SIGCHLD, &new_action, NULL);
|
||||
|
||||
//set and parse configuration
|
||||
Util::Config C;
|
||||
C.parseArgs(argc, argv);
|
||||
|
||||
//setup a new server socket, for the correct interface and port
|
||||
server_socket = Socket::Server("/tmp/mist/http_" CONNECTOR);
|
||||
if (!server_socket.connected()){
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "Error: could not make listening socket\n");
|
||||
#endif
|
||||
return 1;
|
||||
}else{
|
||||
#if DEBUG >= 3
|
||||
fprintf(stderr, "Made a listening socket on %s:%i...\n", C.interface.c_str(), C.listen_port);
|
||||
#endif
|
||||
}
|
||||
|
||||
Util::setUser(C.username);
|
||||
if (C.daemon_mode){Util::Daemonize();}
|
||||
|
||||
while (server_socket.connected()){
|
||||
S = server_socket.accept();
|
||||
if (S.connected()){//check if the new connection is valid
|
||||
pid_t myid = fork();
|
||||
if (myid == 0){//if new child, start MAINHANDLER
|
||||
return MAINHANDLER(S);
|
||||
}else{//otherwise, do nothing or output debugging text
|
||||
#if DEBUG >= 3
|
||||
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}//while connected
|
||||
#if DEBUG >= 1
|
||||
fprintf(stderr, "Server socket closed, exiting.\n");
|
||||
#endif
|
||||
return 0;
|
||||
}//main
|
Loading…
Add table
Reference in a new issue