diff --git a/.gitignore b/.gitignore index 5dcd978f..a4ff9d30 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.la *.lo *.swp +*.orig *.bak *~ .deps @@ -12,6 +13,8 @@ docs nbproject autom4te.cache /Mist* +/libmist.so +/libmist.a /configure /config.* /aclocal.m4 @@ -37,4 +40,6 @@ server.html* .cproject .project .sync +*.pc +*.swp diff --git a/Makefile b/Makefile index 5832dfe1..e026211c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ -prefix = /usr +prefix = ./usr exec_prefix = $(prefix) bindir = $(prefix)/bin +includedir = $(prefix)/include +libdir = $(exec_prefix)/lib PACKAGE_VERSION := $(shell git describe --tags 2> /dev/null || cat VERSION 2> /dev/null || echo "Unknown") DEBUG = 4 @@ -10,7 +12,7 @@ ifeq ($(PACKAGE_VERSION),Unknown) $(warning Version is unknown - consider creating a VERSION file or fixing your git setup.) endif -CPPFLAGS = -Wall -g -O2 +CPPFLAGS = -Wall -g -O2 -fPIC override CPPFLAGS += -funsigned-char -DDEBUG="$(DEBUG)" -DPACKAGE_VERSION="\"$(PACKAGE_VERSION)\"" -DRELEASE="\"$(RELEASE)\"" ifndef NOSHM @@ -21,13 +23,15 @@ ifdef WITH_THREADNAMES override CPPFLAGS += -DWITH_THREADNAMES=1 endif -THREADLIB = -lpthread -LDLIBS = -lmist -lrt +THREADLIB = -lpthread -lrt +LDLIBS = +LDFLAGS = -I${includedir} -L${libdir} -lmist .DEFAULT_GOAL := all -all: controller analysers inputs outputs +lib: libmist.so libmist.a +all: install-lib controller analysers inputs outputs DOXYGEN := $(shell doxygen -v 2> /dev/null) ifdef DOXYGEN @@ -217,20 +221,42 @@ src/controller/server.html: $(lspDATA) $(lspSOURCES) $(lspSOURCESmin) src/controller/server.html.h: src/controller/server.html sourcery cd src/controller; ../../sourcery server.html server_html > server.html.h -docs: src/* Doxyfile +lib_objects := $(patsubst %.cpp,%.o,$(wildcard lib/*.cpp)) + +libmist.so: $(lib_objects) + $(CXX) -shared -o $@ $(LDLIBS) $^ + +libmist.a: $(lib_objects) + $(AR) -rcs $@ $^ + +docs: lib/* src/* Doxyfile Doxyfile doxygen ./Doxyfile > /dev/null clean: - rm -f *.o Mist* sourcery src/controller/server.html src/connectors/embed.js.h src/controller/server.html.h + rm -f lib/*.o libmist.so libmist.a rm -rf ./docs + rm -f *.o Mist* sourcery src/controller/server.html src/connectors/embed.js.h src/controller/server.html.h distclean: clean +install-lib: libmist.so libmist.a lib/*.h + mkdir -p $(DESTDIR)$(includedir)/mist + install -m 644 lib/*.h $(DESTDIR)$(includedir)/mist/ + mkdir -p $(DESTDIR)$(libdir) + install -m 644 libmist.a $(DESTDIR)$(libdir)/libmist.a + install -m 644 libmist.so $(DESTDIR)$(libdir)/libmist.so + $(POST_INSTALL) + install: all + if [ "$$USER" = "root" ]; then ldconfig; else echo "run: sudo ldconfig"; fi mkdir -p $(DESTDIR)$(bindir) install -m 755 ./Mist* $(DESTDIR)$(bindir) uninstall: rm -f $(DESTDIR)$(bindir)/Mist* + rm -f $(DESTDIR)$(includedir)/mist/*.h + rmdir $(DESTDIR)$(includedir)/mist + rm -f $(DESTDIR)$(libdir)/libmist.so + rm -f $(DESTDIR)$(libdir)/libmist.a .PHONY: clean uninstall diff --git a/README b/README index a54bda43..eb74fb6e 100644 --- a/README +++ b/README @@ -1,11 +1,13 @@ _________________________________________________ | MistServer | -| Copyright 2010-2014 DDVTech BV, The Netherlands | +| Copyright 2010-2015 DDVTech BV, The Netherlands | | | | Licensed under the aGPLv3 license | | See COPYING file for full license | |_________________________________________________| +NOTE: TinyThread++ is included also, but *not* copyright DDVTech BV. +License and author information for TinyThread++ can be found in the tinythread.h/cpp files. The latest version of this code can always be found at: https://github.com/DDVTECH/mistserver @@ -19,9 +21,7 @@ Code contributions and bug reports are welcomed! Please submit at: To install using default options, simply run: make && sudo make install -Dependencies: - libmist ( https://github.com/DDVTECH/mistlib ) - libpthread +Dependencies: none The makefile will listen to the following variables: DEBUG Sets the debug message level. 4 is the default (and recommended setting for development), 0 is quiet, 10 is insanely high. @@ -29,10 +29,12 @@ The makefile will listen to the following variables: RELEASE Overrides the release name. You shouldn't need to use this, normally. prefix Prefix to install files to. Defaults to /usr exec_prefix Prefix to install object code and binaries to. Defaults to $(prefix) + includedir Directory to install headers to. Defaults to $(prefix)/include + libdir Directory to install libraries to. Defaults to $(exec_prefix)/lib bindir Directory to install binaries to. Defaults to $(exec_prefix)/bin DESTDIR Global prefix that will be put in front of any and all other file paths. CPPFLAGS Flags for compiling object files. Defaults to -Wall -g -O2 - LDLIBS Libraries to include. Defaults to -lmist + LDLIBS Libraries to include. Defaults to none. THREADLIB Libraries to include for threaded binaries. Defaults to -lpthread WITH_THREADNAMES If set, this will set names of threads in threaded binaries. Defaults to being unset. diff --git a/lib/amf.cpp b/lib/amf.cpp new file mode 100644 index 00000000..3d3fa9bc --- /dev/null +++ b/lib/amf.cpp @@ -0,0 +1,1147 @@ +/// \file amf.cpp +/// Holds all code for the AMF namespace. + +#include "amf.h" +#include "defines.h" +#include +/// 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(unsigned int i) { + if (i >= contents.size()) { + 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. +AMF::Object AMF::Object::getContent(unsigned 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::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::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; +} + +/// Return the contents as a human-readable string. +/// If this object contains other objects, it will call itself recursively +/// and print all nested content as well. +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::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: //is an object, with the classname first + r += Indice().size() / 256; + r += Indice().size() % 256; + r += Indice(); + /* no break */ + case AMF::AMF0_OBJECT: + if (contents.size() > 0) { + for (std::vector::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::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::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::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]; + double * d; // hack to work around strict aliasing + 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 + d = (double *)tmpdbl; + return AMF::Object(name, *d, 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 + d = (double *)tmpdbl; + return AMF::Object(name, *d, 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; + } + DEBUG_MSG(DLVL_ERROR, "Error: Unimplemented AMF type %hhx - returning.", data[i]); + 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::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::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; +} + +/// Return the contents as a human-readable string. +/// If this object contains other objects, it will call itself recursively +/// and print all nested content as well. +std::string AMF::Object3::Print(std::string indent) { + std::stringstream st; + st << indent; + // print my type + switch (myType) { + case AMF::AMF3_UNDEFINED: + st << "Undefined"; + break; + case AMF::AMF3_NULL: + st << "Null"; + break; + case AMF::AMF3_FALSE: + st << "False"; + break; + case AMF::AMF3_TRUE: + st << "True"; + break; + case AMF::AMF3_INTEGER: + st << "Integer"; + break; + case AMF::AMF3_DOUBLE: + st << "Double"; + break; + case AMF::AMF3_STRING: + st << "String"; + break; + case AMF::AMF3_XMLDOC: + st << "XML Doc"; + break; + case AMF::AMF3_DATE: + st << "Date"; + break; + case AMF::AMF3_ARRAY: + st << "Array"; + break; + case AMF::AMF3_OBJECT: + st << "Object"; + break; + case AMF::AMF3_XML: + st << "XML"; + break; + case AMF::AMF3_BYTES: + st << "ByteArray"; + break; + case AMF::AMF3_DDV_CONTAINER: + st << "DDVTech Container"; + break; + } + // print my string indice, if available + st << " " << myIndice << " "; + // print my numeric or string contents + switch (myType) { + case AMF::AMF3_INTEGER: + st << intval; + break; + case AMF::AMF3_DOUBLE: + st << dblval; + break; + case AMF::AMF3_STRING: + case AMF::AMF3_XMLDOC: + case AMF::AMF3_XML: + case AMF::AMF3_BYTES: + if (intval > 0) { + st << "REF" << intval; + } else { + st << strval; + } + break; + case AMF::AMF3_DATE: + if (intval > 0) { + st << "REF" << intval; + } else { + st << dblval; + } + break; + case AMF::AMF3_ARRAY: + case AMF::AMF3_OBJECT: + if (intval > 0) { + st << "REF" << intval; + } + 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::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::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]; + double * d; // hack to work around strict aliasing + 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 + d = (double *)tmpdbl; + return AMF::Object3(name, *d, 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]; + d = (double *)tmpdbl; + i += 8; //skip a double forwards + return AMF::Object3(name, *d, 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; + } + DEBUG_MSG(DLVL_ERROR, "Error: Unimplemented AMF3 type %hhx - returning.", data[i]); + 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 diff --git a/lib/amf.h b/lib/amf.h new file mode 100644 index 00000000..4d560bdd --- /dev/null +++ b/lib/amf.h @@ -0,0 +1,130 @@ +/// \file amf.h +/// Holds all headers for the AMF namespace. + +#pragma once +#include +#include +#include + +/// 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(unsigned int i); + Object getContent(unsigned 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 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); + std::string 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 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 diff --git a/lib/auth.cpp b/lib/auth.cpp new file mode 100644 index 00000000..b6c01a6d --- /dev/null +++ b/lib/auth.cpp @@ -0,0 +1,337 @@ +#include "auth.h" +#include +#include +#include +#include +#include + +namespace Secure { + + /// Calculates a MD5 digest as per rfc1321, returning it as a hexadecimal alphanumeric string. + std::string md5(std::string input) { + return md5(input.data(), input.size()); + } + + /// Calculates a MD5 digest as per rfc1321, returning it as a hexadecimal alphanumeric string. + std::string md5(const char * input, const unsigned int in_len){ + char output[16]; + md5bin(input, in_len, output); + std::stringstream outStr; + for (unsigned int i = 0; i < 16; ++i){ + outStr << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)(output[i] & 0xff); + } + return outStr.str(); + } + + /// Calculates a SHA256 digest as per NSAs SHA-2, returning it as a hexadecimal alphanumeric string. + std::string sha256(std::string input) { + return sha256(input.data(), input.size()); + } + + /// Calculates a SHA256 digest as per NSAs SHA-2, returning it as a hexadecimal alphanumeric string. + std::string sha256(const char * input, const unsigned int in_len){ + char output[32]; + sha256bin(input, in_len, output); + std::stringstream outStr; + for (unsigned int i = 0; i < 32; ++i){ + outStr << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)(output[i] & 0xff); + } + return outStr.str(); + } + + /// Adds 64 bytes of data to the current MD5 hash. + /// hash is the current hash, represented by 4 unsigned longs. + /// data is the 64 bytes of data that need to be added. + static inline void md5_add64(uint32_t * hash, const char * data){ + //Inspired by the pseudocode as available on Wikipedia on March 2nd, 2015. + uint32_t M[16]; + for (unsigned int i = 0; i < 16; ++i){ + M[i] = data[i << 2] | (data[(i<<2)+1] << 8) | (data[(i<<2)+2] << 16) | (data[(i<<2)+3] << 24); + } + static unsigned char shift[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + static uint32_t K[] = { 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; + uint32_t A = hash[0]; + uint32_t B = hash[1]; + uint32_t C = hash[2]; + uint32_t D = hash[3]; + for (unsigned int i = 0; i < 64; ++i){ + uint32_t F, g; + if (i < 16){ + F = (B & C) | ((~B) & D); + g = i; + }else if (i < 32){ + F = (D & B) | ((~D) & C); + g = (5*i + 1) % 16; + }else if (i < 48){ + F = B ^ C ^ D; + g = (3*i + 5) % 16; + }else{ + F = C ^ (B | (~D)); + g = (7*i) % 16; + } + uint32_t dTemp = D; + D = C; + C = B; + uint32_t x = A + F + K[i] + M[g]; + B += (x << shift[i] | (x >> (32-shift[i]))); + A = dTemp; + } + hash[0] += A; + hash[1] += B; + hash[2] += C; + hash[3] += D; + } + + /// Calculates a MD5 digest as per rfc1321, returning it as binary. + /// Assumes output is big enough to contain 16 bytes of data. + void md5bin(const char * input, const unsigned int in_len, char * output){ + //Initialize the hash, according to MD5 spec. + uint32_t hash[] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476}; + //Add as many whole blocks of 64 bytes as possible from the input, until < 64 are left. + unsigned int offset = 0; + while (offset+64 <= in_len){ + md5_add64(hash, input+offset); + offset += 64; + } + //now, copy the remainder to a 64 byte buffer. + char buffer[64]; + memcpy(buffer, input+offset, in_len-offset); + //Calculate how much we've filled in that buffer + offset = in_len - offset; + //We know at least 1 byte must be empty, so we can safely do this + buffer[offset] = 0x80;//append 0x80 + //fill to the end of the buffer with zeroes + memset(buffer+offset+1, 0, 64-offset-1); + if (offset > 55){ + //There's no space for the length, add what we have and zero it + md5_add64(hash, buffer); + memset(buffer, 0, 64); + } + unsigned long long bit_len = in_len << 3; + //Write the length into the last 8 bytes + buffer[56] = (bit_len >> 0) & 0xff; + buffer[57] = (bit_len >> 8) & 0xff; + buffer[58] = (bit_len >> 16) & 0xff; + buffer[59] = (bit_len >> 24) & 0xff; + buffer[60] = (bit_len >> 32) & 0xff; + buffer[61] = (bit_len >> 40) & 0xff; + buffer[62] = (bit_len >> 48) & 0xff; + buffer[63] = (bit_len >> 54) & 0xff; + //Add the last bit of buffer + md5_add64(hash, buffer); + //Write to output + //convert hash to hexadecimal string + output[0 ] = (hash[0] >> 0 ) & 0xff; + output[1 ] = (hash[0] >> 8 ) & 0xff; + output[2 ] = (hash[0] >> 16) & 0xff; + output[3 ] = (hash[0] >> 24) & 0xff; + output[4 ] = (hash[1] >> 0 ) & 0xff; + output[5 ] = (hash[1] >> 8 ) & 0xff; + output[6 ] = (hash[1] >> 16) & 0xff; + output[7 ] = (hash[1] >> 24) & 0xff; + output[8 ] = (hash[2] >> 0 ) & 0xff; + output[9 ] = (hash[2] >> 8 ) & 0xff; + output[10] = (hash[2] >> 16) & 0xff; + output[11] = (hash[2] >> 24) & 0xff; + output[12] = (hash[3] >> 0 ) & 0xff; + output[13] = (hash[3] >> 8 ) & 0xff; + output[14] = (hash[3] >> 16) & 0xff; + output[15] = (hash[3] >> 24) & 0xff; + } + + /// Right rotate function. Shifts bytes off the least significant end, wrapping them to the most significant end. + static inline uint32_t rr(uint32_t x, uint32_t c){ + return ((x << (32 - c)) | ((x & 0xFFFFFFFF) >> c)); + } + + /// Adds 64 bytes of data to the current SHA256 hash. + /// hash is the current hash, represented by 8 unsigned longs. + /// data is the 64 bytes of data that need to be added. + static inline void sha256_add64(uint32_t * hash, const char * data){ + //Inspired by the pseudocode as available on Wikipedia on March 3rd, 2015. + uint32_t w[64]; + for (unsigned int i = 0; i < 16; ++i){ + w[i] = (uint32_t)data[(i<<2)+3] | ((uint32_t)data[(i<<2)+2] << 8) | ((uint32_t)data[(i<<2)+1] << 16) | ((uint32_t)data[(i<<2)+0] << 24); + } + + for (unsigned int i = 16; i < 64; ++i){ + uint32_t s0 = rr(w[i-15], 7) ^ rr(w[i-15], 18) ^ ((w[i-15] & 0xFFFFFFFF ) >> 3); + uint32_t s1 = rr(w[i-2], 17) ^ rr(w[i-2], 19) ^ ((w[i-2] & 0xFFFFFFFF) >> 10); + w[i] = w[i-16] + s0 + w[i-7] + s1; + } + + static uint32_t k[] = {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + uint32_t a = hash[0]; + uint32_t b = hash[1]; + uint32_t c = hash[2]; + uint32_t d = hash[3]; + uint32_t e = hash[4]; + uint32_t f = hash[5]; + uint32_t g = hash[6]; + uint32_t h = hash[7]; + for (unsigned int i = 0; i < 64; ++i){ + uint32_t temp1 = h + (rr(e, 6) ^ rr(e, 11) ^ rr(e, 25)) + (g^(e&(f^g))) + k[i] + w[i]; + uint32_t temp2 = (rr(a, 2) ^ rr(a, 13) ^ rr(a, 22)) + ((a&b)|(c&(a|b))); + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; + hash[4] += e; + hash[5] += f; + hash[6] += g; + hash[7] += h; + } + + /// Calculates a SHA256 digest as per NSAs SHA-2, returning it as binary. + /// Assumes output is big enough to contain 16 bytes of data. + void sha256bin(const char * input, const unsigned int in_len, char * output){ + //Initialize the hash, according to MD5 spec. + uint32_t hash[] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; + //Add as many whole blocks of 64 bytes as possible from the input, until < 64 are left. + unsigned int offset = 0; + while (offset+64 <= in_len){ + sha256_add64(hash, input+offset); + offset += 64; + } + //now, copy the remainder to a 64 byte buffer. + char buffer[64]; + memcpy(buffer, input+offset, in_len-offset); + //Calculate how much we've filled in that buffer + offset = in_len - offset; + //We know at least 1 byte must be empty, so we can safely do this + buffer[offset] = 0x80;//append 0x80 + //fill to the end of the buffer with zeroes + memset(buffer+offset+1, 0, 64-offset-1); + if (offset > 55){ + //There's no space for the length, add what we have and zero it + sha256_add64(hash, buffer); + memset(buffer, 0, 64); + } + unsigned long long bit_len = in_len << 3; + //Write the length into the last 8 bytes + buffer[56] = (bit_len >> 54) & 0xff; + buffer[57] = (bit_len >> 48) & 0xff; + buffer[58] = (bit_len >> 40) & 0xff; + buffer[59] = (bit_len >> 32) & 0xff; + buffer[60] = (bit_len >> 24) & 0xff; + buffer[61] = (bit_len >> 16) & 0xff; + buffer[62] = (bit_len >> 8) & 0xff; + buffer[63] = (bit_len >> 0) & 0xff; + //Add the last bit of buffer + sha256_add64(hash, buffer); + //Write result to output + output[3] = hash[0] & 0xff; + output[2] = (hash[0] >> 8) & 0xff; + output[1] = (hash[0] >> 16) & 0xff; + output[0] = (hash[0] >> 24) & 0xff; + output[7] = hash[1] & 0xff; + output[6] = (hash[1] >> 8) & 0xff; + output[5] = (hash[1] >> 16) & 0xff; + output[4] = (hash[1] >> 24) & 0xff; + output[11] = hash[2] & 0xff; + output[10] = (hash[2] >> 8) & 0xff; + output[9] = (hash[2] >> 16) & 0xff; + output[8] = (hash[2] >> 24) & 0xff; + output[15] = hash[3] & 0xff; + output[14] = (hash[3] >> 8) & 0xff; + output[13] = (hash[3] >> 16) & 0xff; + output[12] = (hash[3] >> 24) & 0xff; + output[19] = hash[4] & 0xff; + output[18] = (hash[4] >> 8) & 0xff; + output[17] = (hash[4] >> 16) & 0xff; + output[16] = (hash[4] >> 24) & 0xff; + output[23] = hash[5] & 0xff; + output[22] = (hash[5] >> 8) & 0xff; + output[21] = (hash[5] >> 16) & 0xff; + output[20] = (hash[5] >> 24) & 0xff; + output[27] = hash[6] & 0xff; + output[26] = (hash[6] >> 8) & 0xff; + output[25] = (hash[6] >> 16) & 0xff; + output[24] = (hash[6] >> 24) & 0xff; + output[31] = hash[7] & 0xff; + output[30] = (hash[7] >> 8) & 0xff; + output[29] = (hash[7] >> 16) & 0xff; + output[28] = (hash[7] >> 24) & 0xff; + } + + + + /// Performs HMAC on msg with given key. + /// Uses given hasher function, requires hashSize to be set accordingly. + /// Output is returned as hexadecimal alphanumeric string. + /// The hasher function must be the "bin" version of the hasher to have a compatible function signature. + std::string hmac(std::string msg, std::string key, unsigned int hashSize, void hasher(const char *, const unsigned int, char*), unsigned int blockSize){ + return hmac(msg.data(), msg.size(), key.data(), key.size(), hashSize, hasher, blockSize); + } + + /// Performs HMAC on msg with given key. + /// Uses given hasher function, requires hashSize to be set accordingly. + /// Output is returned as hexadecimal alphanumeric string. + /// The hasher function must be the "bin" version of the hasher to have a compatible function signature. + std::string hmac(const char * msg, const unsigned int msg_len, const char * key, const unsigned int key_len, unsigned int hashSize, void hasher(const char *, const unsigned int, char*), unsigned int blockSize){ + char output[hashSize]; + hmacbin(msg, msg_len, key, key_len, hashSize, hasher, blockSize, output); + std::stringstream outStr; + for (unsigned int i = 0; i < hashSize; ++i){ + outStr << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)(output[i] & 0xff); + } + return outStr.str(); + } + + /// Performs HMAC on msg with given key. + /// Uses given hasher function, requires hashSize to be set accordingly. + /// Output is written in binary form to output, and assumes hashSize bytes are available to be written to. + /// The hasher function must be the "bin" version of the hasher to have a compatible function signature. + void hmacbin(const char * msg, const unsigned int msg_len, const char * key, const unsigned int key_len, unsigned int hashSize, void hasher(const char*, const unsigned int, char*), unsigned int blockSize, char * output){ + char key_data[blockSize];//holds key as used in HMAC algorithm + if (key_len > blockSize){ + //If the key given is too big, hash it. + hasher(key, key_len, key_data); + memset(key_data+hashSize, 0, blockSize-hashSize); + }else{ + //Otherwise, use as-is, zero-padded if too small. + memcpy(key_data, key, key_len); + memset(key_data+key_len, 0, blockSize-key_len); + } + //key_data now contains hashSize bytes of key data, treated as per spec. + char inner[blockSize+msg_len];//holds data for inner hash + char outer[blockSize+hashSize];//holds data for outer hash + for (unsigned int i = 0; i < blockSize; ++i){ + inner[i] = key_data[i] ^ 0x36; + outer[i] = key_data[i] ^ 0x5c; + } + //Copy the message to the inner hash data buffer + memcpy(inner+blockSize, msg, msg_len); + //Calculate the inner hash + hasher(inner, blockSize+msg_len, outer+blockSize); + //Calculate the outer hash + hasher(outer, blockSize+hashSize, output); + } + + /// Convenience function that returns the hexadecimal alphanumeric HMAC-SHA256 of msg and key + std::string hmac_sha256(std::string msg, std::string key){ + return hmac_sha256(msg.data(), msg.size(), key.data(), key.size()); + } + + /// Convenience function that returns the hexadecimal alphanumeric HMAC-SHA256 of msg and key + std::string hmac_sha256(const char * msg, const unsigned int msg_len, const char * key, const unsigned int key_len){ + return hmac(msg, msg_len, key, key_len, 32, sha256bin, 64); + } + + /// Convenience function that sets output to the HMAC-SHA256 of msg and key in binary format. + /// Assumes at least 32 bytes are available for writing in output. + void hmac_sha256bin(const char * msg, const unsigned int msg_len, const char * key, const unsigned int key_len, char * output){ + return hmacbin(msg, msg_len, key, key_len, 32, sha256bin, 64, output); + } + +} + diff --git a/lib/auth.h b/lib/auth.h new file mode 100644 index 00000000..ec5132c4 --- /dev/null +++ b/lib/auth.h @@ -0,0 +1,25 @@ +#pragma once +#include + +namespace Secure { + //MD5 hashing functions + std::string md5(std::string input); + std::string md5(const char * input, const unsigned int in_len); + void md5bin(const char * input, const unsigned int in_len, char * output); + + //SHA256 hashing functions + std::string sha256(std::string input); + std::string sha256(const char * input, const unsigned int in_len); + void sha256bin(const char * input, const unsigned int in_len, char * output); + + //Generic HMAC functions + std::string hmac(std::string msg, std::string key, unsigned int hashSize, void hasher(const char *, const unsigned int, char*), unsigned int blockSize); + std::string hmac(const char * msg, const unsigned int msg_len, const char * key, const unsigned int key_len, unsigned int hashSize, void hasher(const char *, const unsigned int, char*), unsigned int blockSize); + void hmacbin(const char * msg, const unsigned int msg_len, const char * key, const unsigned int key_len, unsigned int hashSize, void hasher(const char*, const unsigned int, char*), unsigned int blockSize, char * output); + //Specific HMAC functions + std::string hmac_sha256(std::string msg, std::string key); + std::string hmac_sha256(const char * msg, const unsigned int msg_len, const char * key, const unsigned int key_len); + void hmac_sha256bin(const char * msg, const unsigned int msg_len, const char * key, const unsigned int key_len, char * output); + +} + diff --git a/lib/base64.cpp b/lib/base64.cpp new file mode 100644 index 00000000..7048616c --- /dev/null +++ b/lib/base64.cpp @@ -0,0 +1,86 @@ +#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; +} diff --git a/lib/base64.h b/lib/base64.h new file mode 100644 index 00000000..684c4ba1 --- /dev/null +++ b/lib/base64.h @@ -0,0 +1,12 @@ +#pragma once +#include + +/// Holds base64 decoding and encoding functions. +class Base64 { + private: + static const std::string chars; + static inline bool is_base64(unsigned char c); + public: + static std::string encode(std::string const input); + static std::string decode(std::string const & encoded_string); +}; diff --git a/lib/bitfields.cpp b/lib/bitfields.cpp new file mode 100644 index 00000000..6813299c --- /dev/null +++ b/lib/bitfields.cpp @@ -0,0 +1,64 @@ +#include "bitfields.h" + +/// Takes a pointer, offset bitcount and data bitcount, returning the unsigned int read from the givens. +/// offsetBits may be > 7, in which case offsetBits / 8 is added to the pointer automatically. +/// This function assumes Most Significant Bits first. +/// If dataBits > 64, only the last 64 bits are returned. +unsigned long long Bit::getMSB(char * pointer, unsigned int offsetBits, unsigned int dataBits){ + //If the offset is a whole byte or more, add the whole bytes to the pointer instead. + pointer += offsetBits >> 3; + //The offset is now guaranteed less than a whole byte. + offsetBits &= 0x07; + unsigned long long retVal = 0; + //Now we parse the remaining bytes + while (dataBits){ + //Calculate how many bits we're reading from this byte + //We assume all except for the offset + unsigned int curBits = 8 - offsetBits; + //If that is too much, we use the remainder instead + if (curBits > dataBits){ + curBits = dataBits; + } + //First, shift the current return value by the amount of bits we're adding + retVal <<= curBits; + //Next, add those bits from the current pointer position at the correct offset, increasing the pointer by one + retVal |= ((int)(*(pointer++)) << offsetBits) >> (8 - curBits); + //Finally, set the offset to zero and remove curBits from dataBits. + offsetBits = 0; + dataBits -= curBits; + }//Loop until we run out of dataBits, then return the result + return retVal; +} + +/// Takes a pointer, offset bitcount and data bitcount, setting to given value. +/// offsetBits may be > 7, in which case offsetBits / 8 is added to the pointer automatically. +/// This function assumes Most Significant Bits first. +/// WARNING: UNFINISHED. DO NOT USE. +/// \todo Finish writing this - untested atm. +void Bit::setMSB(char * pointer, unsigned int offsetBits, unsigned int dataBits, unsigned long long value){ + //Set the pointer to the last byte we need to be setting + pointer += (offsetBits + dataBits) >> 3; + //The offset is now guaranteed less than a whole byte. + offsetBits = (offsetBits + dataBits) & 0x07; + unsigned long long retVal = 0; + //Now we set the remaining bytes + while (dataBits){ + //Calculate how many bits we're setting in this byte + //We assume all that will fit in the current byte + unsigned int curBits = offsetBits; + //If that is too much, we use the remainder instead + if (curBits > dataBits){ + curBits = dataBits; + } + //Set the current pointer position at the correct offset, increasing the pointer by one + retVal |= ((int)(*(pointer++)) << offsetBits) >> (8 - curBits); + *pointer = (((*pointer) << offsetBits) >> offsetBits) | ((value & 0xFF) << (8 - offsetBits)); + --pointer; + //Finally, shift the current value by the amount of bits we're adding + value >>= offsetBits; + //... and set the offset to eight and remove curBits from dataBits. + offsetBits = 8; + dataBits -= curBits; + }//Loop until we run out of dataBits, then return the result +} + diff --git a/lib/bitfields.h b/lib/bitfields.h new file mode 100644 index 00000000..7af1d8a6 --- /dev/null +++ b/lib/bitfields.h @@ -0,0 +1,68 @@ + + + +namespace Bit{ + //bitfield getters + unsigned long long getMSB(char * pointer, unsigned int offsetBits, unsigned int dataBits); + unsigned long long getByName(char * pointer); + //bitfield setters + void setMSB(char * pointer, unsigned int offsetBits, unsigned int dataBits, unsigned long long value); + void setByName(char * pointer); + + //Host to binary/binary to host functions - similar to kernel ntoh/hton functions. + + /// Retrieves a short in network order from the pointer p. + inline unsigned short btohs(char * p) { + return ((unsigned short)p[0] << 8) | p[1]; + } + + /// Stores a short value of val in network order to the pointer p. + inline void htobs(char * p, unsigned short val) { + p[0] = (val >> 8) & 0xFF; + p[1] = val & 0xFF; + } + + /// Retrieves a long in network order from the pointer p. + inline unsigned long btohl(char * p) { + return ((unsigned long)p[0] << 24) | ((unsigned long)p[1] << 16) | ((unsigned long)p[2] << 8) | p[3]; + } + + /// Stores a long value of val in network order to the pointer p. + inline void htobl(char * p, unsigned long val) { + p[0] = (val >> 24) & 0xFF; + p[1] = (val >> 16) & 0xFF; + p[2] = (val >> 8) & 0xFF; + p[3] = val & 0xFF; + } + + /// Retrieves a long in network order from the pointer p. + inline unsigned long btoh24(char * p) { + return ((unsigned long)p[0] << 16) | ((unsigned long)p[1] << 8) | p[2]; + } + + /// Stores a long value of val in network order to the pointer p. + inline void htob24(char * p, unsigned long val) { + p[0] = (val >> 16) & 0xFF; + p[1] = (val >> 8) & 0xFF; + p[2] = val & 0xFF; + } + + /// Retrieves a long long in network order from the pointer p. + inline unsigned long long btohll(char * p) { + return ((unsigned long long)p[0] << 56) | ((unsigned long long)p[1] << 48) | ((unsigned long long)p[2] << 40) | ((unsigned long long)p[3] << 32) | ((unsigned long)p[4] << 24) | ((unsigned long)p[5] << 16) | ((unsigned long)p[6] << 8) | p[7]; + } + + /// Stores a long value of val in network order to the pointer p. + inline void htobll(char * p, unsigned long long val) { + p[0] = (val >> 56) & 0xFF; + p[1] = (val >> 48) & 0xFF; + p[2] = (val >> 40) & 0xFF; + p[3] = (val >> 32) & 0xFF; + p[4] = (val >> 24) & 0xFF; + p[5] = (val >> 16) & 0xFF; + p[6] = (val >> 8) & 0xFF; + p[7] = val & 0xFF; + } + +} + diff --git a/lib/bitstream.cpp b/lib/bitstream.cpp new file mode 100644 index 00000000..ef513913 --- /dev/null +++ b/lib/bitstream.cpp @@ -0,0 +1,215 @@ +#include "bitstream.h" +#include "defines.h" +#include +#include + +namespace Utils { + bitstream::bitstream() { + data = NULL; + offset = 0; + dataSize = 0; + bufferSize = 0; + } + + bool bitstream::checkBufferSize(unsigned int size) { + if (size > bufferSize) { + void * temp = realloc(data, size); + if (temp) { + data = (char *) temp; + bufferSize = size; + return true; + } else { + return false; + } + } else { + return true; + } + } + + void bitstream::append(char * input, size_t bytes) { + if (checkBufferSize(dataSize + bytes)) { + memcpy(data + dataSize, input, bytes); + dataSize += bytes; + } + } + + void bitstream::append(std::string input) { + append((char *)input.c_str(), input.size()); + } + + bool bitstream::peekOffset(size_t peekOffset) { + peekOffset += offset; + return ((data[peekOffset >> 3]) >> (7 - (peekOffset & 7))) & 1; + } + + long long unsigned int bitstream::peek(size_t count) { + if (count > 64) { + DEBUG_MSG(DLVL_WARN, "Can not read %d bits into a long long unsigned int!", (int)count); + //return 0; + } + if (count > size()) { + DEBUG_MSG(DLVL_ERROR, "Not enough bits left in stream. Left: %d requested: %d", (int)size(), (int)count); + return 0; + } + long long unsigned int retval = 0; + size_t curPlace = 0; + size_t readSize; + size_t readOff; + char readBuff; + while (curPlace < count) { + readBuff = data[(int)((offset + curPlace) / 8)]; + readSize = 8; + readOff = (offset + curPlace) % 8; //the reading offset within the byte + if (readOff != 0) { + //if we start our read not on the start of a byte + //curplace and retval should both be 0 + //this should be the first read that aligns reading to bytes, if we read over the end of read byte + //we cut the MSb off of the buffer by bit mask + readSize -= readOff;//defining starting bit + readBuff = readBuff & ((1 << readSize) - 1);//bitmasking + } + //up until here we assume we read to the end of the byte + if (count - curPlace < readSize) { //if we do not read to the end of the byte + //we cut off the LSb off of the read buffer by bitshift + readSize = count - curPlace; + readBuff = readBuff >> (8 - readSize - readOff); + } + retval = (retval << readSize) + readBuff; + curPlace += readSize; + } + return retval; + } + + long long unsigned int bitstream::get(size_t count) { + if (count <= size()) { + long long unsigned int retVal; + retVal = peek(count); + skip(count); + return retVal; + } else { + return 0; + } + } + + void bitstream::skip(size_t count) { + if (count <= size()) { + offset += count; + } else { + offset = dataSize * 8; + } + + } + + long long unsigned int bitstream::size() { + return (dataSize * 8) - offset; + } + + void bitstream::clear() { + dataSize = 0; + offset = 0; + } + + void bitstream::flush() { + memmove(data, data + (offset / 8), dataSize - (offset / 8)); + dataSize -= offset / 8; + offset %= 8; + } + + long long unsigned int bitstream::golombPeeker() { + for (size_t i = 0; i < 64 && i < size(); i++) { + if (peekOffset(i)) { + return peek((i * 2) + 1); + } + } + return 0; + } + + long long unsigned int bitstream::golombGetter() { + for (size_t i = 0; i < 64 && i < size(); i++) { + if (peekOffset(i)) { + return get((i * 2) + 1); + } + } + return 0; + } + + long long int bitstream::getExpGolomb() { + long long unsigned int temp = golombGetter(); + return (temp >> 1) * (1 - ((temp & 1) << 1)); //Is actually return (temp / 2) * (1 - (temp & 1) * 2); + } + + long long unsigned int bitstream::getUExpGolomb() { + return golombGetter() - 1; + } + + long long int bitstream::peekExpGolomb() { + long long unsigned int temp = golombPeeker(); + return (temp >> 1) * (1 - ((temp & 1) << 1)); //Is actually return (temp / 2) * (1 - (temp & 1) * 2); + } + + long long unsigned int bitstream::peekUExpGolomb() { + return golombPeeker() - 1; + } + +//Note: other bitstream here + bitstreamLSBF::bitstreamLSBF() { + readBufferOffset = 0; + readBuffer = 0; + } + + void bitstreamLSBF::append(char * input, size_t bytes) { + data.append(input, bytes); + fixData(); + } + + void bitstreamLSBF::append(std::string & input) { + data += input; + fixData(); + } + + long long unsigned int bitstreamLSBF::size() { + return data.size() * 8 + readBufferOffset; + } + + long long unsigned int bitstreamLSBF::get(size_t count) { + if (count <= 32 && count <= readBufferOffset) { + long long unsigned int retval = readBuffer & (((long long unsigned int)1 << count) - 1); + readBuffer = readBuffer >> count; + readBufferOffset -= count; + fixData(); + return retval; + } + return 42; + } + + void bitstreamLSBF::skip(size_t count) { + if (count <= 32 && count <= readBufferOffset) { + readBuffer = readBuffer >> count; + readBufferOffset -= count; + fixData(); + } + } + + long long unsigned int bitstreamLSBF::peek(size_t count) { + if (count <= 32 && count <= readBufferOffset) { + return readBuffer & ((1 << count) - 1); + } + return 0; + } + + void bitstreamLSBF::clear() { + data = ""; + readBufferOffset = 0; + readBuffer = 0; + } + + void bitstreamLSBF::fixData() { + unsigned int pos=0; + while (readBufferOffset <= 32 && data.size() != 0) { + readBuffer |= (((long long unsigned int)data[pos]) << readBufferOffset); + pos++; + readBufferOffset += 8; + } + data.erase(0, pos); + } +} diff --git a/lib/bitstream.h b/lib/bitstream.h new file mode 100644 index 00000000..d34d6424 --- /dev/null +++ b/lib/bitstream.h @@ -0,0 +1,60 @@ +#include + +namespace Utils { + class bitstream { + public: + bitstream(); + bitstream & operator<< (std::string input) { + append(input); + return *this; + }; + bitstream & operator<< (char input) { + append(std::string(input, 1)); + return *this; + }; + void append(char * input, size_t bytes); + void append(std::string input); + long long unsigned int size(); + void skip(size_t count); + long long unsigned int get(size_t count); + long long unsigned int peek(size_t count); + bool peekOffset(size_t peekOffset); + void flush(); + void clear(); + long long int getExpGolomb(); + long long unsigned int getUExpGolomb(); + long long int peekExpGolomb(); + long long unsigned int peekUExpGolomb(); + private: + bool checkBufferSize(unsigned int size); + long long unsigned int golombGetter(); + long long unsigned int golombPeeker(); + char * data; + size_t offset; + size_t dataSize; + size_t bufferSize; + }; + + class bitstreamLSBF { + public: + bitstreamLSBF(); + bitstreamLSBF & operator<< (std::string input) { + append(input); + return *this; + }; + void append(char * input, size_t bytes); + void append(std::string & input); + long long unsigned int size(); + void skip(size_t count); + long long unsigned int get(size_t count); + long long unsigned int peek(size_t count); + void clear(); + std::string data; + private: + long long unsigned int readBuffer; + unsigned int readBufferOffset; + void fixData(); + }; +} + + diff --git a/lib/checksum.h b/lib/checksum.h new file mode 100644 index 00000000..d2aeba6d --- /dev/null +++ b/lib/checksum.h @@ -0,0 +1,229 @@ +namespace checksum { + inline unsigned int crc32c(unsigned int crc, const char * data, size_t len) { + static const unsigned int table[256] = { + 0x00000000U, 0x04C11DB7U, 0x09823B6EU, 0x0D4326D9U, + 0x130476DCU, 0x17C56B6BU, 0x1A864DB2U, 0x1E475005U, + 0x2608EDB8U, 0x22C9F00FU, 0x2F8AD6D6U, 0x2B4BCB61U, + 0x350C9B64U, 0x31CD86D3U, 0x3C8EA00AU, 0x384FBDBDU, + 0x4C11DB70U, 0x48D0C6C7U, 0x4593E01EU, 0x4152FDA9U, + 0x5F15ADACU, 0x5BD4B01BU, 0x569796C2U, 0x52568B75U, + 0x6A1936C8U, 0x6ED82B7FU, 0x639B0DA6U, 0x675A1011U, + 0x791D4014U, 0x7DDC5DA3U, 0x709F7B7AU, 0x745E66CDU, + 0x9823B6E0U, 0x9CE2AB57U, 0x91A18D8EU, 0x95609039U, + 0x8B27C03CU, 0x8FE6DD8BU, 0x82A5FB52U, 0x8664E6E5U, + 0xBE2B5B58U, 0xBAEA46EFU, 0xB7A96036U, 0xB3687D81U, + 0xAD2F2D84U, 0xA9EE3033U, 0xA4AD16EAU, 0xA06C0B5DU, + 0xD4326D90U, 0xD0F37027U, 0xDDB056FEU, 0xD9714B49U, + 0xC7361B4CU, 0xC3F706FBU, 0xCEB42022U, 0xCA753D95U, + 0xF23A8028U, 0xF6FB9D9FU, 0xFBB8BB46U, 0xFF79A6F1U, + 0xE13EF6F4U, 0xE5FFEB43U, 0xE8BCCD9AU, 0xEC7DD02DU, + 0x34867077U, 0x30476DC0U, 0x3D044B19U, 0x39C556AEU, + 0x278206ABU, 0x23431B1CU, 0x2E003DC5U, 0x2AC12072U, + 0x128E9DCFU, 0x164F8078U, 0x1B0CA6A1U, 0x1FCDBB16U, + 0x018AEB13U, 0x054BF6A4U, 0x0808D07DU, 0x0CC9CDCAU, + 0x7897AB07U, 0x7C56B6B0U, 0x71159069U, 0x75D48DDEU, + 0x6B93DDDBU, 0x6F52C06CU, 0x6211E6B5U, 0x66D0FB02U, + 0x5E9F46BFU, 0x5A5E5B08U, 0x571D7DD1U, 0x53DC6066U, + 0x4D9B3063U, 0x495A2DD4U, 0x44190B0DU, 0x40D816BAU, + 0xACA5C697U, 0xA864DB20U, 0xA527FDF9U, 0xA1E6E04EU, + 0xBFA1B04BU, 0xBB60ADFCU, 0xB6238B25U, 0xB2E29692U, + 0x8AAD2B2FU, 0x8E6C3698U, 0x832F1041U, 0x87EE0DF6U, + 0x99A95DF3U, 0x9D684044U, 0x902B669DU, 0x94EA7B2AU, + 0xE0B41DE7U, 0xE4750050U, 0xE9362689U, 0xEDF73B3EU, + 0xF3B06B3BU, 0xF771768CU, 0xFA325055U, 0xFEF34DE2U, + 0xC6BCF05FU, 0xC27DEDE8U, 0xCF3ECB31U, 0xCBFFD686U, + 0xD5B88683U, 0xD1799B34U, 0xDC3ABDEDU, 0xD8FBA05AU, + 0x690CE0EEU, 0x6DCDFD59U, 0x608EDB80U, 0x644FC637U, + 0x7A089632U, 0x7EC98B85U, 0x738AAD5CU, 0x774BB0EBU, + 0x4F040D56U, 0x4BC510E1U, 0x46863638U, 0x42472B8FU, + 0x5C007B8AU, 0x58C1663DU, 0x558240E4U, 0x51435D53U, + 0x251D3B9EU, 0x21DC2629U, 0x2C9F00F0U, 0x285E1D47U, + 0x36194D42U, 0x32D850F5U, 0x3F9B762CU, 0x3B5A6B9BU, + 0x0315D626U, 0x07D4CB91U, 0x0A97ED48U, 0x0E56F0FFU, + 0x1011A0FAU, 0x14D0BD4DU, 0x19939B94U, 0x1D528623U, + 0xF12F560EU, 0xF5EE4BB9U, 0xF8AD6D60U, 0xFC6C70D7U, + 0xE22B20D2U, 0xE6EA3D65U, 0xEBA91BBCU, 0xEF68060BU, + 0xD727BBB6U, 0xD3E6A601U, 0xDEA580D8U, 0xDA649D6FU, + 0xC423CD6AU, 0xC0E2D0DDU, 0xCDA1F604U, 0xC960EBB3U, + 0xBD3E8D7EU, 0xB9FF90C9U, 0xB4BCB610U, 0xB07DABA7U, + 0xAE3AFBA2U, 0xAAFBE615U, 0xA7B8C0CCU, 0xA379DD7BU, + 0x9B3660C6U, 0x9FF77D71U, 0x92B45BA8U, 0x9675461FU, + 0x8832161AU, 0x8CF30BADU, 0x81B02D74U, 0x857130C3U, + 0x5D8A9099U, 0x594B8D2EU, 0x5408ABF7U, 0x50C9B640U, + 0x4E8EE645U, 0x4A4FFBF2U, 0x470CDD2BU, 0x43CDC09CU, + 0x7B827D21U, 0x7F436096U, 0x7200464FU, 0x76C15BF8U, + 0x68860BFDU, 0x6C47164AU, 0x61043093U, 0x65C52D24U, + 0x119B4BE9U, 0x155A565EU, 0x18197087U, 0x1CD86D30U, + 0x029F3D35U, 0x065E2082U, 0x0B1D065BU, 0x0FDC1BECU, + 0x3793A651U, 0x3352BBE6U, 0x3E119D3FU, 0x3AD08088U, + 0x2497D08DU, 0x2056CD3AU, 0x2D15EBE3U, 0x29D4F654U, + 0xC5A92679U, 0xC1683BCEU, 0xCC2B1D17U, 0xC8EA00A0U, + 0xD6AD50A5U, 0xD26C4D12U, 0xDF2F6BCBU, 0xDBEE767CU, + 0xE3A1CBC1U, 0xE760D676U, 0xEA23F0AFU, 0xEEE2ED18U, + 0xF0A5BD1DU, 0xF464A0AAU, 0xF9278673U, 0xFDE69BC4U, + 0x89B8FD09U, 0x8D79E0BEU, 0x803AC667U, 0x84FBDBD0U, + 0x9ABC8BD5U, 0x9E7D9662U, 0x933EB0BBU, 0x97FFAD0CU, + 0xAFB010B1U, 0xAB710D06U, 0xA6322BDFU, 0xA2F33668U, + 0xBCB4666DU, 0xB8757BDAU, 0xB5365D03U, 0xB1F740B4U, + }; + + while (len > 0) { + crc = table[*data ^ ((crc >> 24) & 0xff)] ^ (crc << 8); + data++; + len--; + } + return crc; + } + + inline unsigned int crc32LE(unsigned int crc, const char * data, size_t len) { + static const unsigned int table[256] = { + 0x00000000U, 0x77073096U, 0xee0e612cU, 0x990951baU, + 0x076dc419U, 0x706af48fU, 0xe963a535U, 0x9e6495a3U, + 0x0edb8832U, 0x79dcb8a4U, 0xe0d5e91eU, 0x97d2d988U, + 0x09b64c2bU, 0x7eb17cbdU, 0xe7b82d07U, 0x90bf1d91U, + 0x1db71064U, 0x6ab020f2U, 0xf3b97148U, 0x84be41deU, + 0x1adad47dU, 0x6ddde4ebU, 0xf4d4b551U, 0x83d385c7U, + 0x136c9856U, 0x646ba8c0U, 0xfd62f97aU, 0x8a65c9ecU, + 0x14015c4fU, 0x63066cd9U, 0xfa0f3d63U, 0x8d080df5U, + 0x3b6e20c8U, 0x4c69105eU, 0xd56041e4U, 0xa2677172U, + 0x3c03e4d1U, 0x4b04d447U, 0xd20d85fdU, 0xa50ab56bU, + 0x35b5a8faU, 0x42b2986cU, 0xdbbbc9d6U, 0xacbcf940U, + 0x32d86ce3U, 0x45df5c75U, 0xdcd60dcfU, 0xabd13d59U, + 0x26d930acU, 0x51de003aU, 0xc8d75180U, 0xbfd06116U, + 0x21b4f4b5U, 0x56b3c423U, 0xcfba9599U, 0xb8bda50fU, + 0x2802b89eU, 0x5f058808U, 0xc60cd9b2U, 0xb10be924U, + 0x2f6f7c87U, 0x58684c11U, 0xc1611dabU, 0xb6662d3dU, + 0x76dc4190U, 0x01db7106U, 0x98d220bcU, 0xefd5102aU, + 0x71b18589U, 0x06b6b51fU, 0x9fbfe4a5U, 0xe8b8d433U, + 0x7807c9a2U, 0x0f00f934U, 0x9609a88eU, 0xe10e9818U, + 0x7f6a0dbbU, 0x086d3d2dU, 0x91646c97U, 0xe6635c01U, + 0x6b6b51f4U, 0x1c6c6162U, 0x856530d8U, 0xf262004eU, + 0x6c0695edU, 0x1b01a57bU, 0x8208f4c1U, 0xf50fc457U, + 0x65b0d9c6U, 0x12b7e950U, 0x8bbeb8eaU, 0xfcb9887cU, + 0x62dd1ddfU, 0x15da2d49U, 0x8cd37cf3U, 0xfbd44c65U, + 0x4db26158U, 0x3ab551ceU, 0xa3bc0074U, 0xd4bb30e2U, + 0x4adfa541U, 0x3dd895d7U, 0xa4d1c46dU, 0xd3d6f4fbU, + 0x4369e96aU, 0x346ed9fcU, 0xad678846U, 0xda60b8d0U, + 0x44042d73U, 0x33031de5U, 0xaa0a4c5fU, 0xdd0d7cc9U, + 0x5005713cU, 0x270241aaU, 0xbe0b1010U, 0xc90c2086U, + 0x5768b525U, 0x206f85b3U, 0xb966d409U, 0xce61e49fU, + 0x5edef90eU, 0x29d9c998U, 0xb0d09822U, 0xc7d7a8b4U, + 0x59b33d17U, 0x2eb40d81U, 0xb7bd5c3bU, 0xc0ba6cadU, + 0xedb88320U, 0x9abfb3b6U, 0x03b6e20cU, 0x74b1d29aU, + 0xead54739U, 0x9dd277afU, 0x04db2615U, 0x73dc1683U, + 0xe3630b12U, 0x94643b84U, 0x0d6d6a3eU, 0x7a6a5aa8U, + 0xe40ecf0bU, 0x9309ff9dU, 0x0a00ae27U, 0x7d079eb1U, + 0xf00f9344U, 0x8708a3d2U, 0x1e01f268U, 0x6906c2feU, + 0xf762575dU, 0x806567cbU, 0x196c3671U, 0x6e6b06e7U, + 0xfed41b76U, 0x89d32be0U, 0x10da7a5aU, 0x67dd4accU, + 0xf9b9df6fU, 0x8ebeeff9U, 0x17b7be43U, 0x60b08ed5U, + 0xd6d6a3e8U, 0xa1d1937eU, 0x38d8c2c4U, 0x4fdff252U, + 0xd1bb67f1U, 0xa6bc5767U, 0x3fb506ddU, 0x48b2364bU, + 0xd80d2bdaU, 0xaf0a1b4cU, 0x36034af6U, 0x41047a60U, + 0xdf60efc3U, 0xa867df55U, 0x316e8eefU, 0x4669be79U, + 0xcb61b38cU, 0xbc66831aU, 0x256fd2a0U, 0x5268e236U, + 0xcc0c7795U, 0xbb0b4703U, 0x220216b9U, 0x5505262fU, + 0xc5ba3bbeU, 0xb2bd0b28U, 0x2bb45a92U, 0x5cb36a04U, + 0xc2d7ffa7U, 0xb5d0cf31U, 0x2cd99e8bU, 0x5bdeae1dU, + 0x9b64c2b0U, 0xec63f226U, 0x756aa39cU, 0x026d930aU, + 0x9c0906a9U, 0xeb0e363fU, 0x72076785U, 0x05005713U, + 0x95bf4a82U, 0xe2b87a14U, 0x7bb12baeU, 0x0cb61b38U, + 0x92d28e9bU, 0xe5d5be0dU, 0x7cdcefb7U, 0x0bdbdf21U, + 0x86d3d2d4U, 0xf1d4e242U, 0x68ddb3f8U, 0x1fda836eU, + 0x81be16cdU, 0xf6b9265bU, 0x6fb077e1U, 0x18b74777U, + 0x88085ae6U, 0xff0f6a70U, 0x66063bcaU, 0x11010b5cU, + 0x8f659effU, 0xf862ae69U, 0x616bffd3U, 0x166ccf45U, + 0xa00ae278U, 0xd70dd2eeU, 0x4e048354U, 0x3903b3c2U, + 0xa7672661U, 0xd06016f7U, 0x4969474dU, 0x3e6e77dbU, + 0xaed16a4aU, 0xd9d65adcU, 0x40df0b66U, 0x37d83bf0U, + 0xa9bcae53U, 0xdebb9ec5U, 0x47b2cf7fU, 0x30b5ffe9U, + 0xbdbdf21cU, 0xcabac28aU, 0x53b39330U, 0x24b4a3a6U, + 0xbad03605U, 0xcdd70693U, 0x54de5729U, 0x23d967bfU, + 0xb3667a2eU, 0xc4614ab8U, 0x5d681b02U, 0x2a6f2b94U, + 0xb40bbe37U, 0xc30c8ea1U, 0x5a05df1bU, 0x2d02ef8dU + }; + + while (len > 0) { + crc = table[*data ^ ((crc >> 24) & 0xff)] ^ (crc << 8); + data++; + len--; + } + return crc; + } + + inline unsigned int crc32(unsigned int crc, const char * data, size_t len) { + static const unsigned int table[256] = { + 0x00000000U, 0xB71DC104U, 0x6E3B8209U, 0xD926430DU, + 0xDC760413U, 0x6B6BC517U, 0xB24D861AU, 0x0550471EU, + 0xB8ED0826U, 0x0FF0C922U, 0xD6D68A2FU, 0x61CB4B2BU, + 0x649B0C35U, 0xD386CD31U, 0x0AA08E3CU, 0xBDBD4F38U, + 0x70DB114CU, 0xC7C6D048U, 0x1EE09345U, 0xA9FD5241U, + 0xACAD155FU, 0x1BB0D45BU, 0xC2969756U, 0x758B5652U, + 0xC836196AU, 0x7F2BD86EU, 0xA60D9B63U, 0x11105A67U, + 0x14401D79U, 0xA35DDC7DU, 0x7A7B9F70U, 0xCD665E74U, + 0xE0B62398U, 0x57ABE29CU, 0x8E8DA191U, 0x39906095U, + 0x3CC0278BU, 0x8BDDE68FU, 0x52FBA582U, 0xE5E66486U, + 0x585B2BBEU, 0xEF46EABAU, 0x3660A9B7U, 0x817D68B3U, + 0x842D2FADU, 0x3330EEA9U, 0xEA16ADA4U, 0x5D0B6CA0U, + 0x906D32D4U, 0x2770F3D0U, 0xFE56B0DDU, 0x494B71D9U, + 0x4C1B36C7U, 0xFB06F7C3U, 0x2220B4CEU, 0x953D75CAU, + 0x28803AF2U, 0x9F9DFBF6U, 0x46BBB8FBU, 0xF1A679FFU, + 0xF4F63EE1U, 0x43EBFFE5U, 0x9ACDBCE8U, 0x2DD07DECU, + 0x77708634U, 0xC06D4730U, 0x194B043DU, 0xAE56C539U, + 0xAB068227U, 0x1C1B4323U, 0xC53D002EU, 0x7220C12AU, + 0xCF9D8E12U, 0x78804F16U, 0xA1A60C1BU, 0x16BBCD1FU, + 0x13EB8A01U, 0xA4F64B05U, 0x7DD00808U, 0xCACDC90CU, + 0x07AB9778U, 0xB0B6567CU, 0x69901571U, 0xDE8DD475U, + 0xDBDD936BU, 0x6CC0526FU, 0xB5E61162U, 0x02FBD066U, + 0xBF469F5EU, 0x085B5E5AU, 0xD17D1D57U, 0x6660DC53U, + 0x63309B4DU, 0xD42D5A49U, 0x0D0B1944U, 0xBA16D840U, + 0x97C6A5ACU, 0x20DB64A8U, 0xF9FD27A5U, 0x4EE0E6A1U, + 0x4BB0A1BFU, 0xFCAD60BBU, 0x258B23B6U, 0x9296E2B2U, + 0x2F2BAD8AU, 0x98366C8EU, 0x41102F83U, 0xF60DEE87U, + 0xF35DA999U, 0x4440689DU, 0x9D662B90U, 0x2A7BEA94U, + 0xE71DB4E0U, 0x500075E4U, 0x892636E9U, 0x3E3BF7EDU, + 0x3B6BB0F3U, 0x8C7671F7U, 0x555032FAU, 0xE24DF3FEU, + 0x5FF0BCC6U, 0xE8ED7DC2U, 0x31CB3ECFU, 0x86D6FFCBU, + 0x8386B8D5U, 0x349B79D1U, 0xEDBD3ADCU, 0x5AA0FBD8U, + 0xEEE00C69U, 0x59FDCD6DU, 0x80DB8E60U, 0x37C64F64U, + 0x3296087AU, 0x858BC97EU, 0x5CAD8A73U, 0xEBB04B77U, + 0x560D044FU, 0xE110C54BU, 0x38368646U, 0x8F2B4742U, + 0x8A7B005CU, 0x3D66C158U, 0xE4408255U, 0x535D4351U, + 0x9E3B1D25U, 0x2926DC21U, 0xF0009F2CU, 0x471D5E28U, + 0x424D1936U, 0xF550D832U, 0x2C769B3FU, 0x9B6B5A3BU, + 0x26D61503U, 0x91CBD407U, 0x48ED970AU, 0xFFF0560EU, + 0xFAA01110U, 0x4DBDD014U, 0x949B9319U, 0x2386521DU, + 0x0E562FF1U, 0xB94BEEF5U, 0x606DADF8U, 0xD7706CFCU, + 0xD2202BE2U, 0x653DEAE6U, 0xBC1BA9EBU, 0x0B0668EFU, + 0xB6BB27D7U, 0x01A6E6D3U, 0xD880A5DEU, 0x6F9D64DAU, + 0x6ACD23C4U, 0xDDD0E2C0U, 0x04F6A1CDU, 0xB3EB60C9U, + 0x7E8D3EBDU, 0xC990FFB9U, 0x10B6BCB4U, 0xA7AB7DB0U, + 0xA2FB3AAEU, 0x15E6FBAAU, 0xCCC0B8A7U, 0x7BDD79A3U, + 0xC660369BU, 0x717DF79FU, 0xA85BB492U, 0x1F467596U, + 0x1A163288U, 0xAD0BF38CU, 0x742DB081U, 0xC3307185U, + 0x99908A5DU, 0x2E8D4B59U, 0xF7AB0854U, 0x40B6C950U, + 0x45E68E4EU, 0xF2FB4F4AU, 0x2BDD0C47U, 0x9CC0CD43U, + 0x217D827BU, 0x9660437FU, 0x4F460072U, 0xF85BC176U, + 0xFD0B8668U, 0x4A16476CU, 0x93300461U, 0x242DC565U, + 0xE94B9B11U, 0x5E565A15U, 0x87701918U, 0x306DD81CU, + 0x353D9F02U, 0x82205E06U, 0x5B061D0BU, 0xEC1BDC0FU, + 0x51A69337U, 0xE6BB5233U, 0x3F9D113EU, 0x8880D03AU, + 0x8DD09724U, 0x3ACD5620U, 0xE3EB152DU, 0x54F6D429U, + 0x7926A9C5U, 0xCE3B68C1U, 0x171D2BCCU, 0xA000EAC8U, + 0xA550ADD6U, 0x124D6CD2U, 0xCB6B2FDFU, 0x7C76EEDBU, + 0xC1CBA1E3U, 0x76D660E7U, 0xAFF023EAU, 0x18EDE2EEU, + 0x1DBDA5F0U, 0xAAA064F4U, 0x738627F9U, 0xC49BE6FDU, + 0x09FDB889U, 0xBEE0798DU, 0x67C63A80U, 0xD0DBFB84U, + 0xD58BBC9AU, 0x62967D9EU, 0xBBB03E93U, 0x0CADFF97U, + 0xB110B0AFU, 0x060D71ABU, 0xDF2B32A6U, 0x6836F3A2U, + 0x6D66B4BCU, 0xDA7B75B8U, 0x035D36B5U, 0xB440F7B1U + }; + + const char * tmpData = data; + const char * end = tmpData + len; + while(tmpData < end){ + crc = table[((unsigned char) crc) ^ *tmpData++] ^ (crc >> 8); + } + return crc; + } +} diff --git a/lib/config.cpp b/lib/config.cpp new file mode 100644 index 00000000..debc51c8 --- /dev/null +++ b/lib/config.cpp @@ -0,0 +1,643 @@ +/// \file config.cpp +/// Contains generic functions for managing configuration. + +#include "config.h" +#include "defines.h" +#include "timing.h" +#include "tinythread.h" +#include "stream.h" +#include +#include + +#ifdef __CYGWIN__ +#include +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__MACH__) +#include +#else +#include +#endif +#if defined(__APPLE__) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //for getMyExec + +bool Util::Config::is_active = false; +unsigned int Util::Config::printDebugLevel = DEBUG;// +std::string Util::Config::libver = PACKAGE_VERSION; + +Util::Config::Config() { + //global options here + vals["debug"]["long"] = "debug"; + vals["debug"]["short"] = "g"; + vals["debug"]["arg"] = "integer"; + vals["debug"]["help"] = "The debug level at which messages need to be printed."; + vals["debug"]["value"].append((long long)DEBUG); + /*capabilities["optional"]["debug level"]["name"] = "debug"; + capabilities["optional"]["debug level"]["help"] = "The debug level at which messages need to be printed."; + capabilities["optional"]["debug level"]["option"] = "--debug"; + capabilities["optional"]["debug level"]["type"] = "integer";*/ +} + +/// Creates a new configuration manager. +Util::Config::Config(std::string cmd, std::string version) { + vals.null(); + long_count = 2; + vals["cmd"]["value"].append(cmd); + vals["version"]["long"] = "version"; + vals["version"]["short"] = "v"; + vals["version"]["help"] = "Display library and application version, then exit."; + vals["help"]["long"] = "help"; + vals["help"]["short"] = "h"; + vals["help"]["help"] = "Display usage and version information, then exit."; + vals["version"]["value"].append((std::string)PACKAGE_VERSION); + vals["version"]["value"].append(version); + vals["debug"]["long"] = "debug"; + vals["debug"]["short"] = "g"; + vals["debug"]["arg"] = "integer"; + vals["debug"]["help"] = "The debug level at which messages need to be printed."; + vals["debug"]["value"].append((long long)DEBUG); +} + +/// Adds an option to the configuration parser. +/// The option needs an unique name (doubles will overwrite the previous) and can contain the following in the option itself: +///\code +/// { +/// "short":"o", //The short option letter +/// "long":"onName", //The long option +/// "short_off":"n", //The short option-off letter +/// "long_off":"offName", //The long option-off +/// "arg":"integer", //The type of argument, if required. +/// "value":[], //The default value(s) for this option if it is not given on the commandline. +/// "arg_num":1, //The count this value has on the commandline, after all the options have been processed. +/// "help":"Blahblahblah" //The helptext for this option. +/// } +///\endcode +void Util::Config::addOption(std::string optname, JSON::Value option) { + vals[optname] = option; + if (!vals[optname].isMember("value") && vals[optname].isMember("default")) { + vals[optname]["value"].append(vals[optname]["default"]); + vals[optname].removeMember("default"); + } + long_count = 0; + for (JSON::ObjIter it = vals.ObjBegin(); it != vals.ObjEnd(); it++) { + if (it->second.isMember("long")) { + long_count++; + } + if (it->second.isMember("long_off")) { + long_count++; + } + } +} + +/// Prints a usage message to the given output. +void Util::Config::printHelp(std::ostream & output) { + unsigned int longest = 0; + std::map args; + for (JSON::ObjIter it = vals.ObjBegin(); it != vals.ObjEnd(); it++) { + unsigned int current = 0; + if (it->second.isMember("long")) { + current += it->second["long"].asString().size() + 4; + } + if (it->second.isMember("short")) { + current += it->second["short"].asString().size() + 3; + } + if (current > longest) { + longest = current; + } + current = 0; + if (it->second.isMember("long_off")) { + current += it->second["long_off"].asString().size() + 4; + } + if (it->second.isMember("short_off")) { + current += it->second["short_off"].asString().size() + 3; + } + if (current > longest) { + longest = current; + } + if (it->second.isMember("arg_num")) { + current = it->first.size() + 3; + if (current > longest) { + longest = current; + } + args[it->second["arg_num"].asInt()] = it->first; + } + } + output << "Usage: " << getString("cmd") << " [options]"; + for (std::map::iterator i = args.begin(); i != args.end(); i++) { + if (vals[i->second].isMember("value") && vals[i->second]["value"].size()) { + output << " [" << i->second << "]"; + } else { + output << " " << i->second; + } + } + output << std::endl << std::endl; + for (JSON::ObjIter it = vals.ObjBegin(); it != vals.ObjEnd(); it++) { + std::string f; + if (it->second.isMember("long") || it->second.isMember("short")) { + if (it->second.isMember("long") && it->second.isMember("short")) { + f = "--" + it->second["long"].asString() + ", -" + it->second["short"].asString(); + } else { + if (it->second.isMember("long")) { + f = "--" + it->second["long"].asString(); + } + if (it->second.isMember("short")) { + f = "-" + it->second["short"].asString(); + } + } + while (f.size() < longest) { + f.append(" "); + } + if (it->second.isMember("arg")) { + output << f << "(" << it->second["arg"].asString() << ") " << it->second["help"].asString() << std::endl; + } else { + output << f << it->second["help"].asString() << std::endl; + } + } + if (it->second.isMember("long_off") || it->second.isMember("short_off")) { + if (it->second.isMember("long_off") && it->second.isMember("short_off")) { + f = "--" + it->second["long_off"].asString() + ", -" + it->second["short_off"].asString(); + } else { + if (it->second.isMember("long_off")) { + f = "--" + it->second["long_off"].asString(); + } + if (it->second.isMember("short_off")) { + f = "-" + it->second["short_off"].asString(); + } + } + while (f.size() < longest) { + f.append(" "); + } + if (it->second.isMember("arg")) { + output << f << "(" << it->second["arg"].asString() << ") " << it->second["help"].asString() << std::endl; + } else { + output << f << it->second["help"].asString() << std::endl; + } + } + if (it->second.isMember("arg_num")) { + f = it->first; + while (f.size() < longest) { + f.append(" "); + } + output << f << "(" << it->second["arg"].asString() << ") " << it->second["help"].asString() << std::endl; + } + } +} + +/// Parses commandline arguments. +/// Calls exit if an unknown option is encountered, printing a help message. +bool Util::Config::parseArgs(int & argc, char ** & argv) { + int opt = 0; + std::string shortopts; + struct option * longOpts = (struct option *)calloc(long_count + 1, sizeof(struct option)); + int long_i = 0; + int arg_count = 0; + if (vals.size()) { + for (JSON::ObjIter it = vals.ObjBegin(); it != vals.ObjEnd(); it++) { + if (it->second.isMember("short")) { + shortopts += it->second["short"].asString(); + if (it->second.isMember("arg")) { + shortopts += ":"; + } + } + if (it->second.isMember("short_off")) { + shortopts += it->second["short_off"].asString(); + if (it->second.isMember("arg")) { + shortopts += ":"; + } + } + if (it->second.isMember("long")) { + longOpts[long_i].name = it->second["long"].asString().c_str(); + longOpts[long_i].val = it->second["short"].asString()[0]; + if (it->second.isMember("arg")) { + longOpts[long_i].has_arg = 1; + } + long_i++; + } + if (it->second.isMember("long_off")) { + longOpts[long_i].name = it->second["long_off"].asString().c_str(); + longOpts[long_i].val = it->second["short_off"].asString()[0]; + if (it->second.isMember("arg")) { + longOpts[long_i].has_arg = 1; + } + long_i++; + } + if (it->second.isMember("arg_num") && !(it->second.isMember("value") && it->second["value"].size())) { + if (it->second["arg_num"].asInt() > arg_count) { + arg_count = it->second["arg_num"].asInt(); + } + } + } + } + while ((opt = getopt_long(argc, argv, shortopts.c_str(), longOpts, 0)) != -1) { + switch (opt) { + case 'h': + case '?': + printHelp(std::cout); + case 'v': + std::cout << "Library version: " PACKAGE_VERSION << std::endl; + std::cout << "Application version: " << getString("version") << std::endl; + exit(1); + break; + default: + for (JSON::ObjIter it = vals.ObjBegin(); it != vals.ObjEnd(); it++) { + if (it->second.isMember("short") && it->second["short"].asString()[0] == opt) { + if (it->second.isMember("arg")) { + it->second["value"].append((std::string)optarg); + } else { + it->second["value"].append((long long int)1); + } + break; + } + if (it->second.isMember("short_off") && it->second["short_off"].asString()[0] == opt) { + it->second["value"].append((long long int)0); + } + } + break; + } + } //commandline options parser + free(longOpts); //free the long options array + long_i = 1; //re-use long_i as an argument counter + while (optind < argc) { //parse all remaining options, ignoring anything unexpected. + for (JSON::ObjIter it = vals.ObjBegin(); it != vals.ObjEnd(); it++) { + if (it->second.isMember("arg_num") && it->second["arg_num"].asInt() == long_i) { + it->second["value"].append((std::string)argv[optind]); + break; + } + } + optind++; + long_i++; + } + if (long_i <= arg_count) { + return false; + } + printDebugLevel = getInteger("debug"); + return true; +} + +/// Returns a reference to the current value of an option or default if none was set. +/// If the option does not exist, this exits the application with a return code of 37. +JSON::Value & Util::Config::getOption(std::string optname, bool asArray) { + if (!vals.isMember(optname)) { + std::cout << "Fatal error: a non-existent option '" << optname << "' was accessed." << std::endl; + exit(37); + } + if (!vals[optname].isMember("value") || !vals[optname]["value"].isArray()) { + vals[optname]["value"].append(JSON::Value()); + } + if (asArray) { + return vals[optname]["value"]; + } else { + int n = vals[optname]["value"].size(); + return vals[optname]["value"][n - 1]; + } +} + +/// Returns the current value of an option or default if none was set as a string. +/// Calls getOption internally. +std::string Util::Config::getString(std::string optname) { + return getOption(optname).asString(); +} + +/// Returns the current value of an option or default if none was set as a long long int. +/// Calls getOption internally. +long long int Util::Config::getInteger(std::string optname) { + return getOption(optname).asInt(); +} + +/// Returns the current value of an option or default if none was set as a bool. +/// Calls getOption internally. +bool Util::Config::getBool(std::string optname) { + return getOption(optname).asBool(); +} + +struct callbackData { + Socket::Connection * sock; + int (*cb)(Socket::Connection &); +}; + +static void callThreadCallback(void * cDataArg) { + DEBUG_MSG(DLVL_INSANE, "Thread for %p started", cDataArg); + callbackData * cData = (callbackData *)cDataArg; + cData->cb(*(cData->sock)); + cData->sock->close(); + delete cData->sock; + delete cData; + DEBUG_MSG(DLVL_INSANE, "Thread for %p ended", cDataArg); +} + +int Util::Config::threadServer(Socket::Server & server_socket, int (*callback)(Socket::Connection &)) { + while (is_active && server_socket.connected()) { + Socket::Connection S = server_socket.accept(); + if (S.connected()) { //check if the new connection is valid + callbackData * cData = new callbackData; + cData->sock = new Socket::Connection(S); + cData->cb = callback; + //spawn a new thread for this connection + tthread::thread T(callThreadCallback, (void *)cData); + //detach it, no need to keep track of it anymore + T.detach(); + DEBUG_MSG(DLVL_HIGH, "Spawned new thread for socket %i", S.getSocket()); + } else { + Util::sleep(10); //sleep 10ms + } + } + server_socket.close(); + return 0; +} + +int Util::Config::forkServer(Socket::Server & server_socket, int (*callback)(Socket::Connection &)) { + while (is_active && server_socket.connected()) { + Socket::Connection 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 + server_socket.drop(); + return callback(S); + } else { //otherwise, do nothing or output debugging text + DEBUG_MSG(DLVL_HIGH, "Forked new process %i for socket %i", (int)myid, S.getSocket()); + S.drop(); + } + } else { + Util::sleep(10); //sleep 10ms + } + } + server_socket.close(); + return 0; +} + +int Util::Config::serveThreadedSocket(int (*callback)(Socket::Connection &)) { + Socket::Server server_socket; + if (vals.isMember("socket")) { + server_socket = Socket::Server(Util::getTmpFolder() + getString("socket")); + } + if (vals.isMember("listen_port") && vals.isMember("listen_interface")) { + server_socket = Socket::Server(getInteger("listen_port"), getString("listen_interface"), false); + } + if (!server_socket.connected()) { + DEBUG_MSG(DLVL_DEVEL, "Failure to open socket"); + return 1; + } + DEBUG_MSG(DLVL_DEVEL, "Activating threaded server: %s", getString("cmd").c_str()); + activate(); + return threadServer(server_socket, callback); +} + +int Util::Config::serveForkedSocket(int (*callback)(Socket::Connection & S)) { + Socket::Server server_socket; + if (vals.isMember("socket")) { + server_socket = Socket::Server(Util::getTmpFolder() + getString("socket")); + } + if (vals.isMember("listen_port") && vals.isMember("listen_interface")) { + server_socket = Socket::Server(getInteger("listen_port"), getString("listen_interface"), false); + } + if (!server_socket.connected()) { + DEBUG_MSG(DLVL_DEVEL, "Failure to open socket"); + return 1; + } + DEBUG_MSG(DLVL_DEVEL, "Activating forked server: %s", getString("cmd").c_str()); + activate(); + return forkServer(server_socket, callback); +} + +/// Activated the stored config. This will: +/// - Drop permissions to the stored "username", if any. +/// - Daemonize the process if "daemonize" exists and is true. +/// - Set is_active to true. +/// - Set up a signal handler to set is_active to false for the SIGINT, SIGHUP and SIGTERM signals. +void Util::Config::activate() { + if (vals.isMember("username")) { + setUser(getString("username")); + vals.removeMember("username"); + } + if (vals.isMember("daemonize") && getBool("daemonize")) { + if (vals.isMember("logfile") && getString("logfile") != "") { + Daemonize(true); + } else { + Daemonize(false); + } + vals.removeMember("daemonize"); + } + struct sigaction new_action; + struct sigaction cur_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); + //check if a child signal handler isn't set already, if so, set it. + sigaction(SIGCHLD, 0, &cur_action); + if (cur_action.sa_handler == SIG_DFL || cur_action.sa_handler == SIG_IGN) { + sigaction(SIGCHLD, &new_action, NULL); + } + is_active = true; +} + +/// Basic signal handler. Sets is_active to false if it receives +/// a SIGINT, SIGHUP or SIGTERM signal, reaps children for the SIGCHLD +/// signal, and ignores all other signals. +void Util::Config::signal_handler(int signum) { + switch (signum) { + case SIGINT: //these three signals will set is_active to false. + case SIGHUP: + case SIGTERM: + is_active = false; + break; + case SIGCHLD: { //when a child dies, reap it. + int status; + pid_t ret = -1; + while (ret != 0) { + ret = waitpid(-1, &status, WNOHANG); + if (ret < 0 && errno != EINTR) { + break; + } + } + break; + } + default: //other signals are ignored + break; + } +} //signal_handler + +/// Adds the default connector options. Also updates the capabilities structure with the default options. +/// Besides the options addBasicConnectorOptions adds, this function also adds port and interface options. +void Util::Config::addConnectorOptions(int port, JSON::Value & capabilities) { + JSON::Value option; + option.null(); + option["long"] = "port"; + option["short"] = "p"; + option["arg"] = "integer"; + option["help"] = "TCP port to listen on"; + option["value"].append((long long)port); + addOption("listen_port", option); + capabilities["optional"]["port"]["name"] = "TCP port"; + capabilities["optional"]["port"]["help"] = "TCP port to listen on - default if unprovided is " + option["value"][0u].asString(); + capabilities["optional"]["port"]["type"] = "uint"; + capabilities["optional"]["port"]["option"] = "--port"; + capabilities["optional"]["port"]["default"] = option["value"][0u]; + + option.null(); + option["long"] = "interface"; + option["short"] = "i"; + option["arg"] = "string"; + option["help"] = "Interface address to listen on, or 0.0.0.0 for all available interfaces."; + option["value"].append("0.0.0.0"); + addOption("listen_interface", option); + capabilities["optional"]["interface"]["name"] = "Interface"; + capabilities["optional"]["interface"]["help"] = "Address of the interface to listen on - default if unprovided is all interfaces"; + capabilities["optional"]["interface"]["option"] = "--interface"; + capabilities["optional"]["interface"]["type"] = "str"; + + addBasicConnectorOptions(capabilities); +} //addConnectorOptions + +/// Adds the default connector options. Also updates the capabilities structure with the default options. +void Util::Config::addBasicConnectorOptions(JSON::Value & capabilities) { + JSON::Value option; + option.null(); + option["long"] = "username"; + option["short"] = "u"; + option["arg"] = "string"; + option["help"] = "Username to drop privileges to, or root to not drop provileges."; + option["value"].append("root"); + addOption("username", option); + capabilities["optional"]["username"]["name"] = "Username"; + capabilities["optional"]["username"]["help"] = "Username to drop privileges to - default if unprovided means do not drop privileges"; + capabilities["optional"]["username"]["option"] = "--username"; + capabilities["optional"]["username"]["type"] = "str"; + + + if (capabilities.isMember("socket")) { + option.null(); + option["arg"] = "string"; + option["help"] = "Socket name that can be connected to for this connector."; + option["value"].append(capabilities["socket"]); + addOption("socket", option); + } + + option.null(); + option["long"] = "daemon"; + option["short"] = "d"; + option["long_off"] = "nodaemon"; + option["short_off"] = "n"; + option["help"] = "Whether or not to daemonize the process after starting."; + option["value"].append(0ll); + addOption("daemonize", option); + + option.null(); + option["long"] = "json"; + option["short"] = "j"; + option["help"] = "Output connector info in JSON format, then exit."; + option["value"].append(0ll); + addOption("json", option); +} + + + +/// Gets directory the current executable is stored in. +std::string Util::getMyPath() { + char mypath[500]; +#ifdef __CYGWIN__ + GetModuleFileName(0, mypath, 500); +#else +#ifdef __APPLE__ + memset(mypath, 0, 500); + unsigned int refSize = 500; + int ret = _NSGetExecutablePath(mypath, &refSize); +#else + int ret = readlink("/proc/self/exe", mypath, 500); + if (ret != -1) { + mypath[ret] = 0; + } else { + mypath[0] = 0; + } +#endif +#endif + std::string tPath = mypath; + size_t slash = tPath.rfind('/'); + if (slash == std::string::npos) { + slash = tPath.rfind('\\'); + if (slash == std::string::npos) { + return ""; + } + } + tPath.resize(slash + 1); + return tPath; +} + +/// Gets all executables in getMyPath that start with "Mist". +void Util::getMyExec(std::deque & execs) { + std::string path = Util::getMyPath(); +#ifdef __CYGWIN__ + path += "\\Mist*"; + WIN32_FIND_DATA FindFileData; + HANDLE hdl = FindFirstFile(path.c_str(), &FindFileData); + while (hdl != INVALID_HANDLE_VALUE) { + execs.push_back(FindFileData.cFileName); + if (!FindNextFile(hdl, &FindFileData)) { + FindClose(hdl); + hdl = INVALID_HANDLE_VALUE; + } + } +#else + DIR * d = opendir(path.c_str()); + if (!d) { + return; + } + struct dirent * dp; + do { + errno = 0; + if ((dp = readdir(d))) { + if (strncmp(dp->d_name, "Mist", 4) == 0) { + execs.push_back(dp->d_name); + } + } + } while (dp != NULL); + closedir(d); +#endif +} + +/// 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) { + DEBUG_MSG(DLVL_ERROR, "Error: could not setuid %s: could not get PID", username.c_str()); + return; + } else { + if (setuid(user_info->pw_uid) != 0) { + DEBUG_MSG(DLVL_ERROR, "Error: could not setuid %s: not allowed", username.c_str()); + } else { + DEBUG_MSG(DLVL_DEVEL, "Change user to %s", username.c_str()); + } + } + } +} + +/// 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(bool notClose) { + DEBUG_MSG(DLVL_DEVEL, "Going into background mode..."); + int noClose = 0; + if (notClose) { + noClose = 1; + } + if (daemon(1, noClose) < 0) { + DEBUG_MSG(DLVL_ERROR, "Failed to daemonize: %s", strerror(errno)); + } +} diff --git a/lib/config.h b/lib/config.h new file mode 100644 index 00000000..642194ae --- /dev/null +++ b/lib/config.h @@ -0,0 +1,59 @@ +/// \file config.h +/// Contains generic function headers for managing configuration. + +#pragma once + +#ifndef PACKAGE_VERSION +#define PACKAGE_VERSION "unknown" +#endif + +#include +#include "json.h" + +/// Contains utility code, not directly related to streaming media +namespace Util { + + /// Deals with parsing configuration from commandline options. + class Config { + private: + JSON::Value vals; ///< Holds all current config values + int long_count; + static void signal_handler(int signum); + public: + //variables + static std::string libver; ///< Version number of the library as a string. + static bool is_active; ///< Set to true by activate(), set to false by the signal handler. + static unsigned int printDebugLevel; + //functions + Config(); + Config(std::string cmd, std::string version); + void addOption(std::string optname, JSON::Value option); + void printHelp(std::ostream & output); + bool parseArgs(int & argc, char ** & argv); + JSON::Value & getOption(std::string optname, bool asArray = false); + std::string getString(std::string optname); + long long int getInteger(std::string optname); + bool getBool(std::string optname); + void activate(); + int threadServer(Socket::Server & server_socket, int (*callback)(Socket::Connection & S)); + int forkServer(Socket::Server & server_socket, int (*callback)(Socket::Connection & S)); + int serveThreadedSocket(int (*callback)(Socket::Connection & S)); + int serveForkedSocket(int (*callback)(Socket::Connection & S)); + int servePlainSocket(int (*callback)(Socket::Connection & S)); + void addBasicConnectorOptions(JSON::Value & capabilities); + void addConnectorOptions(int port, JSON::Value & capabilities); + }; + + /// Gets directory the current executable is stored in. + std::string getMyPath(); + + /// Gets all executables in getMyPath that start with "Mist". + void getMyExec(std::deque & execs); + + /// Will set the active user to the named username. + void setUser(std::string user); + + /// Will turn the current process into a daemon. + void Daemonize(bool notClose = false); + +} diff --git a/lib/converter.cpp b/lib/converter.cpp new file mode 100644 index 00000000..752b0664 --- /dev/null +++ b/lib/converter.cpp @@ -0,0 +1,328 @@ +#include +#include +#include +#include +#include +#include + +#include "timing.h" +#include "converter.h" +#include "procs.h" +#include "config.h" + +namespace Converter { + + ///\brief The base constructor + Converter::Converter() { + fillFFMpegEncoders(); + } + + ///\brief A function that fill the internal variables with values provided by examing ffmpeg output + /// + ///Checks for the following encoders: + /// - AAC + /// - H264 + /// - MP3 + void Converter::fillFFMpegEncoders() { + std::vector cmd; + cmd.reserve(3); + cmd.push_back((char *)"ffmpeg"); + cmd.push_back((char *)"-encoders"); + cmd.push_back(NULL); + int outFD = -1; + Util::Procs::StartPiped("FFMpegInfo", &cmd[0], 0, &outFD, 0); + while (Util::Procs::isActive("FFMpegInfo")) { + Util::sleep(100); + } + FILE * outFile = fdopen(outFD, "r"); + char * fileBuf = 0; + size_t fileBufLen = 0; + while (!(feof(outFile) || ferror(outFile)) && (getline(&fileBuf, &fileBufLen, outFile) != -1)) { + if (strstr(fileBuf, "aac") || strstr(fileBuf, "AAC")) { + strtok(fileBuf, " \t"); + allCodecs["ffmpeg"][strtok(NULL, " \t")] = "aac"; + } + if (strstr(fileBuf, "h264") || strstr(fileBuf, "H264")) { + strtok(fileBuf, " \t"); + allCodecs["ffmpeg"][strtok(NULL, " \t")] = "h264"; + } + if (strstr(fileBuf, "mp3") || strstr(fileBuf, "MP3")) { + strtok(fileBuf, " \t"); + allCodecs["ffmpeg"][strtok(NULL, " \t")] = "mp3"; + } + } + fclose(outFile); + } + + ///\brief A function to obtain all available codecs that have been obtained from the encoders. + ///\return A reference to the allCodecs member. + converterInfo & Converter::getCodecs() { + return allCodecs; + } + + ///\brief A function to obtain the available encoders in JSON format. + ///\return A JSON::Value containing all encoder:codec pairs. + JSON::Value Converter::getEncoders() { + JSON::Value result; + for (converterInfo::iterator convIt = allCodecs.begin(); convIt != allCodecs.end(); convIt++) { + for (codecInfo::iterator codIt = convIt->second.begin(); codIt != convIt->second.end(); codIt++) { + if (codIt->second == "h264") { + result[convIt->first]["video"][codIt->first] = codIt->second; + } else { + result[convIt->first]["audio"][codIt->first] = codIt->second; + + } + } + } + return result; + } + + ///\brief Looks in a given path for all files that could be converted + ///\param myPath The location to look at, this should be a folder. + ///\return A JSON::Value containing all media files in the location, with their corresponding metadata values. + JSON::Value Converter::queryPath(std::string myPath) { + char const * cmd[3] = {0, 0, 0}; + std::string mistPath = Util::getMyPath() + "MistInfo"; + cmd[0] = mistPath.c_str(); + JSON::Value result; + DIR * Dirp = opendir(myPath.c_str()); + struct stat StatBuf; + if (Dirp) { + dirent * entry; + while ((entry = readdir(Dirp))) { + if (stat(std::string(myPath + "/" + entry->d_name).c_str(), &StatBuf) == -1) { + continue; + } + if ((StatBuf.st_mode & S_IFREG) == 0) { + continue; + } + std::string fileName = entry->d_name; + std::string mijnPad = std::string(myPath + (myPath[myPath.size() - 1] == '/' ? "" : "/") + entry->d_name); + cmd[1] = mijnPad.c_str(); + result[fileName] = JSON::fromString(Util::Procs::getOutputOf((char * const *)cmd)); + } + } + return result; + } + + ///\brief Start a conversion with the given parameters + ///\param name The name to use for logging the conversion. + ///\param parameters The parameters, accepted are the following: + /// - input The input url + /// - output The output url + /// - encoder The encoder to use + /// - video An object containing video parameters, if not existant no video will be output. Values are: + /// - width The width of the resulting video + /// - height The height of the resulting video + /// - codec The codec to encode video in, or copy to use the current codec + /// - fpks The framerate in fps * 1000 + /// - audio An object containing audio parameters, if not existant no audio will be output. Values are: + /// - codec The codec to encode audio in, or copy to use the current codec + /// - samplerate The target samplerate for the audio, in hz + void Converter::startConversion(std::string name, JSON::Value parameters) { + if (!parameters.isMember("input")) { + statusHistory[name] = "No input file supplied"; + return; + } + if (!parameters.isMember("output")) { + statusHistory[name] = "No output file supplied"; + return; + } + struct stat statBuf; + std::string outPath = parameters["output"].asString(); + outPath = outPath.substr(0, outPath.rfind('/')); + int statRes = stat(outPath.c_str(), & statBuf); + if (statRes == -1 || !S_ISDIR(statBuf.st_mode)) { + statusHistory[name] = "Output path is either non-existent, or not a path."; + return; + } + if (!parameters.isMember("encoder")) { + statusHistory[name] = "No encoder specified"; + return; + } + if (allCodecs.find(parameters["encoder"]) == allCodecs.end()) { + statusHistory[name] = "Can not find encoder " + parameters["encoder"].asString(); + return; + } + if (parameters.isMember("video")) { + if (parameters["video"].isMember("width") && !parameters["video"].isMember("height")) { + statusHistory[name] = "No height parameter given"; + return; + } + if (parameters["video"].isMember("height") && !parameters["video"].isMember("width")) { + statusHistory[name] = "No width parameter given"; + return; + } + } + std::stringstream encoderCommand; + if (parameters["encoder"] == "ffmpeg") { + encoderCommand << "ffmpeg -i "; + encoderCommand << parameters["input"].asString() << " "; + if (parameters.isMember("video")) { + if (!parameters["video"].isMember("codec") || parameters["video"]["codec"] == "copy") { + encoderCommand << "-vcodec copy "; + } else { + codecInfo::iterator vidCodec = allCodecs["ffmpeg"].find(parameters["video"]["codec"]); + if (vidCodec == allCodecs["ffmpeg"].end()) { + statusHistory[name] = "Can not find video codec " + parameters["video"]["codec"].asString(); + return; + } + encoderCommand << "-vcodec " << vidCodec->first << " "; + if (parameters["video"]["codec"].asString() == "h264") { + //Enforce baseline + encoderCommand << "-preset slow -profile:v baseline -level 30 "; + } + if (parameters["video"].isMember("fpks")) { + encoderCommand << "-r " << parameters["video"]["fpks"].asInt() / 1000 << " "; + } + if (parameters["video"].isMember("width")) { + encoderCommand << "-s " << parameters["video"]["width"].asInt() << "x" << parameters["video"]["height"].asInt() << " "; + } + ///\todo Keyframe interval (different in older and newer versions of ffmpeg?) + } + } else { + encoderCommand << "-vn "; + } + if (parameters.isMember("audio")) { + if (!parameters["audio"].isMember("codec")) { + encoderCommand << "-acodec copy "; + } else { + codecInfo::iterator audCodec = allCodecs["ffmpeg"].find(parameters["audio"]["codec"]); + if (audCodec == allCodecs["ffmpeg"].end()) { + statusHistory[name] = "Can not find audio codec " + parameters["audio"]["codec"].asString(); + return; + } + if (audCodec->second == "aac") { + encoderCommand << "-strict -2 "; + } + encoderCommand << "-acodec " << audCodec->first << " "; + if (parameters["audio"].isMember("samplerate")) { + encoderCommand << "-ar " << parameters["audio"]["samplerate"].asInt() << " "; + } + } + } else { + encoderCommand << "-an "; + } + encoderCommand << "-f flv -"; + } + int statusFD = -1; + Util::Procs::StartPiped2(name, encoderCommand.str(), Util::getMyPath() + "MistFLV2DTSC -o " + parameters["output"].asString(), 0, 0, &statusFD, 0); + parameters["statusFD"] = statusFD; + allConversions[name] = parameters; + allConversions[name]["status"]["duration"] = "?"; + allConversions[name]["status"]["progress"] = 0; + allConversions[name]["status"]["frame"] = 0; + allConversions[name]["status"]["time"] = 0; + } + + ///\brief Updates the internal status of the converter class. + /// + ///Will check for each running conversion whether it is still running, and update its status accordingly + void Converter::updateStatus() { + if (allConversions.size()) { + std::map::iterator cIt; + bool hasChanged = true; + while (hasChanged && allConversions.size()) { + hasChanged = false; + for (cIt = allConversions.begin(); cIt != allConversions.end(); cIt++) { + if (Util::Procs::isActive(cIt->first)) { + int statusFD = dup(cIt->second["statusFD"].asInt()); + fsync(statusFD); + FILE * statusFile = fdopen(statusFD, "r"); + char * fileBuf = 0; + size_t fileBufLen = 0; + fseek(statusFile, 0, SEEK_END); + std::string line; + int totalTime = 0; + do { + getdelim(&fileBuf, &fileBufLen, '\r', statusFile); + line = fileBuf; + if (line.find("Duration") != std::string::npos) { + int curOffset = line.find("Duration: ") + 10; + totalTime += atoi(line.substr(curOffset, 2).c_str()) * 60 * 60 * 1000; + totalTime += atoi(line.substr(curOffset + 3, 2).c_str()) * 60 * 1000; + totalTime += atoi(line.substr(curOffset + 6, 2).c_str()) * 1000; + totalTime += atoi(line.substr(curOffset + 9, 2).c_str()) * 10; + cIt->second["duration"] = totalTime; + } + } while (!feof(statusFile) && line.find("frame") != 0); //"frame" is the fist word on an actual status line of ffmpeg + if (!feof(statusFile)) { + cIt->second["status"] = parseFFMpegStatus(line); + cIt->second["status"]["duration"] = cIt->second["duration"]; + cIt->second["status"]["progress"] = (cIt->second["status"]["time"].asInt() * 100) / cIt->second["duration"].asInt(); + } else { + line.erase(line.end() - 1); + line = line.substr(line.rfind("\n") + 1); + cIt->second["status"] = line; + } + free(fileBuf); + fclose(statusFile); + } else { + if (statusHistory.find(cIt->first) == statusHistory.end()) { + statusHistory[cIt->first] = "Conversion successful, running DTSCFix"; + Util::Procs::Start(cIt->first + "DTSCFix", Util::getMyPath() + "MistDTSCFix " + cIt->second["output"].asString()); + } + allConversions.erase(cIt); + hasChanged = true; + break; + } + } + } + } + if (statusHistory.size()) { + std::map::iterator sIt; + for (sIt = statusHistory.begin(); sIt != statusHistory.end(); sIt++) { + if (statusHistory[sIt->first].find("DTSCFix") != std::string::npos) { + if (Util::Procs::isActive(sIt->first + "DTSCFIX")) { + continue; + } + statusHistory[sIt->first] = "Conversion successful"; + } + } + } + } + + ///\brief Parses a single ffmpeg status line into a JSON format + ///\param statusLine The current status of ffmpeg + ///\return A JSON::Value with the following values set: + /// - frame The current last encoded frame + /// - time The current last encoded timestamp + JSON::Value Converter::parseFFMpegStatus(std::string statusLine) { + JSON::Value result; + int curOffset = statusLine.find("frame=") + 6; + result["frame"] = atoi(statusLine.substr(curOffset, statusLine.find("fps=") - curOffset).c_str()); + curOffset = statusLine.find("time=") + 5; + int myTime = 0; + myTime += atoi(statusLine.substr(curOffset, 2).c_str()) * 60 * 60 * 1000; + myTime += atoi(statusLine.substr(curOffset + 3, 2).c_str()) * 60 * 1000; + myTime += atoi(statusLine.substr(curOffset + 6, 2).c_str()) * 1000; + myTime += atoi(statusLine.substr(curOffset + 9, 2).c_str()) * 10; + result["time"] = myTime; + return result; + } + + ///\brief Obtain the current internal status of the conversion class + ///\return A JSON::Value with the status of each conversion + JSON::Value Converter::getStatus() { + updateStatus(); + JSON::Value result; + if (allConversions.size()) { + for (std::map::iterator cIt = allConversions.begin(); cIt != allConversions.end(); cIt++) { + result[cIt->first] = cIt->second["status"]; + result[cIt->first]["details"] = cIt->second; + } + } + if (statusHistory.size()) { + std::map::iterator sIt; + for (sIt = statusHistory.begin(); sIt != statusHistory.end(); sIt++) { + result[sIt->first] = sIt->second; + } + } + return result; + } + + ///\brief Clears the status history of all conversions + void Converter::clearStatus() { + statusHistory.clear(); + } +} diff --git a/lib/converter.h b/lib/converter.h new file mode 100644 index 00000000..a082e994 --- /dev/null +++ b/lib/converter.h @@ -0,0 +1,35 @@ +#include +#include + +#include "json.h" + +///\brief A typedef to simplify accessing all codecs +typedef std::map codecInfo; +///\brief A typedef to simplify accessing all encoders +typedef std::map converterInfo; + +///\brief A namespace containing all functions for handling the conversion API +namespace Converter { + + ///\brief A class containing the basic conversion API functionality + class Converter { + public: + Converter(); + converterInfo & getCodecs(); + JSON::Value getEncoders(); + JSON::Value queryPath(std::string myPath); + void startConversion(std::string name, JSON::Value parameters); + void updateStatus(); + JSON::Value getStatus(); + void clearStatus(); + JSON::Value parseFFMpegStatus(std::string statusLine); + private: + void fillFFMpegEncoders(); + ///\brief Holds a list of all current known codecs + converterInfo allCodecs; + ///\brief Holds a list of all the current conversions + std::map allConversions; + ///\brief Stores the status of all conversions, and the history + std::map statusHistory; + }; +} diff --git a/lib/defines.h b/lib/defines.h new file mode 100644 index 00000000..cbf9bff7 --- /dev/null +++ b/lib/defines.h @@ -0,0 +1,69 @@ +// Defines to print debug messages. +#ifndef MIST_DEBUG +#define MIST_DEBUG 1 +#define DLVL_NONE 0 // All debugging disabled. +#define DLVL_FAIL 1 // Only messages about failed operations. +#define DLVL_ERROR 2 // Only messages about errors and failed operations. +#define DLVL_WARN 3 // Warnings, errors, and fail messages. +#define DLVL_DEVEL 4 // All of the above, plus status messages handy during development. +#define DLVL_INFO 4 // All of the above, plus status messages handy during development. +#define DLVL_MEDIUM 5 // Slightly more than just development-level messages. +#define DLVL_HIGH 6 // Verbose debugging messages. +#define DLVL_VERYHIGH 7 // Very verbose debugging messages. +#define DLVL_EXTREME 8 // Everything is reported in extreme detail. +#define DLVL_INSANE 9 // Everything is reported in insane detail. +#define DLVL_DONTEVEN 10 // All messages enabled, even pointless ones. +#if DEBUG > -1 + +#include +#include +#include "config.h" +static const char * DBG_LVL_LIST[] = {"NONE", "FAIL", "ERROR", "WARN", "INFO", "MEDIUM", "HIGH", "VERYHIGH", "EXTREME", "INSANE", "DONTEVEN"}; + +#if !defined(__APPLE__) && !defined(__MACH__) && defined(__GNUC__) +#include + +#if DEBUG >= DLVL_DEVEL +#define DEBUG_MSG(lvl, msg, ...) if (Util::Config::printDebugLevel >= lvl){fprintf(stderr, "%s|%s|%d|%s:%d|" msg "\n", DBG_LVL_LIST[lvl], program_invocation_short_name, getpid(), __FILE__, __LINE__, ##__VA_ARGS__);} +#else +#define DEBUG_MSG(lvl, msg, ...) if (Util::Config::printDebugLevel >= lvl){fprintf(stderr, "%s|%s|%d||" msg "\n", DBG_LVL_LIST[lvl], program_invocation_short_name, getpid(), ##__VA_ARGS__);} +#endif +#else +#if DEBUG >= DLVL_DEVEL +#define DEBUG_MSG(lvl, msg, ...) if (Util::Config::printDebugLevel >= lvl){fprintf(stderr, "%s||%d|%s:%d|" msg "\n", DBG_LVL_LIST[lvl], getpid(), __FILE__, __LINE__, ##__VA_ARGS__);} +#else +#define DEBUG_MSG(lvl, msg, ...) if (Util::Config::printDebugLevel >= lvl){fprintf(stderr, "%s||%d||" msg "\n", DBG_LVL_LIST[lvl], getpid(), ##__VA_ARGS__);} +#endif +#endif + +#else + +#define DEBUG_MSG(lvl, msg, ...) // Debugging disabled. + +#endif + +#define FAIL_MSG(msg, ...) DEBUG_MSG(DLVL_FAIL, msg, ##__VA_ARGS__) +#define ERROR_MSG(msg, ...) DEBUG_MSG(DLVL_ERROR, msg, ##__VA_ARGS__) +#define WARN_MSG(msg, ...) DEBUG_MSG(DLVL_WARN, msg, ##__VA_ARGS__) +#define DEVEL_MSG(msg, ...) DEBUG_MSG(DLVL_DEVEL, msg, ##__VA_ARGS__) +#define INFO_MSG(msg, ...) DEBUG_MSG(DLVL_DEVEL, msg, ##__VA_ARGS__) +#define MEDIUM_MSG(msg, ...) DEBUG_MSG(DLVL_MEDIUM, msg, ##__VA_ARGS__) +#define HIGH_MSG(msg, ...) DEBUG_MSG(DLVL_HIGH, msg, ##__VA_ARGS__) +#define VERYHIGH_MSG(msg, ...) DEBUG_MSG(DLVL_VERYHIGH, msg, ##__VA_ARGS__) +#define EXTREME_MSG(msg, ...) DEBUG_MSG(DLVL_EXTREME, msg, ##__VA_ARGS__) +#define INSANE_MSG(msg, ...) DEBUG_MSG(DLVL_INSANE, msg, ##__VA_ARGS__) +#define DONTEVEN_MSG(msg, ...) DEBUG_MSG(DLVL_DONTEVEN, msg, ##__VA_ARGS__) + +#endif + +/// The size used for stream header pages under Windows, where they cannot be size-detected. +#define DEFAULT_META_PAGE_SIZE 16 * 1024 * 1024 + +/// The size used for stream data pages under Windows, where they cannot be size-detected. +#define DEFAULT_DATA_PAGE_SIZE 25 * 1024 * 1024 + +/// The size used for server configuration pages. +#define DEFAULT_CONF_PAGE_SIZE 4 * 1024 * 1024 + +/// The position from where on stream data pages are switched over to the next page. +#define FLIP_DATA_PAGE_SIZE 8 * 1024 * 1024 diff --git a/lib/dtsc.cpp b/lib/dtsc.cpp new file mode 100644 index 00000000..e0bbcd4d --- /dev/null +++ b/lib/dtsc.cpp @@ -0,0 +1,1093 @@ +/// \file dtsc.cpp +/// Holds all code for DDVTECH Stream Container parsing/generation. + +#include "dtsc.h" +#include "defines.h" +#include +#include //for memcmp +#include //for htonl/ntohl +char DTSC::Magic_Header[] = "DTSC"; +char DTSC::Magic_Packet[] = "DTPD"; +char DTSC::Magic_Packet2[] = "DTP2"; + +/// Initializes a DTSC::Stream with only one packet buffer. +DTSC::Stream::Stream() { + datapointertype = DTSC::INVALID; + buffercount = 1; + buffertime = 0; +} + +/// 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, unsigned int bufferTime) { + datapointertype = DTSC::INVALID; + if (rbuffers < 1) { + rbuffers = 1; + } + buffercount = rbuffers; + buffertime = bufferTime; +} + +/// This function does nothing, it's supposed to be overridden. +/// It will be called right before a buffer position is deleted. +void DTSC::Stream::deletionCallback(livePos deleting) {} + +/// 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() { + if (!buffers.size()) { + return 0; + } + return buffers.rbegin()->second["time"].asInt(); +} + +/// 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; + } + unsigned int i = 0; + JSON::Value meta; + JSON::fromDTMI((unsigned char *)buffer.c_str() + 8, len, i, meta); + metadata = Meta(meta); + buffer.erase(0, len + 8); + if (buffer.length() <= 8) { + return false; + } + } + int version = 0; + if (memcmp(buffer.c_str(), DTSC::Magic_Packet, 4) == 0) { + version = 1; + } + if (memcmp(buffer.c_str(), DTSC::Magic_Packet2, 4) == 0) { + version = 2; + } + if (version) { + len = ntohl(((uint32_t *)buffer.c_str())[1]); + if (buffer.length() < len + 8) { + return false; + } + JSON::Value newPack; + unsigned int i = 0; + if (version == 1) { + JSON::fromDTMI((unsigned char *)buffer.c_str() + 8, len, i, newPack); + } + if (version == 2) { + JSON::fromDTMI2((unsigned char *)buffer.c_str() + 8, len, i, newPack); + } + buffer.erase(0, len + 8); + addPacket(newPack); + syncing = false; + return true; + } +#if DEBUG >= DLVL_WARN + if (!syncing) { + DEBUG_MSG(DLVL_WARN, "Invalid DTMI data detected - re-syncing"); + syncing = true; + } +#endif + size_t magic_search = buffer.find(Magic_Packet); + size_t magic_search2 = buffer.find(Magic_Packet2); + if (magic_search2 == std::string::npos) { + if (magic_search == std::string::npos) { + buffer.clear(); + } else { + buffer.erase(0, magic_search); + } + } else { + buffer.erase(0, magic_search2); + } + } + return false; +} + +/// Attempts to parse a packet from the given Socket::Buffer. +/// Returns true if successful, removing the parsed part from the buffer. +/// Returns false if invalid or not enough data is in the buffer. +/// \arg buffer The Socket::Buffer to attempt to parse. +bool DTSC::Stream::parsePacket(Socket::Buffer & buffer) { + uint32_t len; + static bool syncing = false; + if (buffer.available(8)) { + std::string header_bytes = buffer.copy(8); + if (memcmp(header_bytes.c_str(), DTSC::Magic_Header, 4) == 0) { + len = ntohl(((uint32_t *)header_bytes.c_str())[1]); + if (!buffer.available(len + 8)) { + return false; + } + unsigned int i = 0; + std::string wholepacket = buffer.remove(len + 8); + JSON::Value meta; + JSON::fromDTMI((unsigned char *)wholepacket.c_str() + 8, len, i, meta); + addMeta(meta); + //recursively calls itself until failure or data packet instead of header + return parsePacket(buffer); + } + int version = 0; + if (memcmp(header_bytes.c_str(), DTSC::Magic_Packet, 4) == 0) { + version = 1; + } + if (memcmp(header_bytes.c_str(), DTSC::Magic_Packet2, 4) == 0) { + version = 2; + } + if (version) { + len = ntohl(((uint32_t *)header_bytes.c_str())[1]); + if (!buffer.available(len + 8)) { + return false; + } + JSON::Value newPack; + unsigned int i = 0; + std::string wholepacket = buffer.remove(len + 8); + if (version == 1) { + JSON::fromDTMI((unsigned char *)wholepacket.c_str() + 8, len, i, newPack); + } + if (version == 2) { + JSON::fromDTMI2((unsigned char *)wholepacket.c_str() + 8, len, i, newPack); + } + addPacket(newPack); + syncing = false; + return true; + } +#if DEBUG >= DLVL_WARN + if (!syncing) { + DEBUG_MSG(DLVL_WARN, "Invalid DTMI data detected - syncing"); + syncing = true; + } +#endif + buffer.get().clear(); + } + return false; +} + +/// Adds a keyframe packet to all tracks, so the stream can be fully played. +void DTSC::Stream::endStream() { + if (!metadata.tracks.size()) { + return; + } + for (std::map::iterator it = metadata.tracks.begin(); it != metadata.tracks.end(); it++) { + JSON::Value newPack; + newPack["time"] = (long long)it->second.lastms; + newPack["trackid"] = it->first; + newPack["keyframe"] = 1ll; + newPack["data"] = ""; + addPacket(newPack); + } +} + +/// Blocks until either the stream has metadata available or the sourceSocket errors. +/// This function is intended to be run before any commands are sent and thus will not throw away anything important. +/// It will time out after 3 seconds, disconnecting the sourceSocket. +void DTSC::Stream::waitForMeta(Socket::Connection & sourceSocket, bool closeOnError){ + bool wasBlocking = sourceSocket.isBlocking(); + sourceSocket.setBlocking(false); + //cancel the attempt after 5000 milliseconds + long long int start = Util::getMS(); + while (!metadata && sourceSocket.connected() && Util::getMS() - start < 3000) { + //we have data? attempt to read header + if (sourceSocket.Received().size()) { + //return value is ignored because we're not interested in data packets, just metadata. + parsePacket(sourceSocket.Received()); + } + //still no header? check for more data + if (!metadata) { + if (sourceSocket.spool()) { + //more received? attempt to read + //return value is ignored because we're not interested in data packets, just metadata. + parsePacket(sourceSocket.Received()); + } else { + //nothing extra to receive? wait a bit and retry + Util::sleep(10); + } + } + } + sourceSocket.setBlocking(wasBlocking); + //if the timeout has passed, close the socket + if (Util::getMS() - start >= 3000 && closeOnError){ + sourceSocket.close(); + //and optionally print a debug message that this happened + DEBUG_MSG(DLVL_DEVEL, "Timing out while waiting for metadata"); + } +} + +/// Blocks until either the stream encounters a pause mark or the sourceSocket errors. +/// This function is intended to be run after the 'q' command is sent, throwing away superfluous packets. +/// It will time out after 5 seconds, disconnecting the sourceSocket. +void DTSC::Stream::waitForPause(Socket::Connection & sourceSocket) { + bool wasBlocking = sourceSocket.isBlocking(); + sourceSocket.setBlocking(false); + //cancel the attempt after 5000 milliseconds + long long int start = Util::getMS(); + while (lastType() != DTSC::PAUSEMARK && sourceSocket.connected() && Util::getMS() - start < 5000) { + //we have data? parse it + if (sourceSocket.Received().size()) { + //return value is ignored because we're not interested. + parsePacket(sourceSocket.Received()); + } + //still no pause mark? check for more data + if (lastType() != DTSC::PAUSEMARK) { + if (sourceSocket.spool()) { + //more received? attempt to read + //return value is ignored because we're not interested in data packets, just metadata. + parsePacket(sourceSocket.Received()); + } else { + //nothing extra to receive? wait a bit and retry + Util::sleep(10); + } + } + } + sourceSocket.setBlocking(wasBlocking); + //if the timeout has passed, close the socket + if (Util::getMS() - start >= 5000) { + sourceSocket.close(); + //and optionally print a debug message that this happened + DEBUG_MSG(DLVL_DEVEL, "Timing out while waiting for pause break"); + } +} + +/// Resets the stream by clearing the buffers and keyframes, making sure to call the deletionCallback first. +void DTSC::Stream::resetStream() { + for (std::map::iterator it = buffers.begin(); it != buffers.end(); it++) { + deletionCallback(it->first); + } + buffers.clear(); + keyframes.clear(); +} + +/// Adds a set of metadata to the steam. +/// This is implemented by simply replacing the current metadata. +/// This effectively resets the stream. +void DTSC::Stream::addMeta(JSON::Value & newMeta) { + metadata = Meta(newMeta); +} + +/// Adds a single DTSC packet to the stream, updating the internal metadata if needed. +void DTSC::Stream::addPacket(JSON::Value & newPack) { + livePos newPos; + newPos.trackID = newPack["trackid"].asInt(); + newPos.seekTime = newPack["time"].asInt(); + if (!metadata.tracks.count(newPos.trackID) && (!newPack.isMember("mark") || newPack["mark"].asStringRef() != "pause")) { + return; + } + if (buffercount > 1 && metadata.tracks[newPos.trackID].keys.size() > 1 && newPos.seekTime < (long long unsigned int)metadata.tracks[newPos.trackID].keys.rbegin()->getTime()) { + resetStream(); + } + while (buffers.count(newPos) > 0) { + newPos.seekTime++; + } + while (buffercount == 1 && buffers.size() > 0) { + cutOneBuffer(); + } + buffers[newPos] = newPack; + datapointertype = INVALID; + std::string tmp = ""; + if (newPack.isMember("trackid") && newPack["trackid"].asInt() > 0) { + tmp = metadata.tracks[newPack["trackid"].asInt()].type; + } + if (newPack.isMember("datatype")) { + tmp = newPack["datatype"].asStringRef(); + } + if (tmp == "video") { + datapointertype = VIDEO; + } + if (tmp == "audio") { + datapointertype = AUDIO; + } + if (tmp == "meta") { + datapointertype = META; + } + if (tmp == "pause_marker" || (newPack.isMember("mark") && newPack["mark"].asStringRef() == "pause")) { + datapointertype = PAUSEMARK; + } + if (buffercount > 1) { + metadata.update(newPack); + if (newPack.isMember("keyframe") || (long long unsigned int)metadata.tracks[newPos.trackID].keys.rbegin()->getTime() == newPos.seekTime) { + keyframes[newPos.trackID].insert(newPos); + } + metadata.live = true; + //throw away buffers if buffer time is met + int trid = buffers.begin()->first.trackID; + int firstTime = buffers.begin()->first.seekTime; + int lastTime = buffers.rbegin()->first.seekTime - buffertime; + while ((!metadata.tracks[trid].keys.size() && firstTime < lastTime) || (metadata.tracks[trid].keys.size() && metadata.tracks[trid].keys.rbegin()->getTime() < lastTime) || (metadata.tracks[trid].keys.size() > 2 && metadata.tracks[trid].keys.rbegin()->getTime() - firstTime > buffertime)) { + cutOneBuffer(); + trid = buffers.begin()->first.trackID; + firstTime = buffers.begin()->first.seekTime; + } + metadata.bufferWindow = buffertime; + } + +} + +/// Deletes a the first part of the buffer, updating the keyframes list and metadata as required. +/// Will print a warning if a track has less than 2 keyframes left because of this. +void DTSC::Stream::cutOneBuffer() { + if (!buffers.size()) { + return; + } + int trid = buffers.begin()->first.trackID; + long long unsigned int delTime = buffers.begin()->first.seekTime; + if (buffercount > 1) { + while (keyframes[trid].size() > 0 && keyframes[trid].begin()->seekTime <= delTime) { + keyframes[trid].erase(keyframes[trid].begin()); + } + while (metadata.tracks[trid].keys.size() && (long long unsigned int)metadata.tracks[trid].keys[0].getTime() <= delTime) { + for (int i = 0; i < metadata.tracks[trid].keys[0].getParts(); i++) { + metadata.tracks[trid].parts.pop_front(); + } + metadata.tracks[trid].keys.pop_front(); + } + if (metadata.tracks[trid].keys.size()) { + metadata.tracks[trid].firstms = metadata.tracks[trid].keys[0].getTime(); + //delete fragments of which the beginning can no longer be reached + while (metadata.tracks[trid].fragments.size() && metadata.tracks[trid].fragments[0].getNumber() < metadata.tracks[trid].keys[0].getNumber()) { + metadata.tracks[trid].fragments.pop_front(); + //increase the missed fragments counter + metadata.tracks[trid].missedFrags++; + } + } else { + metadata.tracks[trid].fragments.clear(); + } + } + deletionCallback(buffers.begin()->first); + buffers.erase(buffers.begin()); +} + +/// 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 buffers.rbegin()->second["data"].strVal; +} + +/// Returns the packet in this buffer number. +/// \arg num Buffer number. +JSON::Value & DTSC::Stream::getPacket(livePos num) { + static JSON::Value empty; + if (buffers.find(num) == buffers.end()) { + return empty; + } + return buffers[num]; +} + +JSON::Value & DTSC::Stream::getPacket() { + return buffers.begin()->second; +} + +/// 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() { + for (std::map::iterator it = metadata.tracks.begin(); it != metadata.tracks.end(); it++) { + if (it->second.type == "video") { + return true; + } + } + return false; +} + +/// Returns true if the current stream contains at least one audio track. +bool DTSC::Stream::hasAudio() { + for (std::map::iterator it = metadata.tracks.begin(); it != metadata.tracks.end(); it++) { + if (it->second.type == "audio") { + return true; + } + } + return false; +} + +void DTSC::Stream::setBufferTime(unsigned int ms) { + buffertime = ms; +} + +std::string & DTSC::Stream::outPacket() { + static std::string emptystring; + if (!buffers.size() || !buffers.rbegin()->second.isObject()) { + return emptystring; + } + return buffers.rbegin()->second.toNetPacked(); +} + +/// Returns a packed DTSC packet, ready to sent over the network. +std::string & DTSC::Stream::outPacket(livePos num) { + static std::string emptystring; + if (buffers.find(num) == buffers.end() || !buffers[num].isObject()) return emptystring; + return buffers[num].toNetPacked(); +} + +/// Returns a packed DTSC header, ready to sent over the network. +std::string & DTSC::Stream::outHeader() { + return metadata.toJSON().toNetPacked(); +} + +/// Constructs a new Ring, at the given buffer position. +/// \arg v Position for buffer. +DTSC::Ring::Ring(livePos v) { + b = v; + waiting = false; + starved = false; + updated = false; + playCount = 0; +} + +/// 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() { + livePos tmp = buffers.begin()->first; + std::map >::iterator it; + for (it = keyframes.begin(); it != keyframes.end(); it++) { + if ((*it->second.begin()).seekTime > tmp.seekTime) { + tmp = *it->second.begin(); + } + } + return new DTSC::Ring(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 (ptr) { + delete ptr; + } +} + +/// Returns 0 if seeking is possible, -1 if the wanted frame is too old, 1 if the wanted frame is too new. +/// This function looks in the header - not in the buffered data itself. +int DTSC::Stream::canSeekms(unsigned int ms) { + bool too_late = false; + //no tracks? Frame too new by definition. + if (!metadata.tracks.size()) { + return 1; + } + //loop trough all the tracks + for (std::map::iterator it = metadata.tracks.begin(); it != metadata.tracks.end(); it++) { + if (it->second.keys.size()) { + if (it->second.keys[0].getTime() <= ms && it->second.keys[it->second.keys.size() - 1].getTime() >= ms) { + return 0; + } + if (it->second.keys[0].getTime() > ms) { + too_late = true; + } + } + } + //did we spot a track already past this point? return too late. + if (too_late) { + return -1; + } + //otherwise, assume not available yet + return 1; +} + +DTSC::livePos DTSC::Stream::msSeek(unsigned int ms, std::set & allowedTracks) { + std::set seekTracks = allowedTracks; + livePos result = buffers.begin()->first; + for (std::set::iterator it = allowedTracks.begin(); it != allowedTracks.end(); it++) { + if (metadata.tracks[*it].type == "video") { + int trackNo = *it; + seekTracks.clear(); + seekTracks.insert(trackNo); + break; + } + } + for (std::map::iterator bIt = buffers.begin(); bIt != buffers.end(); bIt++) { + if (seekTracks.find(bIt->first.trackID) != seekTracks.end()) { + // if (bIt->second.isMember("keyframe")){ + result = bIt->first; + if (bIt->first.seekTime >= ms) { + return result; + } + //} + } + } + return result; +} + +/// Returns whether the current position is the last currently available position within allowedTracks. +/// Simply returns the result of getNext(pos, allowedTracks) == pos +bool DTSC::Stream::isNewest(DTSC::livePos & pos, std::set & allowedTracks) { + return getNext(pos, allowedTracks) == pos; +} + +/// Returns the next available position within allowedTracks, or the current position if no next is availble. +DTSC::livePos DTSC::Stream::getNext(DTSC::livePos & pos, std::set & allowedTracks) { + std::map::iterator iter = buffers.upper_bound(pos); + while (iter != buffers.end()) { + if (allowedTracks.count(iter->first.trackID)) { + return iter->first; + } + iter++; + } + return pos; +} + +/// Properly cleans up the object for erasing. +/// Drops all Ring classes that have been given out. +DTSC::Stream::~Stream() { +} + +DTSC::File::File() { + F = 0; + buffer = malloc(4); + endPos = 0; +} + +DTSC::File::File(const File & rhs) { + buffer = malloc(4); + *this = rhs; +} + +DTSC::File & DTSC::File::operator =(const File & rhs) { + created = rhs.created; + if (rhs.F) { + F = fdopen(dup(fileno(rhs.F)), (created ? "w+b" : "r+b")); + } else { + F = 0; + } + endPos = rhs.endPos; + if (rhs.myPack) { + myPack = rhs.myPack; + } + metaStorage = rhs.metaStorage; + metadata = metaStorage; + currtime = rhs.currtime; + lastreadpos = rhs.lastreadpos; + headerSize = rhs.headerSize; + trackMapping = rhs.trackMapping; + memcpy(buffer, rhs.buffer, 4); + return *this; +} + +DTSC::File::operator bool() const { + return F; +} + +/// Open a filename for DTSC reading/writing. +/// If create is true and file does not exist, attempt to create. +DTSC::File::File(std::string filename, bool create) { + buffer = malloc(8); + if (create) { + F = fopen(filename.c_str(), "w+b"); + if (!F) { + DEBUG_MSG(DLVL_ERROR, "Could not create file %s: %s", filename.c_str(), strerror(errno)); + return; + } + //write an empty header + fseek(F, 0, SEEK_SET); + fwrite(DTSC::Magic_Header, 4, 1, F); + memset(buffer, 0, 4); + fwrite(buffer, 4, 1, F); //write 4 zero-bytes + headerSize = 0; + } else { + F = fopen(filename.c_str(), "r+b"); + } + created = create; + if (!F) { + DEBUG_MSG(DLVL_ERROR, "Could not open file %s", filename.c_str()); + return; + } + fseek(F, 0, SEEK_END); + endPos = ftell(F); + + bool sepHeader = false; + if (!create) { + fseek(F, 0, SEEK_SET); + if (fread(buffer, 4, 1, F) != 1) { + DEBUG_MSG(DLVL_ERROR, "Can't read file contents of %s", filename.c_str()); + fclose(F); + F = 0; + return; + } + if (memcmp(buffer, DTSC::Magic_Header, 4) != 0) { + if (memcmp(buffer, DTSC::Magic_Packet2, 4) != 0 && memcmp(buffer, DTSC::Magic_Packet, 4) != 0) { + DEBUG_MSG(DLVL_ERROR, "%s is not a valid DTSC file", filename.c_str()); + fclose(F); + F = 0; + return; + } else { + metadata.moreheader = -1; + } + } + } + //we now know the first 4 bytes are DTSC::Magic_Header and we have a valid file + fseek(F, 4, SEEK_SET); + if (fread(buffer, 4, 1, F) != 1) { + fseek(F, 4, SEEK_SET); + memset(buffer, 0, 4); + fwrite(buffer, 4, 1, F); //write 4 zero-bytes + } else { + headerSize = ntohl(((uint32_t *)buffer)[0]); + } + if (metadata.moreheader != -1) { + if (!sepHeader) { + readHeader(0); + fseek(F, 8 + headerSize, SEEK_SET); + } else { + fseek(F, 0, SEEK_SET); + } + } else { + fseek(F, 0, SEEK_SET); + File Fhead(filename + ".dtsh"); + if (Fhead) { + metaStorage = Fhead.metaStorage; + metadata = metaStorage; + } + } + currframe = 0; +} + + +/// Returns the header metadata for this file as JSON::Value. +DTSC::Meta & DTSC::File::getMeta() { + return metadata; +} + + +/// (Re)writes the given string to the header area if the size is the same as the existing header. +/// Forces a write if force is set to true. +bool DTSC::File::writeHeader(std::string & header, bool force) { + if (headerSize != header.size() && !force) { + DEBUG_MSG(DLVL_ERROR, "Could not overwrite header - not equal size"); + return false; + } + headerSize = header.size(); + int pSize = htonl(header.size()); + fseek(F, 4, SEEK_SET); + int tmpret = fwrite((void *)(&pSize), 4, 1, F); + if (tmpret != 1) { + return false; + } + fseek(F, 8, SEEK_SET); + int ret = fwrite(header.c_str(), headerSize, 1, F); + fseek(F, 8 + headerSize, SEEK_SET); + return (ret == 1); +} + +/// Adds the given string as a new header to the end of the file. +/// \returns The positon the header was written at, or 0 on failure. +long long int DTSC::File::addHeader(std::string & header) { + fseek(F, 0, SEEK_END); + long long int writePos = ftell(F); + int hSize = htonl(header.size()); + int ret = fwrite(DTSC::Magic_Header, 4, 1, F); //write header + if (ret != 1) { + return 0; + } + ret = fwrite((void *)(&hSize), 4, 1, F); //write size + if (ret != 1) { + return 0; + } + ret = fwrite(header.c_str(), header.size(), 1, F); //write contents + if (ret != 1) { + return 0; + } + fseek(F, 0, SEEK_END); + endPos = ftell(F); + return writePos; //return position written at +} + +/// Reads the header at the given file position. +/// If the packet could not be read for any reason, the reason is printed. +/// Reading the header means the file position is moved to after the header. +void DTSC::File::readHeader(int pos) { + fseek(F, pos, SEEK_SET); + if (fread(buffer, 4, 1, F) != 1) { + if (feof(F)) { + DEBUG_MSG(DLVL_DEVEL, "End of file reached while reading header @ %d", pos); + } else { + DEBUG_MSG(DLVL_ERROR, "Could not read header @ %d", pos); + } + metadata = Meta(); + return; + } + if (memcmp(buffer, DTSC::Magic_Header, 4) != 0) { + DEBUG_MSG(DLVL_ERROR, "Invalid header - %.4s != %.4s @ %i", (char *)buffer, DTSC::Magic_Header, pos); + metadata = Meta(); + return; + } + if (fread(buffer, 4, 1, F) != 1) { + DEBUG_MSG(DLVL_ERROR, "Could not read header size @ %i", pos); + metadata = Meta(); + return; + } + long packSize = ntohl(((unsigned long *)buffer)[0]) + 8; + std::string strBuffer; + strBuffer.resize(packSize); + if (packSize) { + fseek(F, pos, SEEK_SET); + if (fread((void *)strBuffer.c_str(), packSize, 1, F) != 1) { + DEBUG_MSG(DLVL_ERROR, "Could not read header packet @ %i", pos); + metadata = Meta(); + return; + } + metadata = Meta(DTSC::Packet(strBuffer.data(), strBuffer.size(),true)); + } + //if there is another header, read it and replace metadata with that one. + if (metadata.moreheader) { + if (metadata.moreheader < getBytePosEOF()) { + readHeader(metadata.moreheader); + return; + } + } + if (!metadata.live){ + metadata.vod = true; + } +} + +long int DTSC::File::getBytePosEOF() { + return endPos; +} + +long int DTSC::File::getBytePos() { + return ftell(F); +} + +bool DTSC::File::reachedEOF() { + return feof(F); +} + +/// Reads the packet available at the current file position. +/// If the packet could not be read for any reason, the reason is printed. +/// Reading the packet means the file position is increased to the next packet. +void DTSC::File::seekNext() { + if (!currentPositions.size()) { + DEBUG_MSG(DLVL_WARN, "No seek positions set - returning empty packet."); + myPack.null(); + return; + } + fseek(F, currentPositions.begin()->bytePos, SEEK_SET); + if (reachedEOF()) { + myPack.null(); + return; + } + clearerr(F); + if (!metadata.merged) { + seek_time(currentPositions.begin()->seekTime + 1, currentPositions.begin()->trackID); + fseek(F, currentPositions.begin()->bytePos, SEEK_SET); + } + currentPositions.erase(currentPositions.begin()); + lastreadpos = ftell(F); + if (fread(buffer, 4, 1, F) != 1) { + if (feof(F)) { + DEBUG_MSG(DLVL_DEVEL, "End of file reached while seeking @ %i", (int)lastreadpos); + } else { + DEBUG_MSG(DLVL_ERROR, "Could not seek to next @ %i", (int)lastreadpos); + } + myPack.null(); + return; + } + if (memcmp(buffer, DTSC::Magic_Header, 4) == 0) { + seek_time(myPack.getTime() + 1, myPack.getTrackId(), true); + return seekNext(); + } + long long unsigned int version = 0; + if (memcmp(buffer, DTSC::Magic_Packet, 4) == 0) { + version = 1; + } + if (memcmp(buffer, DTSC::Magic_Packet2, 4) == 0) { + version = 2; + } + if (version == 0) { + DEBUG_MSG(DLVL_ERROR, "Invalid packet header @ %#x - %.4s != %.4s @ %d", (unsigned int)lastreadpos, (char *)buffer, DTSC::Magic_Packet2, (int)lastreadpos); + myPack.null(); + return; + } + if (fread(buffer, 4, 1, F) != 1) { + DEBUG_MSG(DLVL_ERROR, "Could not read packet size @ %d", (int)lastreadpos); + myPack.null(); + return; + } + long packSize = ntohl(((unsigned long *)buffer)[0]); + char * packBuffer = (char *)malloc(packSize + 8); + if (version == 1) { + memcpy(packBuffer, "DTPD", 4); + } else { + memcpy(packBuffer, "DTP2", 4); + } + memcpy(packBuffer + 4, buffer, 4); + if (fread((void *)(packBuffer + 8), packSize, 1, F) != 1) { + DEBUG_MSG(DLVL_ERROR, "Could not read packet @ %d", (int)lastreadpos); + myPack.null(); + free(packBuffer); + return; + } + myPack.reInit(packBuffer, packSize + 8); + free(packBuffer); + if (metadata.merged) { + int tempLoc = getBytePos(); + char newHeader[20]; + bool insert = false; + seekPos tmpPos; + if (fread((void *)newHeader, 20, 1, F) == 1) { + if (memcmp(newHeader, DTSC::Magic_Packet2, 4) == 0) { + tmpPos.bytePos = tempLoc; + tmpPos.trackID = ntohl(((int *)newHeader)[2]); + tmpPos.seekTime = 0; + if (selectedTracks.find(tmpPos.trackID) != selectedTracks.end()) { + tmpPos.seekTime = ((long long unsigned int)ntohl(((int *)newHeader)[3])) << 32; + tmpPos.seekTime += ntohl(((int *)newHeader)[4]); + insert = true; + } else { + long tid = myPack.getTrackId(); + for (unsigned int i = 0; i != metadata.tracks[tid].keys.size(); i++) { + if ((unsigned long long)metadata.tracks[tid].keys[i].getTime() > myPack.getTime()) { + tmpPos.seekTime = metadata.tracks[tid].keys[i].getTime(); + tmpPos.bytePos = metadata.tracks[tid].keys[i].getBpos(); + tmpPos.trackID = tid; + insert = true; + break; + } + } + } + if (currentPositions.size()) { + for (std::set::iterator curPosIter = currentPositions.begin(); curPosIter != currentPositions.end(); curPosIter++) { + if ((*curPosIter).trackID == tmpPos.trackID && (*curPosIter).seekTime >= tmpPos.seekTime) { + insert = false; + break; + } + } + } + } + } + if (insert){ + if (tmpPos.seekTime > 0xffffffffffffff00ll){ + tmpPos.seekTime = 0; + } + currentPositions.insert(tmpPos); + } else { + seek_time(myPack.getTime() + 1, myPack.getTrackId(), true); + } + seek_bpos(tempLoc); + } +} + +void DTSC::File::parseNext(){ + lastreadpos = ftell(F); + if (fread(buffer, 4, 1, F) != 1) { + if (feof(F)) { + DEBUG_MSG(DLVL_DEVEL, "End of file reached @ %d", (int)lastreadpos); + } else { + DEBUG_MSG(DLVL_ERROR, "Could not read header @ %d", (int)lastreadpos); + } + myPack.null(); + return; + } + if (memcmp(buffer, DTSC::Magic_Header, 4) == 0) { + if (lastreadpos != 0) { + readHeader(lastreadpos); + std::string tmp = metaStorage.toNetPacked(); + myPack.reInit(tmp.data(), tmp.size()); + DEBUG_MSG(DLVL_DEVEL, "Read another header"); + } else { + if (fread(buffer, 4, 1, F) != 1) { + DEBUG_MSG(DLVL_ERROR, "Could not read header size @ %d", (int)lastreadpos); + myPack.null(); + return; + } + long packSize = ntohl(((unsigned long *)buffer)[0]); + std::string strBuffer = "DTSC"; + strBuffer.append((char *)buffer, 4); + strBuffer.resize(packSize + 8); + if (fread((void *)(strBuffer.c_str() + 8), packSize, 1, F) != 1) { + DEBUG_MSG(DLVL_ERROR, "Could not read header @ %d", (int)lastreadpos); + myPack.null(); + return; + } + myPack.reInit(strBuffer.data(), strBuffer.size()); + } + return; + } + long long unsigned int version = 0; + if (memcmp(buffer, DTSC::Magic_Packet, 4) == 0) { + version = 1; + } + if (memcmp(buffer, DTSC::Magic_Packet2, 4) == 0) { + version = 2; + } + if (version == 0) { + DEBUG_MSG(DLVL_ERROR, "Invalid packet header @ %#x - %.4s != %.4s @ %d", (unsigned int)lastreadpos, (char *)buffer, DTSC::Magic_Packet2, (int)lastreadpos); + myPack.null(); + return; + } + if (fread(buffer, 4, 1, F) != 1) { + DEBUG_MSG(DLVL_ERROR, "Could not read packet size @ %d", (int)lastreadpos); + myPack.null(); + return; + } + long packSize = ntohl(((unsigned long *)buffer)[0]); + char * packBuffer = (char *)malloc(packSize + 8); + if (version == 1) { + memcpy(packBuffer, "DTPD", 4); + } else { + memcpy(packBuffer, "DTP2", 4); + } + memcpy(packBuffer + 4, buffer, 4); + if (fread((void *)(packBuffer + 8), packSize, 1, F) != 1) { + DEBUG_MSG(DLVL_ERROR, "Could not read packet @ %d", (int)lastreadpos); + myPack.null(); + free(packBuffer); + return; + } + myPack.reInit(packBuffer, packSize + 8); + free(packBuffer); +} + +/// Returns the byte positon of the start of the last packet that was read. +long long int DTSC::File::getLastReadPos() { + return lastreadpos; +} + +/// Returns the internal buffer of the last read packet in raw binary format. +DTSC::Packet & DTSC::File::getPacket() { + return myPack; +} + +bool DTSC::File::seek_time(unsigned int ms, unsigned int trackNo, bool forceSeek) { + seekPos tmpPos; + tmpPos.trackID = trackNo; + if (!forceSeek && myPack && ms > myPack.getTime() && trackNo >= myPack.getTrackId()) { + tmpPos.seekTime = myPack.getTime(); + tmpPos.bytePos = getBytePos(); + } else { + tmpPos.seekTime = 0; + tmpPos.bytePos = 0; + } + if (reachedEOF()) { + clearerr(F); + seek_bpos(0); + tmpPos.bytePos = 0; + tmpPos.seekTime = 0; + } + DTSC::Track & trackRef = metadata.tracks[trackNo]; + for (unsigned int i = 0; i < trackRef.keys.size(); i++) { + long keyTime = trackRef.keys[i].getTime(); + if (keyTime > ms) { + break; + } + if ((long long unsigned int)keyTime > tmpPos.seekTime) { + tmpPos.seekTime = keyTime; + tmpPos.bytePos = trackRef.keys[i].getBpos(); + } + } + bool foundPacket = false; + while (!foundPacket) { + lastreadpos = ftell(F); + if (reachedEOF()) { + DEBUG_MSG(DLVL_WARN, "Reached EOF during seek to %u in track %d - aborting @ %lld", ms, trackNo, lastreadpos); + return false; + } + //Seek to first packet after ms. + seek_bpos(tmpPos.bytePos); + //read the header + char header[20]; + if (fread((void *)header, 20, 1, F) != 1){ + DEBUG_MSG(DLVL_WARN, "Could not read header from file. Much sadface."); + return false; + } + //check if packetID matches, if not, skip size + 8 bytes. + int packSize = ntohl(((int *)header)[1]); + unsigned int packID = ntohl(((int *)header)[2]); + if (memcmp(header, Magic_Packet2, 4) != 0 || packID != trackNo) { + if (memcmp(header, "DT", 2) != 0) { + DEBUG_MSG(DLVL_WARN, "Invalid header during seek to %u in track %d @ %lld - resetting bytePos from %lld to zero", ms, trackNo, lastreadpos, tmpPos.bytePos); + tmpPos.bytePos = 0; + continue; + } + tmpPos.bytePos += 8 + packSize; + continue; + } + //get timestamp of packet, if too large, break, if not, skip size bytes. + long long unsigned int myTime = ((long long unsigned int)ntohl(((int *)header)[3]) << 32); + myTime += ntohl(((int *)header)[4]); + tmpPos.seekTime = myTime; + if (myTime >= ms) { + foundPacket = true; + } else { + tmpPos.bytePos += 8 + packSize; + continue; + } + } + //DEBUG_MSG(DLVL_HIGH, "Seek to %u:%d resulted in %lli", trackNo, ms, tmpPos.seekTime); + if (tmpPos.seekTime > 0xffffffffffffff00ll){ + tmpPos.seekTime = 0; + } + currentPositions.insert(tmpPos); + return true; +} + +/// Attempts to seek to the given time in ms within the file. +/// Returns true if successful, false otherwise. +bool DTSC::File::seek_time(unsigned int ms) { + currentPositions.clear(); + if (selectedTracks.size()) { + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) { + seek_time(ms, (*it), true); + } + } + return true; +} + +bool DTSC::File::seek_bpos(int bpos) { + if (fseek(F, bpos, SEEK_SET) == 0) { + return true; + } + return false; +} + +void DTSC::File::rewritePacket(std::string & newPacket, int bytePos) { + fseek(F, bytePos, SEEK_SET); + fwrite(newPacket.c_str(), newPacket.size(), 1, F); + fseek(F, 0, SEEK_END); + if (ftell(F) > endPos) { + endPos = ftell(F); + } +} + +void DTSC::File::writePacket(std::string & newPacket) { + fseek(F, 0, SEEK_END); + fwrite(newPacket.c_str(), newPacket.size(), 1, F); //write contents + fseek(F, 0, SEEK_END); + endPos = ftell(F); +} + +void DTSC::File::writePacket(JSON::Value & newPacket) { + writePacket(newPacket.toNetPacked()); +} + +bool DTSC::File::atKeyframe() { + if (myPack.getFlag("keyframe")) { + return true; + } + long long int bTime = myPack.getTime(); + DTSC::Track & trackRef = metadata.tracks[myPack.getTrackId()]; + for (unsigned int i = 0; i < trackRef.keys.size(); i++) { + if (trackRef.keys[i].getTime() >= bTime) { + return (trackRef.keys[i].getTime() == bTime); + } + } + return false; +} + +void DTSC::File::selectTracks(std::set & tracks) { + selectedTracks = tracks; + currentPositions.clear(); + seek_time(0); +} + +/// Close the file if open +DTSC::File::~File() { + if (F) { + fclose(F); + F = 0; + } + free(buffer); +} diff --git a/lib/dtsc.h b/lib/dtsc.h new file mode 100644 index 00000000..1395386f --- /dev/null +++ b/lib/dtsc.h @@ -0,0 +1,421 @@ +/// \file dtsc.h +/// Holds all headers for DDVTECH Stream Container parsing/generation. + +#pragma once +#include +#include +#include //for uint64_t +#include +#include +#include +#include //for FILE +#include "json.h" +#include "socket.h" +#include "timing.h" + +#define DTSC_INT 0x01 +#define DTSC_STR 0x02 +#define DTSC_OBJ 0xE0 +#define DTSC_ARR 0x0A +#define DTSC_CON 0xFF + +namespace DTSC { + + ///\brief This enum holds all possible datatypes for DTSC packets. + enum datatype { + AUDIO, ///< Stream Audio data + VIDEO, ///< Stream Video data + META, ///< Stream Metadata + PAUSEMARK, ///< Pause marker + MODIFIEDHEADER, ///< Modified header data. + 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 + extern char Magic_Packet2[]; ///< The magic bytes for a DTSC packet version 2 + + ///\brief A simple structure used for ordering byte seek positions. + struct seekPos { + ///\brief Less-than comparison for seekPos structures. + ///\param rhs The seekPos to compare with. + ///\return Whether this object is smaller than rhs. + bool operator < (const seekPos & rhs) const { + if (seekTime < rhs.seekTime) { + return true; + } else { + if (seekTime == rhs.seekTime) { + if (trackID < rhs.trackID) { + return true; + } + } + } + return false; + } + long long unsigned int seekTime;///< Stores the timestamp of the DTSC packet referenced by this structure. + long long unsigned int bytePos;///< Stores the byteposition of the DTSC packet referenced by this structure. + unsigned int trackID;///< Stores the track the DTSC packet referenced by this structure is associated with. + }; + + enum packType { + DTSC_INVALID, + DTSC_HEAD, + DTSC_V1, + DTSC_V2 + }; + + /// This class allows scanning through raw binary format DTSC data. + /// It can be used as an iterator or as a direct accessor. + class Scan { + public: + Scan(); + Scan(char * pointer, size_t len); + operator bool() const; + std::string toPrettyString(unsigned int indent = 0); + bool hasMember(std::string indice); + bool hasMember(const char * indice, const unsigned int ind_len); + Scan getMember(std::string indice); + Scan getMember(const char * indice); + Scan getMember(const char * indice, const unsigned int ind_len); + Scan getIndice(unsigned int num); + std::string getIndiceName(unsigned int num); + unsigned int getSize(); + + char getType(); + bool asBool(); + long long asInt(); + std::string asString(); + void getString(char *& result, unsigned int & len); + JSON::Value asJSON(); + private: + char * p; + size_t len; + }; + + /// DTSC::Packets can currently be three types: + /// DTSC_HEAD packets are the "DTSC" header string, followed by 4 bytes len and packed content. + /// DTSC_V1 packets are "DTPD", followed by 4 bytes len and packed content. + /// DTSC_V2 packets are "DTP2", followed by 4 bytes len, 4 bytes trackID, 8 bytes time, and packed content. + /// The len is always without the first 8 bytes counted. + class Packet { + public: + Packet(); + Packet(const Packet & rhs); + Packet(const char * data_, unsigned int len, bool noCopy = false); + ~Packet(); + void null(); + void operator = (const Packet & rhs); + operator bool() const; + packType getVersion() const; + void reInit(const char * data_, unsigned int len, bool noCopy = false); + void genericFill(long long packTime, long long packOffset, long long packTrack, char * packData, long long packDataSize, long long packBytePos, bool isKeyframe); + void getString(const char * identifier, char *& result, unsigned int & len) const; + void getString(const char * identifier, std::string & result) const; + void getInt(const char * identifier, int & result) const; + int getInt(const char * identifier) const; + void getFlag(const char * identifier, bool & result) const; + bool getFlag(const char * identifier) const; + bool hasMember(const char * identifier) const; + long long unsigned int getTime() const; + long int getTrackId() const; + char * getData() const; + int getDataLen() const; + int getPayloadLen() const; + JSON::Value toJSON() const; + Scan getScan() const; + protected: + bool master; + packType version; + void resize(unsigned int size); + char * data; + unsigned int bufferLen; + unsigned int dataLen; + }; + + /// A simple structure used for ordering byte seek positions. + struct livePos { + livePos() { + seekTime = 0; + trackID = 0; + } + livePos(const livePos & rhs) { + seekTime = rhs.seekTime; + trackID = rhs.trackID; + } + void operator = (const livePos & rhs) { + seekTime = rhs.seekTime; + trackID = rhs.trackID; + } + bool operator == (const livePos & rhs) { + return seekTime == rhs.seekTime && trackID == rhs.trackID; + } + bool operator != (const livePos & rhs) { + return seekTime != rhs.seekTime || trackID != rhs.trackID; + } + bool operator < (const livePos & rhs) const { + if (seekTime < rhs.seekTime) { + return true; + } else { + if (seekTime > rhs.seekTime) { + return false; + } + } + return (trackID < rhs.trackID); + } + long long unsigned int seekTime; + unsigned int trackID; + }; + + /// A part from the DTSC::Stream ringbuffer. + /// Holds information about a buffer that will stay consistent + class Ring { + public: + Ring(livePos v); + livePos b; + //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. + volatile bool updated; ///< If true, this Ring should write a new header. + volatile int playCount; + }; + + + ///\brief Basic class for storage of data associated with single DTSC packets, a.k.a. parts. + class Part { + public: + long getSize(); + void setSize(long newSize); + short getDuration(); + void setDuration(short newDuration); + long getOffset(); + void setOffset(long newOffset); + char * getData(); + void toPrettyString(std::ostream & str, int indent = 0); + private: + ///\brief Data storage for this Part. + /// + /// - 3 bytes: MSB storage of the payload size of this packet in bytes. + /// - 2 bytes: MSB storage of the duration of this packet in milliseconds. + /// - 4 bytes: MSB storage of the presentation time offset of this packet in milliseconds. + char data[9]; + }; + + ///\brief Basic class for storage of data associated with keyframes. + /// + /// When deleting this object, make sure to remove all DTSC::Part associated with it, if any. If you fail doing this, it *will* cause data corruption. + class Key { + public: + unsigned long long getBpos(); + void setBpos(unsigned long long newBpos); + unsigned long getLength(); + void setLength(unsigned long newLength); + unsigned long getNumber(); + void setNumber(unsigned long newNumber); + unsigned short getParts(); + void setParts(unsigned short newParts); + unsigned long long getTime(); + void setTime(unsigned long long newTime); + char * getData(); + void toPrettyString(std::ostream & str, int indent = 0); + private: + ///\brief Data storage for this Key. + /// + /// - 5 bytes: MSB storage of the position of the first packet of this keyframe within the file. + /// - 3 bytes: MSB storage of the duration of this keyframe. + /// - 2 bytes: MSB storage of the number of this keyframe. + /// - 2 bytes: MSB storage of the amount of parts in this keyframe. + /// - 4 bytes: MSB storage of the timestamp associated with this keyframe's first packet. + char data[16]; + }; + + ///\brief Basic class for storage of data associated with fragments. + class Fragment { + public: + unsigned long getDuration(); + void setDuration(unsigned long newDuration); + char getLength(); + void setLength(char newLength); + unsigned long getNumber(); + void setNumber(unsigned long newNumber); + unsigned long getSize(); + void setSize(unsigned long newSize); + char * getData(); + void toPrettyString(std::ostream & str, int indent = 0); + private: + ///\Brief Data storage for this Fragment. + /// + /// - 4 bytes: duration (in milliseconds) + /// - 1 byte: length (amount of keyframes) + /// - 2 bytes: number of first keyframe in fragment + /// - 4 bytes: size of fragment in bytes + char data[11]; + }; + + ///\brief Class for storage of track data + class Track { + public: + Track(); + Track(JSON::Value & trackRef); + Track(Scan & trackRef); + + inline operator bool() const { + return (parts.size() && keySizes.size() && (keySizes.size() == keys.size())); + } + void update(long long packTime, long long packOffset, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size = 5000); + int getSendLen(); + void send(Socket::Connection & conn); + void writeTo(char *& p); + JSON::Value toJSON(); + std::deque fragments; + std::deque keys; + std::deque keySizes; + std::deque parts; + Key & getKey(unsigned int keyNum); + unsigned int timeToKeynum(unsigned int timestamp); + unsigned int timeToFragnum(unsigned int timestamp); + void reset(); + void toPrettyString(std::ostream & str, int indent = 0, int verbosity = 0); + + std::string getIdentifier(); + std::string getWritableIdentifier(); + unsigned int trackID; + unsigned long long firstms; + unsigned long long lastms; + int bps; + int missedFrags; + std::string init; + std::string codec; + std::string type; + //audio only + int rate; + int size; + int channels; + //video only + int width; + int height; + int fpks; + }; + + ///\brief Class for storage of meta data + class Meta{ + /// \todo Make toJSON().toNetpacked() shorter + public: + Meta(); + Meta(const DTSC::Packet & source); + Meta(JSON::Value & meta); + + inline operator bool() const { //returns if the object contains valid meta data BY LOOKING AT vod/live FLAGS + return vod || live; + } + void reinit(const DTSC::Packet & source); + void update(DTSC::Packet & pack, unsigned long segment_size = 5000); + void updatePosOverride(DTSC::Packet & pack, unsigned long bpos); + void update(JSON::Value & pack, unsigned long segment_size = 5000); + void update(long long packTime, long long packOffset, long long packTrack, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize = 0, unsigned long segment_size = 5000); + unsigned int getSendLen(); + void send(Socket::Connection & conn); + void writeTo(char * p); + JSON::Value toJSON(); + void reset(); + void toPrettyString(std::ostream & str, int indent = 0, int verbosity = 0); + //members: + std::map tracks; + bool vod; + bool live; + bool merged; + long long int moreheader; + long long int bufferWindow; + }; + + /// A simple wrapper class that will open a file and allow easy reading/writing of DTSC data from/to it. + class File { + public: + File(); + File(const File & rhs); + File(std::string filename, bool create = false); + File & operator = (const File & rhs); + operator bool() const; + ~File(); + Meta & getMeta(); + long long int getLastReadPos(); + bool writeHeader(std::string & header, bool force = false); + long long int addHeader(std::string & header); + long int getBytePosEOF(); + long int getBytePos(); + bool reachedEOF(); + void seekNext(); + void parseNext(); + DTSC::Packet & getPacket(); + bool seek_time(unsigned int ms); + bool seek_time(unsigned int ms, unsigned int trackNo, bool forceSeek = false); + bool seek_bpos(int bpos); + void rewritePacket(std::string & newPacket, int bytePos); + void writePacket(std::string & newPacket); + void writePacket(JSON::Value & newPacket); + bool atKeyframe(); + void selectTracks(std::set & tracks); + private: + long int endPos; + void readHeader(int pos); + DTSC::Packet myPack; + JSON::Value metaStorage; + Meta metadata; + std::map trackMapping; + long long int currtime; + long long int lastreadpos; + int currframe; + FILE * F; + unsigned long headerSize; + void * buffer; + bool created; + std::set currentPositions; + std::set selectedTracks; + }; + //FileWriter + + /// 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(); + virtual ~Stream(); + Stream(unsigned int buffers, unsigned int bufferTime = 0); + Meta metadata; + JSON::Value & getPacket(); + JSON::Value & getPacket(livePos num); + datatype lastType(); + std::string & lastData(); + bool hasVideo(); + bool hasAudio(); + bool parsePacket(std::string & buffer); + bool parsePacket(Socket::Buffer & buffer); + std::string & outPacket(); + std::string & outPacket(livePos num); + std::string & outHeader(); + Ring * getRing(); + unsigned int getTime(); + void dropRing(Ring * ptr); + int canSeekms(unsigned int ms); + livePos msSeek(unsigned int ms, std::set & allowedTracks); + void setBufferTime(unsigned int ms); + bool isNewest(DTSC::livePos & pos, std::set & allowedTracks); + DTSC::livePos getNext(DTSC::livePos & pos, std::set & allowedTracks); + void endStream(); + void waitForMeta(Socket::Connection & sourceSocket, bool closeOnError = true); + void waitForPause(Socket::Connection & sourceSocket); + protected: + void cutOneBuffer(); + void resetStream(); + std::map buffers; + std::map > keyframes; + virtual void addPacket(JSON::Value & newPack); + virtual void addMeta(JSON::Value & newMeta); + datatype datapointertype; + unsigned int buffercount; + unsigned int buffertime; + std::map trackMapping; + virtual void deletionCallback(livePos deleting); + }; +} + diff --git a/lib/dtscmeta.cpp b/lib/dtscmeta.cpp new file mode 100644 index 00000000..9a69ddd8 --- /dev/null +++ b/lib/dtscmeta.cpp @@ -0,0 +1,1722 @@ +#include "dtsc.h" +#include "defines.h" +#include "bitfields.h" +#include +#include +#include + +#define AUDIO_KEY_INTERVAL 5000 ///< This define controls the keyframe interval for non-video tracks, such as audio and metadata tracks. + +namespace DTSC { + /// Default constructor for packets - sets a null pointer and invalid packet. + Packet::Packet() { + data = NULL; + bufferLen = 0; + dataLen = 0; + master = false; + version = DTSC_INVALID; + } + + /// Copy constructor for packets, copies an existing packet with same noCopy flag as original. + Packet::Packet(const Packet & rhs) { + Packet(rhs.data, rhs.dataLen, !rhs.master); + } + + /// Data constructor for packets, either references or copies a packet from raw data. + Packet::Packet(const char * data_, unsigned int len, bool noCopy) { + master = false; + bufferLen = 0; + data = NULL; + reInit(data_, len, noCopy); + } + + /// This destructor clears frees the data pointer if the packet was not a reference. + Packet::~Packet() { + if (master && data) { + free(data); + } + } + + /// Copier for packets, copies an existing packet with same noCopy flag as original. + /// If going from copy to noCopy, this will free the data pointer first. + void Packet::operator = (const Packet & rhs) { + if (master && !rhs.master) { + null(); + } + if (rhs) { + reInit(rhs.data, rhs.dataLen, !rhs.master); + } else { + null(); + } + } + + /// Returns true if the packet is deemed valid, false otherwise. + /// Valid packets have a length of at least 8, known header type, and length equal to the length set in the header. + Packet::operator bool() const { + if (!data) { + DEBUG_MSG(DLVL_DONTEVEN, "No data"); + return false; + } + if (dataLen < 8) { + DEBUG_MSG(DLVL_VERYHIGH, "Datalen < 8"); + return false; + } + if (version == DTSC_INVALID) { + DEBUG_MSG(DLVL_VERYHIGH, "No valid version"); + return false; + } + if (ntohl(((int *)data)[1]) + 8 > dataLen) { + DEBUG_MSG(DLVL_VERYHIGH, "Length mismatch"); + return false; + } + return true; + } + + /// Returns the recognized packet type. + /// This type is set by reInit and all constructors, and then just referenced from there on. + packType Packet::getVersion() const { + return version; + } + + /// Resets this packet back to the same state as if it had just been freshly constructed. + /// If needed, this frees the data pointer. + void Packet::null() { + if (master && data) { + free(data); + } + master = false; + data = NULL; + bufferLen = 0; + dataLen = 0; + version = DTSC_INVALID; + } + + /// Internally used resize function for when operating in copy mode and the internal buffer is too small. + /// It will only resize up, never down. + ///\param len The length th scale the buffer up to if necessary + void Packet::resize(unsigned int len) { + if (master && len > bufferLen) { + char * tmp = (char *)realloc(data, len); + if (tmp) { + data = tmp; + bufferLen = len; + } else { + DEBUG_MSG(DLVL_FAIL, "Out of memory on parsing a packet"); + } + } + } + + ///\brief Initializes a packet with new data + ///\param data_ The new data for the packet + ///\param len The length of the data pointed to by data_ + ///\param noCopy Determines whether to make a copy or not + void Packet::reInit(const char * data_, unsigned int len, bool noCopy) { + if (!data_) { + DEBUG_MSG(DLVL_DEVEL, "ReInit received a null pointer with len %d, ignoring", len); + null(); + return; + } + if (!data_[0] && !data_[1] && !data_[2] && !data_[3]){ + null(); + return; + } + if (data_[0] != 'D' || data_[1] != 'T') { + unsigned int twlen = len; + if (twlen > 20){twlen = 20;} + DEBUG_MSG(DLVL_HIGH, "ReInit received a pointer that didn't start with 'DT' but with %s (%u) - data corruption?", JSON::Value(std::string(data_, twlen)).toString().c_str(), len); + null(); + return; + } + if (len <= 0) { + len = ntohl(((int *)data_)[1]) + 8; + } + //clear any existing controlled contents + if (master && noCopy) { + null(); + } + //set control flag to !noCopy + master = !noCopy; + //either copy the data, or only the pointer, depending on flag + if (noCopy) { + data = (char *)data_; + } else { + resize(len); + memcpy(data, data_, len); + } + //check header type and store packet length + dataLen = len; + version = DTSC_INVALID; + if (len > 3) { + if (!memcmp(data, Magic_Packet2, 4)) { + version = DTSC_V2; + } else { + if (!memcmp(data, Magic_Packet, 4)) { + version = DTSC_V1; + } else { + if (!memcmp(data, Magic_Header, 4)) { + version = DTSC_HEAD; + } else { + DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with invalid header"); + return; + } + } + } + } else { + DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with size < 4"); + return; + } + } + + /// Re-initializes this Packet to contain a generic DTSC packet with the given data fields. + void Packet::genericFill(long long packTime, long long packOffset, long long packTrack, char * packData, long long packDataSize, long long packBytePos, bool isKeyframe){ + null(); + master = true; + //time and trackID are part of the 20-byte header. + //the container object adds 4 bytes (plus 2+namelen for each content, see below) + //offset, if non-zero, adds 9 bytes (integer type) and 8 bytes (2+namelen) + //bpos, if >= 0, adds 9 bytes (integer type) and 6 bytes (2+namelen) + //keyframe, if true, adds 9 bytes (integer type) and 10 bytes (2+namelen) + //data adds packDataSize+5 bytes (string type) and 6 bytes (2+namelen) + unsigned int sendLen = 24 + (packOffset?17:0) + (packBytePos>=0?15:0) + (isKeyframe?19:0) + packDataSize+11; + resize(sendLen); + //set internal variables + version = DTSC_V2; + dataLen = sendLen; + //write the first 20 bytes + memcpy(data, "DTP2", 4); + unsigned int tmpLong = htonl(sendLen - 8); + memcpy(data+4, (char *)&tmpLong, 4); + tmpLong = htonl(packTrack); + memcpy(data+8, (char *)&tmpLong, 4); + tmpLong = htonl((int)(packTime >> 32)); + memcpy(data+12, (char *)&tmpLong, 4); + tmpLong = htonl((int)(packTime & 0xFFFFFFFF)); + memcpy(data+16, (char *)&tmpLong, 4); + data[20] = 0xE0;//start container object + unsigned int offset = 21; + if (packOffset){ + memcpy(data+offset, "\000\006offset\001", 9); + tmpLong = htonl((int)(packOffset >> 32)); + memcpy(data+offset+9, (char *)&tmpLong, 4); + tmpLong = htonl((int)(packOffset & 0xFFFFFFFF)); + memcpy(data+offset+13, (char *)&tmpLong, 4); + offset += 17; + } + if (packBytePos){ + memcpy(data+offset, "\000\004bpos\001", 7); + tmpLong = htonl((int)(packBytePos >> 32)); + memcpy(data+offset+7, (char *)&tmpLong, 4); + tmpLong = htonl((int)(packBytePos & 0xFFFFFFFF)); + memcpy(data+offset+11, (char *)&tmpLong, 4); + offset += 15; + } + if (isKeyframe){ + memcpy(data+offset, "\000\010keyframe\001\000\000\000\000\000\000\000\001", 19); + offset += 19; + } + memcpy(data+offset, "\000\004data\002", 7); + tmpLong = htonl(packDataSize); + memcpy(data+offset+7, (char *)&tmpLong, 4); + memcpy(data+offset+11, packData, packDataSize); + //finish container with 0x0000EE + memcpy(data+offset+11+packDataSize, "\000\000\356", 3); + } + + /// Helper function for skipping over whole DTSC parts + static char * skipDTSC(char * p, char * max) { + if (p + 1 >= max || p[0] == 0x00) { + return 0;//out of packet! 1 == error + } + if (p[0] == DTSC_INT) { + //int, skip 9 bytes to next value + return p + 9; + } + if (p[0] == DTSC_STR) { + if (p + 4 >= max) { + return 0;//out of packet! + } + return p + 5 + Bit::btohl(p+1); + } + if (p[0] == DTSC_OBJ || p[0] == DTSC_CON) { + p++; + //object, scan contents + while (p[0] + p[1] != 0 && p < max) { //while not encountering 0x0000 (we assume 0x0000EE) + if (p + 2 >= max) { + return 0;//out of packet! + } + p += 2 + Bit::btohs(p);//skip size + //otherwise, search through the contents, if needed, and continue + p = skipDTSC(p, max); + if (!p) { + return 0; + } + } + return p + 3; + } + if (p[0] == DTSC_ARR) { + p++; + //array, scan contents + while (p[0] + p[1] != 0 && p < max) { //while not encountering 0x0000 (we assume 0x0000EE) + //search through contents... + p = skipDTSC(p, max); + if (!p) { + return 0; + } + } + return p + 3; //skip end marker + } + return 0;//out of packet! 1 == error + } + + ///\brief Retrieves a single parameter as a string + ///\param identifier The name of the parameter + ///\param result A location on which the string will be returned + ///\param len An integer in which the length of the string will be returned + void Packet::getString(const char * identifier, char *& result, unsigned int & len) const { + getScan().getMember(identifier).getString(result, len); + } + + ///\brief Retrieves a single parameter as a string + ///\param identifier The name of the parameter + ///\param result The string in which to store the result + void Packet::getString(const char * identifier, std::string & result) const { + result = getScan().getMember(identifier).asString(); + } + + ///\brief Retrieves a single parameter as an integer + ///\param identifier The name of the parameter + ///\param result The result is stored in this integer + void Packet::getInt(const char * identifier, int & result) const { + result = getScan().getMember(identifier).asInt(); + } + + ///\brief Retrieves a single parameter as an integer + ///\param identifier The name of the parameter + ///\result The requested parameter as an integer + int Packet::getInt(const char * identifier) const { + int result; + getInt(identifier, result); + return result; + } + + ///\brief Retrieves a single parameter as a boolean + ///\param identifier The name of the parameter + ///\param result The result is stored in this boolean + void Packet::getFlag(const char * identifier, bool & result) const { + int result_; + getInt(identifier, result_); + result = (bool)result_; + } + + ///\brief Retrieves a single parameter as a boolean + ///\param identifier The name of the parameter + ///\result The requested parameter as a boolean + bool Packet::getFlag(const char * identifier) const { + bool result; + getFlag(identifier, result); + return result; + } + + ///\brief Checks whether a parameter exists + ///\param identifier The name of the parameter + ///\result Whether the parameter exists or not + bool Packet::hasMember(const char * identifier) const { + return getScan().getMember(identifier).getType() > 0; + } + + ///\brief Returns the timestamp of the packet. + ///\return The timestamp of this packet. + long long unsigned int Packet::getTime() const { + if (version != DTSC_V2) { + if (!data) { + return 0; + } + return getInt("time"); + } + return Bit::btohll(data + 12); + } + + ///\brief Returns the track id of the packet. + ///\return The track id of this packet. + long int Packet::getTrackId() const { + if (version != DTSC_V2) { + return getInt("trackid"); + } + return Bit::btohl(data+8); + } + + ///\brief Returns a pointer to the payload of this packet. + ///\return A pointer to the payload of this packet. + char * Packet::getData() const { + return data; + } + + ///\brief Returns the size of this packet. + ///\return The size of this packet. + int Packet::getDataLen() const { + return dataLen; + } + + ///\brief Returns the size of the payload of this packet. + ///\return The size of the payload of this packet. + int Packet::getPayloadLen() const { + if (version == DTSC_V2) { + return dataLen - 20; + } else { + return dataLen - 8; + } + } + + /// Returns a DTSC::Scan instance to the contents of this packet. + /// May return an invalid instance if this packet is invalid. + Scan Packet::getScan() const { + if (!*this || !getDataLen() || !getPayloadLen() || getDataLen() <= getPayloadLen()){ + return Scan(); + } + return Scan(data + (getDataLen() - getPayloadLen()), getPayloadLen()); + } + + ///\brief Converts the packet into a JSON value + ///\return A JSON::Value representation of this packet. + JSON::Value Packet::toJSON() const { + JSON::Value result; + unsigned int i = 8; + if (getVersion() == DTSC_V1) { + JSON::fromDTMI((const unsigned char *)data, dataLen, i, result); + } + if (getVersion() == DTSC_V2) { + JSON::fromDTMI2((const unsigned char *)data, dataLen, i, result); + } + return result; + } + + /// Create an invalid DTSC::Scan object by default. + Scan::Scan() { + p = 0; + len = 0; + } + + + /// Create a DTSC::Scan object from memory pointer. + Scan::Scan(char * pointer, size_t length) { + p = pointer; + len = length; + } + + /// Returns whether the DTSC::Scan object contains valid data. + Scan::operator bool() const { + return (p && len); + } + + /// Returns an object representing the named indice of this object. + /// Returns an invalid object if this indice doesn't exist or this isn't an object type. + Scan Scan::getMember(std::string indice) { + return getMember(indice.data(), indice.size()); + } + + /// Returns an object representing the named indice of this object. + /// Returns an invalid object if this indice doesn't exist or this isn't an object type. + Scan Scan::getMember(const char * indice, const unsigned int ind_len) { + if (getType() != DTSC_OBJ && getType() != DTSC_CON) { + return Scan(); + } + char * i = p + 1; + //object, scan contents + while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE) + if (i + 2 >= p + len) { + return Scan();//out of packet! + } + unsigned int strlen = Bit::btohs(i); + i += 2; + if (ind_len == strlen && strncmp(indice, i, strlen) == 0) { + return Scan(i + strlen, len - (i - p)); + } else { + i = skipDTSC(i + strlen, p + len); + if (!i) { + return Scan(); + } + } + } + return Scan(); + } + + /// Returns an object representing the named indice of this object. + /// Returns an invalid object if this indice doesn't exist or this isn't an object type. + bool Scan::hasMember(std::string indice){ + return getMember(indice.data(), indice.size()); + } + + /// Returns whether an object representing the named indice of this object exists. + /// Returns false if this indice doesn't exist or this isn't an object type. + bool Scan::hasMember(const char * indice, const unsigned int ind_len) { + return getMember(indice, ind_len); + } + + /// Returns an object representing the named indice of this object. + /// Returns an invalid object if this indice doesn't exist or this isn't an object type. + Scan Scan::getMember(const char * indice) { + return getMember(indice, strlen(indice)); + } + + /// Returns the amount of indices if an array, the amount of members if an object, or zero otherwise. + unsigned int Scan::getSize() { + if (getType() == DTSC_ARR) { + char * i = p + 1; + unsigned int arr_indice = 0; + //array, scan contents + while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE) + //search through contents... + arr_indice++; + i = skipDTSC(i, p + len); + if (!i) { + return arr_indice; + } + } + return arr_indice; + } + if (getType() == DTSC_OBJ || getType() == DTSC_CON) { + char * i = p + 1; + unsigned int arr_indice = 0; + //object, scan contents + while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE) + if (i + 2 >= p + len) { + return Scan();//out of packet! + } + unsigned int strlen = Bit::btohs(i); + i += 2; + arr_indice++; + i = skipDTSC(i + strlen, p + len); + if (!i) { + return arr_indice; + } + } + return arr_indice; + } + return 0; + } + + /// Returns an object representing the num-th indice of this array. + /// If not an array but an object, it returns the num-th member, instead. + /// Returns an invalid object if this indice doesn't exist or this isn't an array or object type. + Scan Scan::getIndice(unsigned int num) { + if (getType() == DTSC_ARR) { + char * i = p + 1; + unsigned int arr_indice = 0; + //array, scan contents + while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE) + //search through contents... + if (arr_indice == num) { + return Scan(i, len - (i - p)); + } else { + arr_indice++; + i = skipDTSC(i, p + len); + if (!i) { + return Scan(); + } + } + } + } + if (getType() == DTSC_OBJ || getType() == DTSC_CON) { + char * i = p + 1; + unsigned int arr_indice = 0; + //object, scan contents + while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE) + if (i + 2 >= p + len) { + return Scan();//out of packet! + } + unsigned int strlen = Bit::btohs(i); + i += 2; + if (arr_indice == num) { + return Scan(i + strlen, len - (i - p)); + } else { + arr_indice++; + i = skipDTSC(i + strlen, p + len); + if (!i) { + return Scan(); + } + } + } + } + return Scan(); + } + + /// Returns the name of the num-th member of this object. + /// Returns an empty string on error or when not an object. + std::string Scan::getIndiceName(unsigned int num) { + if (getType() == DTSC_OBJ || getType() == DTSC_CON) { + char * i = p + 1; + unsigned int arr_indice = 0; + //object, scan contents + while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE) + if (i + 2 >= p + len) { + return "";//out of packet! + } + unsigned int strlen = Bit::btohs(i); + i += 2; + if (arr_indice == num) { + return std::string(i, strlen); + } else { + arr_indice++; + i = skipDTSC(i + strlen, p + len); + if (!i) { + return ""; + } + } + } + } + return ""; + } + + /// Returns the first byte of this DTSC value, or 0 on error. + char Scan::getType() { + if (!p) { + return 0; + } + return p[0]; + } + + /// Returns the boolean value of this DTSC value. + /// Numbers are compared to 0. + /// Strings are checked for non-zero length. + /// Objects and arrays are checked for content. + /// Returns false on error or in other cases. + bool Scan::asBool() { + switch (getType()) { + case DTSC_STR: + return (p[1] | p[2] | p[3] | p[4]); + case DTSC_INT: + return (asInt() != 0); + case DTSC_OBJ: + case DTSC_CON: + case DTSC_ARR: + return (p[1] | p[2]); + default: + return false; + } + } + + /// Returns the long long value of this DTSC number value. + /// Will convert string values to numbers, taking octal and hexadecimal types into account. + /// Illegal or invalid values return 0. + long long Scan::asInt() { + switch (getType()) { + case DTSC_INT: + return Bit::btohll(p+1); + case DTSC_STR: + char * str; + unsigned int strlen; + getString(str, strlen); + if (!strlen) { + return 0; + } + return strtoll(str, 0, 0); + default: + return 0; + } + } + + /// Returns the string value of this DTSC string value. + /// Uses getString internally, if a string. + /// Converts integer values to strings. + /// Returns an empty string on error. + std::string Scan::asString() { + switch (getType()) { + case DTSC_INT:{ + std::stringstream st; + st << asInt(); + return st.str(); + } + break; + case DTSC_STR:{ + char * str; + unsigned int strlen; + getString(str, strlen); + return std::string(str, strlen); + } + break; + } + return ""; + } + + /// Sets result to a pointer to the string, and strlen to the lenght of it. + /// Sets both to zero if this isn't a DTSC string value. + /// Attempts absolutely no conversion. + void Scan::getString(char *& result, unsigned int & strlen) { + switch (getType()) { + case DTSC_STR: + result = p + 5; + strlen = Bit::btohl(p+1); + return; + default: + result = 0; + strlen = 0; + return; + } + } + + /// Returns the DTSC scan object as a JSON value + /// Returns an empty object on error. + JSON::Value Scan::asJSON(){ + JSON::Value result; + unsigned int i = 0; + JSON::fromDTMI((const unsigned char*)p, len, i, result); + return result; + } + + /// \todo Move this function to some generic area. Duplicate from json.cpp + static inline char hex2c(char c) { + if (c < 10) { + return '0' + c; + } + if (c < 16) { + return 'A' + (c - 10); + } + return '0'; + } + + /// \todo Move this function to some generic area. Duplicate from json.cpp + static std::string string_escape(const std::string val) { + std::stringstream out; + out << "\""; + for (unsigned int i = 0; i < val.size(); ++i) { + switch (val.data()[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: + if (val.data()[i] < 32 || val.data()[i] > 126) { + out << "\\u00"; + out << hex2c((val.data()[i] >> 4) & 0xf); + out << hex2c(val.data()[i] & 0xf); + } else { + out << val.data()[i]; + } + break; + } + } + out << "\""; + return out.str(); + } + + std::string Scan::toPrettyString(unsigned int indent) { + switch (getType()) { + case DTSC_STR: { + unsigned int strlen = Bit::btohl(p+1); + if (strlen > 250) { + std::stringstream ret; + ret << "\"" << strlen << " bytes of data\""; + return ret.str(); + } + return string_escape(asString()); + } + case DTSC_INT: { + std::stringstream ret; + ret << asInt(); + return ret.str(); + } + case DTSC_OBJ: + case DTSC_CON: { + std::stringstream ret; + ret << "{" << std::endl; + indent += 2; + char * i = p + 1; + bool first = true; + //object, scan contents + while (i[0] + i[1] != 0 && i < p + len) { //while not encountering 0x0000 (we assume 0x0000EE) + if (i + 2 >= p + len) { + indent -= 2; + ret << std::string((size_t)indent, ' ') << "} //walked out of object here"; + return ret.str(); + } + if (!first){ + ret << "," << std::endl; + } + first = false; + unsigned int strlen = Bit::btohs(i); + i += 2; + ret << std::string((size_t)indent, ' ') << "\"" << std::string(i, strlen) << "\": " << Scan(i + strlen, len - (i - p)).toPrettyString(indent); + i = skipDTSC(i + strlen, p + len); + if (!i) { + indent -= 2; + ret << std::string((size_t)indent, ' ') << "} //could not locate next object"; + return ret.str(); + } + } + indent -= 2; + ret << std::endl << std::string((size_t)indent, ' ') << "}"; + return ret.str(); + } + case DTSC_ARR: { + std::stringstream ret; + ret << "[" << std::endl; + indent += 2; + Scan tmpScan; + unsigned int i = 0; + bool first = true; + do { + tmpScan = getIndice(i++); + if (tmpScan.getType()) { + if (!first){ + ret << "," << std::endl; + } + first = false; + ret << std::string((size_t)indent, ' ') << tmpScan.toPrettyString(indent); + } + } while (tmpScan.getType()); + indent -= 2; + ret << std::endl << std::string((size_t)indent, ' ') << "]"; + return ret.str(); + } + default: + return "Error"; + } + } + + + + ///\brief Returns the payloadsize of a part + long Part::getSize() { + return Bit::btoh24(data); + } + + ///\brief Sets the payloadsize of a part + void Part::setSize(long newSize) { + Bit::htob24(data, newSize); + } + + ///\brief Retruns the duration of a part + short Part::getDuration() { + return Bit::btohs(data + 3); + } + + ///\brief Sets the duration of a part + void Part::setDuration(short newDuration) { + Bit::htobs(data + 3, newDuration); + } + + ///\brief returns the offset of a part + long Part::getOffset() { + return Bit::btohl(data + 5); + } + + ///\brief Sets the offset of a part + void Part::setOffset(long newOffset) { + Bit::htobl(data + 5, newOffset); + } + + ///\brief Returns the data of a part + char * Part::getData() { + return data; + } + + ///\brief Converts a part to a human readable string + ///\param str The stringstream to append to + ///\param indent the amount of indentation needed + void Part::toPrettyString(std::ostream & str, int indent) { + str << std::string(indent, ' ') << "Part: Size(" << getSize() << "), Dur(" << getDuration() << "), Offset(" << getOffset() << ")" << std::endl; + } + + ///\brief Returns the byteposition of a keyframe + unsigned long long Key::getBpos() { + return (((unsigned long long)data[0] << 32) | (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4]); + } + + void Key::setBpos(unsigned long long newBpos) { + data[4] = newBpos & 0xFF; + data[3] = (newBpos >> 8) & 0xFF; + data[2] = (newBpos >> 16) & 0xFF; + data[1] = (newBpos >> 24) & 0xFF; + data[0] = (newBpos >> 32) & 0xFF; + } + + unsigned long Key::getLength() { + return Bit::btoh24(data+5); + } + + void Key::setLength(unsigned long newLength) { + Bit::htob24(data+5, newLength); + } + + ///\brief Returns the number of a keyframe + unsigned long Key::getNumber() { + return Bit::btohs(data + 8); + } + + ///\brief Sets the number of a keyframe + void Key::setNumber(unsigned long newNumber) { + Bit::htobs(data + 8, newNumber); + } + + ///\brief Returns the number of parts of a keyframe + unsigned short Key::getParts() { + return Bit::btohs(data + 10); + } + + ///\brief Sets the number of parts of a keyframe + void Key::setParts(unsigned short newParts) { + Bit::htobs(data + 10, newParts); + } + + ///\brief Returns the timestamp of a keyframe + unsigned long long Key::getTime() { + return Bit::btohl(data + 12); + } + + ///\brief Sets the timestamp of a keyframe + void Key::setTime(unsigned long long newTime) { + Bit::htobl(data + 12, newTime); + } + + ///\brief Returns the data of this keyframe struct + char * Key::getData() { + return data; + } + + ///\brief Converts a keyframe to a human readable string + ///\param str The stringstream to append to + ///\param indent the amount of indentation needed + void Key::toPrettyString(std::ostream & str, int indent) { + str << std::string(indent, ' ') << "Key " << getNumber() << ": Pos(" << getBpos() << "), Dur(" << getLength() << "), Parts(" << getParts() << "), Time(" << getTime() << ")" << std::endl; + } + + ///\brief Returns the duration of this fragment + unsigned long Fragment::getDuration() { + return Bit::btohl(data); + } + + ///\brief Sets the duration of this fragment + void Fragment::setDuration(unsigned long newDuration) { + Bit::htobl(data, newDuration); + } + + ///\brief Returns the length of this fragment + char Fragment::getLength() { + return data[4]; + } + + ///\brief Sets the length of this fragment + void Fragment::setLength(char newLength) { + data[4] = newLength; + } + + ///\brief Returns the number of the first keyframe in this fragment + unsigned long Fragment::getNumber() { + return Bit::btohs(data + 5); + } + + ///\brief Sets the number of the first keyframe in this fragment + void Fragment::setNumber(unsigned long newNumber) { + Bit::htobs(data + 5, newNumber); + } + + ///\brief Returns the size of a fragment + unsigned long Fragment::getSize() { + return Bit::btohl(data + 7); + } + + ///\brief Sets the size of a fragement + void Fragment::setSize(unsigned long newSize) { + Bit::htobl(data + 7, newSize); + } + + ///\brief Returns thte data of this fragment structure + char * Fragment::getData() { + return data; + } + + ///\brief Converts a fragment to a human readable string + ///\param str The stringstream to append to + ///\param indent the amount of indentation needed + void Fragment::toPrettyString(std::ostream & str, int indent) { + str << std::string(indent, ' ') << "Fragment " << getNumber() << ": Dur(" << getDuration() << "), Len(" << (int)getLength() << "), Size(" << getSize() << ")" << std::endl; + } + + ///\brief Constructs an empty track + Track::Track() { + trackID = 0; + firstms = 0; + lastms = 0; + bps = 0; + missedFrags = 0; + rate = 0; + size = 0; + channels = 0; + width = 0; + height = 0; + fpks = 0; + } + + ///\brief Constructs a track from a JSON::Value + Track::Track(JSON::Value & trackRef) { + if (trackRef.isMember("fragments") && trackRef["fragments"].isString()) { + Fragment * tmp = (Fragment *)trackRef["fragments"].asStringRef().data(); + fragments = std::deque(tmp, tmp + (trackRef["fragments"].asStringRef().size() / 11)); + } + if (trackRef.isMember("keys") && trackRef["keys"].isString()) { + Key * tmp = (Key *)trackRef["keys"].asStringRef().data(); + keys = std::deque(tmp, tmp + (trackRef["keys"].asStringRef().size() / 16)); + } + if (trackRef.isMember("parts") && trackRef["parts"].isString()) { + Part * tmp = (Part *)trackRef["parts"].asStringRef().data(); + parts = std::deque(tmp, tmp + (trackRef["parts"].asStringRef().size() / 9)); + } + trackID = trackRef["trackid"].asInt(); + firstms = trackRef["firstms"].asInt(); + lastms = trackRef["lastms"].asInt(); + bps = trackRef["bps"].asInt(); + missedFrags = trackRef["missed_frags"].asInt(); + codec = trackRef["codec"].asStringRef(); + type = trackRef["type"].asStringRef(); + init = trackRef["init"].asStringRef(); + if (type == "audio") { + rate = trackRef["rate"].asInt(); + size = trackRef["size"].asInt(); + channels = trackRef["channels"].asInt(); + } + if (type == "video") { + width = trackRef["width"].asInt(); + height = trackRef["height"].asInt(); + fpks = trackRef["fpks"].asInt(); + } + if (trackRef.isMember("keysizes") && trackRef["keysizes"].isString()) { + std::string tmp = trackRef["keysizes"].asStringRef(); + for (unsigned int i = 0; i < tmp.size(); i += 4){ + keySizes.push_back((((long unsigned)tmp[i]) << 24) | (((long unsigned)tmp[i+1]) << 16) | (((long unsigned int)tmp[i+2]) << 8) | tmp[i+3]); + } + } + } + + ///\brief Constructs a track from a JSON::Value + Track::Track(Scan & trackRef) { + if (trackRef.getMember("fragments").getType() == DTSC_STR) { + char * tmp = 0; + unsigned int tmplen = 0; + trackRef.getMember("fragments").getString(tmp, tmplen); + fragments = std::deque((Fragment *)tmp, ((Fragment *)tmp) + (tmplen / 11)); + } + if (trackRef.getMember("keys").getType() == DTSC_STR) { + char * tmp = 0; + unsigned int tmplen = 0; + trackRef.getMember("keys").getString(tmp, tmplen); + keys = std::deque((Key *)tmp, ((Key *)tmp) + (tmplen / 16)); + } + if (trackRef.getMember("parts").getType() == DTSC_STR) { + char * tmp = 0; + unsigned int tmplen = 0; + trackRef.getMember("parts").getString(tmp, tmplen); + parts = std::deque((Part *)tmp, ((Part *)tmp) + (tmplen / 9)); + } + trackID = trackRef.getMember("trackid").asInt(); + firstms = trackRef.getMember("firstms").asInt(); + lastms = trackRef.getMember("lastms").asInt(); + bps = trackRef.getMember("bps").asInt(); + missedFrags = trackRef.getMember("missed_frags").asInt(); + codec = trackRef.getMember("codec").asString(); + type = trackRef.getMember("type").asString(); + init = trackRef.getMember("init").asString(); + if (type == "audio") { + rate = trackRef.getMember("rate").asInt(); + size = trackRef.getMember("size").asInt(); + channels = trackRef.getMember("channels").asInt(); + } + if (type == "video") { + width = trackRef.getMember("width").asInt(); + height = trackRef.getMember("height").asInt(); + fpks = trackRef.getMember("fpks").asInt(); + } + if (trackRef.getMember("keysizes").getType() == DTSC_STR) { + char * tmp = 0; + unsigned int tmplen = 0; + trackRef.getMember("keysizes").getString(tmp, tmplen); + for (unsigned int i = 0; i < tmplen; i += 4){ + keySizes.push_back((((long unsigned)tmp[i]) << 24) | (((long unsigned)tmp[i+1]) << 16) | (((long unsigned int)tmp[i+2]) << 8) | tmp[i+3]); + } + } + } + + ///\brief Updates a track and its metadata given new packet properties. + ///Will also insert keyframes on non-video tracks, and creates fragments + void Track::update(long long packTime, long long packOffset, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size) { + if ((unsigned long long)packTime < lastms) { + DEBUG_MSG(DLVL_WARN, "Received packets for track %u in wrong order (%lld < %llu) - ignoring!", trackID, packTime, lastms); + return; + } + Part newPart; + newPart.setSize(packDataSize); + newPart.setOffset(packOffset); + if (parts.size()) { + parts[parts.size() - 1].setDuration(packTime - lastms); + newPart.setDuration(packTime - lastms); + } else { + newPart.setDuration(0); + } + parts.push_back(newPart); + lastms = packTime; + if (isKeyframe || !keys.size() || (type != "video" && packTime >= AUDIO_KEY_INTERVAL && packTime - (unsigned long long)keys[keys.size() - 1].getTime() >= AUDIO_KEY_INTERVAL)){ + Key newKey; + newKey.setTime(packTime); + newKey.setParts(0); + newKey.setLength(0); + if (keys.size()) { + newKey.setNumber(keys[keys.size() - 1].getNumber() + 1); + keys[keys.size() - 1].setLength(packTime - keys[keys.size() - 1].getTime()); + } else { + newKey.setNumber(1); + } + if (packBytePos >= 0) { //For VoD + newKey.setBpos(packBytePos); + } else { + newKey.setBpos(0); + } + keys.push_back(newKey); + keySizes.push_back(0); + firstms = keys[0].getTime(); + if (!fragments.size() || ((unsigned long long)packTime > segment_size && (unsigned long long)packTime - segment_size >= (unsigned long long)getKey(fragments.rbegin()->getNumber()).getTime())) { + //new fragment + Fragment newFrag; + newFrag.setDuration(0); + newFrag.setLength(1); + newFrag.setNumber(keys[keys.size() - 1].getNumber()); + if (fragments.size()) { + fragments[fragments.size() - 1].setDuration(packTime - getKey(fragments[fragments.size() - 1].getNumber()).getTime()); + if (!bps && fragments[fragments.size() - 1].getDuration() > 1000) { + bps = (fragments[fragments.size() - 1].getSize() * 1000) / fragments[fragments.size() - 1].getDuration(); + } + } + newFrag.setDuration(0); + newFrag.setSize(0); + fragments.push_back(newFrag); + } else { + Fragment & lastFrag = fragments[fragments.size() - 1]; + lastFrag.setLength(lastFrag.getLength() + 1); + } + } + keys.rbegin()->setParts(keys.rbegin()->getParts() + 1); + (*keySizes.rbegin()) += packSendSize; + fragments.rbegin()->setSize(fragments.rbegin()->getSize() + packDataSize); + } + + ///\brief Returns a key given its number, or an empty key if the number is out of bounds + Key & Track::getKey(unsigned int keyNum) { + static Key empty; + if (keyNum < keys[0].getNumber()) { + return empty; + } + if ((keyNum - keys[0].getNumber()) > keys.size()) { + return empty; + } + return keys[keyNum - keys[0].getNumber()]; + } + + unsigned int Track::timeToKeynum(unsigned int timestamp){ + unsigned int result = 0; + for (std::deque::iterator it = keys.begin(); it != keys.end(); it++){ + if (it->getTime() >= timestamp){ + break; + } + result = it->getNumber(); + } + return result; + } + + unsigned int Track::timeToFragnum(unsigned int timestamp){ + unsigned long long int totalTime = firstms; + for (unsigned int i = 0; isecond["trackid"].asInt()) { + tracks[it->second["trackid"].asInt()] = Track(it->second); + } + } + if (meta.isMember("moreheader")) { + moreheader = meta["moreheader"].asInt(); + } else { + moreheader = 0; + } + } + + ///\brief Updates a meta object given a JSON::Value + void Meta::update(JSON::Value & pack, unsigned long segment_size) { + update(pack["time"].asInt(), pack.isMember("offset")?pack["offset"].asInt():0, pack["trackid"].asInt(), pack["data"].asStringRef().size(), pack.isMember("bpos")?pack["bpos"].asInt():-1, pack.isMember("keyframe"), pack.packedSize(), segment_size); + } + + ///\brief Updates a meta object given a DTSC::Packet + void Meta::update(DTSC::Packet & pack, unsigned long segment_size) { + char * data; + unsigned int dataLen; + pack.getString("data", data, dataLen); + update(pack.getTime(), pack.hasMember("offset")?pack.getInt("offset"):0, pack.getTrackId(), dataLen, pack.hasMember("bpos")?pack.getInt("bpos"):-1, pack.hasMember("keyframe"), pack.getDataLen(), segment_size); + } + + ///\brief Updates a meta object given a DTSC::Packet with byte position override. + void Meta::updatePosOverride(DTSC::Packet & pack, unsigned long bpos) { + char * data; + unsigned int dataLen; + pack.getString("data", data, dataLen); + update(pack.getTime(), pack.hasMember("offset")?pack.getInt("offset"):0, pack.getTrackId(), dataLen, bpos, pack.hasMember("keyframe"), pack.getDataLen()); + } + + void Meta::update(long long packTime, long long packOffset, long long packTrack, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size){ + if (!packSendSize){ + //time and trackID are part of the 20-byte header. + //the container object adds 4 bytes (plus 2+namelen for each content, see below) + //offset, if non-zero, adds 9 bytes (integer type) and 8 bytes (2+namelen) + //bpos, if >= 0, adds 9 bytes (integer type) and 6 bytes (2+namelen) + //keyframe, if true, adds 9 bytes (integer type) and 10 bytes (2+namelen) + //data adds packDataSize+5 bytes (string type) and 6 bytes (2+namelen) + packSendSize = 24 + (packOffset?17:0) + (packBytePos>=0?15:0) + (isKeyframe?19:0) + packDataSize+11; + } + vod = (packBytePos >= 0); + live = !vod; + if (packTrack > 0 && tracks.count(packTrack)){ + tracks[packTrack].update(packTime, packOffset, packDataSize, packBytePos, isKeyframe, packSendSize, segment_size); + } + } + + ///\brief Converts a track to a human readable string + ///\param str The stringstream to append to + ///\param indent the amount of indentation needed + ///\param verbosity How verbose the output needs to be + void Track::toPrettyString(std::ostream & str, int indent, int verbosity) { + str << std::string(indent, ' ') << "Track " << getWritableIdentifier() << std::endl; + str << std::string(indent + 2, ' ') << "ID: " << trackID << std::endl; + str << std::string(indent + 2, ' ') << "Firstms: " << firstms << std::endl; + str << std::string(indent + 2, ' ') << "Lastms: " << lastms << std::endl; + str << std::string(indent + 2, ' ') << "Bps: " << bps << std::endl; + if (missedFrags) { + str << std::string(indent + 2, ' ') << "missedFrags: " << missedFrags << std::endl; + } + str << std::string(indent + 2, ' ') << "Codec: " << codec << std::endl; + str << std::string(indent + 2, ' ') << "Type: " << type << std::endl; + str << std::string(indent + 2, ' ') << "Init: "; + for (unsigned int i = 0; i < init.size(); ++i) { + str << std::hex << std::setw(2) << std::setfill('0') << (int)init[i]; + } + str << std::dec << std::endl; + if (type == "audio") { + str << std::string(indent + 2, ' ') << "Rate: " << rate << std::endl; + str << std::string(indent + 2, ' ') << "Size: " << size << std::endl; + str << std::string(indent + 2, ' ') << "Channel: " << channels << std::endl; + } else if (type == "video") { + str << std::string(indent + 2, ' ') << "Width: " << width << std::endl; + str << std::string(indent + 2, ' ') << "Height: " << height << std::endl; + str << std::string(indent + 2, ' ') << "Fpks: " << fpks << std::endl; + } + str << std::string(indent + 2, ' ') << "Fragments: " << fragments.size() << std::endl; + if (verbosity & 0x01) { + for (unsigned int i = 0; i < fragments.size(); i++) { + fragments[i].toPrettyString(str, indent + 4); + } + } + str << std::string(indent + 2, ' ') << "Keys: " << keys.size() << std::endl; + if (verbosity & 0x02) { + for (unsigned int i = 0; i < keys.size(); i++) { + keys[i].toPrettyString(str, indent + 4); + } + } + str << std::string(indent + 2, ' ') << "KeySizes: " << keySizes.size() << std::endl; + if (keySizes.size() && verbosity & 0x02){ + for (unsigned int i = 0; i < keySizes.size(); i++){ + str << std::string(indent + 4, ' ') << "[" << i << "] " << keySizes[i] << std::endl; + } + } + str << std::string(indent + 2, ' ') << "Parts: " << parts.size() << std::endl; + if (verbosity & 0x04) { + for (unsigned int i = 0; i < parts.size(); i++) { + parts[i].toPrettyString(str, indent + 4); + } + } + } + + ///\brief Converts a short to a char* + char * convertShort(short input) { + static char result[2]; + result[0] = (input >> 8) & 0xFF; + result[1] = (input) & 0xFF; + return result; + } + + ///\brief Converts an integer to a char* + char * convertInt(int input) { + static char result[4]; + result[0] = (input >> 24) & 0xFF; + result[1] = (input >> 16) & 0xFF; + result[2] = (input >> 8) & 0xFF; + result[3] = (input) & 0xFF; + return result; + } + + ///\brief Converts a long long to a char* + char * convertLongLong(long long int input) { + static char result[8]; + result[0] = (input >> 56) & 0xFF; + result[1] = (input >> 48) & 0xFF; + result[2] = (input >> 40) & 0xFF; + result[3] = (input >> 32) & 0xFF; + result[4] = (input >> 24) & 0xFF; + result[5] = (input >> 16) & 0xFF; + result[6] = (input >> 8) & 0xFF; + result[7] = (input) & 0xFF; + return result; + } + + + ///\brief Returns a unique identifier for a track + std::string Track::getIdentifier() { + std::stringstream result; + if (type == "") { + result << "metadata_" << trackID; + return result.str(); + } + result << type << "_"; + result << codec << "_"; + if (type == "audio") { + result << channels << "ch_"; + result << rate << "hz"; + } else if (type == "video") { + result << width << "x" << height << "_"; + result << (double)fpks / 1000 << "fps"; + } + return result.str(); + } + + ///\brief Returns a writable identifier for a track, to prevent overwrites on readout + std::string Track::getWritableIdentifier() { + std::stringstream result; + result << getIdentifier() << "_" << trackID; + return result.str(); + } + + ///\brief Determines the "packed" size of a track + int Track::getSendLen() { + int result = 146 + init.size() + codec.size() + type.size() + getWritableIdentifier().size(); + result += fragments.size() * 11; + result += keys.size() * 16; + if (keySizes.size()){ + result += 11 + (keySizes.size() * 4) + 4; + } + result += parts.size() * 9; + if (type == "audio") { + result += 49; + } else if (type == "video") { + result += 48; + } + if (missedFrags) { + result += 23; + } + return result; + } + + ///\brief Writes a pointer to the specified destination + /// + ///Does a memcpy and increases the destination pointer accordingly + static void writePointer(char *& p, const char * src, unsigned int len) { + memcpy(p, src, len); + p += len; + } + + ///\brief Writes a pointer to the specified destination + /// + ///Does a memcpy and increases the destination pointer accordingly + static void writePointer(char *& p, const std::string & src) { + writePointer(p, src.data(), src.size()); + } + + ///\brief Writes a track to a pointer + void Track::writeTo(char *& p) { + writePointer(p, convertShort(getWritableIdentifier().size()), 2); + writePointer(p, getWritableIdentifier()); + writePointer(p, "\340", 1);//Begin track object + writePointer(p, "\000\011fragments\002", 12); + writePointer(p, convertInt(fragments.size() * 11), 4); + for (std::deque::iterator it = fragments.begin(); it != fragments.end(); it++) { + writePointer(p, it->getData(), 11); + } + writePointer(p, "\000\004keys\002", 7); + writePointer(p, convertInt(keys.size() * 16), 4); + for (std::deque::iterator it = keys.begin(); it != keys.end(); it++) { + writePointer(p, it->getData(), 16); + } + writePointer(p, "\000\010keysizes\002,", 11); + writePointer(p, convertInt(keySizes.size() * 4), 4); + std::string tmp; + tmp.reserve(keySizes.size() * 4); + for (unsigned int i = 0; i < keySizes.size(); i++){ + tmp += ((char)keySizes[i] >> 24); + tmp += ((char)keySizes[i] >> 16); + tmp += ((char)keySizes[i] >> 8); + tmp += ((char)keySizes[i]); + } + writePointer(p, tmp.data(), tmp.size()); + writePointer(p, "\000\005parts\002", 8); + writePointer(p, convertInt(parts.size() * 9), 4); + for (std::deque::iterator it = parts.begin(); it != parts.end(); it++) { + writePointer(p, it->getData(), 9); + } + writePointer(p, "\000\007trackid\001", 10); + writePointer(p, convertLongLong(trackID), 8); + if (missedFrags) { + writePointer(p, "\000\014missed_frags\001", 15); + writePointer(p, convertLongLong(missedFrags), 8); + } + writePointer(p, "\000\007firstms\001", 10); + writePointer(p, convertLongLong(firstms), 8); + writePointer(p, "\000\006lastms\001", 9); + writePointer(p, convertLongLong(lastms), 8); + writePointer(p, "\000\003bps\001", 6); + writePointer(p, convertLongLong(bps), 8); + writePointer(p, "\000\004init\002", 7); + writePointer(p, convertInt(init.size()), 4); + writePointer(p, init); + writePointer(p, "\000\005codec\002", 8); + writePointer(p, convertInt(codec.size()), 4); + writePointer(p, codec); + writePointer(p, "\000\004type\002", 7); + writePointer(p, convertInt(type.size()), 4); + writePointer(p, type); + if (type == "audio") { + writePointer(p, "\000\004rate\001", 7); + writePointer(p, convertLongLong(rate), 8); + writePointer(p, "\000\004size\001", 7); + writePointer(p, convertLongLong(size), 8); + writePointer(p, "\000\010channels\001", 11); + writePointer(p, convertLongLong(channels), 8); + } else if (type == "video") { + writePointer(p, "\000\005width\001", 8); + writePointer(p, convertLongLong(width), 8); + writePointer(p, "\000\006height\001", 9); + writePointer(p, convertLongLong(height), 8); + writePointer(p, "\000\004fpks\001", 7); + writePointer(p, convertLongLong(fpks), 8); + } + writePointer(p, "\000\000\356", 3);//End this track Object + } + + ///\brief Writes a track to a socket + void Track::send(Socket::Connection & conn) { + conn.SendNow(convertShort(getWritableIdentifier().size()), 2); + conn.SendNow(getWritableIdentifier()); + conn.SendNow("\340", 1);//Begin track object + conn.SendNow("\000\011fragments\002", 12); + conn.SendNow(convertInt(fragments.size() * 11), 4); + for (std::deque::iterator it = fragments.begin(); it != fragments.end(); it++) { + conn.SendNow(it->getData(), 11); + } + conn.SendNow("\000\004keys\002", 7); + conn.SendNow(convertInt(keys.size() * 16), 4); + for (std::deque::iterator it = keys.begin(); it != keys.end(); it++) { + conn.SendNow(it->getData(), 16); + } + conn.SendNow("\000\010keysizes\002,", 11); + conn.SendNow(convertInt(keySizes.size() * 4), 4); + std::string tmp; + tmp.reserve(keySizes.size() * 4); + for (unsigned int i = 0; i < keySizes.size(); i++){ + tmp += ((char)keySizes[i] >> 24); + tmp += ((char)keySizes[i] >> 16); + tmp += ((char)keySizes[i] >> 8); + tmp += ((char)keySizes[i]); + } + conn.SendNow(tmp.data(), tmp.size()); + conn.SendNow("\000\005parts\002", 8); + conn.SendNow(convertInt(parts.size() * 9), 4); + for (std::deque::iterator it = parts.begin(); it != parts.end(); it++) { + conn.SendNow(it->getData(), 9); + } + conn.SendNow("\000\007trackid\001", 10); + conn.SendNow(convertLongLong(trackID), 8); + if (missedFrags) { + conn.SendNow("\000\014missed_frags\001", 15); + conn.SendNow(convertLongLong(missedFrags), 8); + } + conn.SendNow("\000\007firstms\001", 10); + conn.SendNow(convertLongLong(firstms), 8); + conn.SendNow("\000\006lastms\001", 9); + conn.SendNow(convertLongLong(lastms), 8); + conn.SendNow("\000\003bps\001", 6); + conn.SendNow(convertLongLong(bps), 8); + conn.SendNow("\000\004init\002", 7); + conn.SendNow(convertInt(init.size()), 4); + conn.SendNow(init); + conn.SendNow("\000\005codec\002", 8); + conn.SendNow(convertInt(codec.size()), 4); + conn.SendNow(codec); + conn.SendNow("\000\004type\002", 7); + conn.SendNow(convertInt(type.size()), 4); + conn.SendNow(type); + if (type == "audio") { + conn.SendNow("\000\004rate\001", 7); + conn.SendNow(convertLongLong(rate), 8); + conn.SendNow("\000\004size\001", 7); + conn.SendNow(convertLongLong(size), 8); + conn.SendNow("\000\010channels\001", 11); + conn.SendNow(convertLongLong(channels), 8); + } else if (type == "video") { + conn.SendNow("\000\005width\001", 8); + conn.SendNow(convertLongLong(width), 8); + conn.SendNow("\000\006height\001", 9); + conn.SendNow(convertLongLong(height), 8); + conn.SendNow("\000\004fpks\001", 7); + conn.SendNow(convertLongLong(fpks), 8); + } + conn.SendNow("\000\000\356", 3);//End this track Object + } + + ///\brief Determines the "packed" size of a meta object + unsigned int Meta::getSendLen() { + unsigned int dataLen = 16 + (vod ? 14 : 0) + (live ? 15 : 0) + (merged ? 17 : 0) + (bufferWindow ? 24 : 0) + 21; + for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++) { + dataLen += it->second.getSendLen(); + } + return dataLen + 8; //add 8 bytes header + } + + ///\brief Writes a meta object to a pointer + void Meta::writeTo(char * p) { + int dataLen = getSendLen() - 8; //strip 8 bytes header + writePointer(p, DTSC::Magic_Header, 4); + writePointer(p, convertInt(dataLen), 4); + writePointer(p, "\340\000\006tracks\340", 10); + for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++) { + it->second.writeTo(p); + } + writePointer(p, "\000\000\356", 3);//End tracks object + if (vod) { + writePointer(p, "\000\003vod\001", 6); + writePointer(p, convertLongLong(1), 8); + } + if (live) { + writePointer(p, "\000\004live\001", 7); + writePointer(p, convertLongLong(1), 8); + } + if (merged) { + writePointer(p, "\000\006merged\001", 9); + writePointer(p, convertLongLong(1), 8); + } + if (bufferWindow) { + writePointer(p, "\000\015buffer_window\001", 16); + writePointer(p, convertLongLong(bufferWindow), 8); + } + writePointer(p, "\000\012moreheader\001", 13); + writePointer(p, convertLongLong(moreheader), 8); + writePointer(p, "\000\000\356", 3);//End global object + } + + ///\brief Writes a meta object to a socket + void Meta::send(Socket::Connection & conn) { + int dataLen = getSendLen() - 8; //strip 8 bytes header + conn.SendNow(DTSC::Magic_Header, 4); + conn.SendNow(convertInt(dataLen), 4); + conn.SendNow("\340\000\006tracks\340", 10); + for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++) { + it->second.send(conn); + } + conn.SendNow("\000\000\356", 3);//End tracks object + if (vod) { + conn.SendNow("\000\003vod\001", 6); + conn.SendNow(convertLongLong(1), 8); + } + if (live) { + conn.SendNow("\000\004live\001", 7); + conn.SendNow(convertLongLong(1), 8); + } + if (merged) { + conn.SendNow("\000\006merged\001", 9); + conn.SendNow(convertLongLong(1), 8); + } + if (bufferWindow) { + conn.SendNow("\000\015buffer_window\001", 16); + conn.SendNow(convertLongLong(bufferWindow), 8); + } + conn.SendNow("\000\012moreheader\001", 13); + conn.SendNow(convertLongLong(moreheader), 8); + conn.SendNow("\000\000\356", 3);//End global object + } + + ///\brief Converts a track to a JSON::Value + JSON::Value Track::toJSON() { + JSON::Value result; + std::string tmp; + tmp.reserve(fragments.size() * 11); + for (std::deque::iterator it = fragments.begin(); it != fragments.end(); it++) { + tmp.append(it->getData(), 11); + } + result["fragments"] = tmp; + tmp = ""; + tmp.reserve(keys.size() * 16); + for (std::deque::iterator it = keys.begin(); it != keys.end(); it++) { + tmp.append(it->getData(), 16); + } + result["keys"] = tmp; + tmp = ""; + tmp.reserve(keySizes.size() * 4); + for (unsigned int i = 0; i < keySizes.size(); i++){ + tmp += ((char)(keySizes[i] >> 24)); + tmp += ((char)(keySizes[i] >> 16)); + tmp += ((char)(keySizes[i] >> 8)); + tmp += ((char)keySizes[i]); + } + result["keysizes"] = tmp; + tmp = ""; + tmp.reserve(parts.size() * 9); + for (std::deque::iterator it = parts.begin(); it != parts.end(); it++) { + tmp.append(it->getData(), 9); + } + result["parts"] = tmp; + result["trackid"] = trackID; + result["firstms"] = (long long)firstms; + result["lastms"] = (long long)lastms; + result["bps"] = bps; + if (missedFrags) { + result["missed_frags"] = missedFrags; + } + result["codec"] = codec; + result["type"] = type; + result["init"] = init; + if (type == "audio") { + result["rate"] = rate; + result["size"] = size; + result["channels"] = channels; + } else if (type == "video") { + result["width"] = width; + result["height"] = height; + result["fpks"] = fpks; + } + return result; + } + + ///\brief Converts a meta object to a JSON::Value + JSON::Value Meta::toJSON() { + JSON::Value result; + for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++) { + result["tracks"][it->second.getWritableIdentifier()] = it->second.toJSON(); + } + if (vod) { + result["vod"] = 1ll; + } + if (live) { + result["live"] = 1ll; + } + if (merged) { + result["merged"] = 1ll; + } + if (bufferWindow) { + result["buffer_window"] = bufferWindow; + } + result["moreheader"] = moreheader; + return result; + } + + ///\brief Converts a meta object to a human readable string + ///\param str The stringstream to append to + ///\param indent the amount of indentation needed + ///\param verbosity How verbose the output needs to be + void Meta::toPrettyString(std::ostream & str, int indent, int verbosity) { + for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++) { + it->second.toPrettyString(str, indent, verbosity); + } + if (vod) { + str << std::string(indent, ' ') << "Video on Demand" << std::endl; + } + if (live) { + str << std::string(indent, ' ') << "Live" << std::endl; + } + if (merged) { + str << std::string(indent, ' ') << "Merged file" << std::endl; + } + if (bufferWindow) { + str << std::string(indent, ' ') << "Buffer Window: " << bufferWindow << std::endl; + } + str << std::string(indent, ' ') << "More Header: " << moreheader << std::endl; + } + + ///\brief Resets a meta object, removes all unimportant meta values + void Meta::reset() { + for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++) { + it->second.reset(); + } + } +} + + + diff --git a/lib/filesystem.cpp b/lib/filesystem.cpp new file mode 100644 index 00000000..c2e9b76d --- /dev/null +++ b/lib/filesystem.cpp @@ -0,0 +1,318 @@ +#include "filesystem.h" +#include "defines.h" + +Filesystem::Directory::Directory(std::string PathName, std::string BasePath) { + MyBase = BasePath; + if (PathName[0] == '/') { + PathName.erase(0, 1); + } + if (BasePath[BasePath.size() - 1] != '/') { + BasePath += "/"; + } + MyPath = PathName; + FillEntries(); +} + +Filesystem::Directory::~Directory() { +} + +void Filesystem::Directory::FillEntries() { + ValidDir = true; + struct stat StatBuf; + Entries.clear(); + DIR * Dirp = opendir((MyBase + MyPath).c_str()); + if (!Dirp) { + ValidDir = false; + } else { + dirent * entry; + while ((entry = readdir(Dirp))) { + if (stat((MyBase + MyPath + "/" + entry->d_name).c_str(), &StatBuf) == -1) { + DEBUG_MSG(DLVL_DEVEL, "Skipping %s, reason %s", entry->d_name, strerror(errno)); + continue; + } + ///Convert stat to string + Entries[std::string(entry->d_name)] = StatBuf; + } + } +} + +void Filesystem::Directory::Print() { + /// \todo Remove? Libraries shouldn't print stuff. + if (!ValidDir) { + DEBUG_MSG(DLVL_ERROR, "%s is not a valid directory", (MyBase + MyPath).c_str()); + return; + } + printf("%s:\n", (MyBase + MyPath).c_str()); + for (std::map::iterator it = Entries.begin(); it != Entries.end(); it++) { + printf("\t%s\n", (*it).first.c_str()); + } + printf("\n"); +} + +bool Filesystem::Directory::IsDir() { + return ValidDir; +} + +std::string Filesystem::Directory::PWD() { + return "/" + MyPath; +} + +std::string Filesystem::Directory::LIST(std::vector ActiveStreams) { + FillEntries(); + int MyPermissions; + std::stringstream Converter; + passwd * pwd; //For Username + group * grp; //For Groupname + tm * tm; //For time localisation + char datestring[256]; //For time localisation + + std::string MyLoc = MyBase + MyPath; + if (MyLoc[MyLoc.size() - 1] != '/') { + MyLoc += "/"; + } + + for (std::map::iterator it = Entries.begin(); it != Entries.end(); it++) { + + bool Active = (std::find(ActiveStreams.begin(), ActiveStreams.end(), (*it).first) != ActiveStreams.end()); + if ((Active && (MyVisible[MyPath] & S_ACTIVE)) || ((!Active) && (MyVisible[MyPath] & S_INACTIVE)) || (((*it).second.st_mode / 010000) == 4)) { + if (((*it).second.st_mode / 010000) == 4) { + Converter << 'd'; + } else { + Converter << '-'; + } + MyPermissions = (((*it).second.st_mode % 010000) / 0100); + if (MyPermissions & 4) { + Converter << 'r'; + } else { + Converter << '-'; + } + if (MyPermissions & 2) { + Converter << 'w'; + } else { + Converter << '-'; + } + if (MyPermissions & 1) { + Converter << 'x'; + } else { + Converter << '-'; + } + MyPermissions = (((*it).second.st_mode % 0100) / 010); + if (MyPermissions & 4) { + Converter << 'r'; + } else { + Converter << '-'; + } + if (MyPermissions & 2) { + Converter << 'w'; + } else { + Converter << '-'; + } + if (MyPermissions & 1) { + Converter << 'x'; + } else { + Converter << '-'; + } + MyPermissions = ((*it).second.st_mode % 010); + if (MyPermissions & 4) { + Converter << 'r'; + } else { + Converter << '-'; + } + if (MyPermissions & 2) { + Converter << 'w'; + } else { + Converter << '-'; + } + if (MyPermissions & 1) { + Converter << 'x'; + } else { + Converter << '-'; + } + Converter << ' '; + Converter << (*it).second.st_nlink; + Converter << ' '; + if ((pwd = getpwuid((*it).second.st_uid))) { + Converter << pwd->pw_name; + } else { + Converter << (*it).second.st_uid; + } + Converter << ' '; + if ((grp = getgrgid((*it).second.st_gid))) { + Converter << grp->gr_name; + } else { + Converter << (*it).second.st_gid; + } + Converter << ' '; + Converter << (*it).second.st_size; + Converter << ' '; + tm = localtime(&((*it).second.st_mtime)); + strftime(datestring, sizeof(datestring), "%b %d %H:%M", tm); + Converter << datestring; + Converter << ' '; + Converter << (*it).first; + Converter << '\n'; + } + } + return Converter.str(); +} + +bool Filesystem::Directory::CWD(std::string Path) { + if (Path[0] == '/') { + Path.erase(0, 1); + MyPath = Path; + } else { + if (MyPath != "") { + MyPath += "/"; + } + MyPath += Path; + } + FillEntries(); + printf("New Path: %s\n", MyPath.c_str()); + if (MyPermissions.find(MyPath) != MyPermissions.end()) { + printf("\tPermissions: %d\n", MyPermissions[MyPath]); + } + return SimplifyPath(); +} + +bool Filesystem::Directory::CDUP() { + return CWD(".."); +} + +std::string Filesystem::Directory::RETR(std::string Path) { + std::string Result; + std::string FileName; + if (Path[0] == '/') { + Path.erase(0, 1); + FileName = MyBase + Path; + } else { + FileName = MyBase + MyPath + "/" + Path; + } + std::ifstream File; + File.open(FileName.c_str()); + while (File.good()) { + Result += File.get(); + } + File.close(); + return Result; +} + +void Filesystem::Directory::STOR(std::string Path, std::string Data) { + if (MyPermissions.find(MyPath) == MyPermissions.end() || (MyPermissions[MyPath] & P_STOR)) { + std::string FileName; + if (Path[0] == '/') { + Path.erase(0, 1); + FileName = MyBase + Path; + } else { + FileName = MyBase + MyPath + "/" + Path; + } + std::ofstream File; + File.open(FileName.c_str()); + File << Data; + File.close(); + } +} + +bool Filesystem::Directory::SimplifyPath() { + MyPath += "/"; + std::vector TempPath; + std::string TempString; + for (std::string::iterator it = MyPath.begin(); it != MyPath.end(); it++) { + if ((*it) == '/') { + if (TempString == "..") { + if (!TempPath.size()) { + return false; + } + TempPath.erase((TempPath.end() - 1)); + } else if (TempString != "." && TempString != "") { + TempPath.push_back(TempString); + } + TempString = ""; + } else { + TempString += (*it); + } + } + MyPath = ""; + for (std::vector::iterator it = TempPath.begin(); it != TempPath.end(); it++) { + MyPath += (*it); + if (it != (TempPath.end() - 1)) { + MyPath += "/"; + } + } + if (MyVisible.find(MyPath) == MyVisible.end()) { + MyVisible[MyPath] = S_ALL; + } + return true; +} + +bool Filesystem::Directory::DELE(std::string Path) { + if (MyPermissions.find(MyPath) == MyPermissions.end() || (MyPermissions[MyPath] & P_DELE)) { + std::string FileName; + if (Path[0] == '/') { + Path.erase(0, 1); + FileName = MyBase + Path; + } else { + FileName = MyBase + MyPath + "/" + Path; + } + if (std::remove(FileName.c_str())) { + DEBUG_MSG(DLVL_ERROR, "Removing file %s failed", FileName.c_str()); + return false; + } + return true; + } + return false; +} + +bool Filesystem::Directory::MKD(std::string Path) { + std::string FileName; + if (Path[0] == '/') { + Path.erase(0, 1); + FileName = MyBase + Path; + } else { + FileName = MyBase + MyPath + "/" + Path; + } + if (mkdir(FileName.c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { + DEBUG_MSG(DLVL_ERROR, "Creating directory %s failed", FileName.c_str()); + return false; + } + MyVisible[FileName] = S_ALL; + return true; +} + +bool Filesystem::Directory::Rename(std::string From, std::string To) { + if (MyPermissions.find(MyPath) == MyPermissions.end() || (MyPermissions[MyPath] & P_RNFT)) { + std::string FileFrom; + if (From[0] == '/') { + From.erase(0, 1); + FileFrom = MyBase + From; + } else { + FileFrom = MyBase + MyPath + "/" + From; + } + std::string FileTo; + if (To[0] == '/') { + FileTo = MyBase + To; + } else { + FileTo = MyBase + MyPath + "/" + To; + } + if (std::rename(FileFrom.c_str(), FileTo.c_str())) { + DEBUG_MSG(DLVL_ERROR, "Renaming %s to %s failed", FileFrom.c_str(), FileTo.c_str()); + return false; + } + return true; + } + return false; +} + +void Filesystem::Directory::SetPermissions(std::string Path, char Permissions) { + MyPermissions[Path] = Permissions; +} + +bool Filesystem::Directory::HasPermission(char Permission) { + if (MyPermissions.find(MyPath) == MyPermissions.end() || (MyPermissions[MyPath] & Permission)) { + return true; + } + return false; +} + +void Filesystem::Directory::SetVisibility(std::string Pathname, char Visible) { + MyVisible[Pathname] = Visible; +} diff --git a/lib/filesystem.h b/lib/filesystem.h new file mode 100644 index 00000000..c901fcc2 --- /dev/null +++ b/lib/filesystem.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Filesystem { + enum DIR_Permissions { + P_LIST = 0x01, //List + P_RETR = 0x02, //Retrieve + P_STOR = 0x04, //Store + P_RNFT = 0x08, //Rename From/To + P_DELE = 0x10, //Delete + P_MKD = 0x20, //Make directory + P_RMD = 0x40, //Remove directory + }; + + enum DIR_Show { + S_NONE = 0x00, S_ACTIVE = 0x01, S_INACTIVE = 0x02, S_ALL = 0x03, + }; + + class Directory { + public: + Directory(std::string PathName = "", std::string BasePath = "."); + ~Directory(); + void Print(); + bool IsDir(); + std::string PWD(); + std::string LIST(std::vector ActiveStreams = std::vector()); + bool CWD(std::string Path); + bool CDUP(); + bool DELE(std::string Path); + bool MKD(std::string Path); + std::string RETR(std::string Path); + void STOR(std::string Path, std::string Data); + bool Rename(std::string From, std::string To); + void SetPermissions(std::string PathName, char Permissions); + bool HasPermission(char Permission); + void SetVisibility(std::string Pathname, char Visible); + private: + bool ValidDir; + bool SimplifyPath(); + void FillEntries(); + std::string MyBase; + std::string MyPath; + std::map Entries; + std::map MyPermissions; + std::map MyVisible; + }; +//Directory Class +}//Filesystem namespace diff --git a/lib/flv_tag.cpp b/lib/flv_tag.cpp new file mode 100644 index 00000000..5437f4ea --- /dev/null +++ b/lib/flv_tag.cpp @@ -0,0 +1,1264 @@ +/// \file flv_tag.cpp +/// Holds all code for the FLV namespace. + +#include "amf.h" +#include "rtmpchunks.h" +#include "flv_tag.h" +#include "timing.h" +#include //for Tag::FileLoader +#include //for Tag::FileLoader +#include //for Tag::FileLoader +#include //malloc +#include //memcpy +#include + +/// Holds the last FLV header parsed. +/// Defaults to a audio+video header on FLV version 0x01 if no header received yet. +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; +} + +const char * FLV::Tag::getVideoCodec() { + switch (data[11] & 0x0F) { + case 1: + return "JPEG"; + case 2: + return "H263"; + case 3: + return "ScreenVideo1"; + case 4: + return "VP6"; + case 5: + return "VP6Alpha"; + case 6: + return "ScreenVideo2"; + case 7: + return "H264"; + default: + return "unknown"; + } +} + +const char * FLV::Tag::getAudioCodec() { + switch (data[11] & 0xF0) { + case 0x00: + return "PCMPE"; + case 0x10: + return "ADPCM"; + case 0x20: + return "MP3"; + case 0x30: + return "PCM"; + case 0x40: + case 0x50: + case 0x60: + return "Nellymoser"; + case 0x70: + return "G711A"; + case 0x80: + return "G711mu"; + case 0x90: + return "reserved"; + case 0xA0: + return "AAC"; + case 0xB0: + return "Speex"; + case 0xE0: + return "MP3"; + case 0xF0: + return "DeviceSpecific"; + default: + return "unknown"; + } +} + +/// 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: + R << getVideoCodec() << " 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: + R << getAudioCodec(); + 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 24-bit offset of this tag. +/// Returns 0 if the tag isn't H264 +int FLV::Tag::offset() { + if ((data[11] & 0x0F) == 7) { + return (((data[13] << 16) + (data[14] << 8) + data[15]) << 8) >> 8; + } else { + return 0; + } +} //offset getter + +/// Sets the 24-bit offset of this tag. +/// Ignored if the tag isn't H264 +void FLV::Tag::offset(int o) { + data[13] = (o >> 16) & 0xFF; + data[14] = (o >> 8) & 0XFF; + data[15] = o & 0xFF; +} //offset setter + +/// 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; + len = O.len; + data = 0; + if (len > 0) { + if (checkBufferSize()) { + memcpy(data, O.data, len); + } + } + 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); +} + +/// Generic destructor that frees the allocated memory in the internal data variable, if any. +FLV::Tag::~Tag() { + if (data) { + free(data); + data = 0; + buf = 0; + len = 0; + } +} + +/// 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 (checkBufferSize()) { + memcpy(data, O.data, len); + } else { + len = buf; + } + } + 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) { + std::string tmp = S.getPacket().toNetPacked(); + DTSC::Packet tmpPack(tmp.data(), tmp.size()); + return DTSCLoader(tmpPack, S.metadata.tracks[S.getPacket()["trackid"].asInt()]); +} + +bool FLV::Tag::DTSCLoader(DTSC::Packet & packData, DTSC::Track & track) { + std::string meta_str; + len = 0; + if (track.type == "video") { + char * tmpData = 0; + unsigned int tmpLen = 0; + packData.getString("data", tmpData, tmpLen); + len = tmpLen + 16; + if (track.codec == "H264") { + len += 4; + } + if (!checkBufferSize()) { + return false; + } + if (track.codec == "H264") { + memcpy(data + 16, tmpData, len - 20); + data[12] = 1; + offset(packData.getInt("offset")); + } else { + memcpy(data + 12, tmpData, len - 16); + } + data[11] = 0; + if (track.codec == "H264") { + data[11] |= 7; + } + if (track.codec == "ScreenVideo2") { + data[11] |= 6; + } + if (track.codec == "VP6Alpha") { + data[11] |= 5; + } + if (track.codec == "VP6") { + data[11] |= 4; + } + if (track.codec == "ScreenVideo1") { + data[11] |= 3; + } + if (track.codec == "H263") { + data[11] |= 2; + } + if (track.codec == "JPEG") { + data[11] |= 1; + } + if (packData.getFlag("keyframe")) { + data[11] |= 0x10; + } else { + data[11] |= 0x20; + } + if (packData.getFlag("disposableframe")) { + data[11] |= 0x30; + } + } + if (track.type == "audio") { + char * tmpData = 0; + unsigned int tmpLen = 0; + packData.getString("data", tmpData, tmpLen); + len = tmpLen + 16; + if (track.codec == "AAC") { + len ++; + } + if (!checkBufferSize()) { + return false; + } + if (track.codec == "AAC") { + memcpy(data + 13, tmpData, len - 17); + data[12] = 1; //raw AAC data, not sequence header + } else { + memcpy(data + 12, tmpData, len - 16); + } + unsigned int datarate = track.rate; + data[11] = 0; + if (track.codec == "AAC") { + data[11] |= 0xA0; + } + if (track.codec == "MP3") { + if (datarate == 8000){ + data[11] |= 0xE0; + }else{ + data[11] |= 0x20; + } + } + if (track.codec == "ADPCM") { + data[11] |= 0x10; + } + if (track.codec == "PCM") { + data[11] |= 0x30; + } + if (track.codec == "Nellymoser") { + if (datarate == 8000){ + data[11] |= 0x50; + }else if(datarate == 16000){ + data[11] |= 0x40; + }else{ + data[11] |= 0x60; + } + } + if (track.codec == "G711a") { + data[11] |= 0x70; + } + if (track.codec == "G711mu") { + data[11] |= 0x80; + } + if (track.codec == "Speex") { + data[11] |= 0xB0; + } + if (datarate >= 44100) { + data[11] |= 0x0C; + } else if (datarate >= 22050) { + data[11] |= 0x08; + } else if (datarate >= 11025) { + data[11] |= 0x04; + } + if (track.size == 16) { + data[11] |= 0x02; + } + if (track.channels > 1) { + data[11] |= 0x01; + } + } + if (!len) { + return false; + } + setLen(); + if (track.type == "video") { + data[0] = 0x09; + } + if (track.type == "audio") { + data[0] = 0x08; + } + if (track.type == "meta") { + 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(packData.getTime()); + 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 JSON. +bool FLV::Tag::DTSCVideoInit(DTSC::Track & video) { + //Unknown? Assume H264. + len = 0; + if (video.codec == "?") { + video.codec = "H264"; + } + if (video.codec == "H264") { + len = video.init.size() + 20; + } + if (len <= 0 || !checkBufferSize()) { + return false; + } + memcpy(data + 16, video.init.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 JSON. +bool FLV::Tag::DTSCAudioInit(DTSC::Track & audio) { + len = 0; + //Unknown? Assume AAC. + if (audio.codec == "?") { + audio.codec = "AAC"; + } + if (audio.codec == "AAC") { + len = audio.init.size() + 17; + } + if (len <= 0 || !checkBufferSize()) { + return false; + } + memcpy(data + 13, audio.init.c_str(), len - 17); + data[12] = 0; //AAC sequence header + data[11] = 0; + if (audio.codec == "AAC") { + data[11] += 0xA0; + } + if (audio.codec == "MP3") { + data[11] += 0x20; + } + unsigned int datarate = audio.rate; + if (datarate >= 44100) { + data[11] += 0x0C; + } else if (datarate >= 22050) { + data[11] += 0x08; + } else if (datarate >= 11025) { + data[11] += 0x04; + } + if (audio.size == 16) { + data[11] += 0x02; + } + if (audio.channels > 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; +} + +bool FLV::Tag::DTSCMetaInit(DTSC::Meta & M, std::set & selTracks) { + AMF::Object amfdata("root", AMF::AMF0_DDV_CONTAINER); + amfdata.addContent(AMF::Object("", "onMetaData")); + amfdata.addContent(AMF::Object("", AMF::AMF0_ECMA_ARRAY)); + AMF::Object trinfo = AMF::Object("trackinfo", AMF::AMF0_STRICT_ARRAY); + int i = 0; + unsigned long long mediaLen = 0; + for (std::set::iterator it = selTracks.begin(); it != selTracks.end(); it++) { + if (M.tracks[*it].lastms - M.tracks[*it].firstms > mediaLen) { + mediaLen = M.tracks[*it].lastms - M.tracks[*it].firstms; + } + if (M.tracks[*it].type == "video") { + trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT)); + trinfo.getContentP(i)->addContent(AMF::Object("length", ((double)M.tracks[*it].lastms / 1000) * ((double)M.tracks[*it].fpks / 1000.0), AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->addContent(AMF::Object("timescale", ((double)M.tracks[*it].fpks / 1000.0), AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); + amfdata.getContentP(1)->addContent(AMF::Object("hasVideo", 1, AMF::AMF0_BOOL)); + if (M.tracks[*it].codec == "H264") { + amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 7, AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"avc1")); + } + if (M.tracks[*it].codec == "ScreenVideo2") { + amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 6, AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"sv2")); + } + if (M.tracks[*it].codec == "VP6Alpha") { + amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 5, AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"vp6a")); + } + if (M.tracks[*it].codec == "VP6") { + amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 4, AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"vp6")); + } + if (M.tracks[*it].codec == "ScreenVideo1") { + amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 3, AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"sv1")); + } + if (M.tracks[*it].codec == "H263") { + amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 2, AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"h263")); + } + if (M.tracks[*it].codec == "JPEG") { + amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 1, AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"jpeg")); + } + amfdata.getContentP(1)->addContent(AMF::Object("width", M.tracks[*it].width, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("height", M.tracks[*it].height, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("videoframerate", (double)M.tracks[*it].fpks / 1000.0, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("videodatarate", (double)M.tracks[*it].bps * 128.0, AMF::AMF0_NUMBER)); + ++i; + } + if (M.tracks[*it].type == "audio") { + trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT)); + trinfo.getContentP(i)->addContent(AMF::Object("length", ((double)M.tracks[*it].lastms) * ((double)M.tracks[*it].rate), AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->addContent(AMF::Object("timescale", M.tracks[*it].rate, AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); + amfdata.getContentP(1)->addContent(AMF::Object("hasAudio", 1, AMF::AMF0_BOOL)); + amfdata.getContentP(1)->addContent(AMF::Object("audiodelay", 0, AMF::AMF0_NUMBER)); + if (M.tracks[*it].codec == "AAC") { + amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", (std::string)"mp4a")); + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"mp4a")); + } + if (M.tracks[*it].codec == "MP3") { + amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", (std::string)"mp3")); + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"mp3")); + } + amfdata.getContentP(1)->addContent(AMF::Object("audiochannels", M.tracks[*it].channels, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("audiosamplerate", M.tracks[*it].rate, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("audiosamplesize", M.tracks[*it].size, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("audiodatarate", (double)M.tracks[*it].bps * 128.0, AMF::AMF0_NUMBER)); + ++i; + } + } + if (M.vod) { + amfdata.getContentP(1)->addContent(AMF::Object("duration", mediaLen / 1000, AMF::AMF0_NUMBER)); + } + amfdata.getContentP(1)->addContent(trinfo); + + std::string tmp = amfdata.Pack(); + len = tmp.length() + 15; + if (len <= 0 || !checkBufferSize()) { + return false; + } + memcpy(data + 11, tmp.data(), 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 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, DTSC::Track & videoRef, DTSC::Track & audioRef) { + //Unknown? Assume AAC. + if (audioRef.codec == "?") { + audioRef.codec = "AAC"; + } + //Unknown? Assume H264. + if (videoRef.codec == "?") { + videoRef.codec = "H264"; + } + + AMF::Object amfdata("root", AMF::AMF0_DDV_CONTAINER); + + amfdata.addContent(AMF::Object("", "onMetaData")); + amfdata.addContent(AMF::Object("", AMF::AMF0_ECMA_ARRAY)); + if (S.metadata.vod) { + amfdata.getContentP(1)->addContent(AMF::Object("duration", videoRef.lastms / 1000, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("moovPosition", 40, AMF::AMF0_NUMBER)); + AMF::Object keys("keyframes", AMF::AMF0_OBJECT); + keys.addContent(AMF::Object("filepositions", AMF::AMF0_STRICT_ARRAY)); + keys.addContent(AMF::Object("times", AMF::AMF0_STRICT_ARRAY)); + int total_byterate = 0; + if (videoRef.trackID > 0) { + total_byterate += videoRef.bps; + } + if (audioRef.trackID > 0) { + total_byterate += audioRef.bps; + } + for (unsigned long long i = 0; i < videoRef.lastms / 1000; ++i) { //for each second in the file + keys.getContentP(0)->addContent(AMF::Object("", i * total_byterate, AMF::AMF0_NUMBER)); //multiply by byterate for fake byte positions + keys.getContentP(1)->addContent(AMF::Object("", i, AMF::AMF0_NUMBER)); //seconds + } + amfdata.getContentP(1)->addContent(keys); + } + if (videoRef.trackID > 0) { + amfdata.getContentP(1)->addContent(AMF::Object("hasVideo", 1, AMF::AMF0_BOOL)); + if (videoRef.codec == "H264") { + amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", (std::string)"avc1")); + } + if (videoRef.codec == "VP6") { + amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 4, AMF::AMF0_NUMBER)); + } + if (videoRef.codec == "H263") { + amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 2, AMF::AMF0_NUMBER)); + } + amfdata.getContentP(1)->addContent(AMF::Object("width", videoRef.width, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("height", videoRef.height, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("videoframerate", (double)videoRef.fpks / 1000.0, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("videodatarate", (double)videoRef.bps * 128.0, AMF::AMF0_NUMBER)); + } + if (audioRef.trackID > 0) { + amfdata.getContentP(1)->addContent(AMF::Object("hasAudio", 1, AMF::AMF0_BOOL)); + amfdata.getContentP(1)->addContent(AMF::Object("audiodelay", 0, AMF::AMF0_NUMBER)); + if (audioRef.codec == "AAC") { + amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", (std::string)"mp4a")); + } + if (audioRef.codec == "MP3") { + amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", (std::string)"mp3")); + } + amfdata.getContentP(1)->addContent(AMF::Object("audiochannels", audioRef.channels, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("audiosamplerate", audioRef.rate, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("audiosamplesize", audioRef.size, AMF::AMF0_NUMBER)); + amfdata.getContentP(1)->addContent(AMF::Object("audiodatarate", (double)audioRef.bps * 128.0, AMF::AMF0_NUMBER)); + } + AMF::Object trinfo = AMF::Object("trackinfo", AMF::AMF0_STRICT_ARRAY); + int i = 0; + if (audioRef) { + trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT)); + trinfo.getContentP(i)->addContent(AMF::Object("length", ((double)audioRef.lastms) * ((double)audioRef.rate), AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->addContent(AMF::Object("timescale", audioRef.rate, AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); + if (audioRef.codec == "AAC") { + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"mp4a")); + } + if (audioRef.codec == "MP3") { + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"mp3")); + } + ++i; + } + if (videoRef) { + trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT)); + trinfo.getContentP(i)->addContent( + AMF::Object("length", ((double)videoRef.lastms / 1000) * ((double)videoRef.fpks / 1000.0), AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->addContent(AMF::Object("timescale", ((double)videoRef.fpks / 1000.0), AMF::AMF0_NUMBER)); + trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); + if (videoRef.codec == "H264") { + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"avc1")); + } + if (videoRef.codec == "VP6") { + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"vp6")); + } + if (videoRef.codec == "H263") { + trinfo.getContentP(i)->getContentP(2)->addContent(AMF::Object("sampletype", (std::string)"h263")); + } + ++i; + } + amfdata.getContentP(1)->addContent(trinfo); + + std::string tmp = amfdata.Pack(); + len = tmp.length() + 15; + if (len <= 0 || !checkBufferSize()) { + return false; + } + memcpy(data + 11, tmp.data(), 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 (!checkBufferSize()) { + return false; + } + 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 (len < 15) { + len = 15; + } + if (!checkBufferSize()) { + return false; + } + 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 (!checkBufferSize()) { + return false; + } + 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::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 (len < 15) { + len = 15; + } + if (!checkBufferSize()) { + return false; + } + + 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 { + Util::sleep(100);//sleep 100ms + } + } else { + //if a tag header, calculate length and read tag body + len = data[3] + 15; + len += (data[2] << 8); + len += (data[1] << 16); + if (!checkBufferSize()) { + return false; + } + 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 { + Util::sleep(100);//sleep 100ms + } + } 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; + } else { + Util::sleep(100);//sleep 100ms + } + } + fcntl(fileno(f), F_SETFL, preflags); + return false; +} //FLV_GetPacket + +JSON::Value FLV::Tag::toJSON(DTSC::Meta & metadata) { + JSON::Value pack_out; // Storage for outgoing metadata. + + if (data[0] == 0x12) { + AMF::Object meta_in = AMF::parse((unsigned char *)data + 11, len - 15); + AMF::Object * tmp = 0; + if (meta_in.getContentP(1) && meta_in.getContentP(0) && (meta_in.getContentP(0)->StrValue() == "onMetaData")) { + tmp = meta_in.getContentP(1); + } else { + if (meta_in.getContentP(2) && meta_in.getContentP(1) && (meta_in.getContentP(1)->StrValue() == "onMetaData")) { + tmp = meta_in.getContentP(2); + } + } + if (tmp) { + if (tmp->getContentP("width")) { + metadata.tracks[1].width = (long long int)tmp->getContentP("width")->NumValue(); + } else { + metadata.tracks[1].width = 0; + } + if (tmp->getContentP("height")) { + metadata.tracks[1].height = (long long int)tmp->getContentP("height")->NumValue(); + } else { + metadata.tracks[1].height = 0; + } + if (tmp->getContentP("videoframerate")) { + if (tmp->getContentP("videoframerate")->NumValue()){ + metadata.tracks[1].fpks = (long long int)(tmp->getContentP("videoframerate")->NumValue() * 1000.0); + }else{ + metadata.tracks[1].fpks = JSON::Value(tmp->getContentP("videoframerate")->StrValue()).asInt() * 1000.0; + } + } else { + metadata.tracks[1].fpks = 0; + } + if (tmp->getContentP("videodatarate")) { + metadata.tracks[1].bps = (long long int)(tmp->getContentP("videodatarate")->NumValue() * 1024) / 8; + } else { + metadata.tracks[1].bps = 0; + } + if (tmp->getContentP("audiodatarate")) { + metadata.tracks[2].bps = (long long int)(tmp->getContentP("audiodatarate")->NumValue() * 1024) / 8; + } else { + metadata.tracks[2].bps = 0; + } + if (tmp->getContentP("audiosamplerate")) { + metadata.tracks[2].rate = (long long int)tmp->getContentP("audiosamplerate")->NumValue(); + } else { + metadata.tracks[2].rate = 0; + } + if (tmp->getContentP("audiosamplesize")) { + metadata.tracks[2].size = (long long int)tmp->getContentP("audiosamplesize")->NumValue(); + } else { + metadata.tracks[2].size = 0; + } + if (tmp->getContentP("stereo")) { + if (tmp->getContentP("stereo")->NumValue() == 1) { + metadata.tracks[2].channels = 2; + } else { + metadata.tracks[2].channels = 1; + } + } else { + metadata.tracks[2].channels = 1; + } + for (int i = 0; i < tmp->hasContent(); ++i) { + if (tmp->getContentP(i)->Indice() == "videocodecid" || tmp->getContentP(i)->Indice() == "audiocodecid" || tmp->getContentP(i)->Indice() == "width" || tmp->getContentP(i)->Indice() == "height" || tmp->getContentP(i)->Indice() == "videodatarate" || tmp->getContentP(i)->Indice() == "videoframerate" || tmp->getContentP(i)->Indice() == "audiodatarate" || tmp->getContentP(i)->Indice() == "audiosamplerate" || tmp->getContentP(i)->Indice() == "audiosamplesize" || tmp->getContentP(i)->Indice() == "audiochannels") { + continue; + } + if (tmp->getContentP(i)->NumValue()) { + pack_out["data"][tmp->getContentP(i)->Indice()] = (long long)tmp->getContentP(i)->NumValue(); + } else { + if (tmp->getContentP(i)->StrValue() != "") { + pack_out["data"][tmp->getContentP(i)->Indice()] = tmp->getContentP(i)->StrValue(); + } + } + } + if (pack_out) { + pack_out["datatype"] = "meta"; + pack_out["time"] = tagTime(); + } + } + return pack_out; //empty + } + if (data[0] == 0x08) { + char audiodata = data[11]; + metadata.tracks[2].trackID = 2; + metadata.tracks[2].type = "audio"; + if (metadata.tracks[2].codec == "") { + metadata.tracks[2].codec = getAudioCodec(); + } + if (!metadata.tracks[2].rate) { + switch (audiodata & 0x0C) { + case 0x0: + metadata.tracks[2].rate = 5512; + break; + case 0x4: + metadata.tracks[2].rate = 11025; + break; + case 0x8: + metadata.tracks[2].rate = 22050; + break; + case 0xC: + metadata.tracks[2].rate = 44100; + break; + } + } + if (!metadata.tracks[2].size) { + switch (audiodata & 0x02) { + case 0x0: + metadata.tracks[2].size = 8; + break; + case 0x2: + metadata.tracks[2].size = 16; + break; + } + } + if (!metadata.tracks[2].channels) { + switch (audiodata & 0x01) { + case 0x0: + metadata.tracks[2].channels = 1; + break; + case 0x1: + metadata.tracks[2].channels = 2; + break; + } + } + if (needsInitData() && isInitData()) { + if ((audiodata & 0xF0) == 0xA0) { + metadata.tracks[2].init = std::string((char *)data + 13, (size_t)len - 17); + } else { + metadata.tracks[2].init = std::string((char *)data + 12, (size_t)len - 16); + } + return pack_out; //skip rest of parsing, get next tag. + } + pack_out["time"] = tagTime(); + pack_out["trackid"] = 2; + if ((audiodata & 0xF0) == 0xA0) { + if (len < 18) { + return JSON::Value(); + } + pack_out["data"] = std::string((char *)data + 13, (size_t)len - 17); + } else { + if (len < 17) { + return JSON::Value(); + } + pack_out["data"] = std::string((char *)data + 12, (size_t)len - 16); + } + return pack_out; + } + if (data[0] == 0x09) { + char videodata = data[11]; + if (metadata.tracks[1].codec == "") { + metadata.tracks[1].codec = getVideoCodec(); + } + metadata.tracks[1].type = "video"; + metadata.tracks[1].trackID = 1; + if (needsInitData() && isInitData()) { + if ((videodata & 0x0F) == 7) { + if (len < 21) { + return JSON::Value(); + } + metadata.tracks[1].init = std::string((char *)data + 16, (size_t)len - 20); + } else { + if (len < 17) { + return JSON::Value(); + } + metadata.tracks[1].init = std::string((char *)data + 12, (size_t)len - 16); + } + return pack_out; //skip rest of parsing, get next tag. + } + pack_out["trackid"] = 1; + switch (videodata & 0xF0) { + case 0x10: + pack_out["keyframe"] = 1; + break; + case 0x20: + pack_out["interframe"] = 1; + break; + case 0x30: + pack_out["disposableframe"] = 1; + break; + case 0x40: + pack_out["keyframe"] = 1; + break; + case 0x50: + return JSON::Value(); + break; //the video info byte we just throw away - useless to us... + } + pack_out["time"] = tagTime(); + if ((videodata & 0x0F) == 7) { + switch (data[12]) { + case 1: + pack_out["nalu"] = 1; + break; + case 2: + pack_out["nalu_end"] = 1; + break; + } + pack_out["offset"] = offset(); + if (len < 21) { + return JSON::Value(); + } + pack_out["data"] = std::string((char *)data + 16, (size_t)len - 20); + } else { + if (len < 17) { + return JSON::Value(); + } + pack_out["data"] = std::string((char *)data + 12, (size_t)len - 16); + } + return pack_out; + } + return pack_out; //should never get here +} //FLV::Tag::toJSON + +/// Checks if buf is large enough to contain len. +/// Attempts to resize data buffer if not/ +/// \returns True if buffer is large enough, false otherwise. +bool FLV::Tag::checkBufferSize() { + if (buf < len || !data) { + char * newdata = (char *)realloc(data, len); + // on realloc fail, retain the old data + if (newdata != 0) { + data = newdata; + buf = len; + } else { + len = buf; + return false; + } + } + return true; +} diff --git a/lib/flv_tag.h b/lib/flv_tag.h new file mode 100644 index 00000000..deef0ea7 --- /dev/null +++ b/lib/flv_tag.h @@ -0,0 +1,74 @@ +/// \file flv_tag.h +/// Holds all headers for the FLV namespace. + +#pragma once +#include "socket.h" +#include "dtsc.h" +#include "json.h" +#include + + +//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. + const char * getAudioCodec(); ///< Returns a c-string with the audio codec name. + const char * getVideoCodec(); ///< Returns a c-string with the video codec name. + std::string tagType(); ///< Returns a std::string describing the tag in detail. + unsigned int tagTime(); + void tagTime(unsigned int T); + int offset(); + void offset(int o); + 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); /// & selTracks); + bool DTSCMetaInit(DTSC::Stream & S, DTSC::Track & videoRef, DTSC::Track & audioRef); + JSON::Value toJSON(DTSC::Meta & metadata); + bool MemLoader(char * D, unsigned int S, unsigned int & P); + 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(); + bool checkBufferSize(); + //loader helper functions + bool MemReadUntil(char * buffer, unsigned int count, unsigned int & sofar, char * D, unsigned int S, unsigned int & P); + bool FileReadUntil(char * buffer, unsigned int count, unsigned int & sofar, FILE * f); + //JSON writer helpers + void Meta_Put(JSON::Value & meta, std::string cat, std::string elem, std::string val); + void Meta_Put(JSON::Value & meta, std::string cat, std::string elem, uint64_t val); + bool Meta_Has(JSON::Value & meta, std::string cat, std::string elem); + }; +//Tag + +}//FLV namespace diff --git a/lib/ftp.cpp b/lib/ftp.cpp new file mode 100644 index 00000000..286a8472 --- /dev/null +++ b/lib/ftp.cpp @@ -0,0 +1,557 @@ +#include "ftp.h" + +FTP::User::User(Socket::Connection NewConnection, std::map Credentials) { + Conn = NewConnection; + MyPassivePort = 0; + USER = ""; + PASS = ""; + MODE = MODE_STREAM; + STRU = STRU_FILE; + TYPE = TYPE_ASCII_NONPRINT; + PORT = 20; + RNFR = ""; + AllCredentials = Credentials; + + MyDir = Filesystem::Directory("", FTPBasePath); + MyDir.SetPermissions("", Filesystem::P_LIST); + MyDir.SetPermissions("Unconverted", Filesystem::P_LIST | Filesystem::P_DELE | Filesystem::P_RNFT | Filesystem::P_STOR | Filesystem::P_RETR); + MyDir.SetPermissions("Converted", Filesystem::P_LIST | Filesystem::P_DELE | Filesystem::P_RNFT | Filesystem::P_RETR); + MyDir.SetPermissions("OnDemand", Filesystem::P_LIST | Filesystem::P_RETR); + MyDir.SetPermissions("Live", Filesystem::P_LIST); + + MyDir.SetVisibility("Converted", Filesystem::S_INACTIVE); + MyDir.SetVisibility("OnDemand", Filesystem::S_ACTIVE); + +} + +FTP::User::~User() { +} + +int FTP::User::ParseCommand(std::string Command) { + Commands ThisCmd = CMD_NOCMD; + if (Command.substr(0, 4) == "NOOP") { + ThisCmd = CMD_NOOP; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "USER") { + ThisCmd = CMD_USER; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "PASS") { + ThisCmd = CMD_PASS; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "QUIT") { + ThisCmd = CMD_QUIT; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "PORT") { + ThisCmd = CMD_PORT; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "RETR") { + ThisCmd = CMD_RETR; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "STOR") { + ThisCmd = CMD_STOR; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "TYPE") { + ThisCmd = CMD_TYPE; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "MODE") { + ThisCmd = CMD_MODE; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "STRU") { + ThisCmd = CMD_STRU; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "EPSV") { + ThisCmd = CMD_EPSV; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "PASV") { + ThisCmd = CMD_PASV; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "LIST") { + ThisCmd = CMD_LIST; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "CDUP") { + ThisCmd = CMD_CDUP; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "DELE") { + ThisCmd = CMD_DELE; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "RNFR") { + ThisCmd = CMD_RNFR; + Command.erase(0, 5); + } + if (Command.substr(0, 4) == "RNTO") { + ThisCmd = CMD_RNTO; + Command.erase(0, 5); + } + if (Command.substr(0, 3) == "PWD") { + ThisCmd = CMD_PWD; + Command.erase(0, 4); + } + if (Command.substr(0, 3) == "CWD") { + ThisCmd = CMD_CWD; + Command.erase(0, 4); + } + if (Command.substr(0, 3) == "RMD") { + ThisCmd = CMD_RMD; + Command.erase(0, 4); + } + if (Command.substr(0, 3) == "MKD") { + ThisCmd = CMD_MKD; + Command.erase(0, 4); + } + if (ThisCmd != CMD_RNTO) { + RNFR = ""; + } + switch (ThisCmd) { + case CMD_NOOP: { + return 200; //Command okay. + break; + } + case CMD_USER: { + USER = ""; + PASS = ""; + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + USER = Command; + return 331; //User name okay, need password. + break; + } + case CMD_PASS: { + if (USER == "") { + return 503; + } //Bad sequence of commands + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + PASS = Command; + if (!LoggedIn()) { + USER = ""; + PASS = ""; + return 530; //Not logged in. + } + return 230; + break; + } + case CMD_LIST: { + Socket::Connection Connected = Passive.accept(); + if (Connected.connected()) { + Conn.SendNow("125 Data connection already open; transfer starting.\n"); + } else { + Conn.SendNow("150 File status okay; about to open data connection.\n"); + } + while (!Connected.connected()) { + Connected = Passive.accept(); + } + std::string tmpstr = MyDir.LIST(ActiveStreams); + Connected.SendNow(tmpstr); + Connected.close(); + return 226; + break; + } + case CMD_QUIT: { + return 221; //Service closing control connection. Logged out if appropriate. + break; + } + case CMD_PORT: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + PORT = atoi(Command.c_str()); + return 200; //Command okay. + break; + } + case CMD_EPSV: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + MyPassivePort = (rand() % 9999); + Passive = Socket::Server(MyPassivePort, "0.0.0.0", true); + return 229; + break; + } + case CMD_PASV: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + MyPassivePort = (rand() % 9999) + 49152; + Passive = Socket::Server(MyPassivePort, "0.0.0.0", true); + return 227; + break; + } + case CMD_RETR: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + if (!MyDir.HasPermission(Filesystem::P_RETR)) { + return 550; + } //Access denied. + Socket::Connection Connected = Passive.accept(); + if (Connected.connected()) { + Conn.SendNow("125 Data connection already open; transfer starting.\n"); + } else { + Conn.SendNow("150 File status okay; about to open data connection.\n"); + } + while (!Connected.connected()) { + Connected = Passive.accept(); + } + std::string tmpstr = MyDir.RETR(Command); + Connected.SendNow(tmpstr); + Connected.close(); + return 226; + break; + } + case CMD_STOR: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + if (!MyDir.HasPermission(Filesystem::P_STOR)) { + return 550; + } //Access denied. + Socket::Connection Connected = Passive.accept(); + if (Connected.connected()) { + Conn.SendNow("125 Data connection already open; transfer starting.\n"); + } else { + Conn.SendNow("150 File status okay; about to open data connection.\n"); + } + while (!Connected.connected()) { + Connected = Passive.accept(); + } + std::string Buffer; + while (Connected.spool()) { + } + /// \todo Comment me back in. ^_^ + //Buffer = Connected.Received(); + MyDir.STOR(Command, Buffer); + return 250; + break; + } + case CMD_TYPE: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + if (Command.size() != 1 && Command.size() != 3) { + return 501; + } //Syntax error in parameters or arguments. + switch (Command[0]) { + case 'A': { + if (Command.size() > 1) { + if (Command[1] != ' ') { + return 501; + } //Syntax error in parameters or arguments. + if (Command[2] != 'N') { + return 504; + } //Command not implemented for that parameter. + } + TYPE = TYPE_ASCII_NONPRINT; + break; + } + case 'I': { + if (Command.size() > 1) { + if (Command[1] != ' ') { + return 501; + } //Syntax error in parameters or arguments. + if (Command[2] != 'N') { + return 504; + } //Command not implemented for that parameter. + } + TYPE = TYPE_IMAGE_NONPRINT; + break; + } + default: { + return 504; //Command not implemented for that parameter. + break; + } + } + return 200; //Command okay. + break; + } + case CMD_MODE: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + if (Command.size() != 1) { + return 501; + } //Syntax error in parameters or arguments. + if (Command[0] != 'S') { + return 504; + } //Command not implemented for that parameter. + MODE = MODE_STREAM; + return 200; //Command okay. + break; + } + case CMD_STRU: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + if (Command.size() != 1) { + return 501; + } //Syntax error in parameters or arguments. + switch (Command[0]) { + case 'F': { + STRU = STRU_FILE; + break; + } + case 'R': { + STRU = STRU_RECORD; + break; + } + default: { + return 504; //Command not implemented for that parameter. + break; + } + } + return 200; //Command okay. + break; + } + case CMD_PWD: { + if (!LoggedIn()) { + return 550; + } //Not logged in. + if (Command != "") { + return 501; + } //Syntax error in parameters or arguments. + return 2570; //257 -- 0 to indicate PWD over MKD + break; + } + case CMD_CWD: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + Filesystem::Directory TmpDir = MyDir; + if (TmpDir.CWD(Command)) { + if (TmpDir.IsDir()) { + MyDir = TmpDir; + return 250; + } + } + return 550; + break; + } + case CMD_CDUP: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command != "") { + return 501; + } //Syntax error in parameters or arguments. + Filesystem::Directory TmpDir = MyDir; + if (TmpDir.CDUP()) { + if (TmpDir.IsDir()) { + MyDir = TmpDir; + return 250; + } + } + return 550; + break; + } + case CMD_DELE: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + if (!MyDir.DELE(Command)) { + return 550; + } + return 250; + break; + } + case CMD_RMD: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + if (!MyDir.HasPermission(Filesystem::P_RMD)) { + return 550; + } + if (!MyDir.DELE(Command)) { + return 550; + } + return 250; + break; + } + case CMD_MKD: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + if (!MyDir.HasPermission(Filesystem::P_MKD)) { + return 550; + } + if (!MyDir.MKD(Command)) { + return 550; + } + return 2571; + break; + } + case CMD_RNFR: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + RNFR = Command; + return 350; //Awaiting further information + } + case CMD_RNTO: { + if (!LoggedIn()) { + return 530; + } //Not logged in. + if (Command == "") { + return 501; + } //Syntax error in parameters or arguments. + if (RNFR == "") { + return 503; + } //Bad sequence of commands + if (!MyDir.Rename(RNFR, Command)) { + return 550; + } + return 250; + } + default: { + return 502; //Command not implemented. + break; + } + } +} + +bool FTP::User::LoggedIn() { + if (USER == "" || PASS == "") { + return false; + } + if (!AllCredentials.size()) { + return true; + } + if ((AllCredentials.find(USER) != AllCredentials.end()) && AllCredentials[USER] == PASS) { + return true; + } + return false; +} + +std::string FTP::User::NumToMsg(int MsgNum) { + std::string Result; + switch (MsgNum) { + case 200: { + Result = "200 Message okay.\n"; + break; + } + case 221: { + Result = "221 Service closing control connection. Logged out if appropriate.\n"; + break; + } + case 226: { + Result = "226 Closing data connection.\n"; + break; + } + case 227: { + std::stringstream sstr; + sstr << "227 Entering passive mode (0,0,0,0,"; + sstr << (MyPassivePort >> 8) % 256; + sstr << ","; + sstr << MyPassivePort % 256; + sstr << ").\n"; + Result = sstr.str(); + break; + } + case 229: { + std::stringstream sstr; + sstr << "229 Entering extended passive mode (|||"; + sstr << MyPassivePort; + sstr << "|).\n"; + Result = sstr.str(); + break; + } + case 230: { + Result = "230 User logged in, proceed.\n"; + break; + } + case 250: { + Result = "250 Requested file action okay, completed.\n"; + break; + } + case 2570: { //PWD + Result = "257 \"" + MyDir.PWD() + "\" selected as PWD\n"; + break; + } + case 2571: { //MKD + Result = "257 \"" + MyDir.PWD() + "\" created\n"; + break; + } + case 331: { + Result = "331 User name okay, need password.\n"; + break; + } + case 350: { + Result = "350 Requested file action pending further information\n"; + break; + } + case 501: { + Result = "501 Syntax error in parameters or arguments.\n"; + break; + } + case 502: { + Result = "502 Command not implemented.\n"; + break; + } + case 503: { + Result = "503 Bad sequence of commands.\n"; + break; + } + case 504: { + Result = "504 Command not implemented for that parameter.\n"; + break; + } + case 530: { + Result = "530 Not logged in.\n"; + break; + } + case 550: { + Result = "550 Requested action not taken.\n"; + break; + } + default: { + Result = "Error msg not implemented?\n"; + break; + } + } + return Result; +} diff --git a/lib/ftp.h b/lib/ftp.h new file mode 100644 index 00000000..41350657 --- /dev/null +++ b/lib/ftp.h @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include +#include +#include "./socket.h" +#include "./filesystem.h" +#include + +#include "./json.h" + +namespace FTP { + static std::string FTPBasePath = "/tmp/mist/OnDemand/"; + + enum Mode { + MODE_STREAM, + }; + //FTP::Mode enumeration + + enum Structure { + STRU_FILE, STRU_RECORD, + }; + //FTP::Structure enumeration + + enum Type { + TYPE_ASCII_NONPRINT, TYPE_IMAGE_NONPRINT, + }; + //FTP::Type enumeration + + enum Commands { + CMD_NOCMD, + CMD_NOOP, + CMD_USER, + CMD_PASS, + CMD_QUIT, + CMD_PORT, + CMD_RETR, + CMD_STOR, + CMD_TYPE, + CMD_MODE, + CMD_STRU, + CMD_EPSV, + CMD_PASV, + CMD_LIST, + CMD_PWD, + CMD_CWD, + CMD_CDUP, + CMD_DELE, + CMD_RMD, + CMD_MKD, + CMD_RNFR, + CMD_RNTO, + }; + //FTP::Commands enumeration + + class User { + public: + User(Socket::Connection NewConnection, std::map Credentials); + ~User(); + int ParseCommand(std::string Command); + bool LoggedIn(); + std::string NumToMsg(int MsgNum); + Socket::Connection Conn; + private: + std::map AllCredentials; + std::string USER; + std::string PASS; + Mode MODE; + Structure STRU; + Type TYPE; + int PORT; + Socket::Server Passive; + int MyPassivePort; + Filesystem::Directory MyDir; + std::string RNFR; + std::vector ActiveStreams; + }; +//FTP::User class + +}//FTP Namespace diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp new file mode 100644 index 00000000..4ca23b5b --- /dev/null +++ b/lib/http_parser.cpp @@ -0,0 +1,661 @@ +/// \file http_parser.cpp +/// Holds all code for the HTTP namespace. + +#include "http_parser.h" +#include "timing.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() { + headerOnly = false; + Clean(); +} + +/// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing usage. +void HTTP::Parser::Clean() { + CleanPreserveHeaders(); + headers.clear(); +} + +/// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing usage. +void HTTP::Parser::CleanPreserveHeaders() { + seenHeaders = false; + seenReq = false; + getChunks = false; + doingChunk = 0; + bufferChunks = false; + method = "GET"; + url = "/"; + protocol = "HTTP/1.1"; + body.clear(); + length = 0; + 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::iterator it; + if (protocol.size() < 5 || protocol[4] != '/') { + protocol = "HTTP/1.0"; + } + builder = method + " " + url + " " + protocol + "\r\n"; + for (it = headers.begin(); it != headers.end(); it++) { + if ((*it).first != "" && (*it).second != "") { + builder += (*it).first + ": " + (*it).second + "\r\n"; + } + } + builder += "\r\n" + body; + return builder; +} + +/// Creates and sends a valid HTTP 1.0 or 1.1 request. +/// The request is build from internal variables set before this call is made. +/// To be precise, method, url, protocol, headers and body are used. +void HTTP::Parser::SendRequest(Socket::Connection & conn) { + /// \todo Include GET/POST variable parsing? + std::map::iterator it; + if (protocol.size() < 5 || protocol[4] != '/') { + protocol = "HTTP/1.0"; + } + builder = method + " " + url + " " + protocol + "\r\n"; + conn.SendNow(builder); + for (it = headers.begin(); it != headers.end(); it++) { + if ((*it).first != "" && (*it).second != "") { + builder = (*it).first + ": " + (*it).second + "\r\n"; + conn.SendNow(builder); + } + } + conn.SendNow("\r\n", 2); + conn.SendNow(body); +} + +/// 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::iterator it; + if (protocol.size() < 5 || protocol[4] != '/') { + protocol = "HTTP/1.0"; + } + builder = protocol + " " + code + " " + message + "\r\n"; + for (it = headers.begin(); it != headers.end(); it++) { + if ((*it).first != "" && (*it).second != "") { + if ((*it).first != "Content-Length" || (*it).second != "0") { + builder += (*it).first + ": " + (*it).second + "\r\n"; + } + } + } + builder += "\r\n"; + builder += body; + return builder; +} + +/// 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. +/// \return A string containing a valid HTTP 1.0 or 1.1 response, ready for sending. +/// This function calls this->BuildResponse(this->method,this->url) +std::string & HTTP::Parser::BuildResponse() { + return BuildResponse(method,url); +} + +/// Creates and sends a valid HTTP 1.0 or 1.1 response. +/// The response is partly build from internal variables set before this call is made. +/// To be precise, protocol, headers and body are used. +/// This call will attempt to buffer as little as possible and block until the whole request is sent. +/// \param code The HTTP response code. Usually you want 200. +/// \param message The HTTP response message. Usually you want "OK". +/// \param conn The Socket::Connection to send the response over. +void HTTP::Parser::SendResponse(std::string code, std::string message, Socket::Connection & conn) { + /// \todo Include GET/POST variable parsing? + std::map::iterator it; + if (protocol.size() < 5 || protocol[4] != '/') { + protocol = "HTTP/1.0"; + } + builder = protocol + " " + code + " " + message + "\r\n"; + conn.SendNow(builder); + for (it = headers.begin(); it != headers.end(); it++) { + if ((*it).first != "" && (*it).second != "") { + if ((*it).first != "Content-Length" || (*it).second != "0") { + builder = (*it).first + ": " + (*it).second + "\r\n"; + conn.SendNow(builder); + } + } + } + conn.SendNow("\r\n", 2); + conn.SendNow(body); +} + +/// Creates and sends a valid HTTP 1.0 or 1.1 response, based on the given request. +/// The headers must be set before this call is made. +/// This call sets up chunked transfer encoding if the request was protocol HTTP/1.1, otherwise uses a zero-content-length HTTP/1.0 response. +/// \param code The HTTP response code. Usually you want 200. +/// \param message The HTTP response message. Usually you want "OK". +/// \param request The HTTP request to respond to. +/// \param conn The connection to send over. +void HTTP::Parser::StartResponse(std::string code, std::string message, HTTP::Parser & request, Socket::Connection & conn, bool bufferAllChunks) { + std::string prot = request.protocol; + sendingChunks = (!bufferAllChunks && protocol == "HTTP/1.1" && request.GetHeader("Connection")!="close"); + CleanPreserveHeaders(); + protocol = prot; + if (sendingChunks){ + SetHeader("Transfer-Encoding", "chunked"); + } else { + SetHeader("Connection", "close"); + } + bufferChunks = bufferAllChunks; + if (!bufferAllChunks){ + SendResponse(code, message, conn); + } +} + +/// Creates and sends a valid HTTP 1.0 or 1.1 response, based on the given request. +/// The headers must be set before this call is made. +/// This call sets up chunked transfer encoding if the request was protocol HTTP/1.1, otherwise uses a zero-content-length HTTP/1.0 response. +/// This call simply calls StartResponse("200", "OK", request, conn) +/// \param request The HTTP request to respond to. +/// \param conn The connection to send over. +void HTTP::Parser::StartResponse(HTTP::Parser & request, Socket::Connection & conn, bool bufferAllChunks) { + StartResponse("200", "OK", request, conn, bufferAllChunks); +} + +/// After receiving a header with this object, this function call will: +/// - Forward the headers to the 'to' Socket::Connection. +/// - Retrieve all the body from the 'from' Socket::Connection. +/// - Forward those contents as-is to the 'to' Socket::Connection. +/// It blocks until completed or either of the connections reaches an error state. +void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to) { + SendResponse(url, method, to); + if (getChunks) { + unsigned int proxyingChunk = 0; + while (to.connected() && from.connected()) { + if ((from.Received().size() && (from.Received().size() > 1 || *(from.Received().get().rbegin()) == '\n')) || from.spool()) { + if (proxyingChunk) { + while (proxyingChunk && from.Received().size()) { + unsigned int toappend = from.Received().get().size(); + if (toappend > proxyingChunk) { + toappend = proxyingChunk; + to.SendNow(from.Received().get().c_str(), toappend); + from.Received().get().erase(0, toappend); + } else { + to.SendNow(from.Received().get()); + from.Received().get().clear(); + } + proxyingChunk -= toappend; + } + } else { + //Make sure the received data ends in a newline (\n). + if (*(from.Received().get().rbegin()) != '\n') { + if (from.Received().size() > 1) { + //make a copy of the first part + std::string tmp = from.Received().get(); + //clear the first part, wiping it from the partlist + from.Received().get().clear(); + from.Received().size(); + //take the now first (was second) part, insert the stored part in front of it + from.Received().get().insert(0, tmp); + } else { + Util::sleep(100); + } + if (*(from.Received().get().rbegin()) != '\n') { + continue; + } + } + //forward the size and any empty lines + to.SendNow(from.Received().get()); + + std::string tmpA = from.Received().get().substr(0, from.Received().get().size() - 1); + while (tmpA.find('\r') != std::string::npos) { + tmpA.erase(tmpA.find('\r')); + } + unsigned int chunkLen = 0; + if (!tmpA.empty()) { + for (unsigned int i = 0; i < tmpA.size(); ++i) { + chunkLen = (chunkLen << 4) | unhex(tmpA[i]); + } + if (chunkLen == 0) { + getChunks = false; + to.SendNow("\r\n", 2); + return; + } + proxyingChunk = chunkLen; + } + from.Received().get().clear(); + } + } else { + Util::sleep(100); + } + } + } else { + unsigned int bodyLen = length; + while (bodyLen > 0 && to.connected() && from.connected()) { + if (from.Received().size() || from.spool()) { + if (from.Received().get().size() <= bodyLen) { + to.SendNow(from.Received().get()); + bodyLen -= from.Received().get().size(); + from.Received().get().clear(); + } else { + to.SendNow(from.Received().get().c_str(), bodyLen); + from.Received().get().erase(0, bodyLen); + bodyLen = 0; + } + } else { + Util::sleep(100); + } + } + } +} + +/// 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::getUrl() { + if (url.find('?') != std::string::npos) { + return url.substr(0, url.find('?')); + } else { + return url; + } +} + +/// 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[23]; //ints are never bigger than 22 chars as decimal + 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 Socket::Connection. +/// If a whole request could be read, it is removed from the front of the socket buffer and true returned. +/// If not, as much as can be interpreted is removed and false returned. +/// \param conn The socket to read from. +/// \return True if a whole request or response was read, false otherwise. +bool HTTP::Parser::Read(Socket::Connection & conn) { + //Make sure the received data ends in a newline (\n). + while ((!seenHeaders || (getChunks && !doingChunk)) && conn.Received().get().size() && *(conn.Received().get().rbegin()) != '\n') { + if (conn.Received().size() > 1) { + //make a copy of the first part + std::string tmp = conn.Received().get(); + //clear the first part, wiping it from the partlist + conn.Received().get().clear(); + conn.Received().size(); + //take the now first (was second) part, insert the stored part in front of it + conn.Received().get().insert(0, tmp); + } else { + return false; + } + } + //if a parse succeeds, simply return true + if (parse(conn.Received().get())) { + return true; + } + //otherwise, if we have parts left, call ourselves recursively + if (conn.Received().size()) { + return Read(conn); + } + return false; +} //HTTPReader::Read + +/// 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 and true returned. +/// If not, as much as can be interpreted is removed and false returned. +/// \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; + /// \todo Make this not resize HTTPbuffer in parts, but read all at once and then remove the entire request, like doxygen claims it does? + while (!HTTPbuffer.empty()) { + if (!seenHeaders) { + f = HTTPbuffer.find('\n'); + if (f == std::string::npos) return false; + tmpA = HTTPbuffer.substr(0, f); + if (f + 1 == HTTPbuffer.size()) { + HTTPbuffer.clear(); + } else { + 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) { + if (tmpA.substr(0, 4) == "HTTP") { + protocol = 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); + method = tmpA; + if (url.find('?') != std::string::npos) { + parseVars(url.substr(url.find('?') + 1)); //parse GET variables + url.erase(url.find('?')); + } + url = urlunescape(url); + } else { + seenReq = false; + } + } else { + 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); + protocol = tmpA; + if (url.find('?') != std::string::npos) { + parseVars(url.substr(url.find('?') + 1)); //parse GET variables + url.erase(url.find('?')); + } + url = urlunescape(url); + } else { + seenReq = false; + } + } + } else { + seenReq = false; + } + } else { + if (tmpA.size() == 0) { + seenHeaders = true; + body.clear(); + if (GetHeader("Content-Length") != "") { + length = atoi(GetHeader("Content-Length").c_str()); + if (body.capacity() < length) { + body.reserve(length); + } + } + if (GetHeader("Transfer-Encoding") == "chunked") { + getChunks = true; + doingChunk = 0; + } + } 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 (headerOnly) { + return true; + } + unsigned int toappend = length - body.length(); + if (toappend > 0) { + body.append(HTTPbuffer, 0, toappend); + HTTPbuffer.erase(0, toappend); + } + if (length == body.length()) { + parseVars(body); //parse POST variables + return true; + } else { + return false; + } + } else { + if (getChunks) { + if (headerOnly) { + return true; + } + if (doingChunk) { + unsigned int toappend = HTTPbuffer.size(); + if (toappend > doingChunk) { + toappend = doingChunk; + } + body.append(HTTPbuffer, 0, toappend); + HTTPbuffer.erase(0, toappend); + doingChunk -= toappend; + } else { + f = HTTPbuffer.find('\n'); + if (f == std::string::npos) return false; + tmpA = HTTPbuffer.substr(0, f); + while (tmpA.find('\r') != std::string::npos) { + tmpA.erase(tmpA.find('\r')); + } + unsigned int chunkLen = 0; + if (!tmpA.empty()) { + for (unsigned int i = 0; i < tmpA.size(); ++i) { + chunkLen = (chunkLen << 4) | unhex(tmpA[i]); + } + if (chunkLen == 0) { + getChunks = false; + return true; + } + doingChunk = chunkLen; + } + if (f + 1 == HTTPbuffer.size()) { + HTTPbuffer.clear(); + } else { + HTTPbuffer.erase(0, f + 1); + } + } + return false; + } else { + return true; + } + } + } + } + return false; //empty input +} //HTTPReader::parse + +/// Parses GET or POST-style variable data. +/// Saves to internal variable structure using HTTP::Parser::SetVar. +void HTTP::Parser::parseVars(std::string data) { + std::string varname; + std::string varval; + // position where a part start (e.g. after &) + size_t pos = 0; + while (pos < data.length()) { + size_t nextpos = data.find('&', pos); + if (nextpos == std::string::npos) { + nextpos = data.length(); + } + size_t eq_pos = data.find('=', pos); + if (eq_pos < nextpos) { + // there is a key and value + varname = data.substr(pos, eq_pos - pos); + varval = data.substr(eq_pos + 1, nextpos - eq_pos - 1); + } else { + // no value, only a key + varname = data.substr(pos, nextpos - pos); + varval.clear(); + } + SetVar(urlunescape(varname), urlunescape(varval)); + if (nextpos == std::string::npos) { + // in case the string is gigantic + break; + } + // erase & + pos = nextpos + 1; + } +} + +/// Sends a string in chunked format if protocol is HTTP/1.1, sends as-is otherwise. +/// \param bodypart The data to send. +/// \param conn The connection to use for sending. +void HTTP::Parser::Chunkify(const std::string & bodypart, Socket::Connection & conn) { + Chunkify(bodypart.c_str(), bodypart.size(), conn); +} + +/// Sends a string in chunked format if protocol is HTTP/1.1, sends as-is otherwise. +/// \param data The data to send. +/// \param size The size of the data to send. +/// \param conn The connection to use for sending. +void HTTP::Parser::Chunkify(const char * data, unsigned int size, Socket::Connection & conn) { + static char hexa[] = "0123456789abcdef"; + if (bufferChunks){ + if (size){ + body.append(data, size); + }else{ + SetHeader("Content-Length", body.length()); + SendResponse("200", "OK", conn); + Clean(); + } + return; + } + if (sendingChunks) { + //prepend the chunk size and \r\n + if (!size){ + conn.SendNow("0\r\n\r\n\r\n", 7); + } + size_t offset = 8; + unsigned int t_size = size; + char len[] = "\000\000\000\000\000\000\0000\r\n"; + while (t_size && offset < 9){ + len[--offset] = hexa[t_size & 0xf]; + t_size >>= 4; + } + conn.SendNow(len+offset, 10-offset); + //send the chunk itself + conn.SendNow(data, size); + //append \r\n + conn.SendNow("\r\n", 2); + } else { + //just send the chunk itself + conn.SendNow(data, size); + //close the connection if this was the end of the file + if (!size) { + conn.close(); + Clean(); + } + } +} + +/// 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; +} diff --git a/lib/http_parser.h b/lib/http_parser.h new file mode 100644 index 00000000..0204a045 --- /dev/null +++ b/lib/http_parser.h @@ -0,0 +1,66 @@ +/// \file http_parser.h +/// Holds all headers for the HTTP namespace. + +#pragma once +#include +#include +#include +#include +#include "socket.h" + +/// Holds all HTTP processing related code. +namespace HTTP { + /// Simple class for reading and writing HTTP 1.0 and 1.1. + class Parser { + public: + Parser(); + bool Read(Socket::Connection & conn); + bool Read(std::string & strbuf); + std::string GetHeader(std::string i); + std::string GetVar(std::string i); + std::string getUrl(); + 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 & BuildResponse(std::string code, std::string message); + void SendRequest(Socket::Connection & conn); + void SendResponse(std::string code, std::string message, Socket::Connection & conn); + void StartResponse(std::string code, std::string message, Parser & request, Socket::Connection & conn, bool bufferAllChunks = false); + void StartResponse(Parser & request, Socket::Connection & conn, bool bufferAllChunks = false); + void Chunkify(const std::string & bodypart, Socket::Connection & conn); + void Chunkify(const char * data, unsigned int size, Socket::Connection & conn); + void Proxy(Socket::Connection & from, Socket::Connection & to); + void Clean(); + void CleanPreserveHeaders(); + 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; + bool headerOnly; ///< If true, do not parse body if the length is a known size. + bool bufferChunks; + private: + bool seenHeaders; + bool seenReq; + bool getChunks; + bool sendingChunks; + unsigned int doingChunk; + bool parse(std::string & HTTPbuffer); + void parseVars(std::string data); + std::string builder; + std::string read_buffer; + std::map headers; + std::map vars; + void Trim(std::string & s); + static int unhex(char c); + static std::string hex(char dec); + }; +//HTTP::Parser class + +}//HTTP namespace diff --git a/lib/json.cpp b/lib/json.cpp new file mode 100644 index 00000000..68139a47 --- /dev/null +++ b/lib/json.cpp @@ -0,0 +1,1155 @@ +/// \file json.cpp Holds all JSON-related code. + +#include "json.h" +#include "defines.h" +#include +#include +#include +#include //for uint64_t +#include //for memcpy +#include //for htonl + +static inline char c2hex(char 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; +} + +static inline char hex2c(char c) { + if (c < 10) { + return '0' + c; + } + if (c < 16) { + return 'A' + (c - 10); + } + return '0'; +} + +static std::string read_string(int separator, std::istream & fromstream) { + std::string out; + bool escaped = false; + while (fromstream.good()) { + char c; + fromstream.get(c); + 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': { + char d1, d2, d3, d4; + fromstream.get(d1); + fromstream.get(d2); + fromstream.get(d3); + fromstream.get(d4); + out.append(1, (c2hex(d4) + (c2hex(d3) << 4))); + //We ignore the upper two characters. + // + (c2hex(d2) << 8) + (c2hex(d1) << 16) + break; + } + default: + out.append(1, c); + break; + } + escaped = false; + } else { + if (c == separator) { + return out; + } else { + out.append(1, c); + } + } + } + return out; +} + +static std::string string_escape(const std::string val) { + std::string out = "\""; + for (unsigned int i = 0; i < val.size(); ++i) { + switch (val.data()[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: + if (val.data()[i] < 32 || val.data()[i] > 126) { + out += "\\u00"; + out += hex2c((val.data()[i] >> 4) & 0xf); + out += hex2c(val.data()[i] & 0xf); + } else { + out += val.data()[i]; + } + break; + } + } + out += "\""; + return out; +} + +/// Skips an std::istream forward until any of the following characters is seen: ,]} +static void 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; + bool negative = false; + bool stop = false; + while (!stop && 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); + stop = true; + } else { + std::string tmpstr = read_string(c, fromstream); + (*this)[tmpstr] = JSON::Value(fromstream); + } + break; + case '-': + c = fromstream.get(); + negative = true; + 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) { + stop = true; + break; + } + c = fromstream.get(); + if (reading_array) { + append(JSON::Value(fromstream)); + } + break; + case '}': + if (reading_object) { + c = fromstream.get(); + } + stop = true; + break; + case ']': + if (reading_array) { + c = fromstream.get(); + } + stop = true; + break; + case 't': + case 'T': + skipToEnd(fromstream); + myType = BOOL; + intVal = 1; + stop = true; + break; + case 'f': + case 'F': + skipToEnd(fromstream); + myType = BOOL; + intVal = 0; + stop = true; + break; + case 'n': + case 'N': + skipToEnd(fromstream); + myType = EMPTY; + stop = true; + break; + default: + c = fromstream.get(); //ignore this character + continue; + break; + } + } + if (negative) { + intVal *= -1; + } +} + +/// Sets this JSON::Value to the given string. +JSON::Value::Value(const std::string & val) { + myType = STRING; + strVal = val; + intVal = 0; +} + +/// Sets this JSON::Value to the given string. +JSON::Value::Value(const char * val) { + myType = STRING; + strVal = val; + intVal = 0; +} + +/// Sets this JSON::Value to the given integer. +JSON::Value::Value(long long int val) { + myType = INTEGER; + intVal = val; +} + +/// Compares a JSON::Value to another for equality. +bool JSON::Value::operator==(const JSON::Value & rhs) const { + if (myType != rhs.myType) return false; + if (myType == INTEGER || myType == BOOL) { + return intVal == rhs.intVal; + } + if (myType == STRING) { + return strVal == rhs.strVal; + } + if (myType == EMPTY) { + return true; + } + if (myType == OBJECT) { + if (objVal.size() != rhs.objVal.size()) return false; + for (std::map::const_iterator it = objVal.begin(); it != objVal.end(); ++it) { + if (!rhs.isMember(it->first)) { + return false; + } + if (it->second != rhs.objVal.find(it->first)->second) { + return false; + } + } + return true; + } + if (myType == ARRAY) { + if (arrVal.size() != rhs.arrVal.size()) return false; + int i = 0; + for (std::deque::const_iterator it = arrVal.begin(); it != arrVal.end(); ++it) { + if (*it != rhs.arrVal[i]) { + return false; + } + i++; + } + 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() const { + 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() const { + if (myType == STRING) { + return strVal; + } else { + if (myType == EMPTY) { + return ""; + } else { + return toString(); + } + } +} + +/// Automatic conversion to bool. +/// Returns true if there is anything meaningful stored into this value. +JSON::Value::operator bool() const { + if (myType == STRING) { + return strVal != ""; + } + if (myType == INTEGER) { + return intVal != 0; + } + if (myType == BOOL) { + return intVal != 0; + } + if (myType == OBJECT) { + return size() > 0; + } + if (myType == ARRAY) { + return size() > 0; + } + if (myType == EMPTY) { + return false; + } + return false; //unimplemented? should never happen... +} + +/// Explicit conversion to std::string. +const std::string JSON::Value::asString() const { + return (std::string) * this; +} +/// Explicit conversion to long long int. +const long long int JSON::Value::asInt() const { + return (long long int) * this; +} +/// Explicit conversion to bool. +const bool JSON::Value::asBool() const { + return (bool) * this; +} + +/// Explicit conversion to std::string reference. +/// Returns a direct reference for string type JSON::Value objects, +/// but a reference to a static empty string otherwise. +/// \warning Only save to use when the JSON::Value is a string type! +const std::string & JSON::Value::asStringRef() const { + static std::string ugly_buffer; + if (myType == STRING) { + return strVal; + } + return ugly_buffer; +} + +/// Explicit conversion to c-string. +/// Returns a direct reference for string type JSON::Value objects, +/// a reference to an empty string otherwise. +/// \warning Only save to use when the JSON::Value is a string type! +const char * JSON::Value::c_str() const { + if (myType == STRING) { + return strVal.c_str(); + } + return ""; +} + +/// 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]; +} + +/// Retrieves the JSON::Value at this position in the object. +/// Fails horribly if that values does not exist. +const JSON::Value & JSON::Value::operator[](const std::string i) const { + return objVal.find(i)->second; +} + +/// Retrieves the JSON::Value at this position in the object. +/// Fails horribly if that values does not exist. +const JSON::Value & JSON::Value::operator[](const char * i) const { + return objVal.find(i)->second; +} + +/// Retrieves the JSON::Value at this position in the array. +/// Fails horribly if that values does not exist. +const JSON::Value & JSON::Value::operator[](unsigned int i) const { + return arrVal[i]; +} + +/// Packs 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. +/// As a side effect, this function clear the internal buffer of any object-types. +std::string JSON::Value::toPacked() const { + std::string r; + if (isInt() || isNull() || isBool()) { + r += 0x01; + uint64_t numval = intVal; + 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)); + } + if (isString()) { + r += 0x02; + r += strVal.size() / (256 * 256 * 256); + r += strVal.size() / (256 * 256); + r += strVal.size() / 256; + r += strVal.size() % 256; + r += strVal; + } + if (isObject()) { + r += 0xE0; + if (objVal.size() > 0) { + for (JSON::ObjConstIter it = objVal.begin(); it != objVal.end(); it++) { + if (it->first.size() > 0) { + r += it->first.size() / 256; + r += it->first.size() % 256; + r += it->first; + r += it->second.toPacked(); + } + } + } + r += (char)0x0; + r += (char)0x0; + r += (char)0xEE; + } + if (isArray()) { + r += 0x0A; + for (JSON::ArrConstIter it = arrVal.begin(); it != arrVal.end(); it++) { + r += it->toPacked(); + } + r += (char)0x0; + r += (char)0x0; + r += (char)0xEE; + } + return r; +} +//toPacked + +/// Packs and transfers over the network. +/// If the object is a container type, this function will call itself recursively for all contents. +void JSON::Value::sendTo(Socket::Connection & socket) const { + if (isInt() || isNull() || isBool()) { + socket.SendNow("\001", 1); + int tmpHalf = htonl((int)(intVal >> 32)); + socket.SendNow((char *)&tmpHalf, 4); + tmpHalf = htonl((int)(intVal & 0xFFFFFFFF)); + socket.SendNow((char *)&tmpHalf, 4); + return; + } + if (isString()) { + socket.SendNow("\002", 1); + int tmpVal = htonl((int)strVal.size()); + socket.SendNow((char *)&tmpVal, 4); + socket.SendNow(strVal); + return; + } + if (isObject()) { + if (isMember("trackid") && isMember("time")) { + unsigned int trackid = objVal.find("trackid")->second.asInt(); + long long time = objVal.find("time")->second.asInt(); + unsigned int size = 16; + if (objVal.size() > 0) { + for (JSON::ObjConstIter it = objVal.begin(); it != objVal.end(); it++) { + if (it->first.size() > 0 && it->first != "trackid" && it->first != "time" && it->first != "datatype") { + size += 2 + it->first.size() + it->second.packedSize(); + } + } + } + socket.SendNow("DTP2", 4); + size = htonl(size); + socket.SendNow((char *)&size, 4); + trackid = htonl(trackid); + socket.SendNow((char *)&trackid, 4); + int tmpHalf = htonl((int)(time >> 32)); + socket.SendNow((char *)&tmpHalf, 4); + tmpHalf = htonl((int)(time & 0xFFFFFFFF)); + socket.SendNow((char *)&tmpHalf, 4); + socket.SendNow("\340", 1); + if (objVal.size() > 0) { + for (JSON::ObjConstIter it = objVal.begin(); it != objVal.end(); it++) { + if (it->first.size() > 0 && it->first != "trackid" && it->first != "time" && it->first != "datatype") { + char sizebuffer[2] = {0, 0}; + sizebuffer[0] = (it->first.size() >> 8) & 0xFF; + sizebuffer[1] = it->first.size() & 0xFF; + socket.SendNow(sizebuffer, 2); + socket.SendNow(it->first); + it->second.sendTo(socket); + } + } + } + socket.SendNow("\000\000\356", 3); + return; + } + if (isMember("tracks")) { + socket.SendNow("DTSC", 4); + unsigned int size = htonl(packedSize()); + socket.SendNow((char *)&size, 4); + } + socket.SendNow("\340", 1); + if (objVal.size() > 0) { + for (JSON::ObjConstIter it = objVal.begin(); it != objVal.end(); it++) { + if (it->first.size() > 0) { + char sizebuffer[2] = {0, 0}; + sizebuffer[0] = (it->first.size() >> 8) & 0xFF; + sizebuffer[1] = it->first.size() & 0xFF; + socket.SendNow(sizebuffer, 2); + socket.SendNow(it->first); + it->second.sendTo(socket); + } + } + } + socket.SendNow("\000\000\356", 3); + return; + } + if (isArray()) { + socket.SendNow("\012", 1); + for (JSON::ArrConstIter it = arrVal.begin(); it != arrVal.end(); it++) { + it->sendTo(socket); + } + socket.SendNow("\000\000\356", 3); + return; + } +}//sendTo + +/// Returns the packed size of this Value. +unsigned int JSON::Value::packedSize() const { + if (isInt() || isNull() || isBool()) { + return 9; + } + if (isString()) { + return 5 + strVal.size(); + } + if (isObject()) { + unsigned int ret = 4; + if (objVal.size() > 0) { + for (JSON::ObjConstIter it = objVal.begin(); it != objVal.end(); it++) { + if (it->first.size() > 0) { + ret += 2 + it->first.size() + it->second.packedSize(); + } + } + } + return ret; + } + if (isArray()) { + unsigned int ret = 4; + for (JSON::ArrConstIter it = arrVal.begin(); it != arrVal.end(); it++) { + ret += it->packedSize(); + } + return ret; + } + return 0; +}//packedSize + +/// Pre-packs any object-type JSON::Value to a std::string for transfer over the network, including proper DTMI header. +/// Non-object-types will print an error. +/// The internal buffer is guaranteed to be up-to-date after this function is called. +void JSON::Value::netPrepare() { + if (myType != OBJECT) { + DEBUG_MSG(DLVL_ERROR, "Only objects may be netpacked!"); + return; + } + std::string packed = toPacked(); + //insert proper header for this type of data + int packID = -1; + long long unsigned int time = objVal["time"].asInt(); + std::string dataType; + if (isMember("datatype") || isMember("trackid")) { + dataType = objVal["datatype"].asString(); + if (isMember("trackid")) { + packID = objVal["trackid"].asInt(); + } else { + if (objVal["datatype"].asString() == "video") { + packID = 1; + } + if (objVal["datatype"].asString() == "audio") { + packID = 2; + } + if (objVal["datatype"].asString() == "meta") { + packID = 3; + } + //endmark and the likes... + if (packID == -1) { + packID = 0; + } + } + removeMember("time"); + if (packID != 0) { + removeMember("datatype"); + } + removeMember("trackid"); + packed = toPacked(); + objVal["time"] = (long long int)time; + objVal["datatype"] = dataType; + objVal["trackid"] = packID; + strVal.resize(packed.size() + 20); + memcpy((void *)strVal.c_str(), "DTP2", 4); + } else { + packID = -1; + strVal.resize(packed.size() + 8); + memcpy((void *)strVal.c_str(), "DTSC", 4); + } + //insert the packet length at bytes 4-7 + unsigned int size = packed.size(); + if (packID != -1) { + size += 12; + } + size = htonl(size); + memcpy((void *)(strVal.c_str() + 4), (void *) &size, 4); + //copy the rest of the string + if (packID == -1) { + memcpy((void *)(strVal.c_str() + 8), packed.c_str(), packed.size()); + return; + } + packID = htonl(packID); + memcpy((void *)(strVal.c_str() + 8), (void *) &packID, 4); + int tmpHalf = htonl((int)(time >> 32)); + memcpy((void *)(strVal.c_str() + 12), (void *) &tmpHalf, 4); + tmpHalf = htonl((int)(time & 0xFFFFFFFF)); + memcpy((void *)(strVal.c_str() + 16), (void *) &tmpHalf, 4); + memcpy((void *)(strVal.c_str() + 20), packed.c_str(), packed.size()); +} + +/// Packs any object-type JSON::Value to a std::string for transfer over the network, including proper DTMI header. +/// Non-object-types will print an error and return an empty string. +/// This function returns a reference to an internal buffer where the prepared data is kept. +/// The internal buffer is *not* made stale if any changes occur inside the object - subsequent calls to toPacked() will clear the buffer, +/// calls to netPrepare will guarantee it is up-to-date. +std::string & JSON::Value::toNetPacked() { + static std::string emptystring; + //check if this is legal + if (myType != OBJECT) { + INFO_MSG("Ignored attempt to netpack a non-object."); + return emptystring; + } + //if sneaky storage doesn't contain correct data, re-calculate it + if (strVal.size() == 0 || strVal[0] != 'D' || strVal[1] != 'T') { + netPrepare(); + } + return strVal; +} + +/// Converts this JSON::Value to valid JSON notation and returns it. +/// Makes absolutely no attempts to pretty-print anything. :-) +std::string JSON::Value::toString() const { + 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 (ArrConstIter 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) { + ObjConstIter it3 = ObjEnd(); + --it3; + for (ObjConstIter it2 = ObjBegin(); it2 != ObjEnd(); it2++) { + tmp2 += string_escape(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... +} + +/// Converts this JSON::Value to valid JSON notation and returns it. +/// Makes an attempt at pretty-printing. +std::string JSON::Value::toPrettyString(int indentation) const { + switch (myType) { + case INTEGER: { + std::stringstream st; + st << intVal; + return st.str(); + break; + } + case STRING: { + for (unsigned int i = 0; i < 201 && i < strVal.size(); ++i) { + if (strVal[i] < 32 || strVal[i] > 126 || strVal.size() > 200) { + return "\"" + JSON::Value((long long int)strVal.size()).asString() + " bytes of data\""; + } + } + return string_escape(strVal); + break; + } + case ARRAY: { + if (arrVal.size() > 0) { + std::string tmp = "[\n" + std::string(indentation + 2, ' '); + for (ArrConstIter it = ArrBegin(); it != ArrEnd(); it++) { + tmp += it->toPrettyString(indentation + 2); + if (it + 1 != ArrEnd()) { + tmp += ", "; + } + } + tmp += "\n" + std::string(indentation, ' ') + "]"; + return tmp; + } else { + return "[]"; + } + break; + } + case OBJECT: { + if (objVal.size() > 0) { + bool shortMode = false; + if (size() <= 3 && isMember("len")) { + shortMode = true; + } + std::string tmp2 = "{" + std::string((shortMode ? "" : "\n")); + ObjConstIter it3 = ObjEnd(); + --it3; + for (ObjConstIter it2 = ObjBegin(); it2 != ObjEnd(); it2++) { + tmp2 += (shortMode ? std::string("") : std::string(indentation + 2, ' ')) + string_escape(it2->first) + ":"; + tmp2 += it2->second.toPrettyString(indentation + 2); + if (it2 != it3) { + tmp2 += "," + std::string((shortMode ? " " : "\n")); + } + } + tmp2 += (shortMode ? std::string("") : "\n" + std::string(indentation, ' ')) + "}"; + return tmp2; + } else { + return "{}"; + } + 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 true if this object is an integer. +bool JSON::Value::isInt() const { + return (myType == INTEGER); +} + +/// Returns true if this object is a string. +bool JSON::Value::isString() const { + return (myType == STRING); +} + +/// Returns true if this object is a bool. +bool JSON::Value::isBool() const { + return (myType == BOOL); +} + +/// Returns true if this object is an object. +bool JSON::Value::isObject() const { + return (myType == OBJECT); +} + +/// Returns true if this object is an array. +bool JSON::Value::isArray() const { + return (myType == ARRAY); +} + +/// Returns true if this object is null. +bool JSON::Value::isNull() const { + return (myType == EMPTY); +} + +/// 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(); +} + +/// Returns an iterator to the begin of the object map, if any. +JSON::ObjConstIter JSON::Value::ObjBegin() const { + return objVal.begin(); +} + +/// Returns an iterator to the end of the object map, if any. +JSON::ObjConstIter JSON::Value::ObjEnd() const { + return objVal.end(); +} + +/// Returns an iterator to the begin of the array, if any. +JSON::ArrConstIter JSON::Value::ArrBegin() const { + return arrVal.begin(); +} + +/// Returns an iterator to the end of the array, if any. +JSON::ArrConstIter JSON::Value::ArrEnd() const { + return arrVal.end(); +} + +/// Returns the total of the objects and array size combined. +unsigned int JSON::Value::size() const { + return objVal.size() + arrVal.size(); +} + +/// 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; +} + +/// Parses a single DTMI type - used recursively by the JSON::fromDTMI 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 (defaults to 0). +/// \returns A single JSON::Value, parsed from the raw data. +JSON::Value JSON::fromDTMI(const unsigned char * data, unsigned int len, unsigned int & i) { + JSON::Value ret; + fromDTMI(data, len, i, ret); + return ret; +} + +/// Parses a single DTMI type - used recursively by the JSON::fromDTMI 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 (defaults to 0). +/// \param ret Will be set to JSON::Value, parsed from the raw data. +void JSON::fromDTMI(const unsigned char * data, unsigned int len, unsigned int & i, JSON::Value & ret) { + ret.null(); + if (i >= len) { + return; + } + switch (data[i]) { + case 0x01: { //integer + if (i + 8 >= len) { + return; + } + unsigned char tmpdbl[8]; + 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 + uint64_t * d = (uint64_t *)tmpdbl; + ret = (long long int) * d; + return; + break; + } + case 0x02: { //string + if (i + 4 >= len) { + return; + } + unsigned int 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 + if (i + 4 + tmpi >= len) { + return; + } + i += tmpi + 5; //skip length+size+1 forwards + ret = tmpstr; + return; + break; + } + case 0xFF: //also object + case 0xE0: { //object + ++i; + while (data[i] + data[i + 1] != 0 && i < len) { //while not encountering 0x0000 (we assume 0x0000EE) + if (i + 2 >= len) { + return; + } + unsigned int 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[tmpstr].null(); + fromDTMI(data, len, i, ret[tmpstr]); //add content, recursively parsed, updating i, setting indice to tmpstr + } + i += 3; //skip 0x0000EE + return; + break; + } + case 0x0A: { //array + ++i; + while (data[i] + data[i + 1] != 0 && i < len) { //while not encountering 0x0000 (we assume 0x0000EE) + ret.append(JSON::Value()); + fromDTMI(data, len, i, *--ret.ArrEnd()); //add content, recursively parsed, updating i + } + i += 3; //skip 0x0000EE + return; + break; + } + } + DEBUG_MSG(DLVL_FAIL, "Unimplemented DTMI type %hhx, @ %i / %i - returning.", data[i], i, len); + i += 1; + return; +} //fromOneDTMI + +/// Parses a std::string to a valid JSON::Value. +/// This function will find one DTMI object in the string and return it. +void JSON::fromDTMI(std::string & data, JSON::Value & ret) { + unsigned int i = 0; + return fromDTMI((const unsigned char *)data.c_str(), data.size(), i, ret); +} //fromDTMI + +/// Parses a std::string to a valid JSON::Value. +/// This function will find one DTMI object in the string and return it. +JSON::Value JSON::fromDTMI(std::string & data) { + unsigned int i = 0; + return fromDTMI((const unsigned char *)data.c_str(), data.size(), i); +} //fromDTMI + +void JSON::fromDTMI2(std::string & data, JSON::Value & ret) { + unsigned int i = 0; + fromDTMI2((const unsigned char *)data.c_str(), data.size(), i, ret); + return; +} + +JSON::Value JSON::fromDTMI2(std::string & data) { + JSON::Value ret; + unsigned int i = 0; + fromDTMI2((const unsigned char *)data.c_str(), data.size(), i, ret); + return ret; +} + +void JSON::fromDTMI2(const unsigned char * data, unsigned int len, unsigned int & i, JSON::Value & ret) { + if (len < 13) { + return; + } + long long int tmpTrackID = ntohl(((int *)(data + i))[0]); + long long int tmpTime = ntohl(((int *)(data + i))[1]); + tmpTime <<= 32; + tmpTime += ntohl(((int *)(data + i))[2]); + i += 12; + fromDTMI(data, len, i, ret); + ret["time"] = tmpTime; + ret["trackid"] = tmpTrackID; + return; +} + +JSON::Value JSON::fromDTMI2(const unsigned char * data, unsigned int len, unsigned int & i) { + JSON::Value ret; + fromDTMI2(data, len, i, ret); + return ret; +} diff --git a/lib/json.h b/lib/json.h new file mode 100644 index 00000000..6fe3c239 --- /dev/null +++ b/lib/json.h @@ -0,0 +1,181 @@ +/// \file json.h Holds all JSON-related headers. + +#pragma once +#include +#include +#include +#include +#include +#include "socket.h" + +//empty definition of DTSC::Stream so it can be a friend. +namespace DTSC { + class Stream; +} + +/// JSON-related classes and functions +namespace JSON { + + /// Lists all types of JSON::Value. + enum ValueType { + EMPTY, BOOL, INTEGER, STRING, ARRAY, OBJECT + }; + + class Value; + //forward declaration for below typedef + + typedef std::map::iterator ObjIter; + typedef std::deque::iterator ArrIter; + typedef std::map::const_iterator ObjConstIter; + typedef std::deque::const_iterator ArrConstIter; + + /// A JSON::Value is either a string or an integer, but may also be an object, array or null. + class Value { + private: + ValueType myType; + long long int intVal; + std::string strVal; + std::deque arrVal; + std::map objVal; + public: + //friends + friend class DTSC::Stream; //for access to strVal + //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() const; + operator std::string() const; + operator bool() const; + const std::string asString() const; + const long long int asInt() const; + const bool asBool() const; + const std::string & asStringRef() const; + const char * c_str() const; + //array operator for maps and arrays + Value & operator[](const std::string i); + Value & operator[](const char * i); + Value & operator[](unsigned int i); + const Value & operator[](const std::string i) const; + const Value & operator[](const char * i) const; + const Value & operator[](unsigned int i) const; + //handy functions and others + std::string toPacked() const; + void sendTo(Socket::Connection & socket) const; + unsigned int packedSize() const; + void netPrepare(); + std::string & toNetPacked(); + std::string toString() const; + std::string toPrettyString(int indentation = 0) const; + 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; + bool isInt() const; + bool isString() const; + bool isBool() const; + bool isObject() const; + bool isArray() const; + bool isNull() const; + ObjIter ObjBegin(); + ObjIter ObjEnd(); + ArrIter ArrBegin(); + ArrIter ArrEnd(); + ObjConstIter ObjBegin() const; + ObjConstIter ObjEnd() const; + ArrConstIter ArrBegin() const; + ArrConstIter ArrEnd() const; + unsigned int size() const; + void null(); + }; + + Value fromDTMI2(std::string & data); + Value fromDTMI2(const unsigned char * data, unsigned int len, unsigned int & i); + Value fromDTMI(std::string & data); + Value fromDTMI(const unsigned char * data, unsigned int len, unsigned int & i); + Value fromString(std::string json); + Value fromFile(std::string filename); + void fromDTMI2(std::string & data, Value & ret); + void fromDTMI2(const unsigned char * data, unsigned int len, unsigned int & i, Value & ret); + void fromDTMI(std::string & data, Value & ret); + void fromDTMI(const unsigned char * data, unsigned int len, unsigned int & i, Value & ret); + + template + std::string encodeVector(T begin, T end) { + std::string result; + for (T it = begin; it != end; it++) { + long long int tmp = (*it); + while (tmp >= 0xFFFF) { + result += (char)0xFF; + result += (char)0xFF; + tmp -= 0xFFFF; + } + result += (char)(tmp / 256); + result += (char)(tmp % 256); + } + return result; + } + + template + void decodeVector(std::string input, T & result) { + result.clear(); + unsigned int tmp = 0; + for (int i = 0; i < input.size(); i += 2) { + unsigned int curLen = (input[i] << 8) + input[i + 1]; + tmp += curLen; + if (curLen != 0xFFFF) { + result.push_back(tmp); + tmp = 0; + } + } + } + + template + std::string encodeVector4(T begin, T end) { + std::string result; + for (T it = begin; it != end; it++) { + long long int tmp = (*it); + while (tmp >= 0xFFFFFFFF) { + result += (char)0xFF; + result += (char)0xFF; + result += (char)0xFF; + result += (char)0xFF; + tmp -= 0xFFFFFFFF; + } + result += (char)((tmp & 0xFF000000) >> 24); + result += (char)((tmp & 0x00FF0000) >> 16); + result += (char)((tmp & 0x0000FF00) >> 8); + result += (char)((tmp & 0x000000FF)); + } + return result; + } + + template + void decodeVector4(std::string input, T & result) { + result.clear(); + unsigned int tmp = 0; + for (int i = 0; i < input.size(); i += 4) { + unsigned int curLen = (input[i] << 24) + (input[i + 1] << 16) + (input[i + 2] << 8) + (input[i + 3]); + tmp += curLen; + if (curLen != 0xFFFFFFFF) { + result.push_back(tmp); + tmp = 0; + } + } + } +} diff --git a/lib/mp4.cpp b/lib/mp4.cpp new file mode 100644 index 00000000..2ff3f469 --- /dev/null +++ b/lib/mp4.cpp @@ -0,0 +1,807 @@ +#include //for malloc and free +#include //for memcpy +#include //for htonl and friends +#include "mp4.h" +#include "mp4_adobe.h" +#include "mp4_ms.h" +#include "mp4_generic.h" +#include "json.h" + +#include "defines.h" + +/// Contains all MP4 format related code. +namespace MP4 { + + /// Creates a new box, optionally using the indicated pointer for storage. + /// If manage is set to true, the pointer will be realloc'ed when the box needs to be resized. + /// If the datapointer is NULL, manage is assumed to be true even if explicitly given as false. + /// If managed, the pointer will be free'd upon destruction. + Box::Box(char * datapointer, bool manage) { + + data = datapointer; + managed = manage; + payloadOffset = 8; + if (data == 0) { + clear(); + } else { + data_size = ntohl(((int *)data)[0]); + } + } + + Box::Box(const Box & rs) { + data = rs.data; + managed = false; + payloadOffset = rs.payloadOffset; + if (data == 0) { + clear(); + } else { + data_size = ntohl(((int *)data)[0]); + } + } + + Box & Box::operator = (const Box & rs) { + clear(); + if (data) { + free(data); + data = 0; + } + data = rs.data; + managed = false; + payloadOffset = rs.payloadOffset; + if (data == 0) { + clear(); + } else { + data_size = ntohl(((int *)data)[0]); + } + return *this; + } + + + /// If managed, this will free the data pointer. + Box::~Box() { + if (managed && data) { + free(data); + data = 0; + } + } + + /// Returns the values at byte positions 4 through 7. + std::string Box::getType() { + return std::string(data + 4, 4); + } + + /// Returns true if the given 4-byte boxtype is equal to the values at byte positions 4 through 7. + bool Box::isType(const char * boxType) { + return !memcmp(boxType, data + 4, 4); + } + + /// Reads the first 8 bytes and returns + std::string readBoxType(FILE * newData) { + char retVal[8] = {0, 0, 0, 0, 'e', 'r', 'r', 'o'}; + long long unsigned int pos = ftell(newData); + fread(retVal, 8, 1, newData); + fseek(newData, pos, SEEK_SET); + return std::string(retVal + 4, 4); + } + + ///\todo make good working calcBoxSize with size and payloadoffset calculation + unsigned long int calcBoxSize(char readVal[16]) { + return (unsigned int)ntohl(((int *)readVal)[0]); + } + + bool skipBox(FILE * newData) { + char readVal[16]; + long long unsigned int pos = ftell(newData); + if (fread(readVal, 4, 1, newData)) { + uint64_t size = calcBoxSize(readVal); + if (size == 1) { + if (fread(readVal + 4, 12, 1, newData)) { + size = 0 + ntohl(((int *)readVal)[2]); + size <<= 32; + size += ntohl(((int *)readVal)[3]); + } else { + return false; + } + } else if (size == 0) { + fseek(newData, 0, SEEK_END); + } + DONTEVEN_MSG("skipping size 0x%.8lX", size); + if (fseek(newData, pos + size, SEEK_SET) == 0) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool Box::read(FILE * newData) { + char readVal[16]; + long long unsigned int pos = ftell(newData); + if (fread(readVal, 4, 1, newData)) { + payloadOffset = 8; + uint64_t size = calcBoxSize(readVal); + if (size == 1) { + if (fread(readVal + 4, 12, 1, newData)) { + size = 0 + ntohl(((int *)readVal)[2]); + size <<= 32; + size += ntohl(((int *)readVal)[3]); + payloadOffset = 16; + } else { + return false; + } + } + fseek(newData, pos, SEEK_SET); + data = (char *)realloc(data, size); + data_size = size; + return (fread(data, size, 1, newData) == 1); + } else { + return false; + } + } + + /// Reads out a whole box (if possible) from newData, copying to the internal data storage and removing from the input string. + /// \returns True on success, false otherwise. + bool Box::read(std::string & newData) { + if (!managed) { + return false; + } + if (newData.size() > 4) { + payloadOffset = 8; + uint64_t size = ntohl(((int *)newData.c_str())[0]); + if (size == 1) { + if (newData.size() > 16) { + size = 0 + ntohl(((int *)newData.c_str())[2]); + size <<= 32; + size += ntohl(((int *)newData.c_str())[3]); + payloadOffset = 16; + } else { + return false; + } + } + if (newData.size() >= size) { + data = (char *)realloc(data, size); + data_size = size; + memcpy(data, newData.data(), size); + newData.erase(0, size); + return true; + } + } + return false; + } + + /// Returns the total boxed size of this box, including the header. + uint64_t Box::boxedSize() { + if (payloadOffset == 16) { + return ((uint64_t)ntohl(((int *)data)[2]) << 32) + ntohl(((int *)data)[3]); + } + return ntohl(((int *)data)[0]); + } + + /// Retruns the size of the payload of thix box, excluding the header. + /// This value is defined as boxedSize() - 8. + uint64_t Box::payloadSize() { + return boxedSize() - payloadOffset; + } + + /// Returns a copy of the data pointer. + char * Box::asBox() { + return data; + } + + char * Box::payload() { + return data + payloadOffset; + } + + /// Makes this box managed if it wasn't already, resetting the internal storage to 8 bytes (the minimum). + /// If this box wasn't managed, the original data is left intact - otherwise it is free'd. + /// If it was somehow impossible to allocate 8 bytes (should never happen), this will cause segfaults later. + void Box::clear() { + if (data && managed) { + free(data); + } + managed = true; + payloadOffset = 8; + data = (char *)malloc(8); + if (data) { + data_size = 8; + ((int *)data)[0] = htonl(data_size); + } else { + data_size = 0; + } + } + + /// Attempts to typecast this Box to a more specific type and call the toPrettyString() function of that type. + /// If this failed, it will print out a message saying pretty-printing is not implemented for boxtype. + std::string Box::toPrettyString(uint32_t indent) { + switch (ntohl(*((int *)(data + 4)))) { //type is at this address + case 0x6D666864: + return ((MFHD *)this)->toPrettyString(indent); + break; + case 0x6D6F6F66: + return ((MOOF *)this)->toPrettyString(indent); + break; + case 0x61627374: + return ((ABST *)this)->toPrettyString(indent); + break; + case 0x61667274: + return ((AFRT *)this)->toPrettyString(indent); + break; + case 0x61667261: + return ((AFRA *)this)->toPrettyString(indent); + break; + case 0x61737274: + return ((ASRT *)this)->toPrettyString(indent); + break; + case 0x7472756E: + return ((TRUN *)this)->toPrettyString(indent); + break; + case 0x74726166: + return ((TRAF *)this)->toPrettyString(indent); + break; + case 0x74666864: + return ((TFHD *)this)->toPrettyString(indent); + break; + case 0x61766343: + return ((AVCC *)this)->toPrettyString(indent); + break; + case 0x73647470: + return ((SDTP *)this)->toPrettyString(indent); + break; + case 0x66747970: + return ((FTYP *)this)->toPrettyString(indent); + break; + case 0x6D6F6F76: + return ((MOOV *)this)->toPrettyString(indent); + break; + case 0x6D766578: + return ((MVEX *)this)->toPrettyString(indent); + break; + case 0x74726578: + return ((TREX *)this)->toPrettyString(indent); + break; + case 0x6D667261: + return ((MFRA *)this)->toPrettyString(indent); + break; + case 0x7472616B: + return ((TRAK *)this)->toPrettyString(indent); + break; + case 0x6D646961: + return ((MDIA *)this)->toPrettyString(indent); + break; + case 0x6D696E66: + return ((MINF *)this)->toPrettyString(indent); + break; + case 0x64696E66: + return ((DINF *)this)->toPrettyString(indent); + break; + case 0x6D66726F: + return ((MFRO *)this)->toPrettyString(indent); + break; + case 0x68646C72: + return ((HDLR *)this)->toPrettyString(indent); + break; + case 0x766D6864: + return ((VMHD *)this)->toPrettyString(indent); + break; + case 0x736D6864: + return ((SMHD *)this)->toPrettyString(indent); + break; + case 0x686D6864: + return ((HMHD *)this)->toPrettyString(indent); + break; + case 0x6E6D6864: + return ((NMHD *)this)->toPrettyString(indent); + break; + case 0x6D656864: + return ((MEHD *)this)->toPrettyString(indent); + break; + case 0x7374626C: + return ((STBL *)this)->toPrettyString(indent); + break; + case 0x64726566: + return ((DREF *)this)->toPrettyString(indent); + break; + case 0x75726C20: + return ((URL *)this)->toPrettyString(indent); + break; + case 0x75726E20: + return ((URN *)this)->toPrettyString(indent); + break; + case 0x6D766864: + return ((MVHD *)this)->toPrettyString(indent); + break; + case 0x74667261: + return ((TFRA *)this)->toPrettyString(indent); + break; + case 0x746B6864: + return ((TKHD *)this)->toPrettyString(indent); + break; + case 0x6D646864: + return ((MDHD *)this)->toPrettyString(indent); + break; + case 0x73747473: + return ((STTS *)this)->toPrettyString(indent); + break; + case 0x63747473: + return ((CTTS *)this)->toPrettyString(indent); + break; + case 0x73747363: + return ((STSC *)this)->toPrettyString(indent); + break; + case 0x7374636F: + return ((STCO *)this)->toPrettyString(indent); + break; + case 0x636F3634: + return ((CO64 *)this)->toPrettyString(indent); + break; + case 0x7374737A: + return ((STSZ *)this)->toPrettyString(indent); + break; + case 0x73747364: + return ((STSD *)this)->toPrettyString(indent); + break; + case 0x6D703461://mp4a + case 0x656E6361://enca + return ((MP4A *)this)->toPrettyString(indent); + break; + case 0x61616320: + return ((AAC *)this)->toPrettyString(indent); + break; + case 0x61766331: + return ((AVC1 *)this)->toPrettyString(indent); + break; + case 0x68323634://h264 + case 0x656E6376://encv + return ((H264 *)this)->toPrettyString(indent); + break; + case 0x65647473: + return ((EDTS *)this)->toPrettyString(indent); + break; + case 0x73747373: + return ((STSS *)this)->toPrettyString(indent); + break; + case 0x6D657461: + return ((META *)this)->toPrettyString(indent); + break; + case 0x656C7374: + return ((ELST *)this)->toPrettyString(indent); + break; + case 0x65736473: + return ((ESDS *)this)->toPrettyString(indent); + break; + case 0x75647461: + return ((UDTA *)this)->toPrettyString(indent); + break; + case 0x75756964: + return ((UUID *)this)->toPrettyString(indent); + break; + default: + break; + } + std::string retval = std::string(indent, ' ') + "Unimplemented pretty-printing for box " + std::string(data + 4, 4) + "\n"; + /// \todo Implement hexdump for unimplemented boxes? + return retval; + } + + /// Sets the 8 bits integer at the given index. + /// Attempts to resize the data pointer if the index is out of range. + /// Fails silently if resizing failed. + void Box::setInt8(char newData, size_t index) { + index += payloadOffset; + if (index >= boxedSize()) { + if (!reserve(index, 0, 1)) { + return; + } + } + data[index] = newData; + } + + /// Gets the 8 bits integer at the given index. + /// Attempts to resize the data pointer if the index is out of range. + /// Returns zero if resizing failed. + char Box::getInt8(size_t index) { + index += payloadOffset; + if (index >= boxedSize()) { + if (!reserve(index, 0, 1)) { + return 0; + } + setInt8(0, index - payloadOffset); + } + return data[index]; + } + + /// Sets the 16 bits integer at the given index. + /// Attempts to resize the data pointer if the index is out of range. + /// Fails silently if resizing failed. + void Box::setInt16(short newData, size_t index) { + index += payloadOffset; + if (index + 1 >= boxedSize()) { + if (!reserve(index, 0, 2)) { + return; + } + } + newData = htons(newData); + memcpy(data + index, (char *) &newData, 2); + } + + /// Gets the 16 bits integer at the given index. + /// Attempts to resize the data pointer if the index is out of range. + /// Returns zero if resizing failed. + short Box::getInt16(size_t index) { + index += payloadOffset; + if (index + 1 >= boxedSize()) { + if (!reserve(index, 0, 2)) { + return 0; + } + setInt16(0, index - payloadOffset); + } + short result; + memcpy((char *) &result, data + index, 2); + return ntohs(result); + } + + /// Sets the 24 bits integer at the given index. + /// Attempts to resize the data pointer if the index is out of range. + /// Fails silently if resizing failed. + void Box::setInt24(uint32_t newData, size_t index) { + index += payloadOffset; + if (index + 2 >= boxedSize()) { + if (!reserve(index, 0, 3)) { + return; + } + } + data[index] = (newData & 0x00FF0000) >> 16; + data[index + 1] = (newData & 0x0000FF00) >> 8; + data[index + 2] = (newData & 0x000000FF); + } + + /// Gets the 24 bits integer at the given index. + /// Attempts to resize the data pointer if the index is out of range. + /// Returns zero if resizing failed. + uint32_t Box::getInt24(size_t index) { + index += payloadOffset; + if (index + 2 >= boxedSize()) { + if (!reserve(index, 0, 3)) { + return 0; + } + setInt24(0, index - payloadOffset); + } + uint32_t result = data[index]; + result <<= 8; + result += data[index + 1]; + result <<= 8; + result += data[index + 2]; + return result; + } + + /// Sets the 32 bits integer at the given index. + /// Attempts to resize the data pointer if the index is out of range. + /// Fails silently if resizing failed. + void Box::setInt32(uint32_t newData, size_t index) { + index += payloadOffset; + if (index + 3 >= boxedSize()) { + if (!reserve(index, 0, 4)) { + return; + } + } + ((int *)(data + index))[0] = htonl(newData); + } + + /// Gets the 32 bits integer at the given index. + /// Attempts to resize the data pointer if the index is out of range. + /// Returns zero if resizing failed. + uint32_t Box::getInt32(size_t index) { + index += payloadOffset; + if (index + 3 >= boxedSize()) { + if (!reserve(index, 0, 4)) { + return 0; + } + setInt32(0, index - payloadOffset); + } + return ntohl(((int *)(data + index))[0]); + } + + /// Sets the 64 bits integer at the given index. + /// Attempts to resize the data pointer if the index is out of range. + /// Fails silently if resizing failed. + void Box::setInt64(uint64_t newData, size_t index) { + index += payloadOffset; + if (index + 7 >= boxedSize()) { + if (!reserve(index, 0, 8)) { + return; + } + } + ((int *)(data + index))[0] = htonl((int)(newData >> 32)); + ((int *)(data + index))[1] = htonl((int)(newData & 0xFFFFFFFF)); + } + + /// Gets the 64 bits integer at the given index. + /// Attempts to resize the data pointer if the index is out of range. + /// Returns zero if resizing failed. + uint64_t Box::getInt64(size_t index) { + index += payloadOffset; + if (index + 7 >= boxedSize()) { + if (!reserve(index, 0, 8)) { + return 0; + } + setInt64(0, index - payloadOffset); + } + uint64_t result = ntohl(((int *)(data + index))[0]); + result <<= 32; + result += ntohl(((int *)(data + index))[1]); + return result; + } + + /// Sets the NULL-terminated string at the given index. + /// Will attempt to resize if the string doesn't fit. + /// Fails silently if resizing failed. + void Box::setString(std::string newData, size_t index) { + setString((char *)newData.c_str(), newData.size(), index); + } + + /// Sets the NULL-terminated string at the given index. + /// Will attempt to resize if the string doesn't fit. + /// Fails silently if resizing failed. + void Box::setString(char * newData, size_t size, size_t index) { + index += payloadOffset; + if (index >= boxedSize()) { + if (!reserve(index, 0, 1)) { + return; + } + data[index] = 0; + } + if (getStringLen(index) != size) { + if (!reserve(index, getStringLen(index) + 1, size + 1)) { + return; + } + } + memcpy(data + index, newData, size + 1); + } + + /// Gets the NULL-terminated string at the given index. + /// Will attempt to resize if the string is out of range. + /// Returns null if resizing failed. + char * Box::getString(size_t index) { + index += payloadOffset; + if (index >= boxedSize()) { + if (!reserve(index, 0, 1)) { + return 0; + } + data[index] = 0; + } + return data + index; + } + + /// Returns the length of the NULL-terminated string at the given index. + /// Returns 0 if out of range. + size_t Box::getStringLen(size_t index) { + index += payloadOffset; + if (index >= boxedSize()) { + return 0; + } + return strlen(data + index); + } + + /// Gets a reference to the box at the given index. + /// Do not store or copy this reference, for there will be raptors. + /// Will attempt to resize if out of range. + /// Returns an 8-byte error box if resizing failed. + Box & Box::getBox(size_t index) { + static Box retbox = Box((char *)"\000\000\000\010erro", false); + index += payloadOffset; + if (index + 8 > boxedSize()) { + if (!reserve(index, 0, 8)) { + retbox = Box((char *)"\000\000\000\010erro", false); + return retbox; + } + memcpy(data + index, "\000\000\000\010erro", 8); + } + retbox = Box(data + index, false); + return retbox; + } + + /// Returns the size of the box at the given position. + /// Returns undefined values if there is no box at the given position. + /// Returns 0 if out of range. + size_t Box::getBoxLen(size_t index) { + if ((index + payloadOffset + 8) > boxedSize()) { + return 0; + } + return getBox(index).boxedSize(); + } + + /// Replaces the existing box at the given index by the new box newEntry. + /// Will resize if needed, will reserve new space if out of range. + void Box::setBox(Box & newEntry, size_t index) { + int oldlen = getBoxLen(index); + int newlen = newEntry.boxedSize(); + if (oldlen != newlen && !reserve(index + payloadOffset, oldlen, newlen)) { + return; + } + memcpy(data + index + payloadOffset, newEntry.asBox(), newlen); + } + + /// Attempts to reserve enough space for wanted bytes of data at given position, where current bytes of data is now reserved. + /// This will move any existing data behind the currently reserved space to the proper location after reserving. + /// \returns True on success, false otherwise. + bool Box::reserve(size_t position, size_t current, size_t wanted) { + if (current == wanted) { + return true; + } + if (position > boxedSize()) { + wanted += position - boxedSize(); + } + if (current < wanted) { + //make bigger + if (boxedSize() + (wanted - current) > data_size) { + //realloc if managed, otherwise fail + if (!managed) { + return false; + } + void * ret = realloc(data, boxedSize() + (wanted - current)); + if (!ret) { + return false; + } + data = (char *)ret; + memset(data + boxedSize(), 0, wanted - current); //initialize to 0 + data_size = boxedSize() + (wanted - current); + } + } + //move data behind, if any + if (boxedSize() > (position + current)) { + memmove(data + position + wanted, data + position + current, boxedSize() - (position + current)); + } + //calculate and set new size + if (payloadOffset != 16) { + int newSize = boxedSize() + (wanted - current); + ((int *)data)[0] = htonl(newSize); + } + return true; + } + + fullBox::fullBox() { + setVersion(0); + } + + void fullBox::setVersion(char newVersion) { + setInt8(newVersion, 0); + } + + char fullBox::getVersion() { + return getInt8(0); + } + + void fullBox::setFlags(uint32_t newFlags) { + setInt24(newFlags, 1); + } + + uint32_t fullBox::getFlags() { + return getInt24(1); + } + + std::string fullBox::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent + 1, ' ') << "Version: " << (int)getVersion() << std::endl; + r << std::string(indent + 1, ' ') << "Flags: " << getFlags() << std::endl; + return r.str(); + } + + containerBox::containerBox() { + + } + + uint32_t containerBox::getContentCount() { + int res = 0; + unsigned int tempLoc = 0; + while (tempLoc < boxedSize() - 8) { + res++; + tempLoc += Box(getBox(tempLoc).asBox(), false).boxedSize(); + } + return res; + } + + void containerBox::setContent(Box & newContent, uint32_t no) { + int tempLoc = 0; + unsigned int contentCount = getContentCount(); + for (unsigned int i = 0; i < no; i++) { + if (i < contentCount) { + tempLoc += getBoxLen(tempLoc); + } else { + if (!reserve(tempLoc, 0, (no - contentCount) * 8)) { + return; + }; + memset(data + tempLoc, 0, (no - contentCount) * 8); + tempLoc += (no - contentCount) * 8; + break; + } + } + setBox(newContent, tempLoc); + } + + Box & containerBox::getContent(uint32_t no) { + static Box ret = Box((char *)"\000\000\000\010erro", false); + if (no > getContentCount()) { + return ret; + } + unsigned int i = 0; + int tempLoc = 0; + while (i < no) { + tempLoc += getBoxLen(tempLoc); + i++; + } + return getBox(tempLoc); + } + + std::string containerBox::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[" << getType() << "] Container Box (" << boxedSize() << ")" << std::endl; + Box curBox; + int tempLoc = 0; + int contentCount = getContentCount(); + for (int i = 0; i < contentCount; i++) { + curBox = getContent(i); + r << curBox.toPrettyString(indent + 1); + tempLoc += getBoxLen(tempLoc); + } + return r.str(); + } + + uint32_t containerFullBox::getContentCount() { + int res = 0; + unsigned int tempLoc = 4; + while (tempLoc < boxedSize() - 8) { + res++; + tempLoc += getBoxLen(tempLoc); + } + return res; + } + + void containerFullBox::setContent(Box & newContent, uint32_t no) { + int tempLoc = 4; + unsigned int contentCount = getContentCount(); + for (unsigned int i = 0; i < no; i++) { + if (i < contentCount) { + tempLoc += getBoxLen(tempLoc); + } else { + if (!reserve(tempLoc, 0, (no - contentCount) * 8)) { + return; + }; + memset(data + tempLoc, 0, (no - contentCount) * 8); + tempLoc += (no - contentCount) * 8; + break; + } + } + setBox(newContent, tempLoc); + } + + Box & containerFullBox::getContent(uint32_t no) { + static Box ret = Box((char *)"\000\000\000\010erro", false); + if (no > getContentCount()) { + return ret; + } + unsigned int i = 0; + int tempLoc = 4; + while (i < no) { + tempLoc += getBoxLen(tempLoc); + i++; + } + return getBox(tempLoc); + } + + std::string containerFullBox::toPrettyCFBString(uint32_t indent, std::string boxName) { + std::stringstream r; + r << std::string(indent, ' ') << boxName << " (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + Box curBox; + int tempLoc = 4; + int contentCount = getContentCount(); + for (int i = 0; i < contentCount; i++) { + curBox = getContent(i); + r << curBox.toPrettyString(indent + 1); + tempLoc += getBoxLen(tempLoc); + } + return r.str(); + } +} diff --git a/lib/mp4.h b/lib/mp4.h new file mode 100644 index 00000000..adf3863e --- /dev/null +++ b/lib/mp4.h @@ -0,0 +1,95 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "json.h" +#include "dtsc.h" + +/// Contains all MP4 format related code. +namespace MP4 { + std::string readBoxType(FILE * newData); + bool skipBox(FILE * newData); + + + class Box { + public: + Box(char * datapointer = 0, bool manage = true); + Box(const Box & rs); + Box & operator = (const Box & rs); + ~Box(); + std::string getType(); + bool isType(const char * boxType); + bool read(FILE * newData); + bool read(std::string & newData); + uint64_t boxedSize(); + uint64_t payloadSize(); + char * asBox(); + char * payload(); + void clear(); + std::string toPrettyString(uint32_t indent = 0); + protected: + //integer functions + void setInt8(char newData, size_t index); + char getInt8(size_t index); + void setInt16(short newData, size_t index); + short getInt16(size_t index); + void setInt24(uint32_t newData, size_t index); + uint32_t getInt24(size_t index); + void setInt32(uint32_t newData, size_t index); + uint32_t getInt32(size_t index); + void setInt64(uint64_t newData, size_t index); + uint64_t getInt64(size_t index); + //string functions + void setString(std::string newData, size_t index); + void setString(char * newData, size_t size, size_t index); + char * getString(size_t index); + size_t getStringLen(size_t index); + //box functions + Box & getBox(size_t index); + size_t getBoxLen(size_t index); + void setBox(Box & newEntry, size_t index); + //data functions + bool reserve(size_t position, size_t current, size_t wanted); + //internal variables + char * data; ///< Holds the data of this box + unsigned int data_size; ///< Currently reserved size + bool managed; ///< If false, will not attempt to resize/free the data pointer. + unsigned int payloadOffset; /// getInt8(countLoc)) { + int amount = no + 1 - getInt8(countLoc); + if (!reserve(payloadOffset + tempLoc, 0, amount)) { + return; + }; + memset(data + payloadOffset + tempLoc, 0, amount); + setInt8(no + 1, countLoc); //set new qualityEntryCount + tempLoc += no - i; + } + //now, tempLoc is at position for string number no, and we have at least 1 byte reserved. + setString(newEntry, tempLoc); + } + + ///\return Empty string if no > serverEntryCount(), serverEntry[no] otherwise. + const char * ABST::getServerEntry(uint32_t no) { + if (no + 1 > getServerEntryCount()) { + return ""; + } + int tempLoc = 29 + getStringLen(29) + 1 + 1; //position of first entry + for (unsigned int i = 0; i < no; i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + return getString(tempLoc); + } + + uint32_t ABST::getQualityEntryCount() { + int countLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + countLoc += getStringLen(countLoc) + 1; + } + return getInt8(countLoc); + } + + void ABST::setQualityEntry(std::string & newEntry, uint32_t no) { + int countLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + countLoc += getStringLen(countLoc) + 1; + } + int tempLoc = countLoc + 1; + //attempt to reach the wanted position + unsigned int i; + for (i = 0; i < getInt8(countLoc) && i < no; ++i) { + tempLoc += getStringLen(tempLoc) + 1; + } + //we are now either at the end, or at the right position + //let's reserve any unreserved space... + if (no + 1 > getInt8(countLoc)) { + int amount = no + 1 - getInt8(countLoc); + if (!reserve(payloadOffset + tempLoc, 0, amount)) { + return; + }; + memset(data + payloadOffset + tempLoc, 0, amount); + setInt8(no + 1, countLoc); //set new qualityEntryCount + tempLoc += no - i; + } + //now, tempLoc is at position for string number no, and we have at least 1 byte reserved. + setString(newEntry, tempLoc); + } + + const char * ABST::getQualityEntry(uint32_t no) { + if (no > getQualityEntryCount()) { + return ""; + } + int tempLoc = 29 + getStringLen(29) + 1 + 1; //position of serverentries; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += 1; //first qualityentry + for (unsigned int i = 0; i < no; i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + return getString(tempLoc); + } + + void ABST::setDrmData(std::string newDrm) { + uint32_t tempLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc++; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + setString(newDrm, tempLoc); + } + + char * ABST::getDrmData() { + uint32_t tempLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc++; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + return getString(tempLoc); + } + + void ABST::setMetaData(std::string newMetaData) { + uint32_t tempLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc++; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += getStringLen(tempLoc) + 1; + setString(newMetaData, tempLoc); + } + + char * ABST::getMetaData() { + uint32_t tempLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc++; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += getStringLen(tempLoc) + 1; + return getString(tempLoc); + } + + uint32_t ABST::getSegmentRunTableCount() { + uint32_t tempLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc++; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += getStringLen(tempLoc) + 1; //DrmData + tempLoc += getStringLen(tempLoc) + 1; //MetaData + return getInt8(tempLoc); + } + + void ABST::setSegmentRunTable(ASRT & newSegment, uint32_t no) { + uint32_t tempLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc++; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += getStringLen(tempLoc) + 1; //DrmData + tempLoc += getStringLen(tempLoc) + 1; //MetaData + int countLoc = tempLoc; + tempLoc++; //skip segmentRuntableCount + //attempt to reach the wanted position + unsigned int i; + for (i = 0; i < getInt8(countLoc) && i < no; ++i) { + tempLoc += getBoxLen(tempLoc); + } + //we are now either at the end, or at the right position + //let's reserve any unreserved space... + if (no + 1 > getInt8(countLoc)) { + int amount = no + 1 - getInt8(countLoc); + if (!reserve(payloadOffset + tempLoc, 0, amount * 8)) { + return; + }; + //set empty erro boxes as contents + for (int j = 0; j < amount; ++j) { + memcpy(data + payloadOffset + tempLoc + j * 8, "\000\000\000\010erro", 8); + } + setInt8(no + 1, countLoc); //set new count + tempLoc += (no - i) * 8; + } + //now, tempLoc is at position for string number no, and we have at least an erro box reserved. + setBox(newSegment, tempLoc); + } + + ASRT & ABST::getSegmentRunTable(uint32_t no) { + static Box result; + if (no > getSegmentRunTableCount()) { + static Box res; + return (ASRT &)res; + } + uint32_t tempLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc++; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += getStringLen(tempLoc) + 1; //DrmData + tempLoc += getStringLen(tempLoc) + 1; //MetaData + tempLoc++; //segmentRuntableCount + for (unsigned int i = 0; i < no; ++i) { + tempLoc += getBoxLen(tempLoc); + } + return (ASRT &)getBox(tempLoc); + } + + uint32_t ABST::getFragmentRunTableCount() { + uint32_t tempLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc++; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += getStringLen(tempLoc) + 1; //DrmData + tempLoc += getStringLen(tempLoc) + 1; //MetaData + for (unsigned int i = getInt8(tempLoc++); i != 0; --i) { + tempLoc += getBoxLen(tempLoc); + } + return getInt8(tempLoc); + } + + void ABST::setFragmentRunTable(AFRT & newFragment, uint32_t no) { + uint32_t tempLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc++; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += getStringLen(tempLoc) + 1; //DrmData + tempLoc += getStringLen(tempLoc) + 1; //MetaData + for (unsigned int i = getInt8(tempLoc++); i != 0; --i) { + tempLoc += getBoxLen(tempLoc); + } + int countLoc = tempLoc; + tempLoc++; + //attempt to reach the wanted position + unsigned int i; + for (i = 0; i < getInt8(countLoc) && i < no; ++i) { + tempLoc += getBoxLen(tempLoc); + } + //we are now either at the end, or at the right position + //let's reserve any unreserved space... + if (no + 1 > getInt8(countLoc)) { + unsigned int amount = no + 1 - getInt8(countLoc); + if (!reserve(payloadOffset + tempLoc, 0, amount * 8)) { + return; + }; + //set empty erro boxes as contents + for (unsigned int j = 0; j < amount; ++j) { + memcpy(data + payloadOffset + tempLoc + j * 8, "\000\000\000\010erro", 8); + } + setInt8(no + 1, countLoc); //set new count + tempLoc += (no - i) * 8; + } + //now, tempLoc is at position for string number no, and we have at least 1 byte reserved. + setBox(newFragment, tempLoc); + } + + AFRT & ABST::getFragmentRunTable(uint32_t no) { + static Box result; + if (no >= getFragmentRunTableCount()) { + static Box res; + return (AFRT &)res; + } + uint32_t tempLoc = 29 + getStringLen(29) + 1 + 1; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc++; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += getStringLen(tempLoc) + 1; //DrmData + tempLoc += getStringLen(tempLoc) + 1; //MetaData + for (unsigned int i = getInt8(tempLoc++); i != 0; --i) { + tempLoc += getBoxLen(tempLoc); + } + tempLoc++; + for (unsigned int i = 0; i < no; i++) { + tempLoc += getBoxLen(tempLoc); + } + return (AFRT &)getBox(tempLoc); + } + + std::string ABST::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[abst] Bootstrap Info (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Version " << (int)getVersion() << std::endl; + r << std::string(indent + 1, ' ') << "BootstrapinfoVersion " << getBootstrapinfoVersion() << std::endl; + r << std::string(indent + 1, ' ') << "Profile " << (int)getProfile() << std::endl; + if (getLive()) { + r << std::string(indent + 1, ' ') << "Live" << std::endl; + } else { + r << std::string(indent + 1, ' ') << "Recorded" << std::endl; + } + if (getUpdate()) { + r << std::string(indent + 1, ' ') << "Update" << std::endl; + } else { + r << std::string(indent + 1, ' ') << "Replacement or new table" << std::endl; + } + r << std::string(indent + 1, ' ') << "Timescale " << getTimeScale() << std::endl; + r << std::string(indent + 1, ' ') << "CurrMediaTime " << getCurrentMediaTime() << std::endl; + r << std::string(indent + 1, ' ') << "SmpteTimeCodeOffset " << getSmpteTimeCodeOffset() << std::endl; + r << std::string(indent + 1, ' ') << "MovieIdentifier " << getMovieIdentifier() << std::endl; + r << std::string(indent + 1, ' ') << "ServerEntryTable (" << getServerEntryCount() << ")" << std::endl; + for (unsigned int i = 0; i < getServerEntryCount(); i++) { + r << std::string(indent + 2, ' ') << i << ": " << getServerEntry(i) << std::endl; + } + r << std::string(indent + 1, ' ') << "QualityEntryTable (" << getQualityEntryCount() << ")" << std::endl; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + r << std::string(indent + 2, ' ') << i << ": " << getQualityEntry(i) << std::endl; + } + r << std::string(indent + 1, ' ') << "DrmData " << getDrmData() << std::endl; + r << std::string(indent + 1, ' ') << "MetaData " << getMetaData() << std::endl; + r << std::string(indent + 1, ' ') << "SegmentRunTableEntries (" << getSegmentRunTableCount() << ")" << std::endl; + for (uint32_t i = 0; i < getSegmentRunTableCount(); i++) { + r << ((Box)getSegmentRunTable(i)).toPrettyString(indent + 2); + } + r << std::string(indent + 1, ' ') + "FragmentRunTableEntries (" << getFragmentRunTableCount() << ")" << std::endl; + for (uint32_t i = 0; i < getFragmentRunTableCount(); i++) { + r << ((Box)getFragmentRunTable(i)).toPrettyString(indent + 2); + } + return r.str(); + } + + AFRT::AFRT() { + memcpy(data + 4, "afrt", 4); + setVersion(0); + setUpdate(0); + setTimeScale(1000); + } + + void AFRT::setVersion(char newVersion) { + setInt8(newVersion, 0); + } + + uint32_t AFRT::getVersion() { + return getInt8(0); + } + + void AFRT::setUpdate(uint32_t newUpdate) { + setInt24(newUpdate, 1); + } + + uint32_t AFRT::getUpdate() { + return getInt24(1); + } + + void AFRT::setTimeScale(uint32_t newScale) { + setInt32(newScale, 4); + } + + uint32_t AFRT::getTimeScale() { + return getInt32(4); + } + + uint32_t AFRT::getQualityEntryCount() { + return getInt8(8); + } + + void AFRT::setQualityEntry(std::string & newEntry, uint32_t no) { + int countLoc = 8; + int tempLoc = countLoc + 1; + //attempt to reach the wanted position + unsigned int i; + for (i = 0; i < getQualityEntryCount() && i < no; ++i) { + tempLoc += getStringLen(tempLoc) + 1; + } + //we are now either at the end, or at the right position + //let's reserve any unreserved space... + if (no + 1 > getQualityEntryCount()) { + int amount = no + 1 - getQualityEntryCount(); + if (!reserve(payloadOffset + tempLoc, 0, amount)) { + return; + }; + memset(data + payloadOffset + tempLoc, 0, amount); + setInt8(no + 1, countLoc); //set new qualityEntryCount + tempLoc += no - i; + } + //now, tempLoc is at position for string number no, and we have at least 1 byte reserved. + setString(newEntry, tempLoc); + } + + const char * AFRT::getQualityEntry(uint32_t no) { + if (no + 1 > getQualityEntryCount()) { + return ""; + } + int tempLoc = 9; //position of first quality entry + for (unsigned int i = 0; i < no; i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + return getString(tempLoc); + } + + uint32_t AFRT::getFragmentRunCount() { + int tempLoc = 9; + for (unsigned int i = 0; i < getQualityEntryCount(); ++i) { + tempLoc += getStringLen(tempLoc) + 1; + } + return getInt32(tempLoc); + } + + void AFRT::setFragmentRun(afrt_runtable newRun, uint32_t no) { + int tempLoc = 9; + for (unsigned int i = 0; i < getQualityEntryCount(); ++i) { + tempLoc += getStringLen(tempLoc) + 1; + } + int countLoc = tempLoc; + unsigned int count = getInt32(countLoc); + tempLoc += 4; + for (unsigned int i = 0; i < no; i++) { + if (i + 1 > count) { + setInt32(0, tempLoc); + setInt64(0, tempLoc + 4); + setInt32(1, tempLoc + 12); + } + if (getInt32(tempLoc + 12) == 0) { + tempLoc += 17; + } else { + tempLoc += 16; + } + } + setInt32(newRun.firstFragment, tempLoc); + setInt64(newRun.firstTimestamp, tempLoc + 4); + setInt32(newRun.duration, tempLoc + 12); + if (newRun.duration == 0) { + setInt8(newRun.discontinuity, tempLoc + 16); + } + if (count < no + 1) { + setInt32(no + 1, countLoc); + } + } + + afrt_runtable AFRT::getFragmentRun(uint32_t no) { + afrt_runtable res; + if (no > getFragmentRunCount()) { + return res; + } + int tempLoc = 9; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += 4; + for (unsigned int i = 0; i < no; i++) { + if (getInt32(tempLoc + 12) == 0) { + tempLoc += 17; + } else { + tempLoc += 16; + } + } + res.firstFragment = getInt32(tempLoc); + res.firstTimestamp = getInt64(tempLoc + 4); + res.duration = getInt32(tempLoc + 12); + if (res.duration) { + res.discontinuity = getInt8(tempLoc + 16); + } else { + res.discontinuity = 0; + } + return res; + } + + std::string AFRT::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[afrt] Fragment Run Table (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Version " << (int)getVersion() << std::endl; + if (getUpdate()) { + r << std::string(indent + 1, ' ') << "Update" << std::endl; + } else { + r << std::string(indent + 1, ' ') << "Replacement or new table" << std::endl; + } + r << std::string(indent + 1, ' ') << "Timescale " << getTimeScale() << std::endl; + r << std::string(indent + 1, ' ') << "QualitySegmentUrlModifiers (" << getQualityEntryCount() << ")" << std::endl; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + r << std::string(indent + 2, ' ') << i << ": " << getQualityEntry(i) << std::endl; + } + r << std::string(indent + 1, ' ') << "FragmentRunEntryTable (" << getFragmentRunCount() << ")" << std::endl; + for (unsigned int i = 0; i < getFragmentRunCount(); i++) { + afrt_runtable myRun = getFragmentRun(i); + if (myRun.duration) { + r << std::string(indent + 2, ' ') << i << ": " << myRun.firstFragment << " is at " << ((double)myRun.firstTimestamp / (double)getTimeScale()) + << "s, " << ((double)myRun.duration / (double)getTimeScale()) << "s per fragment." << std::endl; + } else { + r << std::string(indent + 2, ' ') << i << ": " << myRun.firstFragment << " is at " << ((double)myRun.firstTimestamp / (double)getTimeScale()) + << "s, discontinuity type " << myRun.discontinuity << std::endl; + } + } + return r.str(); + } + + ASRT::ASRT() { + memcpy(data + 4, "asrt", 4); + setVersion(0); + setUpdate(0); + } + + void ASRT::setVersion(char newVersion) { + setInt8(newVersion, 0); + } + + uint32_t ASRT::getVersion() { + return getInt8(0); + } + + void ASRT::setUpdate(uint32_t newUpdate) { + setInt24(newUpdate, 1); + } + + uint32_t ASRT::getUpdate() { + return getInt24(1); + } + + uint32_t ASRT::getQualityEntryCount() { + return getInt8(4); + } + + void ASRT::setQualityEntry(std::string & newEntry, uint32_t no) { + int countLoc = 4; + int tempLoc = countLoc + 1; + //attempt to reach the wanted position + unsigned int i; + for (i = 0; i < getQualityEntryCount() && i < no; ++i) { + tempLoc += getStringLen(tempLoc) + 1; + } + //we are now either at the end, or at the right position + //let's reserve any unreserved space... + if (no + 1 > getQualityEntryCount()) { + int amount = no + 1 - getQualityEntryCount(); + if (!reserve(payloadOffset + tempLoc, 0, amount)) { + return; + }; + memset(data + payloadOffset + tempLoc, 0, amount); + setInt8(no + 1, countLoc); //set new qualityEntryCount + tempLoc += no - i; + } + //now, tempLoc is at position for string number no, and we have at least 1 byte reserved. + setString(newEntry, tempLoc); + } + + const char * ASRT::getQualityEntry(uint32_t no) { + if (no > getQualityEntryCount()) { + return ""; + } + int tempLoc = 5; //position of qualityentry count; + for (unsigned int i = 0; i < no; i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + return getString(tempLoc); + } + + uint32_t ASRT::getSegmentRunEntryCount() { + int tempLoc = 5; //position of qualityentry count; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + return getInt32(tempLoc); + } + + void ASRT::setSegmentRun(uint32_t firstSegment, uint32_t fragmentsPerSegment, uint32_t no) { + int tempLoc = 5; //position of qualityentry count; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + tempLoc += getStringLen(tempLoc) + 1; + } + int countLoc = tempLoc; + tempLoc += 4 + no * 8; + if (no + 1 > getInt32(countLoc)) { + setInt32(no + 1, countLoc); //set new qualityEntryCount + } + setInt32(firstSegment, tempLoc); + setInt32(fragmentsPerSegment, tempLoc + 4); + } + + asrt_runtable ASRT::getSegmentRun(uint32_t no) { + asrt_runtable res; + if (no >= getSegmentRunEntryCount()) { + return res; + } + int tempLoc = 5; //position of qualityentry count; + for (unsigned int i = 0; i < getQualityEntryCount(); ++i) { + tempLoc += getStringLen(tempLoc) + 1; + } + tempLoc += 4 + 8 * no; + res.firstSegment = getInt32(tempLoc); + res.fragmentsPerSegment = getInt32(tempLoc + 4); + return res; + } + + std::string ASRT::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[asrt] Segment Run Table (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Version " << getVersion() << std::endl; + if (getUpdate()) { + r << std::string(indent + 1, ' ') << "Update" << std::endl; + } else { + r << std::string(indent + 1, ' ') << "Replacement or new table" << std::endl; + } + r << std::string(indent + 1, ' ') << "QualityEntryTable (" << getQualityEntryCount() << ")" << std::endl; + for (unsigned int i = 0; i < getQualityEntryCount(); i++) { + r << std::string(indent + 2, ' ') << i << ": " << getQualityEntry(i) << std::endl; + } + r << std::string(indent + 1, ' ') << "SegmentRunEntryTable (" << getSegmentRunEntryCount() << ")" << std::endl; + for (unsigned int i = 0; i < getSegmentRunEntryCount(); i++) { + r << std::string(indent + 2, ' ') << i << ": First=" << getSegmentRun(i).firstSegment << ", FragmentsPerSegment=" + << getSegmentRun(i).fragmentsPerSegment << std::endl; + } + return r.str(); + } + AFRA::AFRA() { + memcpy(data + 4, "afra", 4); + setInt32(0, 9); //entrycount = 0 + setFlags(0); + } + + void AFRA::setVersion(uint32_t newVersion) { + setInt8(newVersion, 0); + } + + uint32_t AFRA::getVersion() { + return getInt8(0); + } + + void AFRA::setFlags(uint32_t newFlags) { + setInt24(newFlags, 1); + } + + uint32_t AFRA::getFlags() { + return getInt24(1); + } + + void AFRA::setLongIDs(bool newVal) { + if (newVal) { + setInt8((getInt8(4) & 0x7F) + 0x80, 4); + } else { + setInt8((getInt8(4) & 0x7F), 4); + } + } + + bool AFRA::getLongIDs() { + return getInt8(4) & 0x80; + } + + void AFRA::setLongOffsets(bool newVal) { + if (newVal) { + setInt8((getInt8(4) & 0xBF) + 0x40, 4); + } else { + setInt8((getInt8(4) & 0xBF), 4); + } + } + + bool AFRA::getLongOffsets() { + return getInt8(4) & 0x40; + } + + void AFRA::setGlobalEntries(bool newVal) { + if (newVal) { + setInt8((getInt8(4) & 0xDF) + 0x20, 4); + } else { + setInt8((getInt8(4) & 0xDF), 4); + } + } + + bool AFRA::getGlobalEntries() { + return getInt8(4) & 0x20; + } + + void AFRA::setTimeScale(uint32_t newVal) { + setInt32(newVal, 5); + } + + uint32_t AFRA::getTimeScale() { + return getInt32(5); + } + + uint32_t AFRA::getEntryCount() { + return getInt32(9); + } + + void AFRA::setEntry(afraentry newEntry, uint32_t no) { + int entrysize = 12; + if (getLongOffsets()) { + entrysize = 16; + } + setInt64(newEntry.time, 13 + entrysize * no); + if (getLongOffsets()) { + setInt64(newEntry.offset, 21 + entrysize * no); + } else { + setInt32(newEntry.offset, 21 + entrysize * no); + } + if (no + 1 > getEntryCount()) { + setInt32(no + 1, 9); + } + } + + afraentry AFRA::getEntry(uint32_t no) { + afraentry ret; + int entrysize = 12; + if (getLongOffsets()) { + entrysize = 16; + } + ret.time = getInt64(13 + entrysize * no); + if (getLongOffsets()) { + ret.offset = getInt64(21 + entrysize * no); + } else { + ret.offset = getInt32(21 + entrysize * no); + } + return ret; + } + + uint32_t AFRA::getGlobalEntryCount() { + if (!getGlobalEntries()) { + return 0; + } + int entrysize = 12; + if (getLongOffsets()) { + entrysize = 16; + } + return getInt32(13 + entrysize * getEntryCount()); + } + + void AFRA::setGlobalEntry(globalafraentry newEntry, uint32_t no) { + int offset = 13 + 12 * getEntryCount() + 4; + if (getLongOffsets()) { + offset = 13 + 16 * getEntryCount() + 4; + } + int entrysize = 20; + if (getLongIDs()) { + entrysize += 4; + } + if (getLongOffsets()) { + entrysize += 8; + } + + setInt64(newEntry.time, offset + entrysize * no); + if (getLongIDs()) { + setInt32(newEntry.segment, offset + entrysize * no + 8); + setInt32(newEntry.fragment, offset + entrysize * no + 12); + } else { + setInt16(newEntry.segment, offset + entrysize * no + 8); + setInt16(newEntry.fragment, offset + entrysize * no + 10); + } + if (getLongOffsets()) { + setInt64(newEntry.afraoffset, offset + entrysize * no + entrysize - 16); + setInt64(newEntry.offsetfromafra, offset + entrysize * no + entrysize - 8); + } else { + setInt32(newEntry.afraoffset, offset + entrysize * no + entrysize - 8); + setInt32(newEntry.offsetfromafra, offset + entrysize * no + entrysize - 4); + } + + if (getInt32(offset - 4) < no + 1) { + setInt32(no + 1, offset - 4); + } + } + + globalafraentry AFRA::getGlobalEntry(uint32_t no) { + globalafraentry ret; + int offset = 13 + 12 * getEntryCount() + 4; + if (getLongOffsets()) { + offset = 13 + 16 * getEntryCount() + 4; + } + int entrysize = 20; + if (getLongIDs()) { + entrysize += 4; + } + if (getLongOffsets()) { + entrysize += 8; + } + + ret.time = getInt64(offset + entrysize * no); + if (getLongIDs()) { + ret.segment = getInt32(offset + entrysize * no + 8); + ret.fragment = getInt32(offset + entrysize * no + 12); + } else { + ret.segment = getInt16(offset + entrysize * no + 8); + ret.fragment = getInt16(offset + entrysize * no + 10); + } + if (getLongOffsets()) { + ret.afraoffset = getInt64(offset + entrysize * no + entrysize - 16); + ret.offsetfromafra = getInt64(offset + entrysize * no + entrysize - 8); + } else { + ret.afraoffset = getInt32(offset + entrysize * no + entrysize - 8); + ret.offsetfromafra = getInt32(offset + entrysize * no + entrysize - 4); + } + return ret; + } + + std::string AFRA::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[afra] Fragment Random Access (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Version " << getVersion() << std::endl; + r << std::string(indent + 1, ' ') << "Flags " << getFlags() << std::endl; + r << std::string(indent + 1, ' ') << "Long IDs " << getLongIDs() << std::endl; + r << std::string(indent + 1, ' ') << "Long Offsets " << getLongOffsets() << std::endl; + r << std::string(indent + 1, ' ') << "Global Entries " << getGlobalEntries() << std::endl; + r << std::string(indent + 1, ' ') << "TimeScale " << getTimeScale() << std::endl; + + uint32_t count = getEntryCount(); + r << std::string(indent + 1, ' ') << "Entries (" << count << ") " << std::endl; + for (uint32_t i = 0; i < count; ++i) { + afraentry tmpent = getEntry(i); + r << std::string(indent + 1, ' ') << i << ": Time " << tmpent.time << ", Offset " << tmpent.offset << std::endl; + } + + if (getGlobalEntries()) { + count = getGlobalEntryCount(); + r << std::string(indent + 1, ' ') << "Global Entries (" << count << ") " << std::endl; + for (uint32_t i = 0; i < count; ++i) { + globalafraentry tmpent = getGlobalEntry(i); + r << std::string(indent + 1, ' ') << i << ": T " << tmpent.time << ", S" << tmpent.segment << "F" << tmpent.fragment << ", " + << tmpent.afraoffset << "/" << tmpent.offsetfromafra << std::endl; + } + } + return r.str(); + } +} diff --git a/lib/mp4_adobe.h b/lib/mp4_adobe.h new file mode 100644 index 00000000..ca8b42d0 --- /dev/null +++ b/lib/mp4_adobe.h @@ -0,0 +1,139 @@ +#pragma once +#include "mp4.h" +#include + +namespace MP4 { + //class Box; + + struct afrt_runtable { + uint32_t firstFragment; + uint64_t firstTimestamp; + uint32_t duration; + uint32_t discontinuity; + }; + //fragmentRun + + /// AFRT Box class + class AFRT: public Box { + public: + AFRT(); + void setVersion(char newVersion); + uint32_t getVersion(); + void setUpdate(uint32_t newUpdate); + uint32_t getUpdate(); + void setTimeScale(uint32_t newScale); + uint32_t getTimeScale(); + uint32_t getQualityEntryCount(); + void setQualityEntry(std::string & newQuality, uint32_t no); + const char * getQualityEntry(uint32_t no); + uint32_t getFragmentRunCount(); + void setFragmentRun(afrt_runtable newRun, uint32_t no); + afrt_runtable getFragmentRun(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + //AFRT Box + + struct asrt_runtable { + uint32_t firstSegment; + uint32_t fragmentsPerSegment; + }; + + /// ASRT Box class + class ASRT: public Box { + public: + ASRT(); + void setVersion(char newVersion); + uint32_t getVersion(); + void setUpdate(uint32_t newUpdate); + uint32_t getUpdate(); + uint32_t getQualityEntryCount(); + void setQualityEntry(std::string & newQuality, uint32_t no); + const char * getQualityEntry(uint32_t no); + uint32_t getSegmentRunEntryCount(); + void setSegmentRun(uint32_t firstSegment, uint32_t fragmentsPerSegment, uint32_t no); + asrt_runtable getSegmentRun(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + //ASRT Box + + /// ABST Box class + class ABST: public Box { + public: + ABST(); + void setVersion(char newVersion); + char getVersion(); + void setFlags(uint32_t newFlags); + uint32_t getFlags(); + void setBootstrapinfoVersion(uint32_t newVersion); + uint32_t getBootstrapinfoVersion(); + void setProfile(char newProfile); + char getProfile(); + void setLive(bool newLive); + bool getLive(); + void setUpdate(bool newUpdate); + bool getUpdate(); + void setTimeScale(uint32_t newTimeScale); + uint32_t getTimeScale(); + void setCurrentMediaTime(uint64_t newTime); + uint64_t getCurrentMediaTime(); + void setSmpteTimeCodeOffset(uint64_t newTime); + uint64_t getSmpteTimeCodeOffset(); + void setMovieIdentifier(std::string & newIdentifier); + char * getMovieIdentifier(); + uint32_t getServerEntryCount(); + void setServerEntry(std::string & entry, uint32_t no); + const char * getServerEntry(uint32_t no); + uint32_t getQualityEntryCount(); + void setQualityEntry(std::string & entry, uint32_t no); + const char * getQualityEntry(uint32_t no); + void setDrmData(std::string newDrm); + char * getDrmData(); + void setMetaData(std::string newMetaData); + char * getMetaData(); + uint32_t getSegmentRunTableCount(); + void setSegmentRunTable(ASRT & table, uint32_t no); + ASRT & getSegmentRunTable(uint32_t no); + uint32_t getFragmentRunTableCount(); + void setFragmentRunTable(AFRT & table, uint32_t no); + AFRT & getFragmentRunTable(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + //ABST Box + + struct afraentry { + uint64_t time; + uint64_t offset; + }; + struct globalafraentry { + uint64_t time; + uint32_t segment; + uint32_t fragment; + uint64_t afraoffset; + uint64_t offsetfromafra; + }; + class AFRA: public Box { + public: + AFRA(); + void setVersion(uint32_t newVersion); + uint32_t getVersion(); + void setFlags(uint32_t newFlags); + uint32_t getFlags(); + void setLongIDs(bool newVal); + bool getLongIDs(); + void setLongOffsets(bool newVal); + bool getLongOffsets(); + void setGlobalEntries(bool newVal); + bool getGlobalEntries(); + void setTimeScale(uint32_t newVal); + uint32_t getTimeScale(); + uint32_t getEntryCount(); + void setEntry(afraentry newEntry, uint32_t no); + afraentry getEntry(uint32_t no); + uint32_t getGlobalEntryCount(); + void setGlobalEntry(globalafraentry newEntry, uint32_t no); + globalafraentry getGlobalEntry(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + +} + diff --git a/lib/mp4_generic.cpp b/lib/mp4_generic.cpp new file mode 100644 index 00000000..5dce8443 --- /dev/null +++ b/lib/mp4_generic.cpp @@ -0,0 +1,3001 @@ +#include "mp4_generic.h" +#include "defines.h" + +namespace MP4 { + MFHD::MFHD() { + memcpy(data + 4, "mfhd", 4); + setInt32(0, 0); + } + + void MFHD::setSequenceNumber(uint32_t newSequenceNumber) { + setInt32(newSequenceNumber, 4); + } + + uint32_t MFHD::getSequenceNumber() { + return getInt32(4); + } + + std::string MFHD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[mfhd] Movie Fragment Header (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "SequenceNumber " << getSequenceNumber() << std::endl; + return r.str(); + } + + MOOF::MOOF() { + memcpy(data + 4, "moof", 4); + } + + TRAF::TRAF() { + memcpy(data + 4, "traf", 4); + } + + uint32_t TRAF::getContentCount() { + int res = 0; + unsigned int tempLoc = 0; + while (tempLoc < boxedSize() - 8) { + res++; + tempLoc += getBoxLen(tempLoc); + } + return res; + } + + void TRAF::setContent(Box & newContent, uint32_t no) { + int tempLoc = 0; + unsigned int contentCount = getContentCount(); + for (unsigned int i = 0; i < no; i++) { + if (i < contentCount) { + tempLoc += getBoxLen(tempLoc); + } else { + if (!reserve(tempLoc, 0, (no - contentCount) * 8)) { + return; + }; + memset(data + tempLoc, 0, (no - contentCount) * 8); + tempLoc += (no - contentCount) * 8; + break; + } + } + setBox(newContent, tempLoc); + } + + Box & TRAF::getContent(uint32_t no) { + static Box ret = Box((char *)"\000\000\000\010erro", false); + if (no > getContentCount()) { + return ret; + } + unsigned int i = 0; + int tempLoc = 0; + while (i < no) { + tempLoc += getBoxLen(tempLoc); + i++; + } + return getBox(tempLoc); + } + + std::string TRAF::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[traf] Track Fragment Box (" << boxedSize() << ")" << std::endl; + int contentCount = getContentCount(); + for (int i = 0; i < contentCount; i++) { + Box curBox = Box(getContent(i).asBox(), false); + r << curBox.toPrettyString(indent + 1); + } + return r.str(); + } + + TRUN::TRUN() { + memcpy(data + 4, "trun", 4); + } + + void TRUN::setFlags(uint32_t newFlags) { + setInt24(newFlags, 1); + } + + uint32_t TRUN::getFlags() { + return getInt24(1); + } + + void TRUN::setDataOffset(uint32_t newOffset) { + if (getFlags() & trundataOffset) { + setInt32(newOffset, 8); + } + } + + uint32_t TRUN::getDataOffset() { + if (getFlags() & trundataOffset) { + return getInt32(8); + } else { + return 0; + } + } + + void TRUN::setFirstSampleFlags(uint32_t newSampleFlags) { + if (!(getFlags() & trunfirstSampleFlags)) { + return; + } + if (getFlags() & trundataOffset) { + setInt32(newSampleFlags, 12); + } else { + setInt32(newSampleFlags, 8); + } + } + + uint32_t TRUN::getFirstSampleFlags() { + if (!(getFlags() & trunfirstSampleFlags)) { + return 0; + } + if (getFlags() & trundataOffset) { + return getInt32(12); + } else { + return getInt32(8); + } + } + + uint32_t TRUN::getSampleInformationCount() { + return getInt32(4); + } + + void TRUN::setSampleInformation(trunSampleInformation newSample, uint32_t no) { + uint32_t flags = getFlags(); + uint32_t sampInfoSize = 0; + if (flags & trunsampleDuration) { + sampInfoSize += 4; + } + if (flags & trunsampleSize) { + sampInfoSize += 4; + } + if (flags & trunsampleFlags) { + sampInfoSize += 4; + } + if (flags & trunsampleOffsets) { + sampInfoSize += 4; + } + uint32_t offset = 8; + if (flags & trundataOffset) { + offset += 4; + } + if (flags & trunfirstSampleFlags) { + offset += 4; + } + uint32_t innerOffset = 0; + if (flags & trunsampleDuration) { + setInt32(newSample.sampleDuration, offset + no * sampInfoSize + innerOffset); + innerOffset += 4; + } + if (flags & trunsampleSize) { + setInt32(newSample.sampleSize, offset + no * sampInfoSize + innerOffset); + innerOffset += 4; + } + if (flags & trunsampleFlags) { + setInt32(newSample.sampleFlags, offset + no * sampInfoSize + innerOffset); + innerOffset += 4; + } + if (flags & trunsampleOffsets) { + setInt32(newSample.sampleOffset, offset + no * sampInfoSize + innerOffset); + innerOffset += 4; + } + if (getSampleInformationCount() < no + 1) { + setInt32(no + 1, 4); + } + } + + trunSampleInformation TRUN::getSampleInformation(uint32_t no) { + trunSampleInformation ret; + ret.sampleDuration = 0; + ret.sampleSize = 0; + ret.sampleFlags = 0; + ret.sampleOffset = 0; + if (getSampleInformationCount() < no + 1) { + return ret; + } + uint32_t flags = getFlags(); + uint32_t sampInfoSize = 0; + if (flags & trunsampleDuration) { + sampInfoSize += 4; + } + if (flags & trunsampleSize) { + sampInfoSize += 4; + } + if (flags & trunsampleFlags) { + sampInfoSize += 4; + } + if (flags & trunsampleOffsets) { + sampInfoSize += 4; + } + uint32_t offset = 8; + if (flags & trundataOffset) { + offset += 4; + } + if (flags & trunfirstSampleFlags) { + offset += 4; + } + uint32_t innerOffset = 0; + if (flags & trunsampleDuration) { + ret.sampleDuration = getInt32(offset + no * sampInfoSize + innerOffset); + innerOffset += 4; + } + if (flags & trunsampleSize) { + ret.sampleSize = getInt32(offset + no * sampInfoSize + innerOffset); + innerOffset += 4; + } + if (flags & trunsampleFlags) { + ret.sampleFlags = getInt32(offset + no * sampInfoSize + innerOffset); + innerOffset += 4; + } + if (flags & trunsampleOffsets) { + ret.sampleOffset = getInt32(offset + no * sampInfoSize + innerOffset); + innerOffset += 4; + } + return ret; + } + + std::string TRUN::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[trun] Track Fragment Run (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Version " << (int)getInt8(0) << std::endl; + + uint32_t flags = getFlags(); + r << std::string(indent + 1, ' ') << "Flags"; + if (flags & trundataOffset) { + r << " dataOffset"; + } + if (flags & trunfirstSampleFlags) { + r << " firstSampleFlags"; + } + if (flags & trunsampleDuration) { + r << " sampleDuration"; + } + if (flags & trunsampleSize) { + r << " sampleSize"; + } + if (flags & trunsampleFlags) { + r << " sampleFlags"; + } + if (flags & trunsampleOffsets) { + r << " sampleOffsets"; + } + r << std::endl; + + if (flags & trundataOffset) { + r << std::string(indent + 1, ' ') << "Data Offset " << getDataOffset() << std::endl; + } + if (flags & trundataOffset) { + r << std::string(indent + 1, ' ') << "Sample Flags" << prettySampleFlags(getFirstSampleFlags()) << std::endl; + } + + r << std::string(indent + 1, ' ') << "SampleInformation (" << getSampleInformationCount() << "):" << std::endl; + for (unsigned int i = 0; i < getSampleInformationCount(); ++i) { + r << std::string(indent + 2, ' ') << "[" << i << "] "; + trunSampleInformation samp = getSampleInformation(i); + if (flags & trunsampleDuration) { + r << "Duration=" << samp.sampleDuration << " "; + } + if (flags & trunsampleSize) { + r << "Size=" << samp.sampleSize << " "; + } + if (flags & trunsampleFlags) { + r << "Flags=" << prettySampleFlags(samp.sampleFlags) << " "; + } + if (flags & trunsampleOffsets) { + r << "Offset=" << samp.sampleOffset << " "; + } + r << std::endl; + } + + return r.str(); + } + + std::string prettySampleFlags(uint32_t flag) { + std::stringstream r; + if (flag & noIPicture) { + r << " noIPicture"; + } + if (flag & isIPicture) { + r << " isIPicture"; + } + if (flag & noDisposable) { + r << " noDisposable"; + } + if (flag & isDisposable) { + r << " isDisposable"; + } + if (flag & isRedundant) { + r << " isRedundant"; + } + if (flag & noRedundant) { + r << " noRedundant"; + } + if (flag & noKeySample) { + r << " noKeySample"; + } else { + r << " isKeySample"; + } + return r.str(); + } + + TFHD::TFHD() { + memcpy(data + 4, "tfhd", 4); + } + + void TFHD::setFlags(uint32_t newFlags) { + setInt24(newFlags, 1); + } + + uint32_t TFHD::getFlags() { + return getInt24(1); + } + + void TFHD::setTrackID(uint32_t newID) { + setInt32(newID, 4); + } + + uint32_t TFHD::getTrackID() { + return getInt32(4); + } + + void TFHD::setBaseDataOffset(uint64_t newOffset) { + if (getFlags() & tfhdBaseOffset) { + setInt64(newOffset, 8); + } + } + + uint64_t TFHD::getBaseDataOffset() { + if (getFlags() & tfhdBaseOffset) { + return getInt64(8); + } else { + return 0; + } + } + + void TFHD::setSampleDescriptionIndex(uint32_t newIndex) { + if (!(getFlags() & tfhdSampleDesc)) { + return; + } + int offset = 8; + if (getFlags() & tfhdBaseOffset) { + offset += 8; + } + setInt32(newIndex, offset); + } + + uint32_t TFHD::getSampleDescriptionIndex() { + if (!(getFlags() & tfhdSampleDesc)) { + return 0; + } + int offset = 8; + if (getFlags() & tfhdBaseOffset) { + offset += 8; + } + return getInt32(offset); + } + + void TFHD::setDefaultSampleDuration(uint32_t newDuration) { + if (!(getFlags() & tfhdSampleDura)) { + return; + } + int offset = 8; + if (getFlags() & tfhdBaseOffset) { + offset += 8; + } + if (getFlags() & tfhdSampleDesc) { + offset += 4; + } + setInt32(newDuration, offset); + } + + uint32_t TFHD::getDefaultSampleDuration() { + if (!(getFlags() & tfhdSampleDura)) { + return 0; + } + int offset = 8; + if (getFlags() & tfhdBaseOffset) { + offset += 8; + } + if (getFlags() & tfhdSampleDesc) { + offset += 4; + } + return getInt32(offset); + } + + void TFHD::setDefaultSampleSize(uint32_t newSize) { + if (!(getFlags() & tfhdSampleSize)) { + return; + } + int offset = 8; + if (getFlags() & tfhdBaseOffset) { + offset += 8; + } + if (getFlags() & tfhdSampleDesc) { + offset += 4; + } + if (getFlags() & tfhdSampleDura) { + offset += 4; + } + setInt32(newSize, offset); + } + + uint32_t TFHD::getDefaultSampleSize() { + if (!(getFlags() & tfhdSampleSize)) { + return 0; + } + int offset = 8; + if (getFlags() & tfhdBaseOffset) { + offset += 8; + } + if (getFlags() & tfhdSampleDesc) { + offset += 4; + } + if (getFlags() & tfhdSampleDura) { + offset += 4; + } + return getInt32(offset); + } + + void TFHD::setDefaultSampleFlags(uint32_t newFlags) { + if (!(getFlags() & tfhdSampleFlag)) { + return; + } + int offset = 8; + if (getFlags() & tfhdBaseOffset) { + offset += 8; + } + if (getFlags() & tfhdSampleDesc) { + offset += 4; + } + if (getFlags() & tfhdSampleDura) { + offset += 4; + } + if (getFlags() & tfhdSampleSize) { + offset += 4; + } + setInt32(newFlags, offset); + } + + uint32_t TFHD::getDefaultSampleFlags() { + if (!(getFlags() & tfhdSampleFlag)) { + return 0; + } + int offset = 8; + if (getFlags() & tfhdBaseOffset) { + offset += 8; + } + if (getFlags() & tfhdSampleDesc) { + offset += 4; + } + if (getFlags() & tfhdSampleDura) { + offset += 4; + } + if (getFlags() & tfhdSampleSize) { + offset += 4; + } + return getInt32(offset); + } + + std::string TFHD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[tfhd] Track Fragment Header (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Version " << (int)getInt8(0) << std::endl; + + uint32_t flags = getFlags(); + r << std::string(indent + 1, ' ') << "Flags"; + if (flags & tfhdBaseOffset) { + r << " BaseOffset"; + } + if (flags & tfhdSampleDesc) { + r << " SampleDesc"; + } + if (flags & tfhdSampleDura) { + r << " SampleDura"; + } + if (flags & tfhdSampleSize) { + r << " SampleSize"; + } + if (flags & tfhdSampleFlag) { + r << " SampleFlag"; + } + if (flags & tfhdNoDuration) { + r << " NoDuration"; + } + r << std::endl; + + r << std::string(indent + 1, ' ') << "TrackID " << getTrackID() << std::endl; + + if (flags & tfhdBaseOffset) { + r << std::string(indent + 1, ' ') << "Base Offset " << getBaseDataOffset() << std::endl; + } + if (flags & tfhdSampleDesc) { + r << std::string(indent + 1, ' ') << "Sample Description Index " << getSampleDescriptionIndex() << std::endl; + } + if (flags & tfhdSampleDura) { + r << std::string(indent + 1, ' ') << "Default Sample Duration " << getDefaultSampleDuration() << std::endl; + } + if (flags & tfhdSampleSize) { + r << std::string(indent + 1, ' ') << "Default Same Size " << getDefaultSampleSize() << std::endl; + } + if (flags & tfhdSampleFlag) { + r << std::string(indent + 1, ' ') << "Default Sample Flags " << prettySampleFlags(getDefaultSampleFlags()) << std::endl; + } + + return r.str(); + } + + + AVCC::AVCC() { + memcpy(data + 4, "avcC", 4); + setInt8(0xFF, 4); //reserved + 4-bytes NAL length + } + + void AVCC::setVersion(uint32_t newVersion) { + setInt8(newVersion, 0); + } + + uint32_t AVCC::getVersion() { + return getInt8(0); + } + + void AVCC::setProfile(uint32_t newProfile) { + setInt8(newProfile, 1); + } + + uint32_t AVCC::getProfile() { + return getInt8(1); + } + + void AVCC::setCompatibleProfiles(uint32_t newCompatibleProfiles) { + setInt8(newCompatibleProfiles, 2); + } + + uint32_t AVCC::getCompatibleProfiles() { + return getInt8(2); + } + + void AVCC::setLevel(uint32_t newLevel) { + setInt8(newLevel, 3); + } + + uint32_t AVCC::getLevel() { + return getInt8(3); + } + + void AVCC::setSPSNumber(uint32_t newSPSNumber) { + setInt8(newSPSNumber, 5); + } + + uint32_t AVCC::getSPSNumber() { + return getInt8(5); + } + + void AVCC::setSPS(std::string newSPS) { + setInt16(newSPS.size(), 6); + for (unsigned int i = 0; i < newSPS.size(); i++) { + setInt8(newSPS[i], 8 + i); + } //not null-terminated + } + + uint32_t AVCC::getSPSLen() { + return getInt16(6); + } + + char * AVCC::getSPS() { + return payload() + 8; + } + + void AVCC::setPPSNumber(uint32_t newPPSNumber) { + int offset = 8 + getSPSLen(); + setInt8(newPPSNumber, offset); + } + + uint32_t AVCC::getPPSNumber() { + int offset = 8 + getSPSLen(); + return getInt8(offset); + } + + void AVCC::setPPS(std::string newPPS) { + int offset = 8 + getSPSLen() + 1; + setInt16(newPPS.size(), offset); + for (unsigned int i = 0; i < newPPS.size(); i++) { + setInt8(newPPS[i], offset + 2 + i); + } //not null-terminated + } + + uint32_t AVCC::getPPSLen() { + int offset = 8 + getSPSLen() + 1; + return getInt16(offset); + } + + char * AVCC::getPPS() { + int offset = 8 + getSPSLen() + 3; + return payload() + offset; + } + + std::string AVCC::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[avcC] H.264 Init Data (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Version: " << getVersion() << std::endl; + r << std::string(indent + 1, ' ') << "Profile: " << getProfile() << std::endl; + r << std::string(indent + 1, ' ') << "Compatible Profiles: " << getCompatibleProfiles() << std::endl; + r << std::string(indent + 1, ' ') << "Level: " << getLevel() << std::endl; + r << std::string(indent + 1, ' ') << "SPS Number: " << getSPSNumber() << std::endl; + r << std::string(indent + 2, ' ') << getSPSLen() << " of SPS data" << std::endl; + r << std::string(indent + 1, ' ') << "PPS Number: " << getPPSNumber() << std::endl; + r << std::string(indent + 2, ' ') << getPPSLen() << " of PPS data" << std::endl; + return r.str(); + } + + std::string AVCC::asAnnexB() { + std::stringstream r; + r << (char)0x00 << (char)0x00 << (char)0x00 << (char)0x01; + r.write(getSPS(), getSPSLen()); + r << (char)0x00 << (char)0x00 << (char)0x00 << (char)0x01; + r.write(getPPS(), getPPSLen()); + return r.str(); + } + + void AVCC::setPayload(std::string newPayload) { + if (!reserve(0, payloadSize(), newPayload.size())) { + DEBUG_MSG(DLVL_ERROR, "Cannot allocate enough memory for payload"); + return; + } + memcpy((char *)payload(), (char *)newPayload.c_str(), newPayload.size()); + } + + Descriptor::Descriptor(){ + data = (char*)malloc(2); + data[0] = 0; + data[1] = 0; + size = 2; + master = true; + } + + Descriptor::Descriptor(const char* p, const long unsigned s, const bool m){ + master = m; + if (m){ + Descriptor(); + Descriptor tmp = Descriptor(p, s, false); + resize(tmp.getDataSize()); + memcpy(data, p, s); + }else{ + data = (char*)p; + size = s; + } + } + + char Descriptor::getTag(){ + return data[0]; + } + + void Descriptor::setTag(char t){ + data[0] = t; + } + + unsigned long Descriptor::getDataSize(){ + unsigned int i = 1; + unsigned long s = 0; + for (i = 1; i< size-1; i++){ + s <<= 7; + s |= data[i] & 0x7f; + if ((data[i] & 0x80) != 0x80){ + break; + } + } + return s; + } + + unsigned long Descriptor::getFullSize(){ + unsigned long tmp = getDataSize(); + unsigned long r = tmp+2; + if (tmp > 0x7F){ + ++r; + } + if (tmp > 0x3FFF){ + ++r; + } + if (tmp > 0x1FFFFF){ + ++r; + } + return r; + } + + void Descriptor::resize(unsigned long t){ + if (!master){return;} + unsigned long realLen = t+2; + if (t > 0x7F){ + ++realLen; + } + if (t > 0x3FFF){ + ++realLen; + } + if (t > 0x1FFFFF){ + ++realLen; + } + if (size < realLen){ + char* tmpData = (char*)realloc(data,realLen); + if (tmpData){ + size = realLen; + data = tmpData; + unsigned long offset = realLen-t; + char continueBit = 0; + while (realLen){ + data[--offset] = (0x7f & realLen) | continueBit; + continueBit = 0x80; + realLen >>= 7; + } + }else{ + return; + } + } + } + + char* Descriptor::getData(){ + unsigned int i = 1; + for (i = 1; i< size-1; i++){ + if ((data[i] & 0x80) != 0x80){ + break; + } + } + return data + i + 1; + } + + std::string Descriptor::toPrettyString(uint32_t indent){ + std::stringstream r; + r << std::string(indent, ' ') << "[" << (int)data[0] << "] Unimplemented descriptor (" << getDataSize() << ")" << std::endl; + return r.str(); + } + + ESDescriptor::ESDescriptor (const char* p, const unsigned long l, const bool m) : Descriptor(p,l,m){ + } + + DCDescriptor ESDescriptor::getDecoderConfig(){ + char * p = getData(); + char * max_p = p + getDataSize(); + bool dep = (p[2] & 0x80); + bool url = (p[2] & 0x40); + bool ocr = (p[2] & 0x20); + p += 3; + if (dep){p += 2;} + if (url){p += (1+p[0]);} + if (ocr){p += 2;} + return DCDescriptor(p, max_p-p); + } + + std::string ESDescriptor::toPrettyString(uint32_t indent){ + std::stringstream r; + r << std::string(indent, ' ') << "[" << (int)data[0] << "] ES Descriptor (" << getDataSize() << ")" << std::endl; + char * p = getData(); + char * max_p = p + getDataSize(); + r << std::string(indent+1, ' ') << "ES ID: " << (unsigned int)(((unsigned int)p[0] << 8) | (unsigned int)p[1]) << std::endl; + bool dep = (p[2] & 0x80); + bool url = (p[2] & 0x40); + bool ocr = (p[2] & 0x20); + r << std::string(indent+1, ' ') << "Priority: " << int(p[2] & 0x1f) << std::endl; + p += 3; + if (dep){ + r << std::string(indent+1, ' ') << "Depends on ES ID: " << (unsigned int)(((unsigned int)p[0] << 8) | (unsigned int)p[1]) << std::endl; + p += 2; + } + if (url){ + r << std::string(indent+1, ' ') << "URL: " << std::string(p+1, (unsigned int)p[0]) << std::endl; + p += (1+p[0]); + } + if (ocr){ + r << std::string(indent+1, ' ') << "Timebase derived from ES ID: " << (unsigned int)(((unsigned int)p[0] << 8) | (unsigned int)p[1]) << std::endl; + p += 2; + } + while (p < max_p){ + switch (p[0]){ + case 0x4: { + DCDescriptor d(p, max_p - p); + r << d.toPrettyString(indent+1); + p += d.getFullSize(); + } + default: { + Descriptor d(p, max_p - p); + r << d.toPrettyString(indent+1); + p += d.getFullSize(); + } + } + } + return r.str(); + } + + DCDescriptor::DCDescriptor (const char* p, const unsigned long l, const bool m) : Descriptor(p,l,m){ + } + + DSDescriptor DCDescriptor::getSpecific(){ + char * p = getData(); + char * max_p = p + getDataSize(); + p += 13; + if (p[0] == 0x05){ + return DSDescriptor(p, max_p-p); + }else{ + FAIL_MSG("Expected DSDescriptor (5), but found %d!", (int)p[0]); + return DSDescriptor(0,0); + } + } + + bool DCDescriptor::isAAC(){ + return (getData()[0] == 0x40); + } + + std::string DCDescriptor::toPrettyString(uint32_t indent){ + std::stringstream r; + r << std::string(indent, ' ') << "[" << (int)data[0] << "] DecoderConfig Descriptor (" << getDataSize() << ")" << std::endl; + char * p = getData(); + char * max_p = p + getDataSize(); + int objType = p[0]; + r << std::string(indent+1, ' ') << "Object type: "; + switch (objType){ + case 0x40: r << "AAC (0x40)"; break; + case 0x69: r << "MP3 (0x69)"; break; + default: r << "Unknown (" << objType << ")"; break; + } + r << std::endl; + int streamType = (p[1] >> 2) & 0x3F; + r << std::string(indent+1, ' ') << "Stream type: "; + switch (streamType){ + case 0x4: r << "Video (4)"; break; + case 0x5: r << "Audio (5)"; break; + default: r << "Unknown (" << streamType << ")"; break; + } + r << std::endl; + if (p[1] & 0x2){ + r << std::string(indent+1, ' ') << "Upstream" << std::endl; + } + r << std::string(indent+1, ' ') << "Buffer size: " << (int)(((int)p[2] << 16) | ((int)p[3] << 8) | ((int)p[4])) << std::endl; + r << std::string(indent+1, ' ') << "Max bps: " << (unsigned int)(((unsigned int)p[5] << 24) | ((int)p[6] << 16) | ((int)p[7] << 8) | (int)p[8]) << std::endl; + r << std::string(indent+1, ' ') << "Avg bps: " << (unsigned int)(((unsigned int)p[9] << 24) | ((int)p[10] << 16) | ((int)p[11] << 8) | (int)p[12]) << std::endl; + p += 13; + while (p < max_p){ + switch (p[0]){ + case 0x5: { + DSDescriptor d(p, max_p - p); + r << d.toPrettyString(indent+1); + p += d.getFullSize(); + } + default: { + Descriptor d(p, max_p - p); + r << d.toPrettyString(indent+1); + p += d.getFullSize(); + } + } + } + return r.str(); + } + + DSDescriptor::DSDescriptor (const char* p, const unsigned long l, const bool m) : Descriptor(p,l,m){ + } + + std::string DSDescriptor::toPrettyString(uint32_t indent){ + std::stringstream r; + r << std::string(indent, ' ') << "[" << (int)data[0] << "] Decoder Specific Info (" << getDataSize() << ")" << std::endl; + char * p = getData(); + char * max_p = p + getDataSize(); + r << std::string(indent+1, ' ') << "Data: "; + while (p < max_p) { + r << std::hex << std::setw(2) << std::setfill('0') << (int)p[0] << std::dec; + ++p; + } + r << std::endl; + return r.str(); + } + + std::string DSDescriptor::toString(){ + if (!data){ + return ""; + } + return std::string(getData(), getDataSize()); + } + + ESDS::ESDS() { + memcpy(data + 4, "esds", 4); + } + + ESDS::ESDS(std::string init) { + ///\todo Do this better, in a non-hardcoded way. + memcpy(data + 4, "esds", 4); + reserve(payloadOffset, 0, init.size() ? init.size()+26 : 24); + unsigned int i = 12; + data[i++] = 3;//ES_DescrTag + data[i++] = init.size() ? init.size()+20 : 18;//size + data[i++] = 0;//es_id + data[i++] = 2;//es_id + data[i++] = 0;//priority + data[i++] = 4;//DecoderConfigDescrTag + data[i++] = init.size() ? init.size()+15 : 13;//size + if (init.size()){ + data[i++] = 0x40;//objType AAC + }else{ + data[i++] = 0x69;//objType MP3 + } + data[i++] = 0x14;//streamType audio (5<<2) + data[i++] = 0;//buffer size + data[i++] = 0;//buffer size + data[i++] = 0;//buffer size + data[i++] = 0;//maxbps + data[i++] = 0;//maxbps + data[i++] = 0;//maxbps + data[i++] = 0;//maxbps + data[i++] = 0;//avgbps + data[i++] = 0;//avgbps + data[i++] = 0;//avgbps + data[i++] = 0;//avgbps + if (init.size()){ + data[i++] = 0x5;//DecSpecificInfoTag + data[i++] = init.size(); + memcpy(data+i, init.data(), init.size()); + } + } + + bool ESDS::isAAC(){ + return getESDescriptor().getDecoderConfig().isAAC(); + } + + std::string ESDS::getInitData(){ + return getESDescriptor().getDecoderConfig().getSpecific().toString(); + } + + ESDescriptor ESDS::getESDescriptor(){ + return ESDescriptor(data+12,boxedSize()-12); + } + + std::string ESDS::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[esds] ES Descriptor Box (" << boxedSize() << ")" << std::endl; + r << getESDescriptor().toPrettyString(indent+1); + return r.str(); + } + + FTYP::FTYP() { + memcpy(data + 4, "ftyp", 4); + setMajorBrand("mp41"); + setMinorVersion("Mist"); + setCompatibleBrands("isom", 0); + setCompatibleBrands("iso2", 1); + setCompatibleBrands("avc1", 2); + setCompatibleBrands("mp41", 3); + } + + void FTYP::setMajorBrand(const char * newMajorBrand) { + if (payloadOffset + 3 >= boxedSize()) { + if (!reserve(payloadOffset, 0, 4)) { + return; + } + } + memcpy(data + payloadOffset, newMajorBrand, 4); + } + + std::string FTYP::getMajorBrand() { + return std::string(data + payloadOffset, 4); + } + + void FTYP::setMinorVersion(const char * newMinorVersion) { + if (payloadOffset + 7 >= boxedSize()) { + if (!reserve(payloadOffset + 4, 0, 4)) { + return; + } + } + memcpy(data + payloadOffset + 4, newMinorVersion, 4); + } + + std::string FTYP::getMinorVersion() { + return std::string(data + payloadOffset + 4, 4); + } + + size_t FTYP::getCompatibleBrandsCount() { + return (payloadSize() - 8) / 4; + } + + void FTYP::setCompatibleBrands(const char * newCompatibleBrand, size_t index) { + if (payloadOffset + 8 + index * 4 + 3 >= boxedSize()) { + if (!reserve(payloadOffset + 8 + index * 4, 0, 4)) { + return; + } + } + memcpy(data + payloadOffset + 8 + index * 4, newCompatibleBrand, 4); + } + + std::string FTYP::getCompatibleBrands(size_t index) { + if (index >= getCompatibleBrandsCount()) { + return ""; + } + return std::string(data + payloadOffset + 8 + (index * 4), 4); + } + + std::string FTYP::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[ftyp] File Type (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "MajorBrand: " << getMajorBrand() << std::endl; + r << std::string(indent + 1, ' ') << "MinorVersion: " << getMinorVersion() << std::endl; + r << std::string(indent + 1, ' ') << "CompatibleBrands (" << getCompatibleBrandsCount() << "):" << std::endl; + for (unsigned int i = 0; i < getCompatibleBrandsCount(); i++) { + r << std::string(indent + 2, ' ') << "[" << i << "] CompatibleBrand: " << getCompatibleBrands(i) << std::endl; + } + return r.str(); + } + + MOOV::MOOV() { + memcpy(data + 4, "moov", 4); + } + + MVEX::MVEX() { + memcpy(data + 4, "mvex", 4); + } + + TREX::TREX() { + memcpy(data + 4, "trex", 4); + } + + void TREX::setTrackID(uint32_t newTrackID) { + setInt32(newTrackID, 0); + } + + uint32_t TREX::getTrackID() { + return getInt32(0); + } + + void TREX::setDefaultSampleDescriptionIndex(uint32_t newDefaultSampleDescriptionIndex) { + setInt32(newDefaultSampleDescriptionIndex, 4); + } + + uint32_t TREX::getDefaultSampleDescriptionIndex() { + return getInt32(4); + } + + void TREX::setDefaultSampleDuration(uint32_t newDefaultSampleDuration) { + setInt32(newDefaultSampleDuration, 8); + } + + uint32_t TREX::getDefaultSampleDuration() { + return getInt32(8); + } + + void TREX::setDefaultSampleSize(uint32_t newDefaultSampleSize) { + setInt32(newDefaultSampleSize, 12); + } + + uint32_t TREX::getDefaultSampleSize() { + return getInt32(12); + } + + void TREX::setDefaultSampleFlags(uint32_t newDefaultSampleFlags) { + setInt32(newDefaultSampleFlags, 16); + } + + uint32_t TREX::getDefaultSampleFlags() { + return getInt32(16); + } + + std::string TREX::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[trex] Track Extends (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "TrackID: " << getTrackID() << std::endl; + r << std::string(indent + 1, ' ') << "DefaultSampleDescriptionIndex : " << getDefaultSampleDescriptionIndex() << std::endl; + r << std::string(indent + 1, ' ') << "DefaultSampleDuration : " << getDefaultSampleDuration() << std::endl; + r << std::string(indent + 1, ' ') << "DefaultSampleSize : " << getDefaultSampleSize() << std::endl; + r << std::string(indent + 1, ' ') << "DefaultSampleFlags : " << getDefaultSampleFlags() << std::endl; + return r.str(); + } + + TRAK::TRAK() { + memcpy(data + 4, "trak", 4); + } + + MDIA::MDIA() { + memcpy(data + 4, "mdia", 4); + } + + MINF::MINF() { + memcpy(data + 4, "minf", 4); + } + + DINF::DINF() { + memcpy(data + 4, "dinf", 4); + } + + MFRA::MFRA() { + memcpy(data + 4, "mfra", 4); + } + + MFRO::MFRO() { + memcpy(data + 4, "mfro", 4); + } + + void MFRO::setSize(uint32_t newSize) { + setInt32(newSize, 0); + } + + uint32_t MFRO::getSize() { + return getInt32(0); + } + + std::string MFRO::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[mfro] Movie Fragment Random Access Offset (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Size: " << getSize() << std::endl; + return r.str(); + } + + HDLR::HDLR(std::string & type, std::string name) { + memcpy(data + 4, "hdlr", 4); + //reserve an entire box, except for the string part at the end + if (!reserve(0, 8, 32)) { + return;//on fail, cancel all the things + } + memset(data + payloadOffset, 0, 24); //set all bytes (32 - 8) to zeroes + + if (type == "video") { + setHandlerType("vide"); + } + if (type == "audio") { + setHandlerType("soun"); + } + setName(name); + } + + void HDLR::setHandlerType(const char * newHandlerType) { + memcpy(data + payloadOffset + 8, newHandlerType, 4); + } + + std::string HDLR::getHandlerType() { + return std::string(data + payloadOffset + 8, 4); + } + + void HDLR::setName(std::string newName) { + setString(newName, 24); + } + + std::string HDLR::getName() { + return getString(24); + } + + std::string HDLR::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[hdlr] Handler Reference (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Handler Type: " << getHandlerType() << std::endl; + r << std::string(indent + 1, ' ') << "Name: " << getName() << std::endl; + return r.str(); + } + + //Note: next 4 headers inherit from fullBox, start at byte 4. + VMHD::VMHD() { + memcpy(data + 4, "vmhd", 4); + setGraphicsMode(0); + setOpColor(0, 0); + setOpColor(0, 1); + setOpColor(0, 2); + } + + void VMHD::setGraphicsMode(uint16_t newGraphicsMode) { + setInt16(newGraphicsMode, 4); + } + + uint16_t VMHD::getGraphicsMode() { + return getInt16(4); + } + + uint32_t VMHD::getOpColorCount() { + return 3; + } + + void VMHD::setOpColor(uint16_t newOpColor, size_t index) { + if (index < 3) { + setInt16(newOpColor, 6 + (2 * index)); + } + } + + uint16_t VMHD::getOpColor(size_t index) { + if (index < 3) { + return getInt16(6 + (index * 2)); + } else { + return 0; + } + } + + std::string VMHD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[vmhd] Video Media Header Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "GraphicsMode: " << getGraphicsMode() << std::endl; + for (unsigned int i = 0; i < getOpColorCount(); i++) { + r << std::string(indent + 1, ' ') << "OpColor[" << i << "]: " << getOpColor(i) << std::endl; + } + return r.str(); + } + + SMHD::SMHD() { + memcpy(data + 4, "smhd", 4); + setBalance(0); + setInt16(0, 6); + } + + void SMHD::setBalance(int16_t newBalance) { + setInt16(newBalance, 4); + } + + int16_t SMHD::getBalance() { + return getInt16(4); + } + + std::string SMHD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[smhd] Sound Media Header Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "Balance: " << getBalance() << std::endl; + return r.str(); + } + + HMHD::HMHD() { + memcpy(data + 4, "hmhd", 4); + } + + void HMHD::setMaxPDUSize(uint16_t newMaxPDUSize) { + setInt16(newMaxPDUSize, 4); + } + + uint16_t HMHD::getMaxPDUSize() { + return getInt16(4); + } + + void HMHD::setAvgPDUSize(uint16_t newAvgPDUSize) { + setInt16(newAvgPDUSize, 6); + } + + uint16_t HMHD::getAvgPDUSize() { + return getInt16(6); + } + + void HMHD::setMaxBitRate(uint32_t newMaxBitRate) { + setInt32(newMaxBitRate, 8); + } + + uint32_t HMHD::getMaxBitRate() { + return getInt32(8); + } + + void HMHD::setAvgBitRate(uint32_t newAvgBitRate) { + setInt32(newAvgBitRate, 12); + } + + uint32_t HMHD::getAvgBitRate() { + return getInt32(12); + } + + std::string HMHD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[hmhd] Hint Media Header Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "maxPDUSize: " << getMaxPDUSize() << std::endl; + r << std::string(indent + 1, ' ') << "avgPDUSize: " << getAvgPDUSize() << std::endl; + r << std::string(indent + 1, ' ') << "maxBitRate: " << getMaxBitRate() << std::endl; + r << std::string(indent + 1, ' ') << "avgBitRate: " << getAvgBitRate() << std::endl; + return r.str(); + } + + NMHD::NMHD() { + memcpy(data + 4, "nmhd", 4); + } + + std::string NMHD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[nmhd] Null Media Header Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + return r.str(); + } + + MEHD::MEHD() { + memcpy(data + 4, "mehd", 4); + } + + void MEHD::setFragmentDuration(uint64_t newFragmentDuration) { + if (getVersion() == 0) { + setInt32(newFragmentDuration, 4); + } else { + setInt64(newFragmentDuration, 4); + } + } + + uint64_t MEHD::getFragmentDuration() { + if (getVersion() == 0) { + return getInt32(4); + } else { + return getInt64(4); + } + } + + std::string MEHD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[mehd] Movie Extends Header Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "FragmentDuration: " << getFragmentDuration() << std::endl; + return r.str(); + } + + STBL::STBL() { + memcpy(data + 4, "stbl", 4); + } + + URL::URL() { + memcpy(data + 4, "url ", 4); + } + + void URL::setLocation(std::string newLocation) { + setString(newLocation, 4); + } + + std::string URL::getLocation() { + return std::string(getString(4), getStringLen(4)); + } + + std::string URL::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[url ] URL Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "Location: " << getLocation() << std::endl; + return r.str(); + } + + URN::URN() { + memcpy(data + 4, "urn ", 4); + } + + void URN::setName(std::string newName) { + setString(newName, 4); + } + + std::string URN::getName() { + return std::string(getString(4), getStringLen(4)); + } + + void URN::setLocation(std::string newLocation) { + setString(newLocation, 4 + getStringLen(4) + 1); + } + + std::string URN::getLocation() { + int loc = 4 + getStringLen(4) + 1; + return std::string(getString(loc), getStringLen(loc)); + } + + std::string URN::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[urn ] URN Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "Name: " << getName() << std::endl; + r << std::string(indent + 1, ' ') << "Location: " << getLocation() << std::endl; + return r.str(); + } + + DREF::DREF() { + memcpy(data + 4, "dref", 4); + setVersion(0); + setFlags(0); + setInt32(0, 4); + URL urlBox; + urlBox.setFlags(1); + setDataEntry(urlBox, 0); + } + + uint32_t DREF::getEntryCount() { + return getInt32(4); + } + + void DREF::setDataEntry(fullBox & newDataEntry, size_t index) { + unsigned int i; + uint32_t offset = 8; //start of boxes + for (i = 0; i < getEntryCount() && i < index; i++) { + offset += getBoxLen(offset); + } + if (index + 1 > getEntryCount()) { + int amount = index + 1 - getEntryCount(); + if (!reserve(payloadOffset + offset, 0, amount * 8)) { + return; + } + for (int j = 0; j < amount; ++j) { + memcpy(data + payloadOffset + offset + j * 8, "\000\000\000\010erro", 8); + } + setInt32(index + 1, 4); + offset += (index - i) * 8; + } + setBox(newDataEntry, offset); + } + + Box & DREF::getDataEntry(size_t index) { + uint32_t offset = 8; + if (index > getEntryCount()) { + static Box res; + return (Box &)res; + } + + for (unsigned int i = 0; i < index; i++) { + offset += getBoxLen(offset); + } + return (Box &)getBox(offset); + } + + std::string DREF::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[dref] Data Reference Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "EntryCount: " << getEntryCount() << std::endl; + for (unsigned int i = 0; i < getEntryCount(); i++) { + r << getDataEntry(i).toPrettyString(indent + 1); + } + return r.str(); + } + + MVHD::MVHD(long long unsigned int duration) { + memcpy(data + 4, "mvhd", 4); + + //reserve an entire version 0 box + if (!reserve(0, 8, 108)) { + return;//on fail, cancel all the things + } + memset(data + payloadOffset, 0, 100); //set all bytes (108 - 8) to zeroes + + setTimeScale(1000);//we always use milliseconds + setDuration(duration);//in ms + setRate(0x00010000);//playback rate 1.0X + setVolume(0x0100);//volume 1.0X + setMatrix(0x00010000, 0); + setMatrix(0x00010000, 4); + setMatrix(0x40000000, 8); + setTrackID(0xFFFFFFFF);//empty track numbers is unknown + + } + + void MVHD::setCreationTime(uint64_t newCreationTime) { + if (getVersion() == 0) { + setInt32((uint32_t) newCreationTime, 4); + } else { + setInt64(newCreationTime, 4); + } + } + + uint64_t MVHD::getCreationTime() { + if (getVersion() == 0) { + return (uint64_t)getInt32(4); + } else { + return getInt64(4); + } + } + + void MVHD::setModificationTime(uint64_t newModificationTime) { + if (getVersion() == 0) { + setInt32((uint32_t) newModificationTime, 8); + } else { + setInt64(newModificationTime, 12); + } + } + + uint64_t MVHD::getModificationTime() { + if (getVersion() == 0) { + return (uint64_t)getInt32(8); + } else { + return getInt64(12); + } + } + + void MVHD::setTimeScale(uint32_t newTimeScale) { + if (getVersion() == 0) { + setInt32((uint32_t) newTimeScale, 12); + } else { + setInt32(newTimeScale, 20); + } + } + + uint32_t MVHD::getTimeScale() { + if (getVersion() == 0) { + return getInt32(12); + } else { + return getInt32(20); + } + } + + void MVHD::setDuration(uint64_t newDuration) { + if (getVersion() == 0) { + setInt32((uint32_t) newDuration, 16); + } else { + setInt64(newDuration, 24); + } + } + + uint64_t MVHD::getDuration() { + if (getVersion() == 0) { + return (uint64_t)getInt32(16); + } else { + return getInt64(24); + } + } + + void MVHD::setRate(uint32_t newRate) { + if (getVersion() == 0) { + setInt32(newRate, 20); + } else { + setInt32(newRate, 32); + } + } + + uint32_t MVHD::getRate() { + if (getVersion() == 0) { + return getInt32(20); + } else { + return getInt32(32); + } + } + + void MVHD::setVolume(uint16_t newVolume) { + if (getVersion() == 0) { + setInt16(newVolume, 24); + } else { + setInt16(newVolume, 36); + } + } + + uint16_t MVHD::getVolume() { + if (getVersion() == 0) { + return getInt16(24); + } else { + return getInt16(36); + } + } + //10 bytes reserved in between + uint32_t MVHD::getMatrixCount() { + return 9; + } + + void MVHD::setMatrix(int32_t newMatrix, size_t index) { + int offset = 0; + if (getVersion() == 0) { + offset = 24 + 2 + 10; + } else { + offset = 36 + 2 + 10; + } + setInt32(newMatrix, offset + index * 4); + } + + int32_t MVHD::getMatrix(size_t index) { + int offset = 0; + if (getVersion() == 0) { + offset = 24 + 2 + 10; + } else { + offset = 36 + 2 + 10; + } + return getInt32(offset + index * 4); + } + + //24 bytes of pre-defined in between + void MVHD::setTrackID(uint32_t newTrackID) { + if (getVersion() == 0) { + setInt32(newTrackID, 86); + } else { + setInt32(newTrackID, 98); + } + } + + uint32_t MVHD::getTrackID() { + if (getVersion() == 0) { + return getInt32(86); + } else { + return getInt32(98); + } + } + + std::string MVHD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[mvhd] Movie Header Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "CreationTime: " << getCreationTime() << std::endl; + r << std::string(indent + 1, ' ') << "ModificationTime: " << getModificationTime() << std::endl; + r << std::string(indent + 1, ' ') << "TimeScale: " << getTimeScale() << std::endl; + r << std::string(indent + 1, ' ') << "Duration: " << getDuration() << std::endl; + r << std::string(indent + 1, ' ') << "Rate: " << getRate() << std::endl; + r << std::string(indent + 1, ' ') << "Volume: " << getVolume() << std::endl; + r << std::string(indent + 1, ' ') << "Matrix: "; + for (unsigned int i = 0; i < getMatrixCount(); i++) { + r << getMatrix(i); + if (i != getMatrixCount() - 1) { + r << ", "; + } + } + r << std::endl; + r << std::string(indent + 1, ' ') << "TrackID: " << getTrackID() << std::endl; + return r.str(); + } + + TFRA::TFRA() { + memcpy(data + 4, "dref", 4); + } + + //note, fullbox starts at byte 4 + void TFRA::setTrackID(uint32_t newTrackID) { + setInt32(newTrackID, 4); + } + + uint32_t TFRA::getTrackID() { + return getInt32(4); + } + + void TFRA::setLengthSizeOfTrafNum(char newVal) { + char part = getInt8(11); + setInt8(((newVal & 0x03) << 4) + (part & 0xCF), 11); + } + + char TFRA::getLengthSizeOfTrafNum() { + return (getInt8(11) >> 4) & 0x03; + } + + void TFRA::setLengthSizeOfTrunNum(char newVal) { + char part = getInt8(11); + setInt8(((newVal & 0x03) << 2) + (part & 0xF3), 11); + } + + char TFRA::getLengthSizeOfTrunNum() { + return (getInt8(11) >> 2) & 0x03; + } + + void TFRA::setLengthSizeOfSampleNum(char newVal) { + char part = getInt8(11); + setInt8(((newVal & 0x03)) + (part & 0xFC), 11); + } + + char TFRA::getLengthSizeOfSampleNum() { + return (getInt8(11)) & 0x03; + } + + void TFRA::setNumberOfEntry(uint32_t newNumberOfEntry) { + setInt32(newNumberOfEntry, 12); + } + + uint32_t TFRA::getNumberOfEntry() { + return getInt32(12); + } + + uint32_t TFRA::getTFRAEntrySize() { + int EntrySize = (getVersion() == 1 ? 16 : 8); + EntrySize += getLengthSizeOfTrafNum() + 1; + EntrySize += getLengthSizeOfTrunNum() + 1; + EntrySize += getLengthSizeOfSampleNum() + 1; + return EntrySize; + } + + void TFRA::setTFRAEntry(TFRAEntry newTFRAEntry, uint32_t no) { + if (no + 1 > getNumberOfEntry()) { //if a new entry is issued + uint32_t offset = 16 + getTFRAEntrySize() * getNumberOfEntry();//start of filler in bytes + uint32_t fillsize = (no + 1 - getNumberOfEntry()) * getTFRAEntrySize(); //filler in bytes + if (!reserve(offset, 0, fillsize)) {//filling space + return; + } + setNumberOfEntry(no + 1); + } + uint32_t loc = 16 + no * getTFRAEntrySize(); + if (getVersion() == 1) { + setInt64(newTFRAEntry.time, loc); + setInt64(newTFRAEntry.moofOffset, loc + 8); + loc += 16; + } else { + setInt32(newTFRAEntry.time, loc); + setInt32(newTFRAEntry.moofOffset, loc + 4); + loc += 8; + } + switch (getLengthSizeOfTrafNum()) { + case 0: + setInt8(newTFRAEntry.trafNumber, loc); + break; + case 1: + setInt16(newTFRAEntry.trafNumber, loc); + break; + case 2: + setInt24(newTFRAEntry.trafNumber, loc); + break; + case 3: + setInt32(newTFRAEntry.trafNumber, loc); + break; + } + loc += getLengthSizeOfTrafNum() + 1; + switch (getLengthSizeOfTrunNum()) { + case 0: + setInt8(newTFRAEntry.trunNumber, loc); + break; + case 1: + setInt16(newTFRAEntry.trunNumber, loc); + break; + case 2: + setInt24(newTFRAEntry.trunNumber, loc); + break; + case 3: + setInt32(newTFRAEntry.trunNumber, loc); + break; + } + loc += getLengthSizeOfTrunNum() + 1; + switch (getLengthSizeOfSampleNum()) { + case 0: + setInt8(newTFRAEntry.sampleNumber, loc); + break; + case 1: + setInt16(newTFRAEntry.sampleNumber, loc); + break; + case 2: + setInt24(newTFRAEntry.sampleNumber, loc); + break; + case 3: + setInt32(newTFRAEntry.sampleNumber, loc); + break; + } + } + + TFRAEntry & TFRA::getTFRAEntry(uint32_t no) { + static TFRAEntry retval; + if (no >= getNumberOfEntry()) { + static TFRAEntry inval; + return inval; + } + uint32_t loc = 16 + no * getTFRAEntrySize(); + if (getVersion() == 1) { + retval.time = getInt64(loc); + retval.moofOffset = getInt64(loc + 8); + loc += 16; + } else { + retval.time = getInt32(loc); + retval.moofOffset = getInt32(loc + 4); + loc += 8; + } + switch (getLengthSizeOfTrafNum()) { + case 0: + retval.trafNumber = getInt8(loc); + break; + case 1: + retval.trafNumber = getInt16(loc); + break; + case 2: + retval.trafNumber = getInt24(loc); + break; + case 3: + retval.trafNumber = getInt32(loc); + break; + } + loc += getLengthSizeOfTrafNum() + 1; + switch (getLengthSizeOfTrunNum()) { + case 0: + retval.trunNumber = getInt8(loc); + break; + case 1: + retval.trunNumber = getInt16(loc); + break; + case 2: + retval.trunNumber = getInt24(loc); + break; + case 3: + retval.trunNumber = getInt32(loc); + break; + } + loc += getLengthSizeOfTrunNum() + 1; + switch (getLengthSizeOfSampleNum()) { + case 0: + retval.sampleNumber = getInt8(loc); + break; + case 1: + retval.sampleNumber = getInt16(loc); + break; + case 2: + retval.sampleNumber = getInt24(loc); + break; + case 3: + retval.sampleNumber = getInt32(loc); + break; + } + return retval; + } + + std::string TFRA::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[tfra] Track Fragment Random Access Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "TrackID: " << getTrackID() << std::endl; + r << std::string(indent + 1, ' ') << "lengthSizeOfTrafNum: " << (int)getLengthSizeOfTrafNum() << std::endl; + r << std::string(indent + 1, ' ') << "lengthSizeOfTrunNum: " << (int)getLengthSizeOfTrunNum() << std::endl; + r << std::string(indent + 1, ' ') << "lengthSizeOfSampleNum: " << (int)getLengthSizeOfSampleNum() << std::endl; + r << std::string(indent + 1, ' ') << "NumberOfEntry: " << getNumberOfEntry() << std::endl; + for (unsigned int i = 0; i < getNumberOfEntry(); i++) { + static TFRAEntry temp; + temp = getTFRAEntry(i); + r << std::string(indent + 1, ' ') << "Entry[" << i << "]:" << std::endl; + r << std::string(indent + 2, ' ') << "Time: " << temp.time << std::endl; + r << std::string(indent + 2, ' ') << "MoofOffset: " << temp.moofOffset << std::endl; + r << std::string(indent + 2, ' ') << "TrafNumber: " << temp.trafNumber << std::endl; + r << std::string(indent + 2, ' ') << "TrunNumber: " << temp.trunNumber << std::endl; + r << std::string(indent + 2, ' ') << "SampleNumber: " << temp.sampleNumber << std::endl; + } + return r.str(); + } + + TKHD::TKHD(uint32_t trackId, uint64_t duration, uint32_t width, uint32_t height) { + memcpy(data + 4, "tkhd", 4); + + //reserve an entire version 0 box + if (!reserve(0, 8, 92)) { + return;//on fail, cancel all the things + } + memset(data + payloadOffset, 0, 84); //set all bytes (92 - 8) to zeroes + + setFlags(15);//ENABLED | IN_MOVIE | IN_PREVIEW | IN_POSTER + setTrackID(trackId); + setDuration(duration); + if (width == 0 || height == 0) { + setVolume(0x0100); + } + setMatrix(0x00010000, 0); + setMatrix(0x00010000, 4); + setMatrix(0x40000000, 8); + setWidth(width << 16); + setHeight(height << 16); + } + + void TKHD::setCreationTime(uint64_t newCreationTime) { + if (getVersion() == 0) { + setInt32((uint32_t) newCreationTime, 4); + } else { + setInt64(newCreationTime, 4); + } + } + + uint64_t TKHD::getCreationTime() { + if (getVersion() == 0) { + return (uint64_t)getInt32(4); + } else { + return getInt64(4); + } + } + + void TKHD::setModificationTime(uint64_t newModificationTime) { + if (getVersion() == 0) { + setInt32((uint32_t) newModificationTime, 8); + } else { + setInt64(newModificationTime, 12); + } + } + + uint64_t TKHD::getModificationTime() { + if (getVersion() == 0) { + return (uint64_t)getInt32(8); + } else { + return getInt64(12); + } + } + + void TKHD::setTrackID(uint32_t newTrackID) { + if (getVersion() == 0) { + setInt32(newTrackID, 12); + } else { + setInt32(newTrackID, 20); + } + } + + uint32_t TKHD::getTrackID() { + if (getVersion() == 0) { + return getInt32(12); + } else { + return getInt32(20); + } + } + //note 4 bytes reserved in between + void TKHD::setDuration(uint64_t newDuration) { + if (getVersion() == 0) { + setInt32(newDuration, 20); + } else { + setInt64(newDuration, 28); + } + } + + uint64_t TKHD::getDuration() { + if (getVersion() == 0) { + return (uint64_t)getInt32(20); + } else { + return getInt64(28); + } + } + //8 bytes reserved in between + void TKHD::setLayer(uint16_t newLayer) { + if (getVersion() == 0) { + setInt16(newLayer, 32); + } else { + setInt16(newLayer, 44); + } + } + + uint16_t TKHD::getLayer() { + if (getVersion() == 0) { + return getInt16(32); + } else { + return getInt16(44); + } + } + + void TKHD::setAlternateGroup(uint16_t newAlternateGroup) { + if (getVersion() == 0) { + setInt16(newAlternateGroup, 34); + } else { + setInt16(newAlternateGroup, 46); + } + } + + uint16_t TKHD::getAlternateGroup() { + if (getVersion() == 0) { + return getInt16(34); + } else { + return getInt16(46); + } + } + + void TKHD::setVolume(uint16_t newVolume) { + if (getVersion() == 0) { + setInt16(newVolume, 36); + } else { + setInt16(newVolume, 48); + } + } + + uint16_t TKHD::getVolume() { + if (getVersion() == 0) { + return getInt16(36); + } else { + return getInt16(48); + } + } + //2 bytes reserved in between + uint32_t TKHD::getMatrixCount() { + return 9; + } + + void TKHD::setMatrix(int32_t newMatrix, size_t index) { + int offset = 0; + if (getVersion() == 0) { + offset = 40; + } else { + offset = 52; + } + setInt32(newMatrix, offset + index * 4); + } + + int32_t TKHD::getMatrix(size_t index) { + int offset = 0; + if (getVersion() == 0) { + offset = 40; + } else { + offset = 52; + } + return getInt32(offset + index * 4); + } + + void TKHD::setWidth(uint32_t newWidth) { + if (getVersion() == 0) { + setInt32(newWidth, 76); + } else { + setInt32(newWidth, 88); + } + } + + uint32_t TKHD::getWidth() { + if (getVersion() == 0) { + return getInt32(76); + } else { + return getInt32(88); + } + } + + void TKHD::setHeight(uint32_t newHeight) { + if (getVersion() == 0) { + setInt32(newHeight, 80); + } else { + setInt32(newHeight, 92); + } + } + + uint32_t TKHD::getHeight() { + if (getVersion() == 0) { + return getInt32(80); + } else { + return getInt32(92); + } + } + + std::string TKHD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[tkhd] Track Header Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "CreationTime: " << getCreationTime() << std::endl; + r << std::string(indent + 1, ' ') << "ModificationTime: " << getModificationTime() << std::endl; + r << std::string(indent + 1, ' ') << "TrackID: " << getTrackID() << std::endl; + r << std::string(indent + 1, ' ') << "Duration: " << getDuration() << std::endl; + r << std::string(indent + 1, ' ') << "Layer: " << getLayer() << std::endl; + r << std::string(indent + 1, ' ') << "AlternateGroup: " << getAlternateGroup() << std::endl; + r << std::string(indent + 1, ' ') << "Volume: " << getVolume() << std::endl; + r << std::string(indent + 1, ' ') << "Matrix: "; + for (unsigned int i = 0; i < getMatrixCount(); i++) { + r << getMatrix(i); + if (i != getMatrixCount() - 1) { + r << ", "; + } + } + r << std::endl; + r << std::string(indent + 1, ' ') << "Width: " << (getWidth() >> 16) << "." << (getWidth() & 0xFFFF) << std::endl; + r << std::string(indent + 1, ' ') << "Height: " << (getHeight() >> 16) << "." << (getHeight() & 0xFFFF) << std::endl; + return r.str(); + } + + MDHD::MDHD(uint64_t duration) { + memcpy(data + 4, "mdhd", 4); + //reserve an entire version 0 box + if (!reserve(0, 8, 32)) { + return;//on fail, cancel all the things + } + memset(data + payloadOffset, 0, 24); //set all bytes (32 - 8) to zeroes + + setTimeScale(1000); + setDuration(duration); + } + + void MDHD::setCreationTime(uint64_t newCreationTime) { + if (getVersion() == 0) { + setInt32((uint32_t) newCreationTime, 4); + } else { + setInt64(newCreationTime, 4); + } + } + + uint64_t MDHD::getCreationTime() { + if (getVersion() == 0) { + return (uint64_t)getInt32(4); + } else { + return getInt64(4); + } + } + + void MDHD::setModificationTime(uint64_t newModificationTime) { + if (getVersion() == 0) { + setInt32((uint32_t) newModificationTime, 8); + } else { + setInt64(newModificationTime, 12); + } + } + + uint64_t MDHD::getModificationTime() { + if (getVersion() == 0) { + return (uint64_t)getInt32(8); + } else { + return getInt64(12); + } + } + + void MDHD::setTimeScale(uint32_t newTimeScale) { + if (getVersion() == 0) { + setInt32((uint32_t) newTimeScale, 12); + } else { + setInt32(newTimeScale, 20); + } + } + + uint32_t MDHD::getTimeScale() { + if (getVersion() == 0) { + return getInt32(12); + } else { + return getInt32(20); + } + } + + void MDHD::setDuration(uint64_t newDuration) { + if (getVersion() == 0) { + setInt32((uint32_t) newDuration, 16); + } else { + setInt64(newDuration, 24); + } + } + + uint64_t MDHD::getDuration() { + if (getVersion() == 0) { + return (uint64_t)getInt32(16); + } else { + return getInt64(24); + } + } + + void MDHD::setLanguage(uint16_t newLanguage) { + if (getVersion() == 0) { + setInt16(newLanguage & 0x7F, 20); + } else { + setInt16(newLanguage & 0x7F, 32); + } + } + + uint16_t MDHD::getLanguage() { + if (getVersion() == 0) { + return getInt16(20) & 0x7F; + } else { + return getInt16(32) & 0x7F; + } + } + + std::string MDHD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[mdhd] Media Header Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "CreationTime: " << getCreationTime() << std::endl; + r << std::string(indent + 1, ' ') << "ModificationTime: " << getModificationTime() << std::endl; + r << std::string(indent + 1, ' ') << "TimeScale: " << getTimeScale() << std::endl; + r << std::string(indent + 1, ' ') << "Duration: " << getDuration() << std::endl; + r << std::string(indent + 1, ' ') << "Language: 0x" << std::hex << getLanguage() << std::dec << std::endl; + return r.str(); + } + + STTS::STTS(char v, uint32_t f) { + memcpy(data + 4, "stts", 4); + setVersion(v); + setFlags(f); + setEntryCount(0); + } + + void STTS::setEntryCount(uint32_t newEntryCount) { + setInt32(newEntryCount, 4); + } + + uint32_t STTS::getEntryCount() { + return getInt32(4); + } + + void STTS::setSTTSEntry(STTSEntry newSTTSEntry, uint32_t no) { + if (no + 1 > getEntryCount()) { + setEntryCount(no + 1); + for (unsigned int i = getEntryCount(); i < no; i++) { + setInt64(0, 8 + (i * 8));//filling up undefined entries of 64 bits + } + } + setInt32(newSTTSEntry.sampleCount, 8 + no * 8); + setInt32(newSTTSEntry.sampleDelta, 8 + (no * 8) + 4); + } + + STTSEntry STTS::getSTTSEntry(uint32_t no) { + static STTSEntry retval; + if (no >= getEntryCount()) { + static STTSEntry inval; + return inval; + } + retval.sampleCount = getInt32(8 + (no * 8)); + retval.sampleDelta = getInt32(8 + (no * 8) + 4); + return retval; + } + + std::string STTS::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[stts] Sample Table Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "EntryCount: " << getEntryCount() << std::endl; + for (unsigned int i = 0; i < getEntryCount(); i++) { + static STTSEntry temp; + temp = getSTTSEntry(i); + r << std::string(indent + 1, ' ') << "Entry[" << i << "]:" << std::endl; + r << std::string(indent + 2, ' ') << "SampleCount: " << temp.sampleCount << std::endl; + r << std::string(indent + 2, ' ') << "SampleDelta: " << temp.sampleDelta << std::endl; + } + return r.str(); + + } + + CTTS::CTTS() { + memcpy(data + 4, "ctts", 4); + } + + void CTTS::setEntryCount(uint32_t newEntryCount) { + setInt32(newEntryCount, 4); + } + + uint32_t CTTS::getEntryCount() { + return getInt32(4); + } + + void CTTS::setCTTSEntry(CTTSEntry newCTTSEntry, uint32_t no) { + if (no + 1 > getEntryCount()) { + for (unsigned int i = getEntryCount(); i < no; i++) { + setInt64(0, 8 + (i * 8));//filling up undefined entries of 64 bits + } + } + setInt32(newCTTSEntry.sampleCount, 8 + no * 8); + setInt32(newCTTSEntry.sampleOffset, 8 + (no * 8) + 4); + } + + CTTSEntry CTTS::getCTTSEntry(uint32_t no) { + static CTTSEntry retval; + if (no >= getEntryCount()) { + static CTTSEntry inval; + return inval; + } + retval.sampleCount = getInt32(8 + (no * 8)); + retval.sampleOffset = getInt32(8 + (no * 8) + 4); + return retval; + } + + std::string CTTS::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[stts] Sample Table Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "EntryCount: " << getEntryCount() << std::endl; + for (unsigned int i = 0; i < getEntryCount(); i++) { + static CTTSEntry temp; + temp = getCTTSEntry(i); + r << std::string(indent + 1, ' ') << "Entry[" << i << "]:" << std::endl; + r << std::string(indent + 2, ' ') << "SampleCount: " << temp.sampleCount << std::endl; + r << std::string(indent + 2, ' ') << "SampleOffset: " << temp.sampleOffset << std::endl; + } + return r.str(); + + } + + STSC::STSC(char v, uint32_t f) { + memcpy(data + 4, "stsc", 4); + setVersion(v); + setFlags(f); + setEntryCount(0); + } + + void STSC::setEntryCount(uint32_t newEntryCount) { + setInt32(newEntryCount, 4); + } + + uint32_t STSC::getEntryCount() { + return getInt32(4); + } + + void STSC::setSTSCEntry(STSCEntry newSTSCEntry, uint32_t no) { + if (no + 1 > getEntryCount()) { + setEntryCount(no + 1); + for (unsigned int i = getEntryCount(); i < no; i++) { + setInt64(0, 8 + (i * 12));//filling up undefined entries of 64 bits + setInt32(0, 8 + (i * 12) + 8); + } + } + setInt32(newSTSCEntry.firstChunk, 8 + no * 12); + setInt32(newSTSCEntry.samplesPerChunk, 8 + (no * 12) + 4); + setInt32(newSTSCEntry.sampleDescriptionIndex, 8 + (no * 12) + 8); + } + + STSCEntry STSC::getSTSCEntry(uint32_t no) { + static STSCEntry retval; + if (no >= getEntryCount()) { + static STSCEntry inval; + return inval; + } + retval.firstChunk = getInt32(8 + (no * 12)); + retval.samplesPerChunk = getInt32(8 + (no * 12) + 4); + retval.sampleDescriptionIndex = getInt32(8 + (no * 12) + 8); + return retval; + } + + std::string STSC::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[stsc] Sample To Chunk Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "EntryCount: " << getEntryCount() << std::endl; + for (unsigned int i = 0; i < getEntryCount(); i++) { + static STSCEntry temp; + temp = getSTSCEntry(i); + r << std::string(indent + 1, ' ') << "Entry[" << i << "]: Chunks " << temp.firstChunk << " onward contain " << temp.samplesPerChunk << " samples, description " << temp.sampleDescriptionIndex << std::endl; + } + return r.str(); + } + + STCO::STCO(char v, uint32_t f) { + memcpy(data + 4, "stco", 4); + setVersion(v); + setFlags(f); + setEntryCount(0); + } + + void STCO::setEntryCount(uint32_t newEntryCount) { + setInt32(newEntryCount, 4); + } + + uint32_t STCO::getEntryCount() { + return getInt32(4); + } + + void STCO::setChunkOffset(uint32_t newChunkOffset, uint32_t no) { + setInt32(newChunkOffset, 8 + no * 4); + uint32_t entryCount = getEntryCount(); + //if entrycount is lower than new entry count, update it and fill any skipped entries with zeroes. + if (no + 1 > entryCount) { + setEntryCount(no + 1); + //fill undefined entries, if any (there's only undefined entries if we skipped an entry) + if (no > entryCount){ + memset(data+payloadOffset+8+entryCount*4, 0, 4*(no-entryCount)); + } + } + } + + uint32_t STCO::getChunkOffset(uint32_t no) { + if (no >= getEntryCount()) { + return 0; + } + return getInt32(8 + no * 4); + } + + std::string STCO::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[stco] Chunk Offset Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "EntryCount: " << getEntryCount() << std::endl; + r << std::string(indent + 1, ' ') << "Offsets: "; + for (unsigned long i = 0; i < getEntryCount(); i++) { + r << getChunkOffset(i); + if (i != getEntryCount() - 1){ + r << ", "; + } + } + r << std::endl; + return r.str(); + } + + CO64::CO64(char v, uint32_t f) { + memcpy(data + 4, "co64", 4); + setVersion(v); + setFlags(f); + setEntryCount(0); + } + + void CO64::setEntryCount(uint32_t newEntryCount) { + setInt32(newEntryCount, 4); + } + + uint32_t CO64::getEntryCount() { + return getInt32(4); + } + + void CO64::setChunkOffset(uint64_t newChunkOffset, uint32_t no) { + setInt64(newChunkOffset, 8 + no * 8); + uint32_t entryCount = getEntryCount(); + //if entrycount is lower than new entry count, update it and fill any skipped entries with zeroes. + if (no + 1 > entryCount) { + setEntryCount(no + 1); + //fill undefined entries, if any (there's only undefined entries if we skipped an entry) + if (no > entryCount){ + memset(data+payloadOffset+8+entryCount*8, 0, 8*(no-entryCount)); + } + } + } + + uint64_t CO64::getChunkOffset(uint32_t no) { + if (no >= getEntryCount()) { + return 0; + } + return getInt64(8 + no * 8); + } + + std::string CO64::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[co64] 64-bits Chunk Offset Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "EntryCount: " << getEntryCount() << std::endl; + r << std::string(indent + 1, ' ') << "Offsets: "; + for (unsigned long i = 0; i < getEntryCount(); i++) { + r << getChunkOffset(i); + if (i != getEntryCount() - 1){ + r << ", "; + } + } + r << std::endl; + return r.str(); + } + + STSZ::STSZ(char v, uint32_t f) { + memcpy(data + 4, "stsz", 4); + setVersion(v); + setFlags(f); + setSampleCount(0); + } + + void STSZ::setSampleSize(uint32_t newSampleSize) { + setInt32(newSampleSize, 4); + } + + uint32_t STSZ::getSampleSize() { + return getInt32(4); + } + + void STSZ::setSampleCount(uint32_t newSampleCount) { + setInt32(newSampleCount, 8); + } + + uint32_t STSZ::getSampleCount() { + return getInt32(8); + } + + void STSZ::setEntrySize(uint32_t newEntrySize, uint32_t no) { + if (no + 1 > getSampleCount()) { + setSampleCount(no + 1); + for (unsigned int i = getSampleCount(); i < no; i++) { + setInt32(0, 12 + i * 4);//filling undefined entries + } + } + setInt32(newEntrySize, 12 + no * 4); + } + + uint32_t STSZ::getEntrySize(uint32_t no) { + if (no >= getSampleCount()) { + return 0; + } + long unsigned int retVal = getInt32(12 + no * 4); + if (retVal == 0){ + return getSampleSize(); + }else{ + return retVal; + } + } + + std::string STSZ::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[stsz] Sample Size Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "Global Sample Size: " << getSampleSize() << std::endl; + r << std::string(indent + 1, ' ') << "Sample Count: " << getSampleCount() << std::endl; + + r << std::string(indent + 1, ' ') << "Sample sizes: "; + for (unsigned long i = 0; i < getSampleCount(); i++) { + r << getEntrySize(i); + if (i != getSampleCount() - 1){ + r << ", "; + } + } + r << std::endl; + return r.str(); + } + + SampleEntry::SampleEntry() { + memcpy(data + 4, "erro", 4); + } + + void SampleEntry::setDataReferenceIndex(uint16_t newDataReferenceIndex) { + setInt16(newDataReferenceIndex, 6); + } + + uint16_t SampleEntry::getDataReferenceIndex() { + return getInt16(6); + } + + std::string SampleEntry::toPrettySampleString(uint32_t indent) { + std::stringstream r; + r << std::string(indent + 1, ' ') << "DataReferenceIndex: " << getDataReferenceIndex() << std::endl; + return r.str(); + } + + CLAP::CLAP() { + memcpy(data + 4, "clap", 4); + setHorizOffN(0); + setHorizOffD(0); + setVertOffN(0); + setVertOffD(0); + } + + void CLAP::setCleanApertureWidthN(uint32_t newVal) { + setInt32(newVal, 0); + } + + uint32_t CLAP::getCleanApertureWidthN() { + return getInt32(0); + } + + void CLAP::setCleanApertureWidthD(uint32_t newVal) { + setInt32(newVal, 4); + } + + uint32_t CLAP::getCleanApertureWidthD() { + return getInt32(4); + } + + void CLAP::setCleanApertureHeightN(uint32_t newVal) { + setInt32(newVal, 8); + } + + uint32_t CLAP::getCleanApertureHeightN() { + return getInt32(8); + } + + void CLAP::setCleanApertureHeightD(uint32_t newVal) { + setInt32(newVal, 12); + } + + uint32_t CLAP::getCleanApertureHeightD() { + return getInt32(12); + } + + void CLAP::setHorizOffN(uint32_t newVal) { + setInt32(newVal, 16); + } + + uint32_t CLAP::getHorizOffN() { + return getInt32(16); + } + + void CLAP::setHorizOffD(uint32_t newVal) { + setInt32(newVal, 20); + } + + uint32_t CLAP::getHorizOffD() { + return getInt32(20); + } + + void CLAP::setVertOffN(uint32_t newVal) { + setInt32(newVal, 24); + } + + uint32_t CLAP::getVertOffN() { + return getInt32(24); + } + + void CLAP::setVertOffD(uint32_t newVal) { + setInt32(newVal, 28); + } + + uint32_t CLAP::getVertOffD() { + return getInt32(32); + } + + std::string CLAP::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[clap] Clean Aperture Box (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "CleanApertureWidthN: " << getCleanApertureWidthN() << std::endl; + r << std::string(indent + 1, ' ') << "CleanApertureWidthD: " << getCleanApertureWidthD() << std::endl; + r << std::string(indent + 1, ' ') << "CleanApertureHeightN: " << getCleanApertureHeightN() << std::endl; + r << std::string(indent + 1, ' ') << "CleanApertureHeightD: " << getCleanApertureHeightD() << std::endl; + r << std::string(indent + 1, ' ') << "HorizOffN: " << getHorizOffN() << std::endl; + r << std::string(indent + 1, ' ') << "HorizOffD: " << getHorizOffD() << std::endl; + r << std::string(indent + 1, ' ') << "VertOffN: " << getVertOffN() << std::endl; + r << std::string(indent + 1, ' ') << "VertOffD: " << getVertOffD() << std::endl; + return r.str(); + } + + PASP::PASP() { + memcpy(data + 4, "pasp", 4); + } + + void PASP::setHSpacing(uint32_t newVal) { + setInt32(newVal, 0); + } + + uint32_t PASP::getHSpacing() { + return getInt32(0); + } + + void PASP::setVSpacing(uint32_t newVal) { + setInt32(newVal, 4); + } + + uint32_t PASP::getVSpacing() { + return getInt32(4); + } + + std::string PASP::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[pasp] Pixel Aspect Ratio Box (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "HSpacing: " << getHSpacing() << std::endl; + r << std::string(indent + 1, ' ') << "VSpacing: " << getVSpacing() << std::endl; + return r.str(); + } + + VisualSampleEntry::VisualSampleEntry() { + memcpy(data + 4, "erro", 4); + setHorizResolution(0x00480000); + setVertResolution(0x00480000); + setFrameCount(1); + setCompressorName(""); + setDepth(0x0018); + } + + void VisualSampleEntry::setCodec(const char * newCodec) { + memcpy(data + 4, newCodec, 4); + } + + void VisualSampleEntry::setWidth(uint16_t newWidth) { + setInt16(newWidth, 24); + } + + uint16_t VisualSampleEntry::getWidth() { + return getInt16(24); + } + + void VisualSampleEntry::setHeight(uint16_t newHeight) { + setInt16(newHeight, 26); + } + + uint16_t VisualSampleEntry::getHeight() { + return getInt16(26); + } + + void VisualSampleEntry::setHorizResolution(uint32_t newHorizResolution) { + setInt32(newHorizResolution, 28); + } + + uint32_t VisualSampleEntry::getHorizResolution() { + return getInt32(28); + } + + void VisualSampleEntry::setVertResolution(uint32_t newVertResolution) { + setInt32(newVertResolution, 32); + } + + uint32_t VisualSampleEntry::getVertResolution() { + return getInt32(32); + } + + void VisualSampleEntry::setFrameCount(uint16_t newFrameCount) { + setInt16(newFrameCount, 40); + } + + uint16_t VisualSampleEntry::getFrameCount() { + return getInt16(40); + } + + void VisualSampleEntry::setCompressorName(std::string newCompressorName) { + newCompressorName.resize(32, ' '); + setString(newCompressorName, 42); + } + + std::string VisualSampleEntry::getCompressorName() { + return getString(42); + } + + void VisualSampleEntry::setDepth(uint16_t newDepth) { + setInt16(newDepth, 74); + } + + uint16_t VisualSampleEntry::getDepth() { + return getInt16(74); + } + + void VisualSampleEntry::setCLAP(Box & clap) { + setBox(clap, 78); + } + + Box & VisualSampleEntry::getCLAP() { + static Box ret = Box((char *)"\000\000\000\010erro", false); + if (payloadSize() < 84) { //if the EntryBox is not big enough to hold a CLAP/PASP + return ret; + } + ret = getBox(78); + return ret; + } + + Box & VisualSampleEntry::getPASP() { + static Box ret = Box((char *)"\000\000\000\010erro", false); + if (payloadSize() < 84) { //if the EntryBox is not big enough to hold a CLAP/PASP + return ret; + } + if (payloadSize() < 78 + getBoxLen(78) + 8) { + return ret; + } else { + return getBox(78 + getBoxLen(78)); + } + + } + + std::string VisualSampleEntry::toPrettyVisualString(uint32_t indent, std::string name) { + std::stringstream r; + r << std::string(indent, ' ') << name << " (" << boxedSize() << ")" << std::endl; + r << toPrettySampleString(indent); + r << std::string(indent + 1, ' ') << "Width: " << getWidth() << std::endl; + r << std::string(indent + 1, ' ') << "Height: " << getHeight() << std::endl; + r << std::string(indent + 1, ' ') << "HorizResolution: " << getHorizResolution() << std::endl; + r << std::string(indent + 1, ' ') << "VertResolution: " << getVertResolution() << std::endl; + r << std::string(indent + 1, ' ') << "FrameCount: " << getFrameCount() << std::endl; + r << std::string(indent + 1, ' ') << "CompressorName: " << getCompressorName() << std::endl; + r << std::string(indent + 1, ' ') << "Depth: " << getDepth() << std::endl; + if (!getCLAP().isType("erro")) { + r << getCLAP().toPrettyString(indent + 1); + } + if (!getPASP().isType("erro")) { + r << getPASP().toPrettyString(indent + 1); + } + return r.str(); + } + + AudioSampleEntry::AudioSampleEntry() { + memcpy(data + 4, "erro", 4); + setChannelCount(2); + setSampleSize(16); + setSampleRate(44100); + } + + uint16_t AudioSampleEntry::toAACInit() { + uint16_t result = 0; + result |= (2 & 0x1F) << 11; + result |= (getSampleRate() & 0x0F) << 7; + result |= (getChannelCount() & 0x0F) << 3; + return result; + } + + void AudioSampleEntry::setCodec(const char * newCodec) { + memcpy(data + 4, newCodec, 4); + } + + void AudioSampleEntry::setChannelCount(uint16_t newChannelCount) { + setInt16(newChannelCount, 16); + } + + uint16_t AudioSampleEntry::getChannelCount() { + return getInt16(16); + } + + void AudioSampleEntry::setSampleSize(uint16_t newSampleSize) { + setInt16(newSampleSize, 18); + } + + uint16_t AudioSampleEntry::getSampleSize() { + return getInt16(18); + } + + void AudioSampleEntry::setPreDefined(uint16_t newPreDefined) { + setInt16(newPreDefined, 20); + } + + uint16_t AudioSampleEntry::getPreDefined() { + return getInt16(20); + } + + void AudioSampleEntry::setSampleRate(uint32_t newSampleRate) { + setInt32(newSampleRate << 16, 24); + } + + uint32_t AudioSampleEntry::getSampleRate() { + return getInt32(24) >> 16; + } + + void AudioSampleEntry::setCodecBox(Box & newBox) { + setBox(newBox, 28); + } + + Box & AudioSampleEntry::getCodecBox() { + return getBox(28); + } + + std::string AudioSampleEntry::toPrettyAudioString(uint32_t indent, std::string name) { + std::stringstream r; + r << std::string(indent, ' ') << name << " (" << boxedSize() << ")" << std::endl; + r << toPrettySampleString(indent); + r << std::string(indent + 1, ' ') << "ChannelCount: " << getChannelCount() << std::endl; + r << std::string(indent + 1, ' ') << "SampleSize: " << getSampleSize() << std::endl; + r << std::string(indent + 1, ' ') << "PreDefined: " << getPreDefined() << std::endl; + r << std::string(indent + 1, ' ') << "SampleRate: " << getSampleRate() << std::endl; + r << getCodecBox().toPrettyString(indent + 1) << std::endl; + return r.str(); + } + + MP4A::MP4A() { + memcpy(data + 4, "mp4a", 4); + } + + std::string MP4A::toPrettyString(uint32_t indent) { + return toPrettyAudioString(indent, "[mp4a] MPEG-4 Audio"); + } + + AAC::AAC() { + memcpy(data + 4, "aac ", 4); + } + + std::string AAC::toPrettyString(uint32_t indent) { + return toPrettyAudioString(indent, "[aac ] Advanced Audio Codec"); + } + + AVC1::AVC1() { + memcpy(data + 4, "avc1", 4); + } + + std::string AVC1::toPrettyString(uint32_t indent) { + return toPrettyVisualString(indent, "[avc1] Advanced Video Codec 1"); + } + + H264::H264() { + memcpy(data + 4, "h264", 4); + } + + std::string H264::toPrettyString(uint32_t indent) { + return toPrettyVisualString(indent, "[h264] H.264/MPEG-4 AVC"); + } + + STSD::STSD(char v, uint32_t f) { + memcpy(data + 4, "stsd", 4); + setVersion(v); + setFlags(f); + setEntryCount(0); + } + + void STSD::setEntryCount(uint32_t newEntryCount) { + setInt32(newEntryCount, 4); + } + + uint32_t STSD::getEntryCount() { + return getInt32(4); + } + + void STSD::setEntry(Box & newContent, uint32_t no) { + int tempLoc = 8; + unsigned int entryCount = getEntryCount(); + for (unsigned int i = 0; i < no; i++) { + if (i < entryCount) { + tempLoc += getBoxLen(tempLoc); + } else { + if (!reserve(tempLoc, 0, (no - entryCount) * 8)) { + return; + } + memset(data + tempLoc, 0, (no - entryCount) * 8); + tempLoc += (no - entryCount) * 8; + break; + } + } + setBox(newContent, tempLoc); + if (getEntryCount() < no + 1) { + setEntryCount(no + 1); + } + } + + Box & STSD::getEntry(uint32_t no) { + static Box ret = Box((char *)"\000\000\000\010erro", false); + if (no > getEntryCount()) { + return ret; + } + unsigned int i = 0; + int tempLoc = 8; + while (i < no) { + tempLoc += getBoxLen(tempLoc); + i++; + } + return getBox(tempLoc); + } + + std::string STSD::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[stsd] Sample Description Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "EntrySize: " << getEntryCount() << std::endl; + for (unsigned int i = 0; i < getEntryCount(); i++) { + Box curBox = Box(getEntry(i).asBox(), false); + r << curBox.toPrettyString(indent + 1); + } + return r.str(); + } + + EDTS::EDTS() { + memcpy(data + 4, "edts", 4); + } + + UDTA::UDTA() { + memcpy(data + 4, "udta", 4); + } + + STSS::STSS(char v, uint32_t f) { + memcpy(data + 4, "stss", 4); + setVersion(v); + setFlags(f); + setEntryCount(0); + } + + void STSS::setEntryCount(uint32_t newVal) { + setInt32(newVal, 4); + } + + uint32_t STSS::getEntryCount() { + return getInt32(4); + } + + void STSS::setSampleNumber(uint32_t newVal, uint32_t index) { + if (index >= getEntryCount()) { + setEntryCount(index + 1); + } + setInt32(newVal, 8 + (index * 4)); + } + + uint32_t STSS::getSampleNumber(uint32_t index) { + if (index >= getEntryCount()) { + return 0; + } + return getInt32(8 + (index * 4)); + } + + std::string STSS::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[stss] Sync Sample Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "EntryCount: " << getEntryCount() << std::endl; + + r << std::string(indent + 1, ' ') << "Keyframe sample indexes: "; + for (unsigned long i = 0; i < getEntryCount(); i++) { + r << getSampleNumber(i); + if (i != getEntryCount() - 1){ + r << ", "; + } + } + r << std::endl; + return r.str(); + } + + META::META() { + memcpy(data + 4, "meta", 4); + } + + std::string META::toPrettyString(uint32_t indent) { + return toPrettyCFBString(indent, "[meta] Meta Box"); + } + + ELST::ELST() { + memcpy(data + 4, "elst", 4); + } + + void ELST::setSegmentDuration(uint64_t newVal) { + if (getVersion() == 1) { + setInt64(newVal, 4); + } else { + setInt32(newVal, 4); + } + } + + uint64_t ELST::getSegmentDuration() { + if (getVersion() == 1) { + return getInt64(4); + } else { + return getInt32(4); + } + } + + void ELST::setMediaTime(uint64_t newVal) { + if (getVersion() == 1) { + setInt64(newVal, 12); + } else { + setInt32(newVal, 8); + } + } + + uint64_t ELST::getMediaTime() { + if (getVersion() == 1) { + return getInt64(12); + } else { + return getInt32(8); + } + } + + void ELST::setMediaRateInteger(uint16_t newVal) { + if (getVersion() == 1) { + setInt16(newVal, 20); + } else { + setInt16(newVal, 12); + } + } + + uint16_t ELST::getMediaRateInteger() { + if (getVersion() == 1) { + return getInt16(20); + } else { + return getInt16(12); + } + } + + void ELST::setMediaRateFraction(uint16_t newVal) { + if (getVersion() == 1) { + setInt16(newVal, 22); + } else { + setInt16(newVal, 14); + } + } + + uint16_t ELST::getMediaRateFraction() { + if (getVersion() == 1) { + return getInt16(22); + } else { + return getInt16(14); + } + } + + std::string ELST::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[elst] Edit List Box (" << boxedSize() << ")" << std::endl; + r << fullBox::toPrettyString(indent); + r << std::string(indent + 1, ' ') << "SegmentDuration: " << getSegmentDuration() << std::endl; + r << std::string(indent + 1, ' ') << "MediaTime: " << getMediaTime() << std::endl; + r << std::string(indent + 1, ' ') << "MediaRateInteger: " << getMediaRateInteger() << std::endl; + r << std::string(indent + 1, ' ') << "MediaRateFraction: " << getMediaRateFraction() << std::endl; + return r.str(); + } +} diff --git a/lib/mp4_generic.h b/lib/mp4_generic.h new file mode 100644 index 00000000..381a12dc --- /dev/null +++ b/lib/mp4_generic.h @@ -0,0 +1,677 @@ +#include "mp4.h" + +namespace MP4 { + class MFHD: public Box { + public: + MFHD(); + void setSequenceNumber(uint32_t newSequenceNumber); + uint32_t getSequenceNumber(); + std::string toPrettyString(uint32_t indent = 0); + }; + //MFHD Box + + class MOOF: public containerBox { + public: + MOOF(); + }; + //MOOF Box + + class TRAF: public Box { + public: + TRAF(); + uint32_t getContentCount(); + void setContent(Box & newContent, uint32_t no); + Box & getContent(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + //TRAF Box + + struct trunSampleInformation { + uint32_t sampleDuration; + uint32_t sampleSize; + uint32_t sampleFlags; + uint32_t sampleOffset; + }; + enum trunflags { + trundataOffset = 0x00000001, + trunfirstSampleFlags = 0x00000004, + trunsampleDuration = 0x00000100, + trunsampleSize = 0x00000200, + trunsampleFlags = 0x00000400, + trunsampleOffsets = 0x00000800 + }; + enum sampleflags { + noIPicture = 0x01000000, + isIPicture = 0x02000000, + noDisposable = 0x00400000, + isDisposable = 0x00800000, + isRedundant = 0x00100000, + noRedundant = 0x00200000, + noKeySample = 0x00010000, + isKeySample = 0x00000000, + MUST_BE_PRESENT = 0x1 + }; + std::string prettySampleFlags(uint32_t flag); + class TRUN: public Box { + public: + TRUN(); + void setFlags(uint32_t newFlags); + uint32_t getFlags(); + void setDataOffset(uint32_t newOffset); + uint32_t getDataOffset(); + void setFirstSampleFlags(uint32_t newSampleFlags); + uint32_t getFirstSampleFlags(); + uint32_t getSampleInformationCount(); + void setSampleInformation(trunSampleInformation newSample, uint32_t no); + trunSampleInformation getSampleInformation(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + + enum tfhdflags { + tfhdBaseOffset = 0x000001, + tfhdSampleDesc = 0x000002, + tfhdSampleDura = 0x000008, + tfhdSampleSize = 0x000010, + tfhdSampleFlag = 0x000020, + tfhdNoDuration = 0x010000, + }; + class TFHD: public Box { + public: + TFHD(); + void setFlags(uint32_t newFlags); + uint32_t getFlags(); + void setTrackID(uint32_t newID); + uint32_t getTrackID(); + void setBaseDataOffset(uint64_t newOffset); + uint64_t getBaseDataOffset(); + void setSampleDescriptionIndex(uint32_t newIndex); + uint32_t getSampleDescriptionIndex(); + void setDefaultSampleDuration(uint32_t newDuration); + uint32_t getDefaultSampleDuration(); + void setDefaultSampleSize(uint32_t newSize); + uint32_t getDefaultSampleSize(); + void setDefaultSampleFlags(uint32_t newFlags); + uint32_t getDefaultSampleFlags(); + std::string toPrettyString(uint32_t indent = 0); + }; + + + class AVCC: public Box { + public: + AVCC(); + void setVersion(uint32_t newVersion); + uint32_t getVersion(); + void setProfile(uint32_t newProfile); + uint32_t getProfile(); + void setCompatibleProfiles(uint32_t newCompatibleProfiles); + uint32_t getCompatibleProfiles(); + void setLevel(uint32_t newLevel); + uint32_t getLevel(); + void setSPSNumber(uint32_t newSPSNumber); + uint32_t getSPSNumber(); + void setSPS(std::string newSPS); + uint32_t getSPSLen(); + char * getSPS(); + void setPPSNumber(uint32_t newPPSNumber); + uint32_t getPPSNumber(); + void setPPS(std::string newPPS); + uint32_t getPPSLen(); + char * getPPS(); + std::string asAnnexB(); + void setPayload(std::string newPayload); + std::string toPrettyString(uint32_t indent = 0); + }; + + class Descriptor{ + public: + Descriptor(); + Descriptor(const char* pointer, const unsigned long length, const bool master = false); + char getTag();///< Get type of descriptor + void setTag(char t);///< Set type of descriptor + unsigned long getDataSize();///< Get internal size of descriptor + unsigned long getFullSize();///< Get external size of descriptor + void resize(unsigned long t);///< Resize descriptor + char* getData();///< Returns pointer to start of internal data + std::string toPrettyString(uint32_t indent = 0);///< put it into a pretty string + protected: + unsigned long size;///< Length of data + char* data;///< Pointer to data in memory + bool master; + }; + + /// Implements ISO 14496-1 DecSpecificInfoTag + class DSDescriptor: public Descriptor{ + public: + DSDescriptor (const char* pointer, const unsigned long length, const bool master = false); + std::string toPrettyString(uint32_t indent = 0);///< put it into a pretty string + std::string toString(); ///< Return decoder specific info as standard string in binary format. + }; + + /// Implements ISO 14496-1 DecoderConfigDescrTag + class DCDescriptor: public Descriptor{ + public: + DCDescriptor (const char* pointer, const unsigned long length, const bool master = false); + bool isAAC(); ///< Returns true if this track is AAC. + DSDescriptor getSpecific(); + std::string toPrettyString(uint32_t indent = 0);///< put it into a pretty string + }; + + /// Implements ISO 14496-1 ES_DescrTag + class ESDescriptor: public Descriptor{ + public: + ESDescriptor (const char* pointer, const unsigned long length, const bool master = false); + DCDescriptor getDecoderConfig(); + std::string toPrettyString(uint32_t indent = 0);///< put it into a pretty string + }; + + + + /// Implements ISO 14496-1 SLConfigDescrTag + class SLCDescriptor: public Descriptor{ + }; + + class ESDS: public fullBox { + public: + ESDS(); + ESDS(std::string init); + ESDescriptor getESDescriptor(); + bool isAAC(); + std::string getInitData(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class FTYP: public Box { + public: + FTYP(); + void setMajorBrand(const char * newMajorBrand); + std::string getMajorBrand(); + void setMinorVersion(const char * newMinorVersion); + std::string getMinorVersion(); + size_t getCompatibleBrandsCount(); + void setCompatibleBrands(const char * newCompatibleBrand, size_t index); + std::string getCompatibleBrands(size_t index); + std::string toPrettyString(uint32_t indent = 0); + }; + + class MOOV: public containerBox { + public: + MOOV(); + }; + + class MVEX: public containerBox { + public: + MVEX(); + }; + + class TREX: public Box { + public: + TREX(); + void setTrackID(uint32_t newTrackID); + uint32_t getTrackID(); + void setDefaultSampleDescriptionIndex(uint32_t newDefaultSampleDescriptionIndex); + uint32_t getDefaultSampleDescriptionIndex(); + void setDefaultSampleDuration(uint32_t newDefaultSampleDuration); + uint32_t getDefaultSampleDuration(); + void setDefaultSampleSize(uint32_t newDefaultSampleSize); + uint32_t getDefaultSampleSize(); + void setDefaultSampleFlags(uint32_t newDefaultSampleFlags); + uint32_t getDefaultSampleFlags(); + std::string toPrettyString(uint32_t indent = 0); + }; + + + class MFRA: public containerBox { + public: + MFRA(); + }; + + class TRAK: public containerBox { + public: + TRAK(); + }; + + class MDIA: public containerBox { + public: + MDIA(); + }; + + class MINF: public containerBox { + public: + MINF(); + }; + + class DINF: public containerBox { + public: + DINF(); + }; + + class MFRO: public Box { + public: + MFRO(); + void setSize(uint32_t newSize); + uint32_t getSize(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class HDLR: public Box { + public: + HDLR(std::string & type, std::string name); + void setHandlerType(const char * newHandlerType); + std::string getHandlerType(); + void setName(std::string newName); + std::string getName(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class VMHD: public fullBox { + public: + VMHD(); + void setGraphicsMode(uint16_t newGraphicsMode); + uint16_t getGraphicsMode(); + uint32_t getOpColorCount(); + void setOpColor(uint16_t newOpColor, size_t index); + uint16_t getOpColor(size_t index); + std::string toPrettyString(uint32_t indent = 0); + }; + + class SMHD: public fullBox { + public: + SMHD(); + void setBalance(int16_t newBalance); + int16_t getBalance(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class HMHD: public fullBox { + public: + HMHD(); + void setMaxPDUSize(uint16_t newMaxPDUSize); + uint16_t getMaxPDUSize(); + void setAvgPDUSize(uint16_t newAvgPDUSize); + uint16_t getAvgPDUSize(); + void setMaxBitRate(uint32_t newMaxBitRate); + uint32_t getMaxBitRate(); + void setAvgBitRate(uint32_t newAvgBitRate); + uint32_t getAvgBitRate(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class NMHD: public fullBox { + public: + NMHD(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class MEHD: public fullBox { + public: + MEHD(); + void setFragmentDuration(uint64_t newFragmentDuration); + uint64_t getFragmentDuration(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class STBL: public containerBox { + public: + STBL(); + }; + + class URL: public fullBox { + public: + URL(); + void setLocation(std::string newLocation); + std::string getLocation(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class URN: public fullBox { + public: + URN(); + void setName(std::string newName); + std::string getName(); + void setLocation(std::string newLocation); + std::string getLocation(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class DREF: public fullBox { + public: + DREF(); + uint32_t getEntryCount(); + void setDataEntry(fullBox & newDataEntry, size_t index); + Box & getDataEntry(size_t index); + std::string toPrettyString(uint32_t indent = 0); + }; + + class MVHD: public fullBox { + public: + MVHD(long long unsigned int duration); + void setCreationTime(uint64_t newCreationTime); + uint64_t getCreationTime(); + void setModificationTime(uint64_t newModificationTime); + uint64_t getModificationTime(); + void setTimeScale(uint32_t newTimeScale); + uint32_t getTimeScale(); + void setDuration(uint64_t newDuration); + uint64_t getDuration(); + void setRate(uint32_t newRate); + uint32_t getRate(); + void setVolume(uint16_t newVolume); + uint16_t getVolume(); + uint32_t getMatrixCount(); + void setMatrix(int32_t newMatrix, size_t index); + int32_t getMatrix(size_t index); + void setTrackID(uint32_t newTrackID); + uint32_t getTrackID(); + std::string toPrettyString(uint32_t indent = 0); + }; + + struct TFRAEntry { + uint64_t time; + uint64_t moofOffset; + uint32_t trafNumber; + uint32_t trunNumber; + uint32_t sampleNumber; + }; + + class TFRA: public fullBox { + public: + TFRA(); + void setTrackID(uint32_t newTrackID); + uint32_t getTrackID(); + void setLengthSizeOfTrafNum(char newVal); + char getLengthSizeOfTrafNum(); + void setLengthSizeOfTrunNum(char newVal); + char getLengthSizeOfTrunNum(); + void setLengthSizeOfSampleNum(char newVal); + char getLengthSizeOfSampleNum(); + void setNumberOfEntry(uint32_t newNumberOfEntry); + uint32_t getNumberOfEntry(); + void setTFRAEntry(TFRAEntry newTFRAEntry, uint32_t no); + TFRAEntry & getTFRAEntry(uint32_t no); + uint32_t getTFRAEntrySize(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class TKHD: public fullBox { + public: + TKHD(uint32_t trackId, uint64_t duration, uint32_t width, uint32_t height); + void setCreationTime(uint64_t newCreationTime); + uint64_t getCreationTime(); + void setModificationTime(uint64_t newModificationTime); + uint64_t getModificationTime(); + void setTrackID(uint32_t newTrackID); + uint32_t getTrackID(); + void setDuration(uint64_t newDuration); + uint64_t getDuration(); + + void setLayer(uint16_t newLayer); + uint16_t getLayer(); + void setAlternateGroup(uint16_t newAlternateGroup); + uint16_t getAlternateGroup(); + + void setVolume(uint16_t newVolume); + uint16_t getVolume(); + uint32_t getMatrixCount(); + void setMatrix(int32_t newMatrix, size_t index); + int32_t getMatrix(size_t index); + + void setWidth(uint32_t newWidth); + uint32_t getWidth(); + void setHeight(uint32_t newHeight); + uint32_t getHeight(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class MDHD: public fullBox { + public: + MDHD(uint64_t duration); + void setCreationTime(uint64_t newCreationTime); + uint64_t getCreationTime(); + void setModificationTime(uint64_t newModificationTime); + uint64_t getModificationTime(); + void setTimeScale(uint32_t newTimeScale); + uint32_t getTimeScale(); + void setDuration(uint64_t newDuration); + uint64_t getDuration(); + ///\todo return language properly + void setLanguage(uint16_t newLanguage); + uint16_t getLanguage(); + std::string toPrettyString(uint32_t indent = 0); + }; + + struct STTSEntry { + uint32_t sampleCount; + uint32_t sampleDelta; + }; + + class STTS: public fullBox { + public: + STTS(char v = 1, uint32_t f = 0); + void setEntryCount(uint32_t newEntryCount); + uint32_t getEntryCount(); + void setSTTSEntry(STTSEntry newSTTSEntry, uint32_t no); + STTSEntry getSTTSEntry(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + + struct CTTSEntry { + uint32_t sampleCount; + uint32_t sampleOffset; + }; + + class CTTS: public fullBox { + public: + CTTS(); + void setEntryCount(uint32_t newEntryCount); + uint32_t getEntryCount(); + void setCTTSEntry(CTTSEntry newCTTSEntry, uint32_t no); + CTTSEntry getCTTSEntry(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + + struct STSCEntry { + uint32_t firstChunk; + uint32_t samplesPerChunk; + uint32_t sampleDescriptionIndex; + }; + + class STSC: public fullBox { + public: + STSC(char v = 1, uint32_t f = 0); + void setEntryCount(uint32_t newEntryCount); + uint32_t getEntryCount(); + void setSTSCEntry(STSCEntry newSTSCEntry, uint32_t no); + STSCEntry getSTSCEntry(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + + class STCO: public fullBox { + public: + STCO(char v = 1, uint32_t f = 0); + void setEntryCount(uint32_t newEntryCount); + uint32_t getEntryCount(); + void setChunkOffset(uint32_t newChunkOffset, uint32_t no); + uint32_t getChunkOffset(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + + class CO64: public fullBox { + public: + CO64(char v = 1, uint32_t f = 0); + void setEntryCount(uint32_t newEntryCount); + uint32_t getEntryCount(); + void setChunkOffset(uint64_t newChunkOffset, uint32_t no); + uint64_t getChunkOffset(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + + class STSZ: public fullBox { + public: + STSZ(char v = 1, uint32_t f = 0); + void setSampleSize(uint32_t newSampleSize); + uint32_t getSampleSize(); + void setSampleCount(uint32_t newSampleCount); + uint32_t getSampleCount(); + void setEntrySize(uint32_t newEntrySize, uint32_t no); + uint32_t getEntrySize(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + + class SampleEntry: public Box { + public: + SampleEntry(); + void setDataReferenceIndex(uint16_t newDataReferenceIndex); + uint16_t getDataReferenceIndex(); + std::string toPrettySampleString(uint32_t index); + }; + + class CLAP: public Box { //CleanApertureBox + public: + CLAP(); + void setCleanApertureWidthN(uint32_t newVal); + uint32_t getCleanApertureWidthN(); + void setCleanApertureWidthD(uint32_t newVal); + uint32_t getCleanApertureWidthD(); + void setCleanApertureHeightN(uint32_t newVal); + uint32_t getCleanApertureHeightN(); + void setCleanApertureHeightD(uint32_t newVal); + uint32_t getCleanApertureHeightD(); + void setHorizOffN(uint32_t newVal); + uint32_t getHorizOffN(); + void setHorizOffD(uint32_t newVal); + uint32_t getHorizOffD(); + void setVertOffN(uint32_t newVal); + uint32_t getVertOffN(); + void setVertOffD(uint32_t newVal); + uint32_t getVertOffD(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class PASP: public Box { //PixelAspectRatioBox + public: + PASP(); + void setHSpacing(uint32_t newVal); + uint32_t getHSpacing(); + void setVSpacing(uint32_t newVal); + uint32_t getVSpacing(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class VisualSampleEntry: public SampleEntry { + ///\todo set default values + public: + VisualSampleEntry(); + void setCodec(const char * newCodec); + void setWidth(uint16_t newWidth); + uint16_t getWidth(); + void setHeight(uint16_t newHeight); + uint16_t getHeight(); + void setHorizResolution(uint32_t newHorizResolution); + uint32_t getHorizResolution(); + void setVertResolution(uint32_t newVertResolution); + uint32_t getVertResolution(); + void setFrameCount(uint16_t newFrameCount); + uint16_t getFrameCount(); + void setCompressorName(std::string newCompressorName); + std::string getCompressorName(); + void setDepth(uint16_t newDepth); + uint16_t getDepth(); + Box & getCLAP(); + void setCLAP(Box & clap); + Box & getPASP(); + std::string toPrettyVisualString(uint32_t index = 0, std::string = ""); + }; + + class AudioSampleEntry: public SampleEntry { + public: + ///\todo set default values + AudioSampleEntry(); + void setCodec(const char * newCodec); + void setChannelCount(uint16_t newChannelCount); + uint16_t getChannelCount(); + void setSampleSize(uint16_t newSampleSize); + uint16_t getSampleSize(); + void setPreDefined(uint16_t newPreDefined); + uint16_t getPreDefined(); + void setSampleRate(uint32_t newSampleRate); + uint16_t toAACInit(); + uint32_t getSampleRate(); + void setCodecBox(Box & newBox); + Box & getCodecBox(); + std::string toPrettyAudioString(uint32_t indent = 0, std::string name = ""); + }; + + class MP4A: public AudioSampleEntry { + public: + MP4A(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class AAC: public AudioSampleEntry { + public: + AAC(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class AVC1: public VisualSampleEntry { + public: + AVC1(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class H264: public VisualSampleEntry { + public: + H264(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class STSD: public fullBox { + public: + STSD(char v = 1, uint32_t f = 0); + void setEntryCount(uint32_t newEntryCount); + uint32_t getEntryCount(); + void setEntry(Box & newContent, uint32_t no); + Box & getEntry(uint32_t no); + std::string toPrettyString(uint32_t indent = 0); + }; + + class EDTS: public containerBox { + public: + EDTS(); + }; + + class UDTA: public containerBox { + public: + UDTA(); + }; + + class STSS: public fullBox { + public: + STSS(char v = 1, uint32_t f = 0); + void setEntryCount(uint32_t newVal); + uint32_t getEntryCount(); + void setSampleNumber(uint32_t newVal, uint32_t index); + uint32_t getSampleNumber(uint32_t index); + std::string toPrettyString(uint32_t indent = 0); + }; + + class META: public containerFullBox { + public: + META(); + std::string toPrettyString(uint32_t indent = 0); + }; + + class ELST: public fullBox { + public: + ELST(); + void setSegmentDuration(uint64_t newVal); + uint64_t getSegmentDuration(); + void setMediaTime(uint64_t newVal); + uint64_t getMediaTime(); + void setMediaRateInteger(uint16_t newVal); + uint16_t getMediaRateInteger(); + void setMediaRateFraction(uint16_t newVal); + uint16_t getMediaRateFraction(); + std::string toPrettyString(uint32_t indent = 0); + }; +} diff --git a/lib/mp4_ms.cpp b/lib/mp4_ms.cpp new file mode 100644 index 00000000..47424428 --- /dev/null +++ b/lib/mp4_ms.cpp @@ -0,0 +1,208 @@ +#include "mp4_ms.h" + +namespace MP4 { + + static char 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; + } + + + SDTP::SDTP() { + memcpy(data + 4, "sdtp", 4); + } + + void SDTP::setVersion(uint32_t newVersion) { + setInt8(newVersion, 0); + } + + uint32_t SDTP::getVersion() { + return getInt8(0); + } + + void SDTP::setValue(uint32_t newValue, size_t index) { + setInt8(newValue, index); + } + + uint32_t SDTP::getValue(size_t index) { + return getInt8(index); + } + + std::string SDTP::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[sdtp] Sample Dependancy Type (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Samples: " << (boxedSize() - 12) << std::endl; + for (size_t i = 1; i <= boxedSize() - 12; ++i) { + uint32_t val = getValue(i + 3); + r << std::string(indent + 2, ' ') << "[" << i << "] = "; + switch (val & 3) { + case 0: + r << " "; + break; + case 1: + r << "Redundant, "; + break; + case 2: + r << "Not redundant, "; + break; + case 3: + r << "Error, "; + break; + } + switch (val & 12) { + case 0: + r << " "; + break; + case 4: + r << "Not disposable, "; + break; + case 8: + r << "Disposable, "; + break; + case 12: + r << "Error, "; + break; + } + switch (val & 48) { + case 0: + r << " "; + break; + case 16: + r << "IFrame, "; + break; + case 32: + r << "Not IFrame, "; + break; + case 48: + r << "Error, "; + break; + } + r << "(" << val << ")" << std::endl; + } + return r.str(); + } + + UUID::UUID() { + memcpy(data + 4, "uuid", 4); + setInt64(0, 0); + setInt64(0, 8); + } + + std::string UUID::getUUID() { + std::stringstream r; + r << std::hex; + for (int i = 0; i < 16; ++i) { + if (i == 4 || i == 6 || i == 8 || i == 10) { + r << "-"; + } + r << std::setfill('0') << std::setw(2) << std::right << (int)(data[8 + i]); + } + return r.str(); + } + + void UUID::setUUID(const std::string & uuid_string) { + //reset UUID to zero + for (int i = 0; i < 4; ++i) { + ((uint32_t *)(data + 8))[i] = 0; + } + //set the UUID from the string, char by char + int i = 0; + for (size_t j = 0; j < uuid_string.size(); ++j) { + if (uuid_string[j] == '-') { + continue; + } + data[8 + i / 2] |= (c2hex(uuid_string[j]) << ((~i & 1) << 2)); + ++i; + } + } + + void UUID::setUUID(const char * raw_uuid) { + memcpy(data + 8, raw_uuid, 16); + } + + std::string UUID::toPrettyString(uint32_t indent) { + std::string UUID = getUUID(); + if (UUID == "d4807ef2-ca39-4695-8e54-26cb9e46a79f") { + return ((UUID_TrackFragmentReference *)this)->toPrettyString(indent); + } + std::stringstream r; + r << std::string(indent, ' ') << "[uuid] Extension box (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "UUID: " << UUID << std::endl; + r << std::string(indent + 1, ' ') << "Unknown UUID - ignoring contents." << std::endl; + return r.str(); + } + + UUID_TrackFragmentReference::UUID_TrackFragmentReference() { + setUUID((std::string)"d4807ef2-ca39-4695-8e54-26cb9e46a79f"); + } + + void UUID_TrackFragmentReference::setVersion(uint32_t newVersion) { + setInt8(newVersion, 16); + } + + uint32_t UUID_TrackFragmentReference::getVersion() { + return getInt8(16); + } + + void UUID_TrackFragmentReference::setFlags(uint32_t newFlags) { + setInt24(newFlags, 17); + } + + uint32_t UUID_TrackFragmentReference::getFlags() { + return getInt24(17); + } + + void UUID_TrackFragmentReference::setFragmentCount(uint32_t newCount) { + setInt8(newCount, 20); + } + + uint32_t UUID_TrackFragmentReference::getFragmentCount() { + return getInt8(20); + } + + void UUID_TrackFragmentReference::setTime(size_t num, uint64_t newTime) { + if (getVersion() == 0) { + setInt32(newTime, 21 + (num * 8)); + } else { + setInt64(newTime, 21 + (num * 16)); + } + } + + uint64_t UUID_TrackFragmentReference::getTime(size_t num) { + if (getVersion() == 0) { + return getInt32(21 + (num * 8)); + } else { + return getInt64(21 + (num * 16)); + } + } + + void UUID_TrackFragmentReference::setDuration(size_t num, uint64_t newDuration) { + if (getVersion() == 0) { + setInt32(newDuration, 21 + (num * 8) + 4); + } else { + setInt64(newDuration, 21 + (num * 16) + 8); + } + } + + uint64_t UUID_TrackFragmentReference::getDuration(size_t num) { + if (getVersion() == 0) { + return getInt32(21 + (num * 8) + 4); + } else { + return getInt64(21 + (num * 16) + 8); + } + } + + std::string UUID_TrackFragmentReference::toPrettyString(uint32_t indent) { + std::stringstream r; + r << std::string(indent, ' ') << "[d4807ef2-ca39-4695-8e54-26cb9e46a79f] Track Fragment Reference (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Version: " << getVersion() << std::endl; + r << std::string(indent + 1, ' ') << "Fragments: " << getFragmentCount() << std::endl; + int j = getFragmentCount(); + for (int i = 0; i < j; ++i) { + r << std::string(indent + 2, ' ') << "[" << i << "] Time = " << getTime(i) << ", Duration = " << getDuration(i) << std::endl; + } + return r.str(); + } +} diff --git a/lib/mp4_ms.h b/lib/mp4_ms.h new file mode 100644 index 00000000..882f01dc --- /dev/null +++ b/lib/mp4_ms.h @@ -0,0 +1,40 @@ +#pragma once +#include "mp4.h" + +namespace MP4 { + class SDTP: public Box { + public: + SDTP(); + void setVersion(uint32_t newVersion); + uint32_t getVersion(); + void setValue(uint32_t newValue, size_t index); + uint32_t getValue(size_t index); + std::string toPrettyString(uint32_t indent = 0); + }; + + class UUID: public Box { + public: + UUID(); + std::string getUUID(); + void setUUID(const std::string & uuid_string); + void setUUID(const char * raw_uuid); + std::string toPrettyString(uint32_t indent = 0); + }; + + class UUID_TrackFragmentReference: public UUID { + public: + UUID_TrackFragmentReference(); + void setVersion(uint32_t newVersion); + uint32_t getVersion(); + void setFlags(uint32_t newFlags); + uint32_t getFlags(); + void setFragmentCount(uint32_t newCount); + uint32_t getFragmentCount(); + void setTime(size_t num, uint64_t newTime); + uint64_t getTime(size_t num); + void setDuration(size_t num, uint64_t newDuration); + uint64_t getDuration(size_t num); + std::string toPrettyString(uint32_t indent = 0); + }; + +} diff --git a/lib/nal.cpp b/lib/nal.cpp new file mode 100644 index 00000000..a7c430f7 --- /dev/null +++ b/lib/nal.cpp @@ -0,0 +1,507 @@ +#include "nal.h" +#include "bitstream.h" +#include "defines.h" +#include +#include +#include //for log +namespace h264 { + + ///empty constructor of NAL + NAL::NAL() { + + } + + ///Constructor capable of directly inputting NAL data from a string + ///\param InputData the nal input data, as a string + NAL::NAL(std::string & InputData) { + ReadData(InputData); + } + + ///Gets the raw NAL unit data, as a string + ///\return the raw NAL unit data + std::string NAL::getData() { + return MyData; + } + + ///Reads data from a string,either raw or annexed + ///\param InputData the h264 data, as string + ///\param raw a boolean that determines whether the string contains raw h264 data or not + bool NAL::ReadData(std::string & InputData, bool raw) { + if (raw) { + MyData = InputData; + InputData.clear(); + return true; + } + std::string FullAnnexB; + FullAnnexB += (char)0x00; + FullAnnexB += (char)0x00; + FullAnnexB += (char)0x00; + FullAnnexB += (char)0x01; + std::string ShortAnnexB; + ShortAnnexB += (char)0x00; + ShortAnnexB += (char)0x00; + ShortAnnexB += (char)0x01; + if (InputData.size() < 3) { + //DEBUG_MSG(DLVL_DEVEL, "fal1"); + return false; + } + bool AnnexB = false; + if (InputData.substr(0, 3) == ShortAnnexB) { + + AnnexB = true; + } + if (InputData.substr(0, 4) == FullAnnexB) { + InputData.erase(0, 1); + AnnexB = true; + } + if (AnnexB) { + MyData = ""; + InputData.erase(0, 3); //Intro Bytes + int Location = std::min(InputData.find(ShortAnnexB), InputData.find(FullAnnexB)); + MyData = InputData.substr(0, Location); + InputData.erase(0, Location); + } else { + if (InputData.size() < 4) { + DEBUG_MSG(DLVL_DEVEL, "fal2"); + return false; + } + unsigned int UnitLen = (InputData[0] << 24) + (InputData[1] << 16) + (InputData[2] << 8) + InputData[3]; + if (InputData.size() < 4 + UnitLen) { + DEBUG_MSG(DLVL_DEVEL, "fal3"); + return false; + } + InputData.erase(0, 4); //Remove Length + MyData = InputData.substr(0, UnitLen); + InputData.erase(0, UnitLen); //Remove this unit from the string + + + } + //DEBUG_MSG(DLVL_DEVEL, "tru"); + return true; + } + + ///Returns an annex B prefix + ///\param LongIntro determines whether it is a short or long annex B + ///\return the desired annex B prefix + std::string NAL::AnnexB(bool LongIntro) { + std::string Result; + if (MyData.size()) { + if (LongIntro) { + Result += (char)0x00; + } + Result += (char)0x00; + Result += (char)0x00; + Result += (char)0x01; //Annex B Lead-In + Result += MyData; + } + return Result; + } + + ///Returns raw h264 data as Size Prepended + ///\return the h264 data as Size prepended + std::string NAL::SizePrepended() { + std::string Result; + if (MyData.size()) { + int DataSize = MyData.size(); + Result += (char)((DataSize & 0xFF000000) >> 24); + Result += (char)((DataSize & 0x00FF0000) >> 16); + Result += (char)((DataSize & 0x0000FF00) >> 8); + Result += (char)(DataSize & 0x000000FF); //Size Lead-In + Result += MyData; + } + return Result; + } + + ///returns the nal unit type + ///\return the nal unit type + int NAL::Type() { + return (MyData[0] & 0x1F); + } + + SPS::SPS(std::string & input, bool raw) : NAL() { + ReadData(input, raw); + } + + ///computes SPS data from an SPS nal unit, and saves them in a useful + ///more human-readable format in the parameter spsmeta + ///The function is based on the analyzeSPS() function. If more data needs to be stored in sps meta, + ///refer to that function to determine which variable comes at which place (as all couts have been removed). + ///\param spsmeta the sps metadata, in which data from the sps is stored + ///\todo some h264 sps data types are not supported (due to them containing matrixes and have never been encountered in practice). If needed, these need to be implemented + SPSMeta SPS::getCharacteristics() { + SPSMeta result; + + //For calculating width + unsigned int widthInMbs = 0; + unsigned int cropHorizontal = 0; + + //For calculating height + bool mbsOnlyFlag = 0; + unsigned int heightInMapUnits = 0; + unsigned int cropVertical = 0; + + Utils::bitstream bs; + for (unsigned int i = 1; i < MyData.size(); i++) { + if (i + 2 < MyData.size() && MyData.substr(i, 3) == std::string("\000\000\003", 3)) { + bs << MyData.substr(i, 2); + i += 2; + } else { + bs << MyData.substr(i, 1); + } + } + + char profileIdc = bs.get(8); + //Start skipping unused data + bs.skip(16); + bs.getUExpGolomb(); + if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244 || profileIdc == 44 || profileIdc == 83 || profileIdc == 86 || profileIdc == 118 || profileIdc == 128) { + //chroma format idc + if (bs.getUExpGolomb() == 3) { + bs.skip(1); + } + bs.getUExpGolomb(); + bs.getUExpGolomb(); + bs.skip(1); + if (bs.get(1)) { + DEBUG_MSG(DLVL_DEVEL, "Scaling matrix not implemented yet"); + } + } + bs.getUExpGolomb(); + unsigned int pic_order_cnt_type = bs.getUExpGolomb(); + if (!pic_order_cnt_type) { + bs.getUExpGolomb(); + } else if (pic_order_cnt_type == 1) { + DEBUG_MSG(DLVL_DEVEL, "This part of the implementation is incomplete(2), to be continued. If this message is shown, contact developers immediately."); + } + bs.getUExpGolomb(); + bs.skip(1); + //Stop skipping data and start doing usefull stuff + + + widthInMbs = bs.getUExpGolomb() + 1; + heightInMapUnits = bs.getUExpGolomb() + 1; + + mbsOnlyFlag = bs.get(1);//Gets used in height calculation + if (!mbsOnlyFlag) { + bs.skip(1); + } + bs.skip(1); + //cropping flag + if (bs.get(1)) { + cropHorizontal = bs.getUExpGolomb();//leftOffset + cropHorizontal += bs.getUExpGolomb();//rightOffset + cropVertical = bs.getUExpGolomb();//topOffset + cropVertical += bs.getUExpGolomb();//bottomOffset + } + + //vuiParameters + if (bs.get(1)) { + //Skipping all the paramters we dont use + if (bs.get(1)) { + if (bs.get(8) == 255) { + bs.skip(32); + } + } + if (bs.get(1)) { + bs.skip(1); + } + if (bs.get(1)) { + bs.skip(4); + if (bs.get(1)) { + bs.skip(24); + } + } + if (bs.get(1)) { + bs.getUExpGolomb(); + bs.getUExpGolomb(); + } + + //Decode timing info + if (bs.get(1)) { + unsigned int unitsInTick = bs.get(32); + unsigned int timeScale = bs.get(32); + result.fps = (double)timeScale / (2 * unitsInTick); + bs.skip(1); + } + } + + result.width = (widthInMbs * 16) - (cropHorizontal * 2); + result.height = ((mbsOnlyFlag ? 1 : 2) * heightInMapUnits * 16) - (cropVertical * 2); + return result; + } + + ///Analyzes an SPS nal unit, and prints the values of all existing fields + ///\todo some h264 sps data types are not supported (due to them containing matrixes and have not yet been encountered in practice). If needed, these need to be implemented + void SPS::analyzeSPS() { + if (Type() != 7) { + DEBUG_MSG(DLVL_DEVEL, "This is not an SPS, but type %d", Type()); + return; + } + Utils::bitstream bs; + //put rbsp bytes in mydata + for (unsigned int i = 1; i < MyData.size(); i++) { + //DEBUG_MSG(DLVL_DEVEL, "position %u out of %lu",i,MyData.size()); + if (i + 2 < MyData.size() && MyData.substr(i, 3) == std::string("\000\000\003", 3)) { + bs << MyData.substr(i, 2); + //DEBUG_MSG(DLVL_DEVEL, "0x000003 encountered at i = %u",i); + i += 2; + } else { + bs << MyData.substr(i, 1); + } + } + //bs contains all rbsp bytes, now we can analyze them + std::cout << "seq_parameter_set_data()" << std::endl; + std::cout << std::hex << std::setfill('0') << std::setw(2); + char profileIdc = bs.get(8); + std::cout << "profile idc: " << (unsigned int) profileIdc << std::endl; + std::cout << "constraint_set0_flag: " << bs.get(1) << std::endl; + std::cout << "constraint_set1_flag: " << bs.get(1) << std::endl; + std::cout << "constraint_set2_flag: " << bs.get(1) << std::endl; + std::cout << "constraint_set3_flag: " << bs.get(1) << std::endl; + std::cout << "constraint_set4_flag: " << bs.get(1) << std::endl; + std::cout << "constraint_set5_flag: " << bs.get(1) << std::endl; + std::cout << "reserved_zero_2bits: " << bs.get(2) << std::endl; + std::cout << "level idc: " << bs.get(8) << std::endl; + std::cout << "seq_parameter_set_id: " << bs.getUExpGolomb() << std::endl; + if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244 || profileIdc == 44 || profileIdc == 83 || profileIdc == 86 || profileIdc == 118 || profileIdc == 128) { + chroma_format_idc = bs.getUExpGolomb(); + std::cout << "chroma_format_idc: " << chroma_format_idc << std::endl; + if (chroma_format_idc == 3) { + std::cout << "separate_colour_plane_flag" << bs.get(1) << std::endl; + } + std::cout << "bit_depth_luma_minus8: " << bs.getUExpGolomb() << std::endl; + std::cout << "bit_depth_chroma_minus8: " << bs.getUExpGolomb() << std::endl; + std::cout << "qpprime_y_zero_transform_bypass_flag: " << bs.get(1) << std::endl; + unsigned int seq_scaling_matrix_present_flag = bs.get(1); + std::cout << "seq_scaling_matrix_present_flag: " << seq_scaling_matrix_present_flag << std::endl; + if (seq_scaling_matrix_present_flag) { + for (unsigned int i = 0; i < ((chroma_format_idc != 3) ? 8 : 12) ; i++) { + unsigned int seq_scaling_list_present_flag = bs.get(1); + std::cout << "seq_scaling_list_present_flag: " << seq_scaling_list_present_flag << std::endl; + DEBUG_MSG(DLVL_DEVEL, "not implemented, ending"); + return; + if (seq_scaling_list_present_flag) { + //LevelScale4x4( m, i, j ) = weightScale4x4( i, j ) * normAdjust4x4( m, i, j ) + // + if (i < 6) { + //scaling)list(ScalingList4x4[i],16,UseDefaultScalingMatrix4x4Flag[i] + } else { + //scaling)list(ScalingList4x4[i-6],64,UseDefaultScalingMatrix4x4Flag[i-6] + + } + } + } + } + } + std::cout << "log2_max_frame_num_minus4: " << bs.getUExpGolomb() << std::endl; + unsigned int pic_order_cnt_type = bs.getUExpGolomb(); + std::cout << "pic_order_cnt_type: " << pic_order_cnt_type << std::endl; + if (pic_order_cnt_type == 0) { + std::cout << "log2_max_pic_order_cnt_lsb_minus4: " << bs.getUExpGolomb() << std::endl; + } else if (pic_order_cnt_type == 1) { + DEBUG_MSG(DLVL_DEVEL, "This part of the implementation is incomplete(2), to be continued. If this message is shown, contact developers immediately."); + return; + } + std::cout << "max_num_ref_frames: " << bs.getUExpGolomb() << std::endl; + std::cout << "gaps_in_frame_num_allowed_flag: " << bs.get(1) << std::endl; + std::cout << "pic_width_in_mbs_minus1: " << bs.getUExpGolomb() << std::endl; + std::cout << "pic_height_in_map_units_minus1: " << bs.getUExpGolomb() << std::endl; + unsigned int frame_mbs_only_flag = bs.get(1); + std::cout << "frame_mbs_only_flag: " << frame_mbs_only_flag << std::endl; + if (frame_mbs_only_flag == 0) { + std::cout << "mb_adaptive_frame_field_flag: " << bs.get(1) << std::endl; + } + std::cout << "direct_8x8_inference_flag: " << bs.get(1) << std::endl; + unsigned int frame_cropping_flag = bs.get(1); + std::cout << "frame_cropping_flag: " << frame_cropping_flag << std::endl; + if (frame_cropping_flag != 0) { + std::cout << "frame_crop_left_offset: " << bs.getUExpGolomb() << std::endl; + std::cout << "frame_crop_right_offset: " << bs.getUExpGolomb() << std::endl; + std::cout << "frame_crop_top_offset: " << bs.getUExpGolomb() << std::endl; + std::cout << "frame_crop_bottom_offset: " << bs.getUExpGolomb() << std::endl; + } + unsigned int vui_parameters_present_flag = bs.get(1); + std::cout << "vui_parameters_present_flag: " << vui_parameters_present_flag << std::endl; + if (vui_parameters_present_flag != 0) { + //vuiParameters + unsigned int aspect_ratio_info_present_flag = bs.get(1); + std::cout << "aspect_ratio_info_present_flag: " << aspect_ratio_info_present_flag << std::endl; + if (aspect_ratio_info_present_flag != 0) { + unsigned int aspect_ratio_idc = bs.get(8); + std::cout << "aspect_ratio_idc: " << aspect_ratio_idc << std::endl; + if (aspect_ratio_idc == 255) { + std::cout << "sar_width: " << bs.get(16) << std::endl; + std::cout << "sar_height: " << bs.get(16) << std::endl; + } + } + unsigned int overscan_info_present_flag = bs.get(1); + std::cout << "overscan_info_present_flag: " << overscan_info_present_flag << std::endl; + if (overscan_info_present_flag) { + std::cout << "overscan_appropriate_flag: " << bs.get(1) << std::endl; + } + + unsigned int video_signal_type_present_flag = bs.get(1); + std::cout << "video_signal_type_present_flag: " << video_signal_type_present_flag << std::endl; + if (video_signal_type_present_flag) { + std::cout << "video_format: " << bs.get(3) << std::endl; + std::cout << "video_full_range_flag: " << bs.get(1) << std::endl; + unsigned int colour_description_present_flag = bs.get(1); + std::cout << "colour_description_present_flag: " << colour_description_present_flag << std::endl; + if (colour_description_present_flag) { + std::cout << "colour_primaries: " << bs.get(8) << std::endl; + std::cout << "transfer_characteristics: " << bs.get(8) << std::endl; + std::cout << "matrix_coefficients: " << bs.get(8) << std::endl; + } + } + unsigned int chroma_loc_info_present_flag = bs.get(1); + std::cout << "chroma_loc_info_present_flag: " << chroma_loc_info_present_flag << std::endl; + if (chroma_loc_info_present_flag) { + std::cout << "chroma_sample_loc_type_top_field: " << bs.getUExpGolomb() << std::endl; + std::cout << "chroma_sample_loc_type_bottom_field: " << bs.getUExpGolomb() << std::endl; + } + unsigned int timing_info_present_flag = bs.get(1); + std::cout << "timing_info_present_flag: " << timing_info_present_flag << std::endl; + if (timing_info_present_flag) { + std::cout << "num_units_in_tick: " << bs.get(32) << std::endl; + std::cout << "time_scale: " << bs.get(32) << std::endl; + std::cout << "fixed_frame_rate_flag: " << bs.get(1) << std::endl; + } + unsigned int nal_hrd_parameters_present_flag = bs.get(1); + std::cout << "nal_hrd_parameters_present_flag: " << nal_hrd_parameters_present_flag << std::endl; + if (nal_hrd_parameters_present_flag) { + unsigned int cpb_cnt_minus1 = bs.getUExpGolomb(); + std::cout << "cpb_cnt_minus1: " << cpb_cnt_minus1 << std::endl; + std::cout << "bit_rate_scale: " << bs.get(4) << std::endl; + std::cout << "cpb_rate_scale: " << bs.get(4) << std::endl; + for (unsigned int ssi = 0; ssi <= cpb_cnt_minus1 ; ssi++) { + std::cout << "bit_rate_value_minus1[" << ssi << "]: " << bs.getUExpGolomb() << std::endl; + std::cout << "cpb_size_value_minus1[" << ssi << "]: " << bs.getUExpGolomb() << std::endl; + std::cout << "cbr_flag[" << ssi << "]: " << bs.get(1) << std::endl; + } + std::cout << "initial_cpb_removal_delay_length_minus1: " << bs.get(5) << std::endl; + std::cout << "cpb_removal_delay_length_minus1: " << bs.get(5) << std::endl; + std::cout << "dpb_output_delay_length_minus1: " << bs.get(5) << std::endl; + std::cout << "time_offset_length: " << bs.get(5) << std::endl; + } + unsigned int vcl_hrd_parameters_present_flag = bs.get(1); + std::cout << "vcl_hrd_parameters_present_flag: " << vcl_hrd_parameters_present_flag << std::endl; + if (vcl_hrd_parameters_present_flag) { + unsigned int cpb_cnt_minus1 = bs.getUExpGolomb(); + std::cout << "cpb_cnt_minus1: " << cpb_cnt_minus1 << std::endl; + std::cout << "bit_rate_scale: " << bs.get(4) << std::endl; + std::cout << "cpb_rate_scale: " << bs.get(4) << std::endl; + for (unsigned int ssi = 0; ssi <= cpb_cnt_minus1 ; ssi++) { + std::cout << "bit_rate_value_minus1[" << ssi << "]: " << bs.getUExpGolomb() << std::endl; + std::cout << "cpb_size_value_minus1[" << ssi << "]: " << bs.getUExpGolomb() << std::endl; + std::cout << "cbr_flag[" << ssi << "]: " << bs.get(1) << std::endl; + } + std::cout << "initial_cpb_removal_delay_length_minus1: " << bs.get(5) << std::endl; + std::cout << "cpb_removal_delay_length_minus1: " << bs.get(5) << std::endl; + std::cout << "dpb_output_delay_length_minus1: " << bs.get(5) << std::endl; + std::cout << "time_offset_length: " << bs.get(5) << std::endl; + } + if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) { + std::cout << "low_delay_hrd_flag: " << bs.get(1) << std::endl; + } + std::cout << "pic_struct_present_flag: " << bs.get(1) << std::endl; + unsigned int bitstream_restriction_flag = bs.get(1); + std::cout << "bitstream_restriction_flag: " << bitstream_restriction_flag << std::endl; + if (bitstream_restriction_flag) { + std::cout << "motion_vectors_over_pic_boundaries_flag: " << bs.get(1) << std::endl; + std::cout << "max_bytes_per_pic_denom: " << bs.getUExpGolomb() << std::endl; + std::cout << "max_bits_per_mb_denom: " << bs.getUExpGolomb() << std::endl; + std::cout << "log2_max_mv_length_horizontal: " << bs.getUExpGolomb() << std::endl; + std::cout << "log2_max_mv_length_vertical: " << bs.getUExpGolomb() << std::endl; + std::cout << "num_reorder_frames: " << bs.getUExpGolomb() << std::endl; + std::cout << "max_dec_frame_buffering: " << bs.getUExpGolomb() << std::endl; + } + } + std::cout << std::dec << std::endl; + //DEBUG_MSG(DLVL_DEVEL, "SPS analyser"); + } + + ///Prints the values of all the fields of a PPS nal unit in a human readable format. + ///\todo some features, including analyzable matrices, are not implemented. They were never encountered in practice, so far + void PPS::analyzePPS() { + if (Type() != 8) { + DEBUG_MSG(DLVL_DEVEL, "This is not a PPS, but type %d", Type()); + return; + } + Utils::bitstream bs; + //put rbsp bytes in mydata + for (unsigned int i = 1; i < MyData.size(); i++) { + if (i + 2 < MyData.size() && MyData.substr(i, 3) == std::string("\000\000\003", 3)) { + bs << MyData.substr(i, 2); + i += 2; + } else { + bs << MyData.substr(i, 1); + } + } + //bs contains all rbsp bytes, now we can analyze them + std::cout << "pic_parameter_set_id: " << bs.getUExpGolomb() << std::endl; + std::cout << "seq_parameter_set_id: " << bs.getUExpGolomb() << std::endl; + std::cout << "entropy_coding_mode_flag: " << bs.get(1) << std::endl; + std::cout << "bottom_field_pic_order_in_frame_present_flag: " << bs.get(1) << std::endl; + unsigned int num_slice_groups_minus1 = bs.getUExpGolomb(); + std::cout << "num_slice_groups_minus1: " << num_slice_groups_minus1 << std::endl; + if (num_slice_groups_minus1 > 0) { + unsigned int slice_group_map_type = bs.getUExpGolomb(); + std::cout << "slice_group_map_type: " << slice_group_map_type << std::endl; + if (slice_group_map_type == 0) { + for (unsigned int ig = 0; ig <= num_slice_groups_minus1; ig++) { + std::cout << "runlengthminus1[" << ig << "]: " << bs.getUExpGolomb() << std::endl; + } + } else if (slice_group_map_type == 2) { + for (unsigned int ig = 0; ig <= num_slice_groups_minus1; ig++) { + std::cout << "top_left[" << ig << "]: " << bs.getUExpGolomb() << std::endl; + std::cout << "bottom_right[" << ig << "]: " << bs.getUExpGolomb() << std::endl; + } + } else if (slice_group_map_type == 3 || slice_group_map_type == 4 || slice_group_map_type == 5) { + std::cout << "slice_group_change_direction_flag: " << bs.get(1) << std::endl; + std::cout << "slice_group_change_rate_minus1: " << bs.getUExpGolomb() << std::endl; + } else if (slice_group_map_type == 6) { + unsigned int pic_size_in_map_units_minus1 = bs.getUExpGolomb(); + std::cout << "pic_size_in_map_units_minus1: " << pic_size_in_map_units_minus1 << std::endl; + for (unsigned int i = 0; i <= pic_size_in_map_units_minus1; i++) { + std::cout << "slice_group_id[" << i << "]: " << bs.get((unsigned int)(ceil(log(num_slice_groups_minus1 + 1) / log(2)))) << std::endl; + } + } + } + std::cout << "num_ref_idx_l0_default_active_minus1: " << bs.getUExpGolomb() << std::endl; + std::cout << "num_ref_idx_l1_default_active_minus1: " << bs.getUExpGolomb() << std::endl; + std::cout << "weighted_pred_flag: " << bs.get(1) << std::endl; + std::cout << "weighted_bipred_idc: " << bs.get(2) << std::endl; + std::cout << "pic_init_qp_minus26: " << bs.getExpGolomb() << std::endl; + std::cout << "pic_init_qs_minus26: " << bs.getExpGolomb() << std::endl; + std::cout << "chroma_qp_index_offset: " << bs.getExpGolomb() << std::endl; + std::cout << "deblocking_filter_control_present_flag: " << bs.get(1) << std::endl; + std::cout << "constrained_intra_pred_flag: " << bs.get(1) << std::endl; + std::cout << "redundant_pic_cnt_present_flag: " << bs.get(1) << std::endl; + //check for more data + if (bs.size() == 0) { + return; + } + unsigned int transform_8x8_mode_flag = bs.get(1); + std::cout << "transform_8x8_mode_flag: " << transform_8x8_mode_flag << std::endl; + unsigned int pic_scaling_matrix_present_flag = bs.get(1); + std::cout << "pic_scaling_matrix_present_flag: " << pic_scaling_matrix_present_flag << std::endl; + if (pic_scaling_matrix_present_flag) { + for (unsigned int i = 0; i < 6 + ((chroma_format_idc != 3) ? 2 : 6)*transform_8x8_mode_flag ; i++) { + unsigned int pic_scaling_list_present_flag = bs.get(1); + std::cout << "pic_scaling_list_present_flag[" << i << "]: " << pic_scaling_list_present_flag << std::endl; + if (pic_scaling_list_present_flag) { + std::cout << "under development, pslpf" << std::endl; + return; + if (i < 6) { + //scaling list(ScalingList4x4[i],16,UseDefaultScalingMatrix4x4Flag[ i ]) + } else { + //scaling_list(ScalingList4x4[i],64,UseDefaultScalingMatrix4x4Flag[ i-6 ]) + } + } + } + } + std::cout << "second_chroma_qp_index_offset: " << bs.getExpGolomb() << std::endl; + } + +} + diff --git a/lib/nal.h b/lib/nal.h new file mode 100644 index 00000000..16403ce0 --- /dev/null +++ b/lib/nal.h @@ -0,0 +1,45 @@ +#include +#include + +namespace h264 { + + ///Struct containing pre-calculated metadata of an SPS nal unit. Width and height in pixels, fps in Hz + struct SPSMeta { + unsigned int width; + unsigned int height; + double fps; + }; + + ///Class for analyzing generic nal units + class NAL { + public: + NAL(); + NAL(std::string & InputData); + bool ReadData(std::string & InputData, bool raw = false); + std::string AnnexB(bool LongIntro = false); + std::string SizePrepended(); + int Type(); + std::string getData(); + protected: + unsigned int chroma_format_idc;/// +#include +#include +#include +#include +#include "bitstream.h" + +namespace OGG { + + oggSegment::oggSegment(){ + isKeyframe = 0; + frameNumber = 0; + timeStamp = 0; + framesSinceKeyFrame = 0; + } + + std::deque decodeXiphSize(char * data, size_t len){ + std::deque res; + res.push_back(0); + for (unsigned int i = 0; i < len; i++){ + *res.rbegin() += data[i]; + if (data[i] != 0xFF){ + res.push_back(0); + } + } + if (*res.rbegin() == 0){ + res.resize(res.size() - 1); + } + return res; + } + inline long long unsigned int get_64(char * data){ + long long unsigned int temp = 0; + for (int i = 7; i >= 0; --i){ + temp <<= 8; + temp += data[i]; + } + return temp; + } + + inline long unsigned int get_32(char * data){ + long unsigned int temp = 0; + for (int i = 3; i >= 0; --i){ + temp <<= 8; + temp += data[i]; + } + return temp; + } + + inline void set_64(char * data, long unsigned int val){ + for (int i = 0; i < 8; ++i){ + data[i] = val & 0xFF; + val >>= 8; + } + } + + + inline void set_32(char * data, long unsigned int val){ + for (int i = 0; i < 4; ++i){ + data[i] = val & 0xFF; + val >>= 8; + } + } + + + Page::Page(){ + framesSeen = 0; + lastKeyFrame = 0; + sampleRate = 0; + firstSample = 0; + pageSequenceNumber = 0; + totalFrames = 0; + memset(data, 0, 282); + } + + Page::Page(const Page & rhs){ + framesSeen=rhs.framesSeen; + pageSequenceNumber = rhs.pageSequenceNumber; + lastKeyFrame = rhs.lastKeyFrame; + sampleRate = rhs.sampleRate; + firstSample = rhs.firstSample; + totalFrames= rhs.totalFrames; + memcpy(data, rhs.data, 282); + segments = rhs.segments; + } + + void Page::operator = (const Page & rhs){ + framesSeen=rhs.framesSeen; + pageSequenceNumber = rhs.pageSequenceNumber; + lastKeyFrame = rhs.lastKeyFrame; + firstSample = rhs.firstSample; + sampleRate = rhs.sampleRate; + totalFrames= rhs.totalFrames; + memcpy(data, rhs.data, 282); + segments = rhs.segments; + } + + unsigned int Page::calcPayloadSize(){ + unsigned int retVal = 0; + for (unsigned int i = 0; i < segments.size(); i++){ + retVal += segments[i].size(); + } + return retVal; + } + + /// Reads an OGG Page from the source and if valid, removes it from source. + bool Page::read(std::string & newData){ + int len = newData.size(); + segments.clear(); + if (newData.size() < 27){ + return false; + } + if (newData.substr(0, 4) != "OggS"){ + DEBUG_MSG(DLVL_FAIL, "Invalid Ogg page encountered (magic number wrong: %s) - cannot continue", newData.c_str()); + return false; + } + memcpy(data, newData.c_str(), 27);//copying the header, always 27 bytes + if (newData.size() < 27u + getPageSegments()){ //check input size + return false; + } + newData.erase(0, 27); + memcpy(data + 27, newData.c_str(), getPageSegments()); + newData.erase(0, getPageSegments()); + std::deque segSizes = decodeXiphSize(data + 27, getPageSegments()); + for (std::deque::iterator it = segSizes.begin(); it != segSizes.end(); it++){ + segments.push_back(std::string(newData.data(), *it)); + newData.erase(0, *it); + } + INFO_MSG("Erased %lu bytes from the input", len - newData.size()); + return true; + } + + + bool Page::read(FILE * inFile){ + segments.clear(); + int oriPos = ftell(inFile); + //INFO_MSG("pos: %d", oriPos); + if (!fread(data, 27, 1, inFile)){ + fseek(inFile, oriPos, SEEK_SET); + FAIL_MSG("failed to fread(data, 27, 1, inFile) @ pos %d", oriPos); + return false; + } + if (std::string(data, 4) != "OggS"){ + DEBUG_MSG(DLVL_FAIL, "Invalid Ogg page encountered (magic number wrong: %s) - cannot continue bytePos %d", data, oriPos); + return false; + } + if (!fread(data + 27, getPageSegments(), 1, inFile)){ + fseek(inFile, oriPos, SEEK_SET); + FAIL_MSG("failed to fread(data + 27, getPageSegments() %d, 1, inFile) @ pos %d", getPageSegments(), oriPos); + return false; + } + std::deque segSizes = decodeXiphSize(data + 27, getPageSegments()); + for (std::deque::iterator it = segSizes.begin(); it != segSizes.end(); it++){ + if (*it){ + char * thisSeg = (char *)malloc(*it * sizeof(char)); + if (!thisSeg){ + DEBUG_MSG(DLVL_WARN, "malloc failed"); + } + if (!fread(thisSeg, *it, 1, inFile)){ + DEBUG_MSG(DLVL_WARN, "Unable to read a segment @ pos %d segment size: %d getPageSegments: %d", oriPos, *it, getPageSegments()); + fseek(inFile, oriPos, SEEK_SET); + return false; + } + segments.push_back(std::string(thisSeg, *it)); + free(thisSeg); + }else{ + segments.push_back(std::string("", 0)); + } + + } + + return true; + } + + bool Page::getSegment(unsigned int index, std::string & ret){ + if (index >= segments.size()){ + ret.clear(); + return false; + } + ret = segments[index]; + return true; + } + + const char * Page::getSegment(unsigned int index){ + if (index >= segments.size()){ + return 0; + } + return segments[index].data(); + } + + unsigned long Page::getSegmentLen(unsigned int index){ + if (index >= segments.size()){ + return 0; + } + return segments[index].size(); + } + + void Page::setMagicNumber(){ + memcpy(data, "OggS", 4); + } + + char Page::getVersion(){ + return data[4]; + } + + void Page::setVersion(char newVal){ + data[4] = newVal; + } + + char Page::getHeaderType(){ + return data[5]; + } + + void Page::setHeaderType(char newVal){ + data[5] = newVal; + } + + long long unsigned int Page::getGranulePosition(){ + return get_64(data + 6); + } + + void Page::setGranulePosition(long long unsigned int newVal){ + set_64(data + 6, newVal); + } + + long unsigned int Page::getBitstreamSerialNumber(){ + //return ntohl(((long unsigned int*)(data+14))[0]); + return get_32(data + 14); + } + + void Page::setBitstreamSerialNumber(long unsigned int newVal){ + set_32(data + 14, newVal); + } + + long unsigned int Page::getPageSequenceNumber(){ + return get_32(data + 18); + } + + void Page::setPageSequenceNumber(long unsigned int newVal){ + set_32(data + 18, newVal); + } + + long unsigned int Page::getCRCChecksum(){ + return get_32(data + 22); + } + + void Page::setCRCChecksum(long unsigned int newVal){ + set_32(data + 22, newVal); + } + + char Page::getPageSegments(){ + return data[26]; + } + + inline void Page::setPageSegments(char newVal){ + data[26] = newVal; + } + + bool Page::verifyChecksum(){ + if (getCRCChecksum() == calcChecksum()){ //NOTE: calcChecksum is currently not functional (it will always return 0) + return true; + } else { + return false; + } + } + + bool Page::possiblyContinued(){ + if (getPageSegments() == 255){ + if (data[281] == 255){ + return true; + } + } + return false; + } + + std::string Page::toPrettyString(size_t indent){ + std::stringstream r; + r << std::string(indent, ' ') << "Ogg page" << std::endl; + r << std::string(indent + 2, ' ') << "Version: " << (int)getVersion() << std::endl; + r << std::string(indent + 2, ' ') << "Header type:"; + if (!getHeaderType()){ + r << " Normal"; + } else { + if (getHeaderType() & Continued){ + r << " Continued"; + } + if (getHeaderType() & BeginOfStream){ + r << " BeginOfStream"; + } + if (getHeaderType() & EndOfStream){ + r << " EndOfStream"; + } + } + r << " (" << (int)getHeaderType() << ")" << std::endl; + r << std::string(indent + 2, ' ') << "Granule position: " << std::hex << std::setw(16) << std::setfill('0') << getGranulePosition() << std::dec << std::endl; + r << std::string(indent + 2, ' ') << "Bitstream number: " << getBitstreamSerialNumber() << std::endl; + r << std::string(indent + 2, ' ') << "Sequence number: " << getPageSequenceNumber() << std::endl; + r << std::string(indent + 2, ' ') << "Checksum Correct: " << verifyChecksum() << std::endl; + //r << " Calced Checksum: " << std::hex << calcChecksum() << std::dec << std::endl; + r << std::string(indent + 2, ' ') << "Checksum: " << getCRCChecksum() << std::endl; + r << std::string(indent + 2, ' ') << (int)getPageSegments() << " segments:" << std::endl; + r << std::string(indent + 3, ' '); + for (unsigned int i = 0; i < segments.size(); i++){ + r << " " << segments[i].size(); + } + r << std::endl; + return r.str(); + } + + inline unsigned int crc32(unsigned int crc, const char * data, size_t len){ + static const unsigned int table[256] = { + 0x00000000U, 0x04C11DB7U, 0x09823B6EU, 0x0D4326D9U, + 0x130476DCU, 0x17C56B6BU, 0x1A864DB2U, 0x1E475005U, + 0x2608EDB8U, 0x22C9F00FU, 0x2F8AD6D6U, 0x2B4BCB61U, + 0x350C9B64U, 0x31CD86D3U, 0x3C8EA00AU, 0x384FBDBDU, + 0x4C11DB70U, 0x48D0C6C7U, 0x4593E01EU, 0x4152FDA9U, + 0x5F15ADACU, 0x5BD4B01BU, 0x569796C2U, 0x52568B75U, + 0x6A1936C8U, 0x6ED82B7FU, 0x639B0DA6U, 0x675A1011U, + 0x791D4014U, 0x7DDC5DA3U, 0x709F7B7AU, 0x745E66CDU, + 0x9823B6E0U, 0x9CE2AB57U, 0x91A18D8EU, 0x95609039U, + 0x8B27C03CU, 0x8FE6DD8BU, 0x82A5FB52U, 0x8664E6E5U, + 0xBE2B5B58U, 0xBAEA46EFU, 0xB7A96036U, 0xB3687D81U, + 0xAD2F2D84U, 0xA9EE3033U, 0xA4AD16EAU, 0xA06C0B5DU, + 0xD4326D90U, 0xD0F37027U, 0xDDB056FEU, 0xD9714B49U, + 0xC7361B4CU, 0xC3F706FBU, 0xCEB42022U, 0xCA753D95U, + 0xF23A8028U, 0xF6FB9D9FU, 0xFBB8BB46U, 0xFF79A6F1U, + 0xE13EF6F4U, 0xE5FFEB43U, 0xE8BCCD9AU, 0xEC7DD02DU, + 0x34867077U, 0x30476DC0U, 0x3D044B19U, 0x39C556AEU, + 0x278206ABU, 0x23431B1CU, 0x2E003DC5U, 0x2AC12072U, + 0x128E9DCFU, 0x164F8078U, 0x1B0CA6A1U, 0x1FCDBB16U, + 0x018AEB13U, 0x054BF6A4U, 0x0808D07DU, 0x0CC9CDCAU, + 0x7897AB07U, 0x7C56B6B0U, 0x71159069U, 0x75D48DDEU, + 0x6B93DDDBU, 0x6F52C06CU, 0x6211E6B5U, 0x66D0FB02U, + 0x5E9F46BFU, 0x5A5E5B08U, 0x571D7DD1U, 0x53DC6066U, + 0x4D9B3063U, 0x495A2DD4U, 0x44190B0DU, 0x40D816BAU, + 0xACA5C697U, 0xA864DB20U, 0xA527FDF9U, 0xA1E6E04EU, + 0xBFA1B04BU, 0xBB60ADFCU, 0xB6238B25U, 0xB2E29692U, + 0x8AAD2B2FU, 0x8E6C3698U, 0x832F1041U, 0x87EE0DF6U, + 0x99A95DF3U, 0x9D684044U, 0x902B669DU, 0x94EA7B2AU, + 0xE0B41DE7U, 0xE4750050U, 0xE9362689U, 0xEDF73B3EU, + 0xF3B06B3BU, 0xF771768CU, 0xFA325055U, 0xFEF34DE2U, + 0xC6BCF05FU, 0xC27DEDE8U, 0xCF3ECB31U, 0xCBFFD686U, + 0xD5B88683U, 0xD1799B34U, 0xDC3ABDEDU, 0xD8FBA05AU, + 0x690CE0EEU, 0x6DCDFD59U, 0x608EDB80U, 0x644FC637U, + 0x7A089632U, 0x7EC98B85U, 0x738AAD5CU, 0x774BB0EBU, + 0x4F040D56U, 0x4BC510E1U, 0x46863638U, 0x42472B8FU, + 0x5C007B8AU, 0x58C1663DU, 0x558240E4U, 0x51435D53U, + 0x251D3B9EU, 0x21DC2629U, 0x2C9F00F0U, 0x285E1D47U, + 0x36194D42U, 0x32D850F5U, 0x3F9B762CU, 0x3B5A6B9BU, + 0x0315D626U, 0x07D4CB91U, 0x0A97ED48U, 0x0E56F0FFU, + 0x1011A0FAU, 0x14D0BD4DU, 0x19939B94U, 0x1D528623U, + 0xF12F560EU, 0xF5EE4BB9U, 0xF8AD6D60U, 0xFC6C70D7U, + 0xE22B20D2U, 0xE6EA3D65U, 0xEBA91BBCU, 0xEF68060BU, + 0xD727BBB6U, 0xD3E6A601U, 0xDEA580D8U, 0xDA649D6FU, + 0xC423CD6AU, 0xC0E2D0DDU, 0xCDA1F604U, 0xC960EBB3U, + 0xBD3E8D7EU, 0xB9FF90C9U, 0xB4BCB610U, 0xB07DABA7U, + 0xAE3AFBA2U, 0xAAFBE615U, 0xA7B8C0CCU, 0xA379DD7BU, + 0x9B3660C6U, 0x9FF77D71U, 0x92B45BA8U, 0x9675461FU, + 0x8832161AU, 0x8CF30BADU, 0x81B02D74U, 0x857130C3U, + 0x5D8A9099U, 0x594B8D2EU, 0x5408ABF7U, 0x50C9B640U, + 0x4E8EE645U, 0x4A4FFBF2U, 0x470CDD2BU, 0x43CDC09CU, + 0x7B827D21U, 0x7F436096U, 0x7200464FU, 0x76C15BF8U, + 0x68860BFDU, 0x6C47164AU, 0x61043093U, 0x65C52D24U, + 0x119B4BE9U, 0x155A565EU, 0x18197087U, 0x1CD86D30U, + 0x029F3D35U, 0x065E2082U, 0x0B1D065BU, 0x0FDC1BECU, + 0x3793A651U, 0x3352BBE6U, 0x3E119D3FU, 0x3AD08088U, + 0x2497D08DU, 0x2056CD3AU, 0x2D15EBE3U, 0x29D4F654U, + 0xC5A92679U, 0xC1683BCEU, 0xCC2B1D17U, 0xC8EA00A0U, + 0xD6AD50A5U, 0xD26C4D12U, 0xDF2F6BCBU, 0xDBEE767CU, + 0xE3A1CBC1U, 0xE760D676U, 0xEA23F0AFU, 0xEEE2ED18U, + 0xF0A5BD1DU, 0xF464A0AAU, 0xF9278673U, 0xFDE69BC4U, + 0x89B8FD09U, 0x8D79E0BEU, 0x803AC667U, 0x84FBDBD0U, + 0x9ABC8BD5U, 0x9E7D9662U, 0x933EB0BBU, 0x97FFAD0CU, + 0xAFB010B1U, 0xAB710D06U, 0xA6322BDFU, 0xA2F33668U, + 0xBCB4666DU, 0xB8757BDAU, 0xB5365D03U, 0xB1F740B4U, + }; + + while (len > 0){ + crc = table[*data ^ ((crc >> 24) & 0xff)] ^ (crc << 8); + data++; + len--; + } + return crc; + } + + long unsigned int Page::calcChecksum(){ //implement in sending out page, probably delete this -- probably don't delete this because this function appears to be in use + long unsigned int retVal = 0; + /* + long unsigned int oldChecksum = getCRCChecksum(); + std::string fullPayload; + for (int i = 0; i < segments.size(); i++){ + fullPayload += segments[i]; + } + setCRCChecksum (0); + retVal = crc32( + crc32(0, data, 27 + getPageSegments()),//checksum over pageheader + fullPayload.data(), + fullPayload.size() + );//checksum over content + setCRCChecksum (oldChecksum); + */ + return retVal; + } + + int Page::getPayloadSize(){ + size_t res = 0; + for (unsigned int i = 0; i < segments.size(); i++){ + res += segments[i].size(); + } + return res; + } + + const std::deque & Page::getAllSegments(){ + return segments; + } + + void Page::prepareNext(bool continueMe){ + clear(0, -1, getBitstreamSerialNumber(), getPageSequenceNumber() + 1); + if (continueMe){ //noting that the page will be continued + setHeaderType(OGG::Continued); + } + } + + void Page::clear(char HeaderType, long long unsigned int GranPos, long unsigned int BSN, long unsigned int PSN){ + memset(data, 0, 27); + setMagicNumber(); + setVersion(); + setHeaderType(HeaderType); + setGranulePosition(GranPos); + setBitstreamSerialNumber(BSN); + setPageSequenceNumber(PSN); + } + + + unsigned int Page::addSegment(const std::string & payload){ //returns added bytes + segments.push_back(payload); + return payload.size(); + } + + unsigned int Page::addSegment(const char * content, unsigned int length){ + segments.push_back(std::string(content, length)); + return length; + } + + unsigned int Page::overFlow(){ //returns the amount of bytes that don't fit in this page from the segments; + unsigned int retVal = 0; + unsigned int curSegNum = 0;//the current segment number we are looking at + unsigned int segAmount = 0; + for (unsigned int i = 0; i < segments.size(); i++){ + segAmount = (segments[i].size() / 255) + 1; + if (segAmount + curSegNum > 255){ + retVal += ((segAmount - (255 - curSegNum + 1)) * 255) + (segments[i].size() % 255);//calculate the extra bytes that overflow + curSegNum = 255;//making sure the segment numbers are at maximum + } else { + curSegNum += segAmount; + } + } + return retVal; + } + + void Page::vorbisStuff(){ + Utils::bitstreamLSBF packet; + packet.append(oggSegments.rbegin()->dataString);//this is a heavy operation, it needs to be optimised //todo? + int curPCMSamples = 0; + long long unsigned int packetType = packet.get(1); + if (packetType == 0){ + int tempModes = vorbis::ilog(vorbisModes.size() - 1); + int tempPacket = packet.get(tempModes); + int curBlockFlag = vorbisModes[tempPacket].blockFlag; + curPCMSamples = (1 << blockSize[curBlockFlag]); + if (prevBlockFlag != -1){ + if (curBlockFlag == prevBlockFlag){ + curPCMSamples /= 2; + } else { + curPCMSamples -= (1 << blockSize[0]) / 4 + (1 << blockSize[1]) / 4; + } + } + prevBlockFlag = curBlockFlag; + } else { + ERROR_MSG("Error, Vorbis packet type !=0 actual type: %llu",packetType ); + } + //add to granule position + lastKeyFrame += curPCMSamples; + oggSegments.rbegin()->lastKeyFrameSeen = lastKeyFrame; + } + + ///this calculates the granule position for a DTSC packet + long long unsigned int Page::calculateGranule(oggSegment & currentSegment){ + long long unsigned int tempGranule = 0; + if (codec == OGG::THEORA){ + tempGranule = (currentSegment.lastKeyFrameSeen << split) | currentSegment.framesSinceKeyFrame; + } else if (codec == OGG::VORBIS){ + tempGranule = currentSegment.lastKeyFrameSeen; + } + return tempGranule; + } + + + bool Page::shouldSend(){ + unsigned int totalSegmentSize = 0; + if (!oggSegments.size()){ + return false; + } + if (oggSegments.rbegin()->isKeyframe){ + return true; + } + if (codec == OGG::VORBIS){ + if (lastKeyFrame - firstSample >= sampleRate){ + return true; + } + } + + for (unsigned int i = 0; i < oggSegments.size(); i++){ + totalSegmentSize += (oggSegments[i].dataString.size() / 255) + 1; + } + if (totalSegmentSize >= 255) return true; + + return false; + } + + ///\todo Rewrite this + void Page::sendTo(Socket::Connection & destination, int calcGranule){ //combines all data and sends it to socket + if (!oggSegments.size()){ + DEBUG_MSG(DLVL_HIGH, "!segments.size()"); + return; + } + if (codec == OGG::VORBIS){ + firstSample = lastKeyFrame; + } + int temp = 0; + long unsigned int checksum = 0; //reset checksum + setCRCChecksum(0); + unsigned int numSegments = oggSegments.size(); + int tableIndex = 0; + char tableSize = 0; + //char table[255]; + char * table = (char *)malloc(255); + unsigned int bytesLeft = 0; + for (unsigned int i = 0; i < numSegments; i++){ + //calculate amount of 255 bytes needed to store size (remainder not counted) + temp = (oggSegments[i].dataString.size() / 255); + //if everything still fits in the table + if ((temp + tableIndex + 1) <= 255){ + //set the 255 bytes + memset(table + tableIndex, 255, temp); + //increment tableIndex with 255 byte count + tableIndex += temp; + //set the last table entry to the remainder, and increase tableIndex with one + table[tableIndex++] = (oggSegments[i].dataString.size() % 255); + //update tableSize to be equal to tableIndex + tableSize = tableIndex; + bytesLeft = 0; + } else { + //stuff doesn't fit + //set the rest of the table to 255s + memset(table + tableIndex, 255, (255 - tableIndex)); + //table size is full table in use + tableSize = 255; + //space left on current page, for this segment: (255-tableIndex)*255 + bytesLeft = (255 - tableIndex) * 255; + if (oggSegments[i].dataString.size() == bytesLeft){ + bytesLeft = 0; //segment barely fits. + } + break; + } + } + + if (calcGranule < -1){ + if (numSegments == 1 && bytesLeft){ //no segment ends on this page. + granules = -1; + } else { + unsigned int tempIndex = numSegments - 1; + if (bytesLeft != 0){ + tempIndex = numSegments - 2; + } + granules = calculateGranule(oggSegments[tempIndex]); + } + } else { + granules = calcGranule; //force granule + } + setGranulePosition(granules); + + checksum = crc32(checksum, data, 22);//calculating the checksum over the first part of the page + checksum = crc32(checksum, &tableSize, 1); //calculating the checksum over the segment Table Size + checksum = crc32(checksum, table, tableSize);//calculating the checksum over the segment Table + + DEBUG_MSG(DLVL_DONTEVEN, "numSegments: %d", numSegments); + + for (unsigned int i = 0; i < numSegments; i++){ + //INFO_MSG("checksum, i: %d", i); + if (bytesLeft != 0 && ((i + 1) == numSegments)){ + checksum = crc32(checksum, oggSegments[i].dataString.data(), bytesLeft); + //take only part of this segment + } else { //take the entire segment + checksum = crc32(checksum, oggSegments[i].dataString.data(), oggSegments[i].dataString.size()); + } + } + + setCRCChecksum(checksum); + + destination.SendNow(data, 26); + destination.SendNow(&tableSize, 1); + destination.SendNow(table, tableSize); + + + for (unsigned int i = 0; i < numSegments; i++){ + if (bytesLeft != 0 && ((i + 1) == numSegments)){ + destination.SendNow(oggSegments.begin()->dataString.data(), bytesLeft); + oggSegments.begin()->dataString.erase(0, bytesLeft); + setHeaderType(OGG::Continued);//set continuation flag + break; + //for loop WILL exit after this. + } else { + destination.SendNow(oggSegments.begin()->dataString.data(), oggSegments.begin()->dataString.size()); + //this segment WILL be deleted + oggSegments.erase(oggSegments.begin()); + setHeaderType(OGG::Plain);//not a continuation + } + + } + + //done sending, assume start of new page. + //inc. page num, write to header + pageSequenceNumber++; + setPageSequenceNumber(pageSequenceNumber); + //granule still requires setting! + free(table); + return; + + + } +} + + + + diff --git a/lib/ogg.h b/lib/ogg.h new file mode 100644 index 00000000..e5a7f139 --- /dev/null +++ b/lib/ogg.h @@ -0,0 +1,141 @@ +#pragma once +#include +#include +#include +#include +#include +#include "dtsc.h" +#include "theora.h" +#include "vorbis.h" +#include "socket.h" + +namespace OGG { + + class oggSegment { + public: + oggSegment(); + std::string dataString; + int isKeyframe; + long long unsigned int lastKeyFrameSeen; + long long unsigned int framesSinceKeyFrame; + unsigned int frameNumber; + long long unsigned int timeStamp; + }; + + enum oggCodec {THEORA, VORBIS, OPUS}; + + enum HeaderType { + Plain = 0, + Continued = 1, + BeginOfStream = 2, + EndOfStream = 4 + }; + + std::deque decodeXiphSize(char * data, size_t len); + + + + + class Page { + public: + Page(); + Page(const Page & rhs); + void operator = (const Page & rhs); + bool read(std::string & newData); + bool read(FILE * inFile); + bool getSegment(unsigned int index, std::string & ret); + const char * getSegment(unsigned int index); + unsigned long getSegmentLen(unsigned int index); + void setMagicNumber(); + char getVersion(); + void setVersion(char newVal = 0); + char getHeaderType(); + void setHeaderType(char newVal); + long long unsigned int getGranulePosition(); + void setGranulePosition(long long unsigned int newVal); + long unsigned int getBitstreamSerialNumber(); + void setBitstreamSerialNumber(long unsigned int newVal); + long unsigned int getCRCChecksum(); + void setCRCChecksum(long unsigned int newVal); + long unsigned int getPageSequenceNumber(); + void setPageSequenceNumber(long unsigned int newVal); + char getPageSegments();//get the amount of page segments + inline void setPageSegments(char newVal);//set the amount of page segments + int getPayloadSize(); + const std::deque & getAllSegments(); + + bool possiblyContinued(); + + std::string toPrettyString(size_t indent = 0); + + long unsigned int calcChecksum(); + bool verifyChecksum(); + unsigned int calcPayloadSize(); + //void clear(); + void clear(char HeaderType, long long unsigned int GranPos, long unsigned int BSN, long unsigned int PSN); + void prepareNext(bool continueMe = false);//prepare the page to become the next page + bool setPayload(char * newData, unsigned int length); //probably obsolete + unsigned int addSegment(const std::string & content); //add a segment to the page, returns added bytes + unsigned int addSegment(const char * content, unsigned int length); //add a segment to the page, returns added bytes + void sendTo(Socket::Connection & destination, int calcGranule = -2); //combines all data and sends it to socket + unsigned int setNextSegmentTableEntry(unsigned int entrySize);//returns the size that could not be added to the table + unsigned int overFlow();//returns the amount of bytes that don't fit in this page from the segments; + + long long unsigned int calculateGranule(oggSegment & currentSegment); + bool shouldSend(); + void vorbisStuff();//process most recent segment + long long unsigned int totalFrames; + int granules; + OGG::oggCodec codec; + std::deque oggSegments; //used for ogg output + unsigned int pageSequenceNumber; + + unsigned int framesSeen; + unsigned int lastKeyFrame; + unsigned int firstSample;//for vorbis, to handle "when to send the page" + unsigned int sampleRate;//for vorbis, to handle the sampleRate + int prevBlockFlag; + char blockSize[2]; + std::deque vorbisModes;//modes for vorbis + unsigned int split; //KFGShift for theora + private: + char data[282];//Fulldata + std::deque segments; + + + }; + + class oggTrack { + public: + oggTrack() : KFGShift(0), lastTime(0), parsedHeaders(false), lastPageOffset(0), nxtSegment(0){ } + oggCodec codec; + std::string name; + std::string contBuffer;//buffer for continuing pages + long long unsigned int dtscID; + char KFGShift; + double lastTime; + long long unsigned int lastGran; + bool parsedHeaders; + long long unsigned int lastPageOffset; + unsigned int nxtSegment; + double msPerFrame; + Page myPage; + //Codec specific elements + //theora + // theora::header idHeader;//needed to determine keyframe //bullshit? + //vorbis + std::deque vModes; + char channels; + long long unsigned int blockSize[2]; + //unsigned long getBlockSize(unsigned int vModeIndex); + }; + + class headerPages { + public: + std::map DTSCID2OGGSerial; + std::map DTSCID2seqNum; + std::string parsedPages; + }; +} + + diff --git a/lib/procs.cpp b/lib/procs.cpp new file mode 100644 index 00000000..8bec4a33 --- /dev/null +++ b/lib/procs.cpp @@ -0,0 +1,737 @@ +/// \file procs.cpp +/// Contains generic functions for managing processes. + +#include "procs.h" +#include "defines.h" +#include +#include +#include + +#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__MACH__) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "timing.h" + +std::map Util::Procs::plist; +std::map Util::Procs::exitHandlers; +bool Util::Procs::handler_set = false; + + + +static bool childRunning(pid_t p) { + pid_t ret = waitpid(p, 0, WNOHANG); + if (ret == p) { + return false; + } + if (ret < 0 && errno == EINTR) { + return childRunning(p); + } + return !kill(p, 0); +} + +/// sends sig 0 to process (pid). returns true if process is running +bool Util::Procs::isRunnning(pid_t pid){ + return !kill(pid, 0); +} + +/// Called at exit of any program that used a Start* function. +/// Waits up to 1 second, then sends SIGINT signal to all managed processes. +/// After that waits up to 5 seconds for children to exit, then sends SIGKILL to +/// all remaining children. Waits one more second for cleanup to finish, then exits. +void Util::Procs::exit_handler() { + int waiting = 0; + std::map listcopy = plist; + std::map::iterator it; + if (listcopy.empty()) { + return; + } + + //wait up to 0.5 second for applications to shut down + while (!listcopy.empty() && waiting <= 25) { + for (it = listcopy.begin(); it != listcopy.end(); it++) { + if (!childRunning((*it).first)) { + listcopy.erase(it); + break; + } + if (!listcopy.empty()) { + Util::sleep(20); + ++waiting; + } + } + } + if (listcopy.empty()) { + return; + } + + DEBUG_MSG(DLVL_DEVEL, "Sending SIGINT to remaining %d children", (int)listcopy.size()); + //send sigint to all remaining + if (!listcopy.empty()) { + for (it = listcopy.begin(); it != listcopy.end(); it++) { + DEBUG_MSG(DLVL_DEVEL, "SIGINT %d: %s", (*it).first, (*it).second.c_str()); + kill((*it).first, SIGINT); + } + } + + DEBUG_MSG(DLVL_DEVEL, "Waiting up to 5 seconds for %d children to terminate.", (int)listcopy.size()); + waiting = 0; + //wait up to 5 seconds for applications to shut down + while (!listcopy.empty() && waiting <= 250) { + for (it = listcopy.begin(); it != listcopy.end(); it++) { + if (!childRunning((*it).first)) { + listcopy.erase(it); + break; + } + if (!listcopy.empty()) { + Util::sleep(20); + ++waiting; + } + } + } + if (listcopy.empty()) { + return; + } + + DEBUG_MSG(DLVL_DEVEL, "Sending SIGKILL to remaining %d children", (int)listcopy.size()); + //send sigkill to all remaining + if (!listcopy.empty()) { + for (it = listcopy.begin(); it != listcopy.end(); it++) { + DEBUG_MSG(DLVL_DEVEL, "SIGKILL %d: %s", (*it).first, (*it).second.c_str()); + kill((*it).first, SIGKILL); + } + } + + DEBUG_MSG(DLVL_DEVEL, "Waiting up to a second for %d children to terminate.", (int)listcopy.size()); + waiting = 0; + //wait up to 1 second for applications to shut down + while (!listcopy.empty() && waiting <= 50) { + for (it = listcopy.begin(); it != listcopy.end(); it++) { + if (!childRunning((*it).first)) { + listcopy.erase(it); + break; + } + if (!listcopy.empty()) { + Util::sleep(20); + ++waiting; + } + } + } + if (listcopy.empty()) { + return; + } + DEBUG_MSG(DLVL_DEVEL, "Giving up with %d children left.", (int)listcopy.size()); + +} + +/// Sets up exit and childsig handlers. +/// Called by every Start* function. +void Util::Procs::setHandler() { + if (!handler_set) { + struct sigaction new_action; + new_action.sa_handler = childsig_handler; + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = 0; + sigaction(SIGCHLD, &new_action, NULL); + atexit(exit_handler); + handler_set = true; + } +} + + +/// Used internally to capture child signals and update plist. +void Util::Procs::childsig_handler(int signum) { + if (signum != SIGCHLD) { + return; + } + int status; + pid_t ret = -1; + while (ret != 0) { + ret = waitpid(-1, &status, WNOHANG); + if (ret <= 0) { //ignore, would block otherwise + if (ret == 0 || errno != EINTR) { + return; + } + continue; + } + int exitcode; + if (WIFEXITED(status)) { + exitcode = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + exitcode = -WTERMSIG(status); + } else { // not possible + return; + } + +#if DEBUG >= DLVL_HIGH + std::string pname = plist[ret]; +#endif + plist.erase(ret); +#if DEBUG >= DLVL_HIGH + if (!isActive(pname)) { + DEBUG_MSG(DLVL_HIGH, "Process %s fully terminated", pname.c_str()); + } else { + DEBUG_MSG(DLVL_HIGH, "Child process %d exited", ret); + } +#endif + + if (exitHandlers.count(ret) > 0) { + TerminationNotifier tn = exitHandlers[ret]; + exitHandlers.erase(ret); + tn(ret, exitcode); + } + } +} + + +/// Runs the given command and returns the stdout output as a string. +std::string Util::Procs::getOutputOf(char * const * argv) { + std::string ret; + int fin = 0, fout = -1, ferr = 0; + StartPiped("output_getter", argv, &fin, &fout, &ferr); + while (isActive("output_getter")) { + Util::sleep(100); + } + FILE * outFile = fdopen(fout, "r"); + char * fileBuf = 0; + size_t fileBufLen = 0; + while (!(feof(outFile) || ferror(outFile)) && (getline(&fileBuf, &fileBufLen, outFile) != -1)) { + ret += fileBuf; + } + fclose(outFile); + free(fileBuf); + return ret; +} + +/// Runs the given command and returns the stdout output as a string. +std::string Util::Procs::getOutputOf(std::string cmd) { + std::string ret; + int fin = 0, fout = -1, ferr = 0; + StartPiped("output_getter", cmd, &fin, &fout, &ferr); + while (isActive("output_getter")) { + Util::sleep(100); + } + FILE * outFile = fdopen(fout, "r"); + char * fileBuf = 0; + size_t fileBufLen = 0; + while (!(feof(outFile) || ferror(outFile)) && (getline(&fileBuf, &fileBufLen, outFile) != -1)) { + ret += fileBuf; + } + free(fileBuf); + fclose(outFile); + return ret; +} + + +/// 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); + DEBUG_MSG(DLVL_ERROR, "Error running %s: %s", cmd.c_str(), strerror(errno)); + _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); + } + setHandler(); + pid_t ret = fork(); + if (ret == 0) { + runCmd(cmd); + } else { + if (ret > 0) { + DEBUG_MSG(DLVL_HIGH, "Process %s started, PID %d: %s", name.c_str(), ret, cmd.c_str()); + plist.insert(std::pair(ret, name)); + } else { + DEBUG_MSG(DLVL_ERROR, "Process %s could not be started: fork() failed", name.c_str()); + 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); + } + setHandler(); + int pfildes[2]; + if (pipe(pfildes) == -1) { + DEBUG_MSG(DLVL_ERROR, "Process %s could not be started. Pipe creation failed.", name.c_str()); + 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(ret, name)); + } else { + DEBUG_MSG(DLVL_ERROR, "Process %s could not be started. fork() failed.", name.c_str()); + 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) { + DEBUG_MSG(DLVL_HIGH, "Process %s started, PIDs (%d, %d): %s | %s", name.c_str(), ret, ret2, cmd.c_str(), cmd2.c_str()); + plist.insert(std::pair(ret2, name)); + } else { + DEBUG_MSG(DLVL_ERROR, "Process %s could not be started. fork() failed.", name.c_str()); + 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); + } + setHandler(); + int pfildes[2]; + int pfildes2[2]; + if (pipe(pfildes) == -1) { + DEBUG_MSG(DLVL_ERROR, "Process %s could not be started. Pipe creation failed.", name.c_str()); + return 0; + } + if (pipe(pfildes2) == -1) { + DEBUG_MSG(DLVL_ERROR, "Process %s could not be started. Pipe creation failed.", name.c_str()); + 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(ret, name)); + } else { + DEBUG_MSG(DLVL_ERROR, "Process %s could not be started. fork() failed.", name.c_str()); + 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) { + DEBUG_MSG(DLVL_HIGH, "Process %s started, PIDs (%d, %d): %s | %s", name.c_str(), ret, ret2, cmd.c_str(), cmd2.c_str()); + plist.insert(std::pair(ret2, name)); + } else { + DEBUG_MSG(DLVL_ERROR, "Process %s could not be started. fork() failed.", name.c_str()); + 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) { + DEBUG_MSG(DLVL_HIGH, "Process %s started, PIDs (%d, %d, %d): %s | %s | %s", name.c_str(), ret, ret2, ret3, cmd.c_str(), cmd2.c_str(), cmd3.c_str()); + plist.insert(std::pair(ret3, name)); + } else { + DEBUG_MSG(DLVL_ERROR, "Process %s could not be started. fork() failed.", name.c_str()); + Stop(name); + close(pfildes[1]); + close(pfildes[0]); + close(pfildes2[1]); + close(pfildes2[0]); + return 0; + } + } + + return ret3; +} + +/// Starts a new process with given fds 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 argv Command for this process. +/// \arg fdin Standard input file descriptor. If null, /dev/null is assumed. Otherwise, if arg contains -1, a new fd is automatically allocated and written into this arg. Then the arg will be used as fd. +/// \arg fdout Same as fdin, but for stdout. +/// \arg fdout Same as fdin, but for stderr. +pid_t Util::Procs::StartPiped(std::string name, char * const * argv, int * fdin, int * fdout, int * fderr) { + if (isActive(name)) { + DEBUG_MSG(DLVL_WARN, "Process %s already active - skipping start", name.c_str()); + return getPid(name); + } + int pidtemp = StartPiped(argv, fdin, fdout, fderr); + if (pidtemp > 0) { + plist.insert(std::pair(pidtemp, name)); + } + return pidtemp; +} + +pid_t Util::Procs::StartPiped(char * const * argv, int * fdin, int * fdout, int * fderr) { + pid_t pid; + int pipein[2], pipeout[2], pipeerr[2]; + //DEBUG_MSG(DLVL_DEVEL, "setHandler"); + setHandler(); + if (fdin && *fdin == -1 && pipe(pipein) < 0) { + DEBUG_MSG(DLVL_ERROR, "stdin pipe creation failed for process %s, reason: %s", argv[0], strerror(errno)); + return 0; + } + if (fdout && *fdout == -1 && pipe(pipeout) < 0) { + DEBUG_MSG(DLVL_ERROR, "stdout pipe creation failed for process %s, reason: %s", argv[0], strerror(errno)); + if (*fdin == -1) { + close(pipein[0]); + close(pipein[1]); + } + return 0; + } + if (fderr && *fderr == -1 && pipe(pipeerr) < 0) { + DEBUG_MSG(DLVL_ERROR, "stderr pipe creation failed for process %s, reason: %s", argv[0], strerror(errno)); + if (*fdin == -1) { + close(pipein[0]); + close(pipein[1]); + } + if (*fdout == -1) { + close(pipeout[0]); + close(pipeout[1]); + } + return 0; + } + int devnull = -1; + if (!fdin || !fdout || !fderr) { + devnull = open("/dev/null", O_RDWR); + if (devnull == -1) { + DEBUG_MSG(DLVL_ERROR, "Could not open /dev/null for process %s, reason: %s", argv[0], strerror(errno)); + if (*fdin == -1) { + close(pipein[0]); + close(pipein[1]); + } + if (*fdout == -1) { + close(pipeout[0]); + close(pipeout[1]); + } + if (*fderr == -1) { + close(pipeerr[0]); + close(pipeerr[1]); + } + return 0; + } + } + pid = fork(); + if (pid == 0) { //child + if (!fdin) { + dup2(devnull, STDIN_FILENO); + } else if (*fdin == -1) { + close(pipein[1]); // close unused write end + dup2(pipein[0], STDIN_FILENO); + close(pipein[0]); + } else if (*fdin != STDIN_FILENO) { + dup2(*fdin, STDIN_FILENO); + } + if (!fdout) { + dup2(devnull, STDOUT_FILENO); + } else if (*fdout == -1) { + close(pipeout[0]); // close unused read end + dup2(pipeout[1], STDOUT_FILENO); + close(pipeout[1]); + } else if (*fdout != STDOUT_FILENO) { + dup2(*fdout, STDOUT_FILENO); + } + if (!fderr) { + dup2(devnull, STDERR_FILENO); + } else if (*fderr == -1) { + close(pipeerr[0]); // close unused read end + dup2(pipeerr[1], STDERR_FILENO); + close(pipeerr[1]); + } else if (*fderr != STDERR_FILENO) { + dup2(*fderr, STDERR_FILENO); + } + if (fdin && *fdin != -1 && *fdin != STDIN_FILENO) { + close(*fdin); + } + if (fdout && *fdout != -1 && *fdout != STDOUT_FILENO) { + close(*fdout); + } + if (fderr && *fderr != -1 && *fderr != STDERR_FILENO) { + close(*fderr); + } + if (devnull != -1) { + close(devnull); + } + execvp(argv[0], argv); + DEBUG_MSG(DLVL_ERROR, "execvp failed for process %s, reason: %s", argv[0], strerror(errno)); + exit(42); + } else if (pid == -1) { + DEBUG_MSG(DLVL_ERROR, "fork failed for process %s, reason: %s", argv[0], strerror(errno)); + if (fdin && *fdin == -1) { + close(pipein[0]); + close(pipein[1]); + } + if (fdout && *fdout == -1) { + close(pipeout[0]); + close(pipeout[1]); + } + if (fderr && *fderr == -1) { + close(pipeerr[0]); + close(pipeerr[1]); + } + if (devnull != -1) { + close(devnull); + } + return 0; + } else { //parent + DEBUG_MSG(DLVL_HIGH, "Piped process %s started, PID %d", argv[0], pid); + if (devnull != -1) { + close(devnull); + } + if (fdin && *fdin == -1) { + close(pipein[0]); // close unused end end + *fdin = pipein[1]; + } + if (fdout && *fdout == -1) { + close(pipeout[1]); // close unused write end + *fdout = pipeout[0]; + } + if (fderr && *fderr == -1) { + close(pipeerr[1]); // close unused write end + *fderr = pipeerr[0]; + } + } + return pid; +} + +/// Starts a new process with given fds 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 Command for this process. +/// \arg fdin Standard input file descriptor. If null, /dev/null is assumed. Otherwise, if arg contains -1, a new fd is automatically allocated and written into this arg. Then the arg will be used as fd. +/// \arg fdout Same as fdin, but for stdout. +/// \arg fdout Same as fdin, but for stderr. +pid_t Util::Procs::StartPiped(std::string name, std::string cmd, int * fdin, int * fdout, int * fderr) { + //Convert the given command to a char * [] + 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; + } + return StartPiped(name, args, fdin, fdout, fderr); +} + + +pid_t Util::Procs::StartPiped2(std::string name, std::string cmd1, std::string cmd2, int * fdin, int * fdout, int * fderr1, int * fderr2) { + int pfildes[2]; + if (pipe(pfildes) == -1) { + DEBUG_MSG(DLVL_ERROR, "Pipe creation failed for process %s", name.c_str()); + return 0; + } + pid_t res1 = StartPiped(name, cmd1, fdin, &pfildes[1], fderr1); + if (!res1) { + close(pfildes[1]); + close(pfildes[0]); + return 0; + } + pid_t res2 = StartPiped(name + "receiving", cmd2, &pfildes[0], fdout, fderr2); + if (!res2) { + Stop(res1); + close(pfildes[1]); + close(pfildes[0]); + return 0; + } + //we can close these because the fork in StartPiped() copies them. + close(pfildes[1]); + close(pfildes[0]); + return res1; +} +/// 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) { + kill(name, SIGTERM); +} + +/// Stops the process with this pid, if running. +/// \arg name The PID of the process to murder. +void Util::Procs::Murder(pid_t name) { + kill(name, SIGKILL); +} + +/// (Attempts to) stop all running child processes. +void Util::Procs::StopAll() { + std::map listcopy = plist; + std::map::iterator it; + for (it = listcopy.begin(); it != listcopy.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 listcopy = plist; + std::map::iterator it; + for (it = listcopy.begin(); it != listcopy.end(); it++) { + if ((*it).second == name) { + if (childRunning((*it).first)) { + return true; + } else { + plist.erase((*it).first); + } + } + } + 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) && (kill(name, 0) == 0); +} + +/// 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::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 ""; +} + +/// Registers one notifier function for when a process indentified by PID terminates. +/// \return true if the notifier could be registered, false otherwise. +bool Util::Procs::SetTerminationNotifier(pid_t pid, TerminationNotifier notifier) { + if (plist.find(pid) != plist.end()) { + exitHandlers[pid] = notifier; + return true; + } + return false; +} diff --git a/lib/procs.h b/lib/procs.h new file mode 100644 index 00000000..4a90e233 --- /dev/null +++ b/lib/procs.h @@ -0,0 +1,48 @@ +/// \file procs.h +/// Contains generic function headers for managing processes. + +#pragma once +#include +#include +#include +#include + +/// Contains utility code, not directly related to streaming media +namespace Util { + + typedef void (*TerminationNotifier)(pid_t pid, int exitCode); + + /// Deals with spawning, monitoring and stopping child processes + class Procs { + private: + static std::map plist; ///< Holds active processes + static std::map exitHandlers; ///< termination function, if any + static bool handler_set; ///< If true, the sigchld handler has been setup. + static void childsig_handler(int signum); + static void exit_handler(); + static void runCmd(std::string & cmd); + static void setHandler(); + public: + static std::string getOutputOf(char * const * argv); + static std::string getOutputOf(std::string cmd); + 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 pid_t StartPiped(char * const * argv, int * fdin, int * fdout, int * fderr); + static pid_t StartPiped(std::string name, char * const * argv, int * fdin, int * fdout, int * fderr); + static pid_t StartPiped(std::string name, std::string cmd, int * fdin, int * fdout, int * fderr); + static pid_t StartPiped2(std::string name, std::string cmd1, std::string cmd2, int * fdin, int * fdout, int * fderr1, int * fderr2); + static void Stop(std::string name); + static void Stop(pid_t name); + static void Murder(pid_t name); + static void StopAll(); + static int Count(); + static bool isActive(std::string name); + static bool isActive(pid_t name); + static bool isRunnning(pid_t pid); + static pid_t getPid(std::string name); + static std::string getName(pid_t name); + static bool SetTerminationNotifier(pid_t pid, TerminationNotifier notifier); + }; + +} diff --git a/lib/rtmpchunks.cpp b/lib/rtmpchunks.cpp new file mode 100644 index 00000000..8ada1b37 --- /dev/null +++ b/lib/rtmpchunks.cpp @@ -0,0 +1,678 @@ +/// \file rtmpchunks.cpp +/// Holds all code for the RTMPStream namespace. + +#include "rtmpchunks.h" +#include "defines.h" +#include "flv_tag.h" +#include "timing.h" +#include "auth.h" + +#ifndef FILLER_DATA +#define FILLER_DATA "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent commodo vulputate urna eu commodo. Cras tempor velit nec nulla placerat volutpat. Proin eleifend blandit quam sit amet suscipit. Pellentesque vitae tristique lorem. Maecenas facilisis consequat neque, vitae iaculis eros vulputate ut. Suspendisse ut arcu non eros vestibulum pulvinar id sed erat. Nam dictum tellus vel tellus rhoncus ut mollis tellus fermentum. Fusce volutpat consectetur ante, in mollis nisi euismod vulputate. Curabitur vitae facilisis ligula. Sed sed gravida dolor. Integer eu eros a dolor lobortis ullamcorper. Mauris interdum elit non neque interdum dictum. Suspendisse imperdiet eros sed sapien cursus pulvinar. Vestibulum ut dolor lectus, id commodo elit. Cras convallis varius leo eu porta. Duis luctus sapien nec dui adipiscing quis interdum nunc congue. Morbi pharetra aliquet mauris vitae tristique. Etiam feugiat sapien quis augue elementum id ultricies magna vulputate. Phasellus luctus, leo id egestas consequat, eros tortor commodo neque, vitae hendrerit nunc sem ut odio." +#endif + +std::string RTMPStream::handshake_in; ///< Input for the handshake. +std::string RTMPStream::handshake_out; ///< Output for the handshake. + +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 RTMPStream::lastsend; +/// Holds the last received chunk for every msg_id. +std::map RTMPStream::lastrecv; + +#define P1024 \ + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF" + +char 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, // Genuine Adobe Flash Media Server 001 + 0x65, 0x72, 0x20, 0x30, 0x30, 0x31, 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 + +char 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, // Genuine Adobe Flash Player 001 + 0x65, 0x72, 0x20, 0x30, 0x30, 0x31, 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 + +inline uint32_t GetDigestOffset(uint8_t * pBuffer, uint8_t scheme) { + if (scheme == 0) { + return ((pBuffer[8] + pBuffer[9] + pBuffer[10] + pBuffer[11]) % 728) + 12; + } else { + return ((pBuffer[772] + pBuffer[773] + pBuffer[774] + pBuffer[775]) % 728) + 776; + } +} + +bool ValidateClientScheme(uint8_t * pBuffer, uint8_t scheme) { + uint32_t clientDigestOffset = GetDigestOffset(pBuffer, scheme); + uint8_t pTempBuffer[1536-32]; + memcpy(pTempBuffer, pBuffer, clientDigestOffset); + memcpy(pTempBuffer + clientDigestOffset, pBuffer + clientDigestOffset + 32, 1536 - clientDigestOffset - 32); + char pTempHash[32]; + Secure::hmac_sha256bin((char*)pTempBuffer, 1536 - 32, genuineFPKey, 30, pTempHash); + bool result = (memcmp(pBuffer + clientDigestOffset, pTempHash, 32) == 0); + DEBUG_MSG(DLVL_MEDIUM, "Client scheme validation %hhi %s", scheme, result ? "success" : "failed"); + return result; +} + +/// 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() { + static std::string output; + output.clear(); + bool allow_short = lastsend.count(cs_id); + RTMPStream::Chunk prev = lastsend[cs_id]; + unsigned int tmpi; + unsigned char chtype = 0x00; + if (allow_short && (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 == prev.ts_delta) { + 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; + } + ts_delta = tmpi; + 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 constructor, creates an empty chunk with all values initialized to zero. +RTMPStream::Chunk::Chunk() { + headertype = 0; + 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) { + static RTMPStream::Chunk ch; + ch.cs_id = cs_id; + ch.timestamp = Util::getMS(); + 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) { + static 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 = std::string((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) { + static RTMPStream::Chunk ch; + //Commented bit is more efficient and correct according to RTMP spec. + //Simply passing "4" is the only thing that actually plays correctly, though. + //Adobe, if you're ever reading this... wtf? Seriously. + ch.cs_id = 4;//((unsigned char)tag.data[0]); + ch.timestamp = tag.tagTime(); + ch.len_left = 0; + ch.msg_type_id = (unsigned char)tag.data[0]; + ch.msg_stream_id = 1; + ch.data = std::string(tag.data + 11, (size_t)(tag.len - 15)); + ch.len = ch.data.size(); + ch.real_len = ch.len; + 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) { + static RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = Util::getMS(); + 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) { + static RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = Util::getMS(); + 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) { + static RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = Util::getMS(); + 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) { + static RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = Util::getMS(); + 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, removing data from the input string as it reads. +/// 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; + } + + bool allow_short = lastrecv.count(cs_id); + RTMPStream::Chunk prev = lastrecv[cs_id]; + + //process the rest of the header, for each chunk type + headertype = chunktype & 0xC0; + + DEBUG_MSG(DLVL_DONTEVEN, "Parsing RTMP chunk header (%#.2hhX) at offset %#X", chunktype, RTMPStream::rec_cnt); + + 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++ ]; + ts_delta = timestamp; + 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 (!allow_short) { + DEBUG_MSG(DLVL_WARN, "Warning: Header type 0x40 with no valid previous chunk!"); + } + timestamp = indata[i++ ] * 256 * 256; + timestamp += indata[i++ ] * 256; + timestamp += indata[i++ ]; + if (timestamp != 0x00ffffff) { + ts_delta = timestamp; + timestamp = prev.timestamp + ts_delta; + } + 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 (!allow_short) { + DEBUG_MSG(DLVL_WARN, "Warning: Header type 0x80 with no valid previous chunk!"); + } + timestamp = indata[i++ ] * 256 * 256; + timestamp += indata[i++ ] * 256; + timestamp += indata[i++ ]; + if (timestamp != 0x00ffffff) { + ts_delta = timestamp; + timestamp = prev.timestamp + ts_delta; + } + 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 (!allow_short) { + DEBUG_MSG(DLVL_WARN, "Warning: Header type 0xC0 with no valid previous chunk!"); + } + timestamp = prev.timestamp + prev.ts_delta; + ts_delta = prev.ts_delta; + len = prev.len; + len_left = prev.len_left; + if (len_left > 0){ + timestamp = prev.timestamp; + } + 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; + } + + DEBUG_MSG(DLVL_DONTEVEN, "Parsing RTMP chunk result: len_left=%d, real_len=%d", len_left, real_len); + + //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++ ]; + ts_delta = timestamp; + } + + //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 + +/// Parses the argument Socket::Buffer into the current chunk. +/// Tries to read a whole chunk, removing data from the Buffer as it reads. +/// If a single packet contains a partial chunk, it will remove the packet 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 buffer The input 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(Socket::Buffer & buffer) { + gettimeofday(&RTMPStream::lastrec, 0); + unsigned int i = 0; + if (!buffer.available(3)) { + return false; + } //we want at least 3 bytes + std::string indata = buffer.copy(3); + + unsigned char chunktype = indata[i++ ]; + //read the chunkstream ID properly + switch (chunktype & 0x3F) { + case 0: + cs_id = indata[i++ ] + 64; + break; + case 1: + 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 (!buffer.available(i + 11)) { + return false; + } //can't read whole header + indata = buffer.copy(i + 11); + timestamp = indata[i++ ] * 256 * 256; + timestamp += indata[i++ ] * 256; + timestamp += indata[i++ ]; + ts_delta = timestamp; + 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 (!buffer.available(i + 7)) { + return false; + } //can't read whole header + indata = buffer.copy(i + 7); + if (prev.msg_type_id == 0) { + DEBUG_MSG(DLVL_WARN, "Warning: Header type 0x40 with no valid previous chunk!"); + } + timestamp = indata[i++ ] * 256 * 256; + timestamp += indata[i++ ] * 256; + timestamp += indata[i++ ]; + if (timestamp != 0x00ffffff) { + ts_delta = timestamp; + timestamp = prev.timestamp + ts_delta; + } + 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 (!buffer.available(i + 3)) { + return false; + } //can't read whole header + indata = buffer.copy(i + 3); + if (prev.msg_type_id == 0) { + DEBUG_MSG(DLVL_WARN, "Warning: Header type 0x80 with no valid previous chunk!"); + } + timestamp = indata[i++ ] * 256 * 256; + timestamp += indata[i++ ] * 256; + timestamp += indata[i++ ]; + if (timestamp != 0x00ffffff) { + ts_delta = timestamp; + timestamp = prev.timestamp + ts_delta; + } + 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) { + DEBUG_MSG(DLVL_WARN, "Warning: Header type 0xC0 with no valid previous chunk!"); + } + timestamp = prev.timestamp + prev.ts_delta; + ts_delta = prev.ts_delta; + len = prev.len; + len_left = prev.len_left; + if (len_left > 0){ + timestamp = prev.timestamp; + } + 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 (!buffer.available(i + 4)) { + return false; + } //can't read timestamp + indata = buffer.copy(i + 4); + timestamp = indata[i++ ] * 256 * 256 * 256; + timestamp += indata[i++ ] * 256 * 256; + timestamp += indata[i++ ] * 256; + timestamp += indata[i++ ]; + ts_delta = timestamp; + } + + //read data if length > 0, and allocate it + if (real_len > 0) { + if (!buffer.available(i + real_len)) { + return false; + } //can't read all data (yet) + buffer.remove(i); //remove the header + if (prev.len_left > 0) { + data = prev.data + buffer.remove(real_len); //append the data and remove from buffer + } else { + data = buffer.remove(real_len); //append the data and remove from buffer + } + lastrecv[cs_id] = *this; + RTMPStream::rec_cnt += i + real_len; + if (len_left == 0) { + return true; + } else { + return Parse(buffer); + } + } else { + buffer.remove(i); //remove the header + data = ""; + indata = indata.substr(i + real_len); + lastrecv[cs_id] = *this; + RTMPStream::rec_cnt += i + real_len; + return true; + } +} //Parse + +#include +/// 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 + if (handshake_in.size() < 1537) { + DEBUG_MSG(DLVL_FAIL, "Handshake wasn't filled properly (%lu/1537) - aborting!", handshake_in.size()); + return false; + } + Version = RTMPStream::handshake_in[0]; + uint8_t * Client = (uint8_t *)RTMPStream::handshake_in.data() + 1; + RTMPStream::handshake_out.resize(3073); + uint8_t * Server = (uint8_t *)RTMPStream::handshake_out.data() + 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] = FILLER_DATA[i % sizeof(FILLER_DATA)]; + } //"random" data + + bool encrypted = (Version == 6); + DEBUG_MSG(DLVL_HIGH, "Handshake version is %hhi", Version); + uint8_t _validationScheme = 5; + if (ValidateClientScheme(Client, 0)) _validationScheme = 0; + if (ValidateClientScheme(Client, 1)) _validationScheme = 1; + + DEBUG_MSG(DLVL_HIGH, "Handshake type is %hhi, encryption is %s", _validationScheme, encrypted ? "on" : "off"); + uint32_t serverDigestOffset = GetDigestOffset(Server, _validationScheme); + uint32_t keyChallengeIndex = GetDigestOffset(Client, _validationScheme); + + //FIRST 1536 bytes for server response + char pTempBuffer[1504]; + memcpy(pTempBuffer, Server, serverDigestOffset); + memcpy(pTempBuffer + serverDigestOffset, Server + serverDigestOffset + 32, 1504 - serverDigestOffset); + Secure::hmac_sha256bin(pTempBuffer, 1504, genuineFMSKey, 36, (char*)Server + serverDigestOffset); + + //SECOND 1536 bytes for server response + char pTempHash[32]; + Secure::hmac_sha256bin((char*)Client + keyChallengeIndex, 32, genuineFMSKey, 68, pTempHash); + Secure::hmac_sha256bin((char*)Server + 1536, 1536 - 32, pTempHash, 32, (char*)Server + 1536 * 2 - 32); + + Server[ -1] = Version; + RTMPStream::snd_cnt += 3073; + return true; +} + diff --git a/lib/rtmpchunks.h b/lib/rtmpchunks.h new file mode 100644 index 00000000..bbf6b360 --- /dev/null +++ b/lib/rtmpchunks.h @@ -0,0 +1,70 @@ +/// \file rtmpchunks.h +/// Holds all headers for the RTMPStream namespace. + +#pragma once +#include +#include +#include +#include +#include +#include +#include "socket.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 { + + 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 ts_delta; ///< Last timestamp delta. + 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); + bool Parse(Socket::Buffer & data); + std::string & Pack(); + }; + //RTMPStream::Chunk + + extern std::map lastsend; + extern std::map lastrecv; + + 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 diff --git a/lib/shared_memory.cpp b/lib/shared_memory.cpp new file mode 100644 index 00000000..f20d97f0 --- /dev/null +++ b/lib/shared_memory.cpp @@ -0,0 +1,990 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "defines.h" +#include "shared_memory.h" +#include "stream.h" +#include "procs.h" + +namespace IPC { + /// Stores a long value of val in network order to the pointer p. + static void htobl(char * p, long val) { + p[0] = (val >> 24) & 0xFF; + p[1] = (val >> 16) & 0xFF; + p[2] = (val >> 8) & 0xFF; + p[3] = val & 0xFF; + } + + /// Stores a short value of val in network order to the pointer p. + static void htobs(char * p, short val) { + p[0] = (val >> 8) & 0xFF; + p[1] = val & 0xFF; + } + + + /// Stores a long long value of val in network order to the pointer p. + static void htobll(char * p, long long val) { + p[0] = (val >> 56) & 0xFF; + p[1] = (val >> 48) & 0xFF; + p[2] = (val >> 40) & 0xFF; + p[3] = (val >> 32) & 0xFF; + p[4] = (val >> 24) & 0xFF; + p[5] = (val >> 16) & 0xFF; + p[6] = (val >> 8) & 0xFF; + p[7] = val & 0xFF; + } + + /// Reads a long value of p in host order to val. + static void btohl(char * p, long & val) { + val = ((long)p[0] << 24) | ((long)p[1] << 16) | ((long)p[2] << 8) | p[3]; + } + + /// Reads a short value of p in host order to val. + static void btohs(char * p, unsigned short & val) { + val = ((short)p[0] << 8) | p[1]; + } + + /// Reads a long value of p in host order to val. + static void btohl(char * p, unsigned int & val) { + val = ((long)p[0] << 24) | ((long)p[1] << 16) | ((long)p[2] << 8) | p[3]; + } + + /// Reads a long long value of p in host order to val. + static void btohll(char * p, long long & val) { + val = ((long long)p[0] << 56) | ((long long)p[1] << 48) | ((long long)p[2] << 40) | ((long long)p[3] << 32) | ((long long)p[4] << 24) | ((long long)p[5] << 16) | ((long long)p[6] << 8) | p[7]; + } + + ///\brief Empty semaphore constructor, clears all values + semaphore::semaphore() { +#ifdef __CYGWIN__ + mySem = 0; +#else + mySem = SEM_FAILED; +#endif + } + + ///\brief Constructs a named semaphore + ///\param name The name of the semaphore + ///\param oflag The flags with which to open the semaphore + ///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored otherwise + ///\param value The initial value of the semaphore if O_CREAT is given in oflag, ignored otherwise + semaphore::semaphore(const char * name, int oflag, mode_t mode, unsigned int value) { +#ifdef __CYGWIN__ + mySem = 0; +#else + mySem = SEM_FAILED; +#endif + open(name, oflag, mode, value); + } + + ///\brief The deconstructor + semaphore::~semaphore() { + close(); + } + + + ///\brief Returns whether we have a valid semaphore + semaphore::operator bool() const { +#ifdef __CYGWIN__ + return mySem != 0; +#else + return mySem != SEM_FAILED; +#endif + } + + ///\brief Opens a semaphore + /// + ///Closes currently opened semaphore if needed + ///\param name The name of the semaphore + ///\param oflag The flags with which to open the semaphore + ///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored otherwise + ///\param value The initial value of the semaphore if O_CREAT is given in oflag, ignored otherwise + void semaphore::open(const char * name, int oflag, mode_t mode, unsigned int value) { + close(); + int timer = 0; + while (!(*this) && timer++ < 10){ +#ifdef __CYGWIN__ + mySem = CreateSemaphore(0, value, 1 , std::string("Global\\" + std::string(name)).c_str()); +#else + if (oflag & O_CREAT) { + mySem = sem_open(name, oflag, mode, value); + } else { + mySem = sem_open(name, oflag); + } +#endif + if (!(*this)){ + if (errno == ENOENT){ + Util::wait(500); + }else{ + break; + } + } + } + if (!(*this)){ + DEBUG_MSG(DLVL_VERYHIGH, "Attempt to open semaphore %s: %s", name, strerror(errno)); + } + myName = (char *)name; + } + + ///\brief Returns the current value of the semaphore + int semaphore::getVal() const { +#ifdef __CYGWIN__ + LONG res; + ReleaseSemaphore(mySem, 0, &res);//not really release.... just checking to see if I can get the value this way +#else + int res; + sem_getvalue(mySem, &res); +#endif + return res; + } + + ///\brief Posts to the semaphore, increases its value by one + void semaphore::post() { + if (*this) { +#ifdef __CYGWIN__ + ReleaseSemaphore(mySem, 1, 0); +#else + sem_post(mySem); +#endif + } + } + + ///\brief Waits for the semaphore, decreases its value by one + void semaphore::wait() { + if (*this) { +#ifdef __CYGWIN__ + WaitForSingleObject(mySem, INFINITE); +#else + int tmp; + do { + tmp = sem_wait(mySem); + } while (tmp == -1 && errno == EINTR); +#endif + } + } + + ///\brief Tries to wait for the semaphore, returns true if successfull, false otherwise + bool semaphore::tryWait() { + bool result; +#ifdef __CYGWIN__ + result = WaitForSingleObject(mySem, 0);//wait at most 1ms +#else + result = sem_trywait(mySem); +#endif + return (result == 0); + } + + ///\brief Closes the currently opened semaphore + void semaphore::close() { + if (*this) { +#ifdef __CYGWIN__ + CloseHandle(mySem); + mySem = 0; +#else + sem_close(mySem); + mySem = SEM_FAILED; +#endif + } + } + + ///\brief Unlinks the previously opened semaphore + void semaphore::unlink() { + close(); +#ifndef __CYGWIN__ + if (myName.size()) { + sem_unlink(myName.c_str()); + } +#endif + myName.clear(); + } + + + ///brief Creates a shared page + ///\param name_ The name of the page to be created + ///\param len_ The size to make the page + ///\param master_ Whether to create or merely open the page + ///\param autoBackoff When only opening the page, wait for it to appear or fail + sharedPage::sharedPage(std::string name_, unsigned int len_, bool master_, bool autoBackoff){ + handle = 0; + len = 0; + master = false; + mapped = 0; + init(name_, len_, master_, autoBackoff); + } + + ///\brief Creates a copy of a shared page + ///\param rhs The page to copy + sharedPage::sharedPage(const sharedPage & rhs) { + handle = 0; + len = 0; + master = false; + mapped = 0; + init(rhs.name, rhs.len, rhs.master); + } + + ///\brief Default destructor + sharedPage::~sharedPage() { + close(); + } + +#ifdef SHM_ENABLED + ///\brief Unmaps a shared page if allowed + void sharedPage::unmap() { + if (mapped && len) { +#ifdef __CYGWIN__ + //under Cygwin, the mapped location is shifted by 4 to contain the page size. + UnmapViewOfFile(mapped-4); +#else + munmap(mapped, len); +#endif + mapped = 0; + len = 0; + } + } + + ///\brief Closes a shared page if allowed + void sharedPage::close() { + unmap(); + if (handle > 0) { +#ifdef __CYGWIN__ + CloseHandle(handle); +#else + ::close(handle); + if (master && name != "") { + shm_unlink(name.c_str()); + } +#endif + handle = 0; + } + } + + ///\brief Returns whether the shared page is valid or not + sharedPage::operator bool() const { + return mapped != 0; + } + + ///\brief Assignment operator + void sharedPage::operator =(sharedPage & rhs) { + init(rhs.name, rhs.len, rhs.master); + /// \todo This is bad. The assignment operator changes the rhs value? What the hell? + rhs.master = false;//Make sure the memory does not get unlinked + } + + + ///\brief Initialize a page, de-initialize before if needed + ///\param name_ The name of the page to be created + ///\param len_ The size to make the page + ///\param master_ Whether to create or merely open the page + ///\param autoBackoff When only opening the page, wait for it to appear or fail + void sharedPage::init(std::string name_, unsigned int len_, bool master_, bool autoBackoff) { + close(); + name = name_; + len = len_; + master = master_; + mapped = 0; + if (name.size()) { +#ifdef __CYGWIN__ + if (master) { + //Under cygwin, all pages are 4 bytes longer than claimed. + handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, len+4, name.c_str()); + } else { + int i = 0; + do { + if (i != 0) { + Util::sleep(1000); + } + handle = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, name.c_str()); + i++; + } while (i < 10 && !handle && autoBackoff); + } + if (!handle) { + FAIL_MSG("%s for page %s failed: %s", (master ? "CreateFileMapping" : "OpenFileMapping"), name.c_str(), strerror(errno)); + return; + } + mapped = (char *)MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, 0); + if (!mapped) { + FAIL_MSG("MapViewOfFile for page %s failed: %s", name.c_str(), strerror(errno)); + return; + } + //Under cygwin, the extra 4 bytes contain the real size of the page. + if (master){ + ((unsigned int*)mapped)[0] = len_; + }else{ + len = ((unsigned int*)mapped)[0]; + } + //Now shift by those 4 bytes. + mapped += 4; +#else + handle = shm_open(name.c_str(), (master ? O_CREAT | O_EXCL : 0) | O_RDWR, ACCESSPERMS); + if (handle == -1) { + if (master) { + DEBUG_MSG(DLVL_HIGH, "Overwriting old page for %s", name.c_str()); + handle = shm_open(name.c_str(), O_CREAT | O_RDWR, ACCESSPERMS); + } else { + int i = 0; + while (i < 10 && handle == -1 && autoBackoff) { + i++; + Util::sleep(1000); + handle = shm_open(name.c_str(), O_RDWR, ACCESSPERMS); + } + } + } + if (handle == -1) { + FAIL_MSG("shm_open for page %s failed: %s", name.c_str(), strerror(errno)); + return; + } + if (master) { + if (ftruncate(handle, 0) < 0) { + FAIL_MSG("truncate to zero for page %s failed: %s", name.c_str(), strerror(errno)); + return; + } + if (ftruncate(handle, len) < 0) { + FAIL_MSG("truncate to %lld for page %s failed: %s", len, name.c_str(), strerror(errno)); + return; + } + } else { + struct stat buffStats; + int xRes = fstat(handle, &buffStats); + if (xRes < 0) { + return; + } + len = buffStats.st_size; + } + mapped = (char *)mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, handle, 0); + if (mapped == MAP_FAILED) { + FAIL_MSG("mmap for page %s failed: %s", name.c_str(), strerror(errno)); + mapped = 0; + return; + } +#endif + } + } + +#endif + + ///brief Creates a shared file + ///\param name_ The name of the file to be created + ///\param len_ The size to make the file + ///\param master_ Whether to create or merely open the file + ///\param autoBackoff When only opening the file, wait for it to appear or fail + sharedFile::sharedFile(std::string name_, unsigned int len_, bool master_, bool autoBackoff) : handle(0), name(name_), len(len_), master(master_), mapped(NULL) { + handle = 0; + name = name_; + len = len_; + master = master_; + mapped = 0; + init(name_, len_, master_, autoBackoff); + } + + + ///\brief Creates a copy of a shared page + ///\param rhs The page to copy + sharedFile::sharedFile(const sharedFile & rhs) { + handle = 0; + name = ""; + len = 0; + master = false; + mapped = 0; + init(rhs.name, rhs.len, rhs.master); + } + + ///\brief Returns whether the shared file is valid or not + sharedFile::operator bool() const { + return mapped != 0; + } + + ///\brief Assignment operator + void sharedFile::operator =(sharedFile & rhs) { + init(rhs.name, rhs.len, rhs.master); + rhs.master = false;//Make sure the memory does not get unlinked + } + + ///\brief Unmaps a shared file if allowed + void sharedFile::unmap() { + if (mapped && len) { + munmap(mapped, len); + mapped = 0; + len = 0; + } + } + + /// Unmaps, closes and unlinks (if master and name is set) the shared file. + void sharedFile::close() { + unmap(); + if (handle > 0) { + ::close(handle); + if (master && name != "") { + unlink(name.c_str()); + } + handle = 0; + } + } + + ///\brief Initialize a page, de-initialize before if needed + ///\param name_ The name of the page to be created + ///\param len_ The size to make the page + ///\param master_ Whether to create or merely open the page + ///\param autoBackoff When only opening the page, wait for it to appear or fail + void sharedFile::init(std::string name_, unsigned int len_, bool master_, bool autoBackoff) { + close(); + name = name_; + len = len_; + master = master_; + mapped = 0; + if (name.size()) { + /// \todo Use ACCESSPERMS instead of 0600? + handle = open(std::string(Util::getTmpFolder() + name).c_str(), (master ? O_CREAT | O_TRUNC | O_EXCL : 0) | O_RDWR, (mode_t)0600); + if (handle == -1) { + if (master) { + DEBUG_MSG(DLVL_HIGH, "Overwriting old file for %s", name.c_str()); + handle = open(std::string(Util::getTmpFolder() + name).c_str(), O_CREAT | O_TRUNC | O_RDWR, (mode_t)0600); + } else { + int i = 0; + while (i < 10 && handle == -1 && autoBackoff) { + i++; + Util::sleep(1000); + handle = open(std::string(Util::getTmpFolder() + name).c_str(), O_RDWR, (mode_t)0600); + } + } + } + if (handle == -1) { + perror(std::string("open for file " + name + " failed").c_str()); + return; + } + if (master) { + if (ftruncate(handle, len) < 0) { + perror(std::string("ftruncate to len for file " + name + " failed").c_str()); + return; + } + } else { + struct stat buffStats; + int xRes = fstat(handle, &buffStats); + if (xRes < 0) { + return; + } + len = buffStats.st_size; + } + mapped = (char *)mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, handle, 0); + if (mapped == MAP_FAILED) { + mapped = 0; + return; + } + } + } + + ///\brief Default destructor + sharedFile::~sharedFile() { + close(); + } + + + ///\brief StatExchange constructor, sets the datapointer to the given value + statExchange::statExchange(char * _data) : data(_data) {} + + ///\brief Sets timestamp of the current stats + void statExchange::now(long long int time) { + htobll(data, time); + } + + ///\brief Gets timestamp of the current stats + long long int statExchange::now() { + long long int result; + btohll(data, result); + return result; + } + + ///\brief Sets time currently connected + void statExchange::time(long time) { + htobl(data + 8, time); + } + + ///\brief Gets time currently connected + long statExchange::time() { + long result; + btohl(data + 8, result); + return result; + } + + ///\brief Sets the last viewing second of this user + void statExchange::lastSecond(long time) { + htobl(data + 12, time); + } + + ///\brief Gets the last viewing second of this user + long statExchange::lastSecond() { + long result; + btohl(data + 12, result); + return result; + } + + ///\brief Sets the amount of bytes received + void statExchange::down(long long int bytes) { + htobll(data + 16, bytes); + } + + ///\brief Gets the amount of bytes received + long long int statExchange::down() { + long long int result; + btohll(data + 16, result); + return result; + } + + ///\brief Sets the amount of bytes sent + void statExchange::up(long long int bytes) { + htobll(data + 24, bytes); + } + + ///\brief Gets the amount of bytes sent + long long int statExchange::up() { + long long int result; + btohll(data + 24, result); + return result; + } + + ///\brief Sets the host of this connection + void statExchange::host(std::string name) { + if (name.size() < 16){ + memset(data+32, 0, 16); + } + memcpy(data + 32, name.c_str(), std::min((int)name.size(), 16)); + } + + ///\brief Gets the host of this connection + std::string statExchange::host() { + return std::string(data + 32, 16); + } + + ///\brief Sets the name of the stream this user is viewing + void statExchange::streamName(std::string name) { + size_t splitChar = name.find_first_of("+ "); + if (splitChar != std::string::npos){ + name[splitChar] = '+'; + } + memcpy(data + 48, name.c_str(), std::min((int)name.size(), 100)); + } + + ///\brief Gets the name of the stream this user is viewing + std::string statExchange::streamName() { + return std::string(data + 48, strnlen(data + 48, 100)); + } + + ///\brief Sets the name of the connector through which this user is viewing + void statExchange::connector(std::string name) { + memcpy(data + 148, name.c_str(), std::min((int)name.size(), 20)); + } + + ///\brief Gets the name of the connector through which this user is viewing + std::string statExchange::connector() { + return std::string(data + 148, std::min((int)strlen(data + 148), 20)); + } + + ///\brief Sets checksum field + void statExchange::crc(unsigned int sum) { + htobl(data + 186, sum); + } + + ///\brief Gets checksum field + unsigned int statExchange::crc() { + unsigned int result; + btohl(data + 186, result); + return result; + } + + ///\brief Creates a semaphore guard, locks the semaphore on call + semGuard::semGuard(semaphore * thisSemaphore) : mySemaphore(thisSemaphore) { + mySemaphore->wait(); + } + + ///\brief Destructs a semaphore guard, unlocks the semaphore on call + semGuard::~semGuard() { + mySemaphore->post(); + } + + ///\brief Default constructor, erases all the values + sharedServer::sharedServer() { + payLen = 0; + hasCounter = false; + amount = 0; + } + + ///\brief Desired constructor, initializes after cleaning. + ///\param name The basename of this server + ///\param len The lenght of the payload + ///\param withCounter Whether the content should have a counter + sharedServer::sharedServer(std::string name, int len, bool withCounter) { + sharedServer(); + init(name, len, withCounter); + } + + ///\brief Initialize the server + ///\param name The basename of this server + ///\param len The lenght of the payload + ///\param withCounter Whether the content should have a counter + void sharedServer::init(std::string name, int len, bool withCounter) { + amount = 0; + if (mySemaphore) { + mySemaphore.close(); + } + if (baseName != "") { + mySemaphore.unlink(); + } + myPages.clear(); + baseName = "/" + name; + payLen = len; + hasCounter = withCounter; + mySemaphore.open(baseName.c_str(), O_CREAT | O_EXCL | O_RDWR, ACCESSPERMS, 1); + if (!mySemaphore) { + mySemaphore.open(baseName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1); + } + if (!mySemaphore) { + DEBUG_MSG(DLVL_FAIL, "Creating semaphore failed: %s", strerror(errno)); + return; + } + semGuard tmpGuard(&mySemaphore); + newPage(); + } + + ///\brief The deconstructor + sharedServer::~sharedServer() { + mySemaphore.close(); + mySemaphore.unlink(); + } + + ///\brief Determines whether a sharedServer is valid + sharedServer::operator bool() const { + return myPages.size(); + } + + ///\brief Creates the next page with the correct size + void sharedServer::newPage() { + sharedPage tmp(std::string(baseName.substr(1) + (char)(myPages.size() + (int)'A')), std::min(((8192 * 2)<< myPages.size()), (32 * 1024 * 1024)), true); + myPages.insert(tmp); + tmp.master = false; + DEBUG_MSG(DLVL_VERYHIGH, "Created a new page: %s", tmp.name.c_str()); + } + + ///\brief Deletes the highest allocated page + void sharedServer::deletePage() { + if (myPages.size() == 1) { + DEBUG_MSG(DLVL_WARN, "Can't remove last page for %s", baseName.c_str()); + return; + } + myPages.erase((*myPages.rbegin())); + } + + ///\brief Determines whether an id is currently in use or not + bool sharedServer::isInUse(unsigned int id) { + unsigned int i = 0; + for (std::set::iterator it = myPages.begin(); it != myPages.end(); it++) { + //return if we reached the end + if (!it->mapped || !it->len) { + return false; + } + //not on this page? skip to next. + if (it->len < (id - i)*payLen) { + i += it->len / payLen; + continue; + } + if (hasCounter) { + //counter? return true if it is non-zero. + return (it->mapped[(id - i) * payLen] != 0); + } else { + //no counter - check the entire size for being all zeroes. + for (unsigned int j = 0; j < payLen; ++j) { + if (it->mapped[(id - i)*payLen + j]) { + return true; + } + } + return false; + } + } + //only happens if we run out of pages + return false; + } + + ///\brief Parse each of the possible payload pieces, and runs a callback on it if in use. + void sharedServer::parseEach(void (*callback)(char * data, size_t len, unsigned int id)) { + char * empty = 0; + if (!hasCounter) { + empty = (char *)malloc(payLen * sizeof(char)); + memset(empty, 0, payLen); + } + semGuard tmpGuard(&mySemaphore); + unsigned int id = 0; + unsigned int userCount=0; + unsigned int emptyCount = 0; + for (std::set::iterator it = myPages.begin(); it != myPages.end(); it++) { + if (!it->mapped || !it->len) { + DEBUG_MSG(DLVL_FAIL, "Something went terribly wrong?"); + break; + } + userCount = 0; + unsigned int offset = 0; + while (offset + payLen + (hasCounter ? 1 : 0) <= it->len) { + if (hasCounter) { + if (it->mapped[offset] != 0) { + char * counter = it->mapped+offset; + //increase the count if needed + ++userCount; + if (id >= amount) { + amount = id + 1; + DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount); + } + unsigned short tmpPID = *((unsigned short *)(it->mapped+1+offset+payLen-2)); + if(!Util::Procs::isRunnning(tmpPID) && !(*counter == 126 || *counter == 127 || *counter == 254 || *counter == 255)){ + WARN_MSG("process disappeared, timing out. (pid %d)", tmpPID); + *counter = 126; //if process is already dead, instant timeout. + } + callback(it->mapped + offset + 1, payLen, id); + switch (*counter) { + case 127: + DEBUG_MSG(DLVL_HIGH, "Client %u requested disconnect", id); + break; + case 126: + DEBUG_MSG(DLVL_WARN, "Client %u timed out", id); + break; + case 255: + DEBUG_MSG(DLVL_HIGH, "Client %u disconnected on request", id); + break; + case 254: + DEBUG_MSG(DLVL_WARN, "Client %u disconnect timed out", id); + break; + default: + if(*counter > 10 && *counter < 126 ){ + if(*counter < 30){ + if (*counter > 15){ + WARN_MSG("Process %d is unresponsive",tmpPID); + } + Util::Procs::Stop(tmpPID); //soft kill + } else { + ERROR_MSG("Killing unresponsive process %d", tmpPID); + Util::Procs::Murder(tmpPID); //improved kill + } + } + break; + } + if (*counter == 127 || *counter == 126 || *counter == 255 || *counter == 254) { + memset(it->mapped + offset + 1, 0, payLen); + it->mapped[offset] = 0; + } else { + it->mapped[offset] ++; + } + } else { + //stop if we're past the amount counted and we're empty + if (id >= amount - 1) { + //bring the counter down if this was the last element + if (id == amount - 1) { + amount = id; + DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount); + } + //stop, we're guaranteed no more pages are full at this point + break; + } + } + } else { + if (memcmp(empty, it->mapped + offset, payLen)) { + ++userCount; + //increase the count if needed + if (id >= amount) { + amount = id + 1; + DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount); + } + callback(it->mapped + offset, payLen, id); + } else { + //stop if we're past the amount counted and we're empty + if (id >= amount - 1) { + //bring the counter down if this was the last element + if (id == amount - 1) { + amount = id; + DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount); + } + //stop, we're guaranteed no more pages are full at this point + if (empty) { + free(empty); + } + break; + } + } + } + offset += payLen + (hasCounter ? 1 : 0); + id ++; + } + if(userCount==0) { + ++emptyCount; + } else { + emptyCount=0; + } + } + + if( emptyCount > 1){ + deletePage(); + } else if( !emptyCount ){ + newPage(); + } + + if (empty) { + free(empty); + } + } + + ///\brief Creates an empty shared client + sharedClient::sharedClient() { + hasCounter = 0; + payLen = 0; + offsetOnPage = 0; + } + + ///\brief Copy constructor for sharedClients + ///\param rhs The client ro copy + sharedClient::sharedClient(const sharedClient & rhs) { + baseName = rhs.baseName; + payLen = rhs.payLen; + hasCounter = rhs.hasCounter; +#ifdef __APPLE__ + //note: O_CREAT is only needed for mac, probably + mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0); +#else + mySemaphore.open(baseName.c_str(), O_RDWR); +#endif + if (!mySemaphore) { + DEBUG_MSG(DLVL_FAIL, "Creating semaphore failed: %s", strerror(errno)); + return; + } + semGuard tmpGuard(&mySemaphore); + myPage.init(rhs.myPage.name, rhs.myPage.len, rhs.myPage.master); + offsetOnPage = rhs.offsetOnPage; + } + + ///\brief Assignment operator + void sharedClient::operator =(const sharedClient & rhs) { + baseName = rhs.baseName; + payLen = rhs.payLen; + hasCounter = rhs.hasCounter; +#ifdef __APPLE__ + //note: O_CREAT is only needed for mac, probably + mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0); +#else + mySemaphore.open(baseName.c_str(), O_RDWR); +#endif + if (!mySemaphore) { + DEBUG_MSG(DLVL_FAIL, "Creating copy of semaphore %s failed: %s", baseName.c_str(), strerror(errno)); + return; + } + semGuard tmpGuard(&mySemaphore); + myPage.init(rhs.myPage.name, rhs.myPage.len, rhs.myPage.master); + offsetOnPage = rhs.offsetOnPage; + } + + ///\brief SharedClient Constructor, allocates space on the correct page. + ///\param name The basename of the server to connect to + ///\param len The size of the payload to allocate + ///\param withCounter Whether or not this payload has a counter + sharedClient::sharedClient(std::string name, int len, bool withCounter) : baseName("/"+name), payLen(len), offsetOnPage(-1), hasCounter(withCounter) { +#ifdef __APPLE__ + //note: O_CREAT is only needed for mac, probably + mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0); +#else + mySemaphore.open(baseName.c_str(), O_RDWR); +#endif + if (!mySemaphore) { + DEBUG_MSG(DLVL_FAIL, "Creating semaphore %s failed: %s", baseName.c_str(), strerror(errno)); + return; + } + char * empty = 0; + if (!hasCounter) { + empty = (char *)malloc(payLen * sizeof(char)); + if (!empty) { + DEBUG_MSG(DLVL_FAIL, "Failed to allocate %u bytes for empty payload!", payLen); + return; + } + memset(empty, 0, payLen); + } + while (offsetOnPage == -1){ + { + semGuard tmpGuard(&mySemaphore); + for (char i = 'A'; i <= 'Z'; i++) { + myPage.init(baseName.substr(1) + i, (4096 << (i - 'A')), false, false); + if (!myPage.mapped){ + break; + } + int offset = 0; + while (offset + payLen + (hasCounter ? 1 : 0) <= myPage.len) { + if ((hasCounter && myPage.mapped[offset] == 0) || (!hasCounter && !memcmp(myPage.mapped + offset, empty, payLen))) { + offsetOnPage = offset; + if (hasCounter) { + myPage.mapped[offset] = 1; + *((unsigned short *)(myPage.mapped+1+offset+len-2))=getpid(); + } + break; + } + offset += payLen + (hasCounter ? 1 : 0); + } + if (offsetOnPage != -1) { + break; + } + } + } + if (offsetOnPage == -1){ + Util::wait(500); + } + } + free(empty); + } + + ///\brief The deconstructor + sharedClient::~sharedClient() { + mySemaphore.close(); + } + + ///\brief Writes data to the shared data + void sharedClient::write(char * data, int len) { + if (hasCounter) { + keepAlive(); + } + memcpy(myPage.mapped + offsetOnPage + (hasCounter ? 1 : 0), data, std::min(len, payLen)); + } + + ///\brief Indicate that the process is done using this piece of memory, set the counter to finished + void sharedClient::finish() { + if (!myPage.mapped) { + return; + } + if (!hasCounter) { + DEBUG_MSG(DLVL_WARN, "Trying to time-out an element without counters"); + return; + } + if (myPage.mapped) { + semGuard tmpGuard(&mySemaphore); + myPage.mapped[offsetOnPage] = 127; + } + } + + ///\brief Re-initialize the counter + void sharedClient::keepAlive() { + if (!hasCounter) { + DEBUG_MSG(DLVL_WARN, "Trying to keep-alive an element without counters"); + return; + } + if (myPage.mapped[offsetOnPage] < 128) { + myPage.mapped[offsetOnPage] = 1; + } else { + DEBUG_MSG(DLVL_WARN, "Trying to keep-alive an element that needs to timeout, ignoring"); + } + } + + ///\brief Get a pointer to the data of this client + char * sharedClient::getData() { + if (!myPage.mapped) { + return 0; + } + return (myPage.mapped + offsetOnPage + (hasCounter ? 1 : 0)); + } +} + diff --git a/lib/shared_memory.h b/lib/shared_memory.h new file mode 100644 index 00000000..4567927e --- /dev/null +++ b/lib/shared_memory.h @@ -0,0 +1,224 @@ +#pragma once +#include +#include + +#include "timing.h" + +#ifdef __CYGWIN__ +#include +#else +#include +#endif + +#define STAT_EX_SIZE 172 +#define PLAY_EX_SIZE 32 + +namespace IPC { + + ///\brief A class used for the exchange of statistics over shared memory. + class statExchange { + public: + statExchange(char * _data); + void now(long long int time); + long long int now(); + void time(long time); + long time(); + void lastSecond(long time); + long lastSecond(); + void down(long long int bytes); + long long int down(); + void up(long long int bytes); + long long int up(); + void host(std::string name); + std::string host(); + void streamName(std::string name); + std::string streamName(); + void connector(std::string name); + std::string connector(); + void crc(unsigned int sum); + unsigned int crc(); + private: + ///\brief The payload for the stat exchange + /// - 8 byte - now (timestamp of last statistics) + /// - 4 byte - time (duration of the current connection) + /// - 4 byte - lastSecond (last second of content viewed) + /// - 8 byte - down (Number of bytes received from peer) + /// - 8 byte - up (Number of bytes sent to peer) + /// - 16 byte - host (ip address of the peer) + /// - 100 byte - streamName (name of the stream peer is viewing) + /// - 20 byte - connector (name of the connector the peer is using) + /// - 4 byte - CRC32 of user agent (or zero if none) + char * data; + }; + + ///\brief A class used for the abstraction of semaphores + class semaphore { + public: + semaphore(); + semaphore(const char * name, int oflag, mode_t mode, unsigned int value); + ~semaphore(); + operator bool() const; + void open(const char * name, int oflag, mode_t mode = 0, unsigned int value = 0); + int getVal() const; + void post(); + void wait(); + bool tryWait(); + void close(); + void unlink(); + private: +#ifdef __CYGWIN__ + HANDLE mySem; +#else + sem_t * mySem; +#endif + std::string myName; + }; + + ///\brief A class used as a semaphore guard + class semGuard { + public: + semGuard(semaphore * thisSemaphore); + ~semGuard(); + private: + ///\brief The semaphore to guard. + semaphore * mySemaphore; + }; + + ///\brief A class for managing shared files. + class sharedFile { + public: + sharedFile(std::string name_ = "", unsigned int len_ = 0, bool master_ = false, bool autoBackoff = true); + sharedFile(const sharedFile & rhs); + ~sharedFile(); + operator bool() const; + void init(std::string name_, unsigned int len_, bool master_ = false, bool autoBackoff = true); + void operator =(sharedFile & rhs); + bool operator < (const sharedFile & rhs) const { + return name < rhs.name; + } + void close(); + void unmap(); + ///\brief The fd handle of the opened shared file + int handle; + ///\brief The name of the opened shared file + std::string name; + ///\brief The size in bytes of the opened shared file + long long int len; + ///\brief Whether this class should unlink the shared file upon deletion or not + bool master; + ///\brief A pointer to the payload of the file file + char * mapped; + }; + +#ifdef SHM_ENABLED + ///\brief A class for managing shared memory pages. + class sharedPage { + public: + sharedPage(std::string name_ = "", unsigned int len_ = 0, bool master_ = false, bool autoBackoff = true); + sharedPage(const sharedPage & rhs); + ~sharedPage(); + operator bool() const; + void init(std::string name_, unsigned int len_, bool master_ = false, bool autoBackoff = true); + void operator =(sharedPage & rhs); + bool operator < (const sharedPage & rhs) const { + return name < rhs.name; + } + void unmap(); + void close(); + #ifdef __CYGWIN__ + ///\brief The handle of the opened shared memory page + HANDLE handle; + #else + ///\brief The fd handle of the opened shared memory page + int handle; + #endif + ///\brief The name of the opened shared memory page + std::string name; + ///\brief The size in bytes of the opened shared memory page + long long int len; + ///\brief Whether this class should unlink the shared memory upon deletion or not + bool master; + ///\brief A pointer to the payload of the page + char * mapped; + }; +#else + ///\brief A class for handling shared memory pages. + ///Uses shared files at its backbone, defined for portability + class sharedPage: public sharedFile { + public: + sharedPage(std::string name_ = "", unsigned int len_ = 0, bool master_ = false, bool autoBackoff = true); + sharedPage(const sharedPage & rhs); + ~sharedPage(); + }; +#endif + + ///\brief The server part of a server/client model for shared memory. + /// + ///The server manages the shared memory pages, and allocates new pages when needed. + /// + ///Pages are created with a basename + index, where index is in the range of 'A' - 'Z' + ///Each time a page is nearly full, the next page is created with a size double to the previous one. + /// + ///Clients should allocate payLen bytes at a time, possibly with the addition of a counter. + ///If no such length can be allocated, the next page should be tried, and so on. + class sharedServer { + public: + sharedServer(); + sharedServer(std::string name, int len, bool withCounter = false); + void init(std::string name, int len, bool withCounter = false); + ~sharedServer(); + void parseEach(void (*callback)(char * data, size_t len, unsigned int id)); + operator bool() const; + ///\brief The amount of connected clients + unsigned int amount; + private: + bool isInUse(unsigned int id); + void newPage(); + void deletePage(); + ///\brief The basename of the shared pages. + std::string baseName; + ///\brief The length of each consecutive piece of payload + unsigned int payLen; + ///\brief The set of sharedPage structures to manage the actual memory + std::set myPages; + ///\brief A semaphore that is locked upon creation and deletion of the page, to ensure no new data is allocated during this step. + semaphore mySemaphore; + ///\brief Whether the payload has a counter, if so, it is added in front of the payload + bool hasCounter; + }; + + ///\brief The client part of a server/client model for shared memory. + /// + ///The server manages the shared memory pages, and allocates new pages when needed. + /// + ///Pages are created with a basename + index, where index is in the range of 'A' - 'Z' + ///Each time a page is nearly full, the next page is created with a size double to the previous one. + /// + ///Clients should allocate payLen bytes at a time, possibly with the addition of a counter. + ///If no such length can be allocated, the next page should be tried, and so on. + class sharedClient { + public: + sharedClient(); + sharedClient(const sharedClient & rhs); + sharedClient(std::string name, int len, bool withCounter = false); + void operator = (const sharedClient & rhs); + ~sharedClient(); + void write(char * data, int len); + void finish(); + void keepAlive(); + char * getData(); + private: + ///\brief The basename of the shared pages. + std::string baseName; + ///\brief The shared page this client has reserved a space on. + sharedPage myPage; + ///\brief A semaphore that is locked upon trying to allocate space on a page + semaphore mySemaphore; + ///\brief The size in bytes of the opened page + int payLen; + ///\brief The offset of the payload reserved for this client within the opened page + int offsetOnPage; + ///\brief Whether the payload has a counter, if so, it is added in front of the payload + bool hasCounter; + }; +} diff --git a/lib/socket.cpp b/lib/socket.cpp new file mode 100644 index 00000000..6dbf9a3f --- /dev/null +++ b/lib/socket.cpp @@ -0,0 +1,1187 @@ +/// \file socket.cpp +/// A handy Socket wrapper library. +/// Written by Jaron Vietor in 2010 for DDVTech + +#include "socket.h" +#include "timing.h" +#include "defines.h" +#include +#include +#include +#include +#include + +#ifdef __FreeBSD__ +#include +#endif + +#define BUFFER_BLOCKSIZE 4096 //set buffer blocksize to 4KiB + +#ifdef __CYGWIN__ +#define SOCKETSIZE 8092ul +#else +#define SOCKETSIZE 51200ul +#endif + +std::string uint2string(unsigned int i) { + std::stringstream st; + st << i; + return st.str(); +} + +/// Returns the amount of elements in the internal std::deque of std::string objects. +/// The back is popped as long as it is empty, first - this way this function is +/// guaranteed to return 0 if the buffer is empty. +unsigned int Socket::Buffer::size() { + while (data.size() > 0 && data.back().empty()) { + data.pop_back(); + } + return data.size(); +} + +/// Returns either the amount of total bytes available in the buffer or max, whichever is smaller. +unsigned int Socket::Buffer::bytes(unsigned int max) { + unsigned int i = 0; + for (std::deque::iterator it = data.begin(); it != data.end(); ++it) { + i += (*it).size(); + if (i >= max) { + return max; + } + } + return i; +} + +/// Appends this string to the internal std::deque of std::string objects. +/// It is automatically split every BUFFER_BLOCKSIZE bytes. +void Socket::Buffer::append(const std::string & newdata) { + append(newdata.c_str(), newdata.size()); +} + +/// Appends this data block to the internal std::deque of std::string objects. +/// It is automatically split every BUFFER_BLOCKSIZE bytes. +void Socket::Buffer::append(const char * newdata, const unsigned int newdatasize) { + unsigned int i = 0, j = 0; + while (i < newdatasize) { + j = i; + while (j < newdatasize && j - i <= BUFFER_BLOCKSIZE) { + j++; + if (newdata[j - 1] == '\n') { + break; + } + } + if (i != j) { + data.push_front(std::string(newdata + i, (size_t)(j - i))); + i = j; + } else { + break; + } + } + if (data.size() > 5000) { + DEBUG_MSG(DLVL_WARN, "Warning: After %d new bytes, buffer has %d parts!", newdatasize, (int)data.size()); + } +} + +/// Prepends this data block to the internal std::deque of std::string objects. +/// It is _not_ automatically split every BUFFER_BLOCKSIZE bytes. +void Socket::Buffer::prepend(const std::string & newdata) { + data.push_back(newdata); +} + +/// Prepends this data block to the internal std::deque of std::string objects. +/// It is _not_ automatically split every BUFFER_BLOCKSIZE bytes. +void Socket::Buffer::prepend(const char * newdata, const unsigned int newdatasize) { + data.push_back(std::string(newdata, (size_t)newdatasize)); +} + +/// Returns true if at least count bytes are available in this buffer. +bool Socket::Buffer::available(unsigned int count) { + unsigned int i = 0; + for (std::deque::iterator it = data.begin(); it != data.end(); ++it) { + i += (*it).size(); + if (i >= count) { + return true; + } + } + return false; +} + +/// Removes count bytes from the buffer, returning them by value. +/// Returns an empty string if not all count bytes are available. +std::string Socket::Buffer::remove(unsigned int count) { + if (!available(count)) { + return ""; + } + unsigned int i = 0; + std::string ret; + ret.reserve(count); + for (std::deque::reverse_iterator it = data.rbegin(); it != data.rend(); ++it) { + if (i + (*it).size() < count) { + ret.append(*it); + i += (*it).size(); + (*it).clear(); + } else { + ret.append(*it, 0, count - i); + (*it).erase(0, count - i); + break; + } + } + return ret; +} + +/// Copies count bytes from the buffer, returning them by value. +/// Returns an empty string if not all count bytes are available. +std::string Socket::Buffer::copy(unsigned int count) { + if (!available(count)) { + return ""; + } + unsigned int i = 0; + std::string ret; + ret.reserve(count); + for (std::deque::reverse_iterator it = data.rbegin(); it != data.rend(); ++it) { + if (i + (*it).size() < count) { + ret.append(*it); + i += (*it).size(); + } else { + ret.append(*it, 0, count - i); + break; + } + } + return ret; +} + +/// Gets a reference to the back of the internal std::deque of std::string objects. +std::string & Socket::Buffer::get() { + static std::string empty; + if (data.size() > 0) { + return data.back(); + } else { + return empty; + } +} + +/// Completely empties the buffer +void Socket::Buffer::clear() { + data.clear(); +} + +/// 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; + pipes[0] = -1; + pipes[1] = -1; + up = 0; + down = 0; + conntime = Util::epoch(); + Error = false; + Blocking = false; +} //Socket::Connection basic constructor + +/// Simulate a socket using two file descriptors. +/// \param write The filedescriptor to write to. +/// \param read The filedescriptor to read from. +Socket::Connection::Connection(int write, int read) { + sock = -1; + pipes[0] = write; + pipes[1] = read; + up = 0; + down = 0; + conntime = Util::epoch(); + 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; + pipes[0] = -1; + pipes[1] = -1; + up = 0; + down = 0; + conntime = Util::epoch(); + Error = false; + Blocking = false; +} //Socket::Connection basic constructor + +/// Internally used call to make an file descriptor blocking or not. +void setFDBlocking(int FD, bool blocking) { + int flags = fcntl(FD, F_GETFL, 0); + if (!blocking) { + flags |= O_NONBLOCK; + } else { + flags &= !O_NONBLOCK; + } + fcntl(FD, F_SETFL, flags); +} + +/// Internally used call to make an file descriptor blocking or not. +bool isFDBlocking(int FD) { + int flags = fcntl(FD, F_GETFL, 0); + return !(flags & O_NONBLOCK); +} + +/// Set this socket to be blocking (true) or nonblocking (false). +void Socket::Connection::setBlocking(bool blocking) { + if (sock >= 0) { + setFDBlocking(sock, blocking); + } + if (pipes[0] >= 0) { + setFDBlocking(pipes[0], blocking); + } + if (pipes[1] >= 0) { + setFDBlocking(pipes[1], blocking); + } +} + +/// Set this socket to be blocking (true) or nonblocking (false). +bool Socket::Connection::isBlocking() { + if (sock >= 0) { + return isFDBlocking(sock); + } + if (pipes[0] >= 0) { + return isFDBlocking(pipes[0]); + } + if (pipes[1] >= 0) { + return isFDBlocking(pipes[1]); + } + return false; +} + +/// Close connection. The internal socket is closed and then set to -1. +/// If the connection is already closed, nothing happens. +/// This function calls shutdown, thus making the socket unusable in all other +/// processes as well. Do not use on shared sockets that are still in use. +void Socket::Connection::close() { + if (sock != -1) { + shutdown(sock, SHUT_RDWR); + } + drop(); +} //Socket::Connection::close + +/// Close connection. The internal socket is closed and then set to -1. +/// If the connection is already closed, nothing happens. +/// This function does *not* call shutdown, allowing continued use in other +/// processes. +void Socket::Connection::drop() { + if (connected()) { + if (sock != -1) { + DEBUG_MSG(DLVL_HIGH, "Socket %d closed", sock); + errno = EINTR; + while (::close(sock) != 0 && errno == EINTR) { + } + sock = -1; + } + if (pipes[0] != -1) { + errno = EINTR; + while (::close(pipes[0]) != 0 && errno == EINTR) { + } + pipes[0] = -1; + } + if (pipes[1] != -1) { + errno = EINTR; + while (::close(pipes[1]) != 0 && errno == EINTR) { + } + pipes[1] = -1; + } + } +} //Socket::Connection::drop + +/// Returns internal socket number. +int Socket::Connection::getSocket() { + if (sock != -1){ + return sock; + } + if (pipes[0] != -1) { + return pipes[0]; + } + if (pipes[1] != -1) { + return pipes[1]; + } + return -1; +} + +/// Returns non-piped internal socket number. +int Socket::Connection::getPureSocket() { + return sock; +} + +/// Returns a string describing the last error that occured. +/// Only reports errors if an error actually occured - returns the host address or empty string otherwise. +std::string Socket::Connection::getError() { + return remotehost; +} + +/// 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) { + pipes[0] = -1; + pipes[1] = -1; + sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + remotehost = strerror(errno); + DEBUG_MSG(DLVL_FAIL, "Could not create socket! Error: %s", remotehost.c_str()); + return; + } + Error = false; + Blocking = false; + up = 0; + down = 0; + conntime = Util::epoch(); + 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 { + remotehost = strerror(errno); + DEBUG_MSG(DLVL_FAIL, "Could not connect to %s! Error: %s", address.c_str(), remotehost.c_str()); + 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) { + pipes[0] = -1; + pipes[1] = -1; + struct addrinfo * result, *rp, hints; + Error = false; + Blocking = false; + up = 0; + down = 0; + conntime = Util::epoch(); + 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) { + DEBUG_MSG(DLVL_FAIL, "Could not connect to %s:%i! Error: %s", host.c_str(), port, gai_strerror(s)); + close(); + return; + } + + remotehost = ""; + 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; + } + remotehost += strerror(errno); + ::close(sock); + } + freeaddrinfo(result); + + if (rp == 0) { + DEBUG_MSG(DLVL_FAIL, "Could not connect to %s! Error: %s", host.c_str(), remotehost.c_str()); + close(); + } else { + if (nonblock) { + int flags = fcntl(sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + } + } +} //Socket::Connection TCP Contructor + +/// 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) || ((pipes[0] >= 0) && (pipes[1] >= 0)); +} + +/// Returns the time this socket has been connected. +unsigned int Socket::Connection::connTime() { + return conntime; +} + +/// 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 "S " + getHost() + " " + C + " " + uint2string(Util::epoch() - conntime) + " " + uint2string(up) + " " + uint2string(down) + "\n"; +} + +/// Updates the downbuffer internal variable. +/// Returns true if new data was received, false otherwise. +bool Socket::Connection::spool() { + /// \todo Provide better mechanism to prevent overbuffering. + if (downbuffer.size() > 10000) { + return true; + } else { + return iread(downbuffer); + } +} + +bool Socket::Connection::peek() { + /// clear buffer + downbuffer.clear(); + return iread(downbuffer, MSG_PEEK); +} + +/// Returns a reference to the download buffer. +Socket::Buffer & Socket::Connection::Received() { + return downbuffer; +} + +/// Will not buffer anything but always send right away. Blocks. +/// Any data that could not be send will block until it can be send or the connection is severed. +void Socket::Connection::SendNow(const char * data, size_t len) { + bool bing = isBlocking(); + if (!bing) { + setBlocking(true); + } + unsigned int i = iwrite(data, std::min((long unsigned int)len, SOCKETSIZE)); + while (i < len && connected()) { + i += iwrite(data + i, std::min((long unsigned int)(len - i), SOCKETSIZE)); + } + if (!bing) { + setBlocking(false); + } +} + +/// Will not buffer anything but always send right away. Blocks. +/// Any data that could not be send will block until it can be send or the connection is severed. +void Socket::Connection::SendNow(const char * data) { + int len = strlen(data); + SendNow(data, len); +} + +/// Will not buffer anything but always send right away. Blocks. +/// Any data that could not be send will block until it can be send or the connection is severed. +void Socket::Connection::SendNow(const std::string & data) { + SendNow(data.data(), 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. +unsigned int Socket::Connection::iwrite(const void * buffer, int len) { + if (!connected() || len < 1) { + return 0; + } + int r; + if (sock >= 0) { + r = send(sock, buffer, len, 0); + } else { + r = write(pipes[0], buffer, len); + } + if (r < 0) { + switch (errno) { + case EWOULDBLOCK: + return 0; + break; + default: + if (errno != EPIPE && errno != ECONNRESET) { + Error = true; + remotehost = strerror(errno); + DEBUG_MSG(DLVL_WARN, "Could not iwrite data! Error: %s", remotehost.c_str()); + } + close(); + return 0; + break; + } + } + if (r == 0 && (sock >= 0)) { + 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. +/// \param flags Flags to use in the recv call. Ignored on fake sockets. +/// \returns The amount of bytes actually read. +int Socket::Connection::iread(void * buffer, int len, int flags) { + if (!connected() || len < 1) { + return 0; + } + int r; + if (sock >= 0) { + r = recv(sock, buffer, len, flags); + } else { + r = recv(pipes[1], buffer, len, flags); + if (r < 0 && errno == ENOTSOCK){ + r = read(pipes[1], buffer, len); + } + } + if (r < 0) { + switch (errno) { + case EWOULDBLOCK: + return 0; + break; + case EINTR: + return 0; + break; + default: + if (errno != EPIPE) { + Error = true; + remotehost = strerror(errno); + DEBUG_MSG(DLVL_WARN, "Could not iread data! Error: %s", remotehost.c_str()); + } + close(); + return 0; + break; + } + } + if (r == 0) { + close(); + } + down += r; + return r; +} //Socket::Connection::iread + +/// Read call that is compatible with Socket::Buffer. +/// Data is read using iread (which is nonblocking if the Socket::Connection itself is), +/// then appended to end of buffer. +/// \param buffer Socket::Buffer to append data to. +/// \param flags Flags to use in the recv call. Ignored on fake sockets. +/// \return True if new data arrived, false otherwise. +bool Socket::Connection::iread(Buffer & buffer, int flags) { + char cbuffer[BUFFER_BLOCKSIZE]; + int num = iread(cbuffer, BUFFER_BLOCKSIZE, flags); + 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; + } + unsigned int tmp = iwrite((void *)buffer.c_str(), buffer.size()); + if (!tmp) { + return false; + } + buffer = buffer.substr(tmp); + return true; +} //iwrite + +/// Gets hostname for connection, if available. +std::string Socket::Connection::getHost() { + return remotehost; +} + +/// Gets hostname for connection, if available. +/// Guaranteed to be either empty or 16 bytes long. +std::string Socket::Connection::getBinHost() { + if (remotehost.size()){ + struct addrinfo * result, *rp, hints; + 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(remotehost.c_str(), 0, &hints, &result); + if (s != 0) { + DEBUG_MSG(DLVL_FAIL, "Could not resolve '%s'! Error: %s", remotehost.c_str(), gai_strerror(s)); + return ""; + } + char tmpBuffer[17] = "\000\000\000\000\000\000\000\000\000\000\377\377\000\000\000\000"; + for (rp = result; rp != NULL; rp = rp->ai_next) { + if (rp->ai_family == AF_INET){ + memcpy(tmpBuffer + 12, &((sockaddr_in *)rp->ai_addr)->sin_addr.s_addr, 4); + } + if (rp->ai_family == AF_INET6){ + memcpy(tmpBuffer, ((sockaddr_in6 *)rp->ai_addr)->sin6_addr.s6_addr, 16); + } + } + freeaddrinfo(result); + return std::string(tmpBuffer, 16); + }else{ + return ""; + } +} + +/// Sets hostname for connection manually. +/// Overwrites the detected host, thus possibily making it incorrect. +void Socket::Connection::setHost(std::string host) { + remotehost = host; +} + +/// 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 && pipes[0] == B.pipes[0] && pipes[1] == B.pipes[1]; +} + +/// 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 || pipes[0] != B.pipes[0] || pipes[1] != B.pipes[1]; +} + +/// Returns true if the socket is valid. +/// Aliases for Socket::Connection::connected() +Socket::Connection::operator bool() const { + return connected(); +} + +/// Returns true if the given address can be matched with the remote host. +/// Can no longer return true after any socket error have occurred. +bool Socket::Connection::isAddress(std::string addr) { + struct addrinfo * result, *rp, hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; + hints.ai_protocol = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + int s = getaddrinfo(addr.c_str(), 0, &hints, &result); + if (s != 0) { + return false; + } + + char newaddr[INET_ADDRSTRLEN]; + newaddr[0] = 0; + for (rp = result; rp != NULL; rp = rp->ai_next) { + if (rp->ai_family == AF_INET && inet_ntop(rp->ai_family, &(((sockaddr_in *)rp->ai_addr)->sin_addr), newaddr, INET_ADDRSTRLEN)) { + DEBUG_MSG(DLVL_DEVEL, "Comparing: '%s' to '%s'", remotehost.c_str(), newaddr); + if (remotehost == newaddr) { + return true; + } + DEBUG_MSG(DLVL_DEVEL, "Comparing: '%s' to '::ffff:%s'", remotehost.c_str(), newaddr); + if (remotehost == std::string("::ffff:") + newaddr) { + return true; + } + } + if (rp->ai_family == AF_INET6 && inet_ntop(rp->ai_family, &(((sockaddr_in6 *)rp->ai_addr)->sin6_addr), newaddr, INET_ADDRSTRLEN)) { + DEBUG_MSG(DLVL_DEVEL, "Comparing: '%s' to '%s'", remotehost.c_str(), newaddr); + if (remotehost == newaddr) { + return true; + } + } + } + freeaddrinfo(result); + return false; +} + +/// 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)) { + DEBUG_MSG(DLVL_FAIL, "Could not create socket %s:%i! Error: %s", hostname.c_str(), port, errors.c_str()); + 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) { + errors = strerror(errno); + DEBUG_MSG(DLVL_ERROR, "Could not create IPv6 socket %s:%i! Error: %s", hostname.c_str(), port, errors.c_str()); + return false; + } + int on = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); +#ifdef __CYGWIN__ + on = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); +#endif + if (nonblock) { + int flags = fcntl(sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + } + struct sockaddr_in6 addr; + memset(&addr, 0, sizeof(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) { + DEBUG_MSG(DLVL_DEVEL, "IPv6 socket success @ %s:%i", hostname.c_str(), port); + return true; + } else { + errors = strerror(errno); + DEBUG_MSG(DLVL_ERROR, "IPv6 listen failed! Error: %s", errors.c_str()); + close(); + return false; + } + } else { + errors = strerror(errno); + DEBUG_MSG(DLVL_ERROR, "IPv6 Binding %s:%i failed (%s)", hostname.c_str(), port, errors.c_str()); + 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) { + errors = strerror(errno); + DEBUG_MSG(DLVL_ERROR, "Could not create IPv4 socket %s:%i! Error: %s", hostname.c_str(), port, errors.c_str()); + 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; + memset(&addr4, 0, sizeof(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) { + DEBUG_MSG(DLVL_DEVEL, "IPv4 socket success @ %s:%i", hostname.c_str(), port); + return true; + } else { + errors = strerror(errno); + DEBUG_MSG(DLVL_ERROR, "IPv4 listen failed! Error: %s", errors.c_str()); + close(); + return false; + } + } else { + errors = strerror(errno); + DEBUG_MSG(DLVL_ERROR, "IPv4 Binding %s:%i failed (%s)", hostname.c_str(), port, errors.c_str()); + 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) { + errors = strerror(errno); + DEBUG_MSG(DLVL_ERROR, "Could not create unix socket %s! Error: %s", address.c_str(), errors.c_str()); + 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 { + errors = strerror(errno); + DEBUG_MSG(DLVL_ERROR, "Unix listen failed! Error: %s", errors.c_str()); + close(); + return; + } + } else { + errors = strerror(errno); + DEBUG_MSG(DLVL_ERROR, "Unix Binding %s failed (%s)", address.c_str(), errors.c_str()); + 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)) { + DEBUG_MSG(DLVL_FAIL, "Error during accept - closing server socket %d.", sock); + close(); + } + } else { + if (addrinfo.sin6_family == AF_INET6) { + tmp.remotehost = inet_ntop(AF_INET6, &(addrinfo.sin6_addr), addrconv, INET6_ADDRSTRLEN); + DEBUG_MSG(DLVL_HIGH, "IPv6 addr [%s]", tmp.remotehost.c_str()); + } + if (addrinfo.sin6_family == AF_INET) { + tmp.remotehost = inet_ntop(AF_INET, &(((sockaddr_in *) &addrinfo)->sin_addr), addrconv, INET6_ADDRSTRLEN); + DEBUG_MSG(DLVL_HIGH, "IPv4 addr [%s]", tmp.remotehost.c_str()); + } + if (addrinfo.sin6_family == AF_UNIX) { + DEBUG_MSG(DLVL_HIGH, "Unix connection"); + tmp.remotehost = "UNIX_SOCKET"; + } + } + return tmp; +} + +/// Set this socket to be blocking (true) or nonblocking (false). +void Socket::Server::setBlocking(bool blocking) { + if (sock >= 0) { + setFDBlocking(sock, blocking); + } +} + +/// Set this socket to be blocking (true) or nonblocking (false). +bool Socket::Server::isBlocking() { + if (sock >= 0) { + return isFDBlocking(sock); + } + return false; +} + +/// Close connection. The internal socket is closed and then set to -1. +/// If the connection is already closed, nothing happens. +/// This function calls shutdown, thus making the socket unusable in all other +/// processes as well. Do not use on shared sockets that are still in use. +void Socket::Server::close() { + if (sock != -1) { + shutdown(sock, SHUT_RDWR); + } + drop(); +} //Socket::Server::close + +/// Close connection. The internal socket is closed and then set to -1. +/// If the connection is already closed, nothing happens. +/// This function does *not* call shutdown, allowing continued use in other +/// processes. +void Socket::Server::drop() { + if (connected()) { + if (sock != -1) { + DEBUG_MSG(DLVL_HIGH, "ServerSocket %d closed", sock); + errno = EINTR; + while (::close(sock) != 0 && errno == EINTR) { + } + sock = -1; + } + } +} //Socket::Server::drop + +/// 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; +} + +/// Create a new UDP Socket. +/// Will attempt to create an IPv6 UDP socket, on fail try a IPV4 UDP socket. +/// If both fail, prints an DLVL_FAIL debug message. +/// \param nonblock Whether the socket should be nonblocking. +Socket::UDPConnection::UDPConnection(bool nonblock) { + sock = socket(AF_INET6, SOCK_DGRAM, 0); + if (sock == -1) { + sock = socket(AF_INET, SOCK_DGRAM, 0); + } + if (sock == -1) { + DEBUG_MSG(DLVL_FAIL, "Could not create UDP socket: %s", strerror(errno)); + } + up = 0; + down = 0; + destAddr = 0; + destAddr_size = 0; + data = 0; + data_size = 0; + data_len = 0; + if (nonblock) { + setBlocking(!nonblock); + } +} //Socket::UDPConnection UDP Contructor + +/// Copies a UDP socket, re-allocating local copies of any needed structures. +/// The data/data_size/data_len variables are *not* copied over. +Socket::UDPConnection::UDPConnection(const UDPConnection & o) { + sock = socket(AF_INET6, SOCK_DGRAM, 0); + if (sock == -1) { + sock = socket(AF_INET, SOCK_DGRAM, 0); + } + if (sock == -1) { + DEBUG_MSG(DLVL_FAIL, "Could not create UDP socket: %s", strerror(errno)); + } + up = 0; + down = 0; + if (o.destAddr && o.destAddr_size) { + destAddr = malloc(o.destAddr_size); + if (destAddr) { + memcpy(destAddr, o.destAddr, o.destAddr_size); + } + } else { + destAddr = 0; + destAddr_size = 0; + } + data = 0; + data_size = 0; + data_len = 0; +} + +/// Closes the UDP socket, cleans up any memory allocated by the socket. +Socket::UDPConnection::~UDPConnection() { + if (sock != -1) { + errno = EINTR; + while (::close(sock) != 0 && errno == EINTR) { + } + sock = -1; + } + if (destAddr) { + free(destAddr); + destAddr = 0; + } + if (data) { + free(data); + data = 0; + } +} + +/// Stores the properties of the receiving end of this UDP socket. +/// This will be the receiving end for all SendNow calls. +void Socket::UDPConnection::SetDestination(std::string destIp, uint32_t port) { + if (destAddr) { + free(destAddr); + destAddr = 0; + } + destAddr = malloc(sizeof(struct sockaddr_in6)); + if (destAddr) { + destAddr_size = sizeof(struct sockaddr_in6); + memset(destAddr, 0, destAddr_size); + ((struct sockaddr_in6 *)destAddr)->sin6_family = AF_INET6; + ((struct sockaddr_in6 *)destAddr)->sin6_port = htons(port); + if (inet_pton(AF_INET6, destIp.c_str(), &(((struct sockaddr_in6 *)destAddr)->sin6_addr)) == 1) { + return; + } + memset(destAddr, 0, destAddr_size); + ((struct sockaddr_in *)destAddr)->sin_family = AF_INET; + ((struct sockaddr_in *)destAddr)->sin_port = htons(port); + if (inet_pton(AF_INET, destIp.c_str(), &(((struct sockaddr_in *)destAddr)->sin_addr)) == 1) { + return; + } + } + free(destAddr); + destAddr = 0; + DEBUG_MSG(DLVL_FAIL, "Could not set destination for UDP socket: %s:%d", destIp.c_str(), port); +}//Socket::UDPConnection SetDestination + +/// Gets the properties of the receiving end of this UDP socket. +/// This will be the receiving end for all SendNow calls. +void Socket::UDPConnection::GetDestination(std::string & destIp, uint32_t & port) { + if (!destAddr || !destAddr_size) { + destIp = ""; + port = 0; + return; + } + char addr_str[INET6_ADDRSTRLEN + 1]; + addr_str[INET6_ADDRSTRLEN] = 0;//set last byte to zero, to prevent walking out of the array + if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET6) { + if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)destAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0) { + destIp = addr_str; + port = ntohs(((struct sockaddr_in6 *)destAddr)->sin6_port); + return; + } + } + if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET) { + if (inet_ntop(AF_INET, &(((struct sockaddr_in *)destAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0) { + destIp = addr_str; + port = ntohs(((struct sockaddr_in *)destAddr)->sin_port); + return; + } + } + destIp = ""; + port = 0; + DEBUG_MSG(DLVL_FAIL, "Could not get destination for UDP socket"); +}//Socket::UDPConnection GetDestination + +/// Sets the socket to be blocking if the parameters is true. +/// Sets the socket to be non-blocking otherwise. +void Socket::UDPConnection::setBlocking(bool blocking) { + if (sock >= 0) { + setFDBlocking(sock, blocking); + } +} + +/// Sends a UDP datagram using the buffer sdata. +/// This function simply calls SendNow(const char*, size_t) +void Socket::UDPConnection::SendNow(const std::string & sdata) { + SendNow(sdata.c_str(), sdata.size()); +} + +/// Sends a UDP datagram using the buffer sdata. +/// sdata is required to be NULL-terminated. +/// This function simply calls SendNow(const char*, size_t) +void Socket::UDPConnection::SendNow(const char * sdata) { + int len = strlen(sdata); + SendNow(sdata, len); +} + +/// Sends a UDP datagram using the buffer sdata of length len. +/// Does not do anything if len < 1. +/// Prints an DLVL_FAIL level debug message if sending failed. +void Socket::UDPConnection::SendNow(const char * sdata, size_t len) { + if (len < 1) { + return; + } + int r = sendto(sock, sdata, len, 0, (sockaddr *)destAddr, destAddr_size); + if (r > 0) { + up += r; + } else { + DEBUG_MSG(DLVL_FAIL, "Could not send UDP data through %d: %s", sock, strerror(errno)); + } +} + +/// Bind to a port number, returning the bound port. +/// Attempts to bind over IPv6 first. +/// If it fails, attempts to bind over IPv4. +/// If that fails too, gives up and returns zero. +/// Prints a debug message at DLVL_FAIL level if binding failed. +/// \return Actually bound port number, or zero on error. +int Socket::UDPConnection::bind(int port) { + struct sockaddr_in6 s6; + s6.sin6_family = AF_INET6; + s6.sin6_addr = in6addr_any; + if (port) { + s6.sin6_port = htons(port); + } + int r = ::bind(sock, (sockaddr *)&s6, sizeof(s6)); + if (r == 0) { + return ntohs(s6.sin6_port); + } + + struct sockaddr_in s4; + s4.sin_family = AF_INET; + s4.sin_addr.s_addr = INADDR_ANY; + if (port) { + s4.sin_port = htons(port); + } + r = ::bind(sock, (sockaddr *)&s4, sizeof(s4)); + if (r == 0) { + return ntohs(s4.sin_port); + } + + DEBUG_MSG(DLVL_FAIL, "Could not bind UDP socket to port %d", port); + return 0; +} + +/// Attempt to receive a UDP packet. +/// This will automatically allocate or resize the internal data buffer if needed. +/// If a packet is received, it will be placed in the "data" member, with it's length in "data_len". +/// \return True if a packet was received, false otherwise. +bool Socket::UDPConnection::Receive() { + int r = recvfrom(sock, data, data_size, MSG_PEEK | MSG_TRUNC, 0, 0); + if (data_size < (unsigned int)r) { + data = (char *)realloc(data, r); + if (data) { + data_size = r; + } else { + data_size = 0; + } + } + socklen_t destsize = destAddr_size; + r = recvfrom(sock, data, data_size, 0, (sockaddr *)destAddr, &destsize); + if (r > 0) { + down += r; + data_len = r; + return true; + } else { + data_len = 0; + return false; + } +} + +int Socket::UDPConnection::getSock() { + return sock; +} diff --git a/lib/socket.h b/lib/socket.h new file mode 100644 index 00000000..62de9d6a --- /dev/null +++ b/lib/socket.h @@ -0,0 +1,149 @@ +/// \file socket.h +/// A handy Socket wrapper library. +/// Written by Jaron Vietor in 2010 for DDVTech + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//for being friendly with Socket::Connection down below +namespace Buffer { + class user; +} + +///Holds Socket tools. +namespace Socket { + + /// A buffer made out of std::string objects that can be efficiently read from and written to. + class Buffer { + private: + std::deque data; + public: + unsigned int size(); + unsigned int bytes(unsigned int max); + void append(const std::string & newdata); + void append(const char * newdata, const unsigned int newdatasize); + void prepend(const std::string & newdata); + void prepend(const char * newdata, const unsigned int newdatasize); + std::string & get(); + bool available(unsigned int count); + std::string remove(unsigned int count); + std::string copy(unsigned int count); + void clear(); + }; + //Buffer + + /// This class is for easy communicating through sockets, either TCP or Unix. + class Connection { + private: + int sock; ///< Internally saved socket number. + int pipes[2]; ///< Internally saved file descriptors for pipe socket simulation. + std::string remotehost; ///< Stores remote host address. + unsigned int up; + unsigned int down; + long long int conntime; + Buffer downbuffer; ///< Stores temporary data coming in. + int iread(void * buffer, int len, int flags = 0); ///< Incremental read call. + unsigned int iwrite(const void * buffer, int len); ///< Incremental write call. + bool iread(Buffer & buffer, int flags = 0); ///< Incremental write call that is compatible with Socket::Buffer. + bool iwrite(std::string & buffer); ///< Write call that is compatible with std::string. + public: + //friends + friend class ::Buffer::user; + //constructors + 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. + Connection(int write, int read); ///< Simulate a socket using two file descriptors. + //generic methods + void close(); ///< Close connection. + void drop(); ///< Close connection without shutdown. + void setBlocking(bool blocking); ///< Set this socket to be blocking (true) or nonblocking (false). + bool isBlocking(); ///< Check if this socket is blocking (true) or nonblocking (false). + std::string getHost(); ///< Gets hostname for connection, if available. + std::string getBinHost(); + void setHost(std::string host); ///< Sets hostname for connection manually. + int getSocket(); ///< Returns internal socket number. + int getPureSocket(); ///< Returns non-piped internal socket number. + std::string getError(); ///< Returns a string describing the last error that occured. + bool connected() const; ///< Returns the connected-state for this socket. + bool isAddress(std::string addr); + //buffered i/o methods + bool spool(); ///< Updates the downbufferinternal variables. + bool peek(); ///< Clears the downbuffer and fills it with peek + Buffer & Received(); ///< Returns a reference to the download buffer. + void SendNow(const std::string & data); ///< Will not buffer anything but always send right away. Blocks. + void SendNow(const char * data); ///< Will not buffer anything but always send right away. Blocks. + void SendNow(const char * data, size_t len); ///< Will not buffer anything but always send right away. Blocks. + //stats related methods + unsigned int connTime();///< Returns the time this socket has been connected. + 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: + std::string errors; ///< Stores errors that may have occured. + 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. + void setBlocking(bool blocking); ///< Set this socket to be blocking (true) or nonblocking (false). + bool connected() const; ///< Returns the connected-state for this socket. + bool isBlocking(); ///< Check if this socket is blocking (true) or nonblocking (false). + void close(); ///< Close connection. + void drop(); ///< Close connection without shutdown. + int getSocket(); ///< Returns internal socket number. + }; + + class UDPConnection { + private: + int sock; ///< Internally saved socket number. + std::string remotehost;///< Stores remote host address + void * destAddr;///< Destination address pointer. + unsigned int destAddr_size;///< Size of the destination address pointer. + unsigned int up;///< Amount of bytes transferred up. + unsigned int down;///< Amount of bytes transferred down. + unsigned int data_size;///< The size in bytes of the allocated space in the data pointer. + public: + char * data;///< Holds the last received packet. + unsigned int data_len; ///< The size in bytes of the last received packet. + UDPConnection(const UDPConnection & o); + UDPConnection(bool nonblock = false); + ~UDPConnection(); + int getSock(); + int bind(int port); + void setBlocking(bool blocking); + void SetDestination(std::string hostname, uint32_t port); + void GetDestination(std::string & hostname, uint32_t & port); + bool Receive(); + void SendNow(const std::string & data); + void SendNow(const char * data); + void SendNow(const char * data, size_t len); + }; + +} diff --git a/lib/stream.cpp b/lib/stream.cpp new file mode 100644 index 00000000..cb1f6242 --- /dev/null +++ b/lib/stream.cpp @@ -0,0 +1,205 @@ +/// \file stream.cpp +/// Utilities for handling streams. + +#include +#include +#include +#include +#include +#include "json.h" +#include "stream.h" +#include "procs.h" +#include "config.h" +#include "socket.h" +#include "defines.h" +#include "shared_memory.h" +#include "dtsc.h" + +std::string Util::getTmpFolder() { + std::string dir; + char * tmp_char = 0; + if (!tmp_char) { + tmp_char = getenv("TMP"); + } + if (!tmp_char) { + tmp_char = getenv("TEMP"); + } + if (!tmp_char) { + tmp_char = getenv("TMPDIR"); + } + if (tmp_char) { + dir = tmp_char; + dir += "/mist"; + } else { +#if defined(_WIN32) || defined(_CYGWIN_) + dir = "C:/tmp/mist"; +#else + dir = "/tmp/mist"; +#endif + } + if (access(dir.c_str(), 0) != 0) { + mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); //attempt to create mist folder - ignore failures + } + return dir + "/"; +} + +/// Filters the streamname, removing invalid characters and converting all +/// letters to lowercase. If a '?' character is found, everything following +/// that character is deleted. The original string is modified. If a '+' or space +/// exists, then only the part before that is sanitized. +void Util::sanitizeName(std::string & streamname) { + //strip anything that isn't numbers, digits or underscores + size_t index = streamname.find_first_of("+ "); + if(index != std::string::npos){ + std::string preplus = streamname.substr(0,index); + sanitizeName(preplus); + streamname = preplus+"+"+streamname.substr(index+1); + return; + } + 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 != '_' && *i != '.'){ + streamname.erase(i); + } else { + *i = tolower(*i); + } + } +} + +/// Starts a process for a VoD stream. +bool Util::startInput(std::string streamname, std::string filename, bool forkFirst) { + if (streamname.size() > 100){ + FAIL_MSG("Stream opening denied: %s is longer than 100 characters (%lu).", streamname.c_str(), streamname.size()); + return false; + } + IPC::sharedPage mistConfOut("!mistConfig", DEFAULT_CONF_PAGE_SIZE); + IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); + configLock.wait(); + DTSC::Scan config = DTSC::Scan(mistConfOut.mapped, mistConfOut.len); + + sanitizeName(streamname); + std::string smp = streamname.substr(0, streamname.find_first_of("+ ")); + //check if smp (everything before + or space) exists + DTSC::Scan stream_cfg = config.getMember("streams").getMember(smp); + if (!stream_cfg){ + DEBUG_MSG(DLVL_MEDIUM, "Stream %s not configured", streamname.c_str()); + configLock.post();//unlock the config semaphore + return false; + } + + //If starting without filename parameter, check if the stream is already active. + //If yes, don't activate again to prevent duplicate inputs. + //It's still possible a duplicate starts anyway, this is caught in the inputs initializer. + //Note: this uses the _whole_ stream name, including + (if any). + //This means "test+a" and "test+b" have separate locks and do not interact with each other. + if (!filename.size()){ + IPC::semaphore playerLock(std::string("/lock_" + streamname).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1); + if (!playerLock.tryWait()) { + playerLock.close(); + DEBUG_MSG(DLVL_MEDIUM, "Stream %s already active - not activating again", streamname.c_str()); + configLock.post();//unlock the config semaphore + return true; + } + playerLock.post(); + playerLock.close(); + filename = stream_cfg.getMember("source").asString(); + } + + + std::string player_bin; + bool selected = false; + long long int curPrio = -1; + //check in curConf for capabilities-inputs--priority/source_match + DTSC::Scan inputs = config.getMember("capabilities").getMember("inputs"); + DTSC::Scan input; + unsigned int input_size = inputs.getSize(); + for (unsigned int i = 0; i < input_size; ++i){ + input = inputs.getIndice(i); + + //if match voor current stream && priority is hoger dan wat we al hebben + if (curPrio < input.getMember("priority").asInt()){ + std::string source = input.getMember("source_match").asString(); + std::string front = source.substr(0,source.find('*')); + std::string back = source.substr(source.find('*')+1); + DEBUG_MSG(DLVL_MEDIUM, "Checking input %s: %s (%s)", inputs.getIndiceName(i).c_str(), input.getMember("name").asString().c_str(), source.c_str()); + + if (filename.substr(0,front.size()) == front && filename.substr(filename.size()-back.size()) == back){ + player_bin = Util::getMyPath() + "MistIn" + input.getMember("name").asString(); + curPrio = input.getMember("priority").asInt(); + selected = true; + } + } + } + + if (!selected){ + configLock.post();//unlock the config semaphore + FAIL_MSG("No compatible input found for stream %s: %s", streamname.c_str(), filename.c_str()); + return false; + } + + //copy the neccessary arguments to separate storage so we can unlock the config semaphore safely + std::map str_args; + //check required parameters + DTSC::Scan required = input.getMember("required"); + unsigned int req_size = required.getSize(); + for (unsigned int i = 0; i < req_size; ++i){ + std::string opt = required.getIndiceName(i); + if (!stream_cfg.getMember(opt)){ + configLock.post();//unlock the config semaphore + FAIL_MSG("Required parameter %s for stream %s missing", opt.c_str(), streamname.c_str()); + return false; + } + str_args[required.getIndice(i).getMember("option").asString()] = stream_cfg.getMember(opt).asString(); + } + //check optional parameters + DTSC::Scan optional = input.getMember("optional"); + unsigned int opt_size = optional.getSize(); + for (unsigned int i = 0; i < opt_size; ++i){ + std::string opt = optional.getIndiceName(i); + DEBUG_MSG(DLVL_VERYHIGH, "Checking optional %u: %s", i, opt.c_str()); + if (stream_cfg.getMember(opt)){ + str_args[optional.getIndice(i).getMember("option").asString()] = stream_cfg.getMember(opt).asString(); + } + } + + //finally, unlock the config semaphore + configLock.post(); + + DEBUG_MSG(DLVL_MEDIUM, "Starting %s -s %s %s", player_bin.c_str(), streamname.c_str(), filename.c_str()); + char * argv[30] = {(char *)player_bin.c_str(), (char *)"-s", (char *)streamname.c_str(), (char *)filename.c_str()}; + int argNum = 3; + std::string debugLvl; + if (Util::Config::printDebugLevel != DEBUG && !str_args.count("--debug")){ + debugLvl = JSON::Value((long long)Util::Config::printDebugLevel).asString(); + argv[++argNum] = (char *)"--debug"; + argv[++argNum] = (char *)debugLvl.c_str(); + } + for (std::map::iterator it = str_args.begin(); it != str_args.end(); ++it){ + argv[++argNum] = (char *)it->first.c_str(); + argv[++argNum] = (char *)it->second.c_str(); + } + argv[++argNum] = (char *)0; + + int pid = 0; + if (forkFirst){ + DEBUG_MSG(DLVL_DONTEVEN, "Forking"); + pid = fork(); + if (pid == -1) { + FAIL_MSG("Forking process for stream %s failed: %s", streamname.c_str(), strerror(errno)); + return false; + } + }else{ + DEBUG_MSG(DLVL_DONTEVEN, "Not forking"); + } + + if (pid == 0){ + DEBUG_MSG(DLVL_DONTEVEN, "execvp"); + execvp(argv[0], argv); + FAIL_MSG("Starting process %s for stream %s failed: %s", argv[0], streamname.c_str(), strerror(errno)); + _exit(42); + } + return true; +} diff --git a/lib/stream.h b/lib/stream.h new file mode 100644 index 00000000..1db6c471 --- /dev/null +++ b/lib/stream.h @@ -0,0 +1,12 @@ +/// \file stream.h +/// Utilities for handling streams. + +#pragma once +#include +#include "socket.h" + +namespace Util { + std::string getTmpFolder(); + void sanitizeName(std::string & streamname); + bool startInput(std::string streamname, std::string filename = "", bool forkFirst = true); +} diff --git a/lib/theora.cpp b/lib/theora.cpp new file mode 100644 index 00000000..f807cb32 --- /dev/null +++ b/lib/theora.cpp @@ -0,0 +1,365 @@ +#include "theora.h" +#include +#include +#include +#include +#include "defines.h" + +namespace theora { + bool header::checkDataSize(unsigned int size){ + if (size > datasize){ + void * tmp = realloc(data, size); + if (tmp){ + data = (char *)tmp; + datasize = size; + return true; + } else { + return false; + } + } else { + return true; + } + } + + uint32_t header::getInt32(size_t index){ + if (datasize >= (index + 3)){ + return (data[index] << 24) + (data[index + 1] << 16) + (data[index + 2] << 8) + data[index + 3]; + } + return 0; + } + + uint32_t header::getInt24(size_t index){ + if (datasize >= (index + 2)){ + return 0 + (data[index] << 16) + (data[index + 1] << 8) + data[index + 2]; + } + return 0; + } + + uint16_t header::getInt16(size_t index){ + if (datasize >= (index + 1)){ + return 0 + (data[index] << 8) + data[index + 1]; + } + return 0; + } + + uint32_t header::commentLen(size_t index){ + if (datasize >= index + 3){ + return data[index] + (data[index + 1] << 8) + (data[index + 2] << 16) + (data[index + 3] << 24); + } + return 0; + } + + header::header(char * newData, unsigned int length){ + data = newData; + datasize = length; + } + + header::~header(){ + } + + bool isHeader(const char * newData, unsigned int length){ + if (length < 7){ + return false; + } + if (! newData[0] & 0x80){ + DEBUG_MSG(DLVL_FAIL, "newdata != 0x80: %.2X", newData[0]); + return false; + } + if (memcmp(newData + 1, "theora", 6) != 0){ + return false; + } + return true; + } + + int header::getHeaderType(){ + return (data[0] & 0x7F); + } + + char header::getVMAJ(){ + if (getHeaderType() == 0){ + return data[7]; + } + return 0; + } + + char header::getVMIN(){ + if (getHeaderType() == 0){ + return data[8]; + } + return 0; + } + + char header::getVREV(){ + if (getHeaderType() == 0){ + return data[9]; + } + return 0; + } + + short header::getFMBW(){ + if (getHeaderType() == 0){ + return getInt16(10); + } + return 0; + } + + short header::getFMBH(){ + if (getHeaderType() == 0){ + return getInt16(12); + } + return 0; + } + + char header::getPICX(){ + if (getHeaderType() == 0){ + return data[20]; + } + return 0; + } + + char header::getPICY(){ + if (getHeaderType() == 0){ + return data[21]; + } + return 0; + } + + char header::getKFGShift(){ + if (getHeaderType() == 0){ + return (getInt16(40) >> 5) & 0x1F; + } + return 0; + } + + long unsigned int header::getFRN(){ + if (getHeaderType() == 0){ + return getInt32(22); + } + return 0; + } + + long unsigned int header::getPICH(){ + if (getHeaderType() == 0){ + return getInt24(17); + } + return 0; + } + + long unsigned int header::getPICW(){ + if (getHeaderType() == 0){ + return getInt24(14); + } + return 0; + } + + long unsigned int header::getFRD(){ + if (getHeaderType() == 0){ + return getInt32(26); + } + return 0; + } + + long unsigned int header::getPARN(){ + if (getHeaderType() == 0){ + return getInt24(30); + } + return 0; + } + + long unsigned int header::getPARD(){ + if (getHeaderType() == 0){ + return getInt24(33); + } + return 0; + } + + char header::getCS(){ + if (getHeaderType() == 0){ + return data[36]; + } + return 0; + } + + long unsigned int header::getNOMBR(){ + if (getHeaderType() == 0){ + return getInt24(37); + } + return 0; + } + + char header::getQUAL(){ + if (getHeaderType() == 0){ + return (data[40] >> 3) & 0x1F; + } + return 0; + } + + char header::getPF(){ + if (getHeaderType() == 0){ + return (data[41] >> 3) & 0x03; + } + return 0; + } + + std::string header::getVendor(){ + if (getHeaderType() != 1){ + return ""; + } + return std::string(data + 11, commentLen(7)); + } + + long unsigned int header::getNComments(){ + if (getHeaderType() != 1){ + return 0; + } + int offset = 11 + commentLen(7); + return commentLen(offset); + } + + char header::getLFLIMS(size_t index){ + if (getHeaderType() != 2){ + return 0; + } + if (index >= 64){ + return 0; + } + char NBITS = (data[0] >> 5) & 0x07; + return NBITS; + } + + std::string header::getUserComment(size_t index){ + if (index >= getNComments()){ + return ""; + } + int offset = 11 + commentLen(7) + 4; + for (size_t i = 0; i < index; i++){ + offset += 4 + commentLen(offset); + } + return std::string(data + offset + 4, commentLen(offset)); + } + + char header::getFTYPE(){ + return (data[0] >> 6) & 0x01; + } + + bool header::isHeader(){ + return ::theora::isHeader(data, datasize); + } + + std::string header::toPrettyString(size_t indent){ + std::stringstream result; + if(!isHeader()){ + result << std::string(indent, ' ') << "Theora Frame" << std::endl; + result << std::string(indent + 2, ' ') << "FType: " << (int)getFTYPE() << std::endl; + return result.str(); + } + result << std::string(indent, ' ') << "Theora header" << std::endl; + result << std::string(indent + 2, ' ') << "HeaderType: " << getHeaderType() << std::endl; + switch (getHeaderType()){ + case 0: + result << std::string(indent + 2, ' ') << "VMAJ: " << (int)getVMAJ() << std::endl; + result << std::string(indent + 2, ' ') << "VMIN: " << (int)getVMIN() << std::endl; + result << std::string(indent + 2, ' ') << "VREV: " << (int)getVREV() << std::endl; + result << std::string(indent + 2, ' ') << "FMBW: " << getFMBW() << std::endl; + result << std::string(indent + 2, ' ') << "FMBH: " << getFMBH() << std::endl; + result << std::string(indent + 2, ' ') << "PICH: " << getPICH() << std::endl; + result << std::string(indent + 2, ' ') << "PICW: " << getPICW() << std::endl; + result << std::string(indent + 2, ' ') << "PICX: " << (int)getPICX() << std::endl; + result << std::string(indent + 2, ' ') << "PICY: " << (int)getPICY() << std::endl; + result << std::string(indent + 2, ' ') << "FRN: " << getFRN() << std::endl; + result << std::string(indent + 2, ' ') << "FRD: " << getFRD() << std::endl; + result << std::string(indent + 2, ' ') << "PARN: " << getPARN() << std::endl; + result << std::string(indent + 2, ' ') << "PARD: " << getPARD() << std::endl; + result << std::string(indent + 2, ' ') << "CS: " << (int)getCS() << std::endl; + result << std::string(indent + 2, ' ') << "NOMBR: " << getNOMBR() << std::endl; + result << std::string(indent + 2, ' ') << "QUAL: " << (int)getQUAL() << std::endl; + result << std::string(indent + 2, ' ') << "KFGShift: " << (int)getKFGShift() << std::endl; + break; + case 1: + result << std::string(indent + 2, ' ') << "Vendor: " << getVendor() << std::endl; + result << std::string(indent + 2, ' ') << "User Comments (" << getNComments() << "):" << std::endl; + for (long unsigned int i = 0; i < getNComments(); i++){ + result << std::string(indent + 4, ' ') << "[" << i << "] " << getUserComment(i) << std::endl; + } + break; + case 2: + result << std::string(indent + 2, ' ') << "NBITS: " << (int)getLFLIMS(0) << std::endl; + } + return result.str(); + } + /* + frame::frame(){ + data = NULL; + datasize = 0; + } + + frame::~frame(){ + if (data){ + free(data); + } + data = 0; + } + + bool frame::checkDataSize(unsigned int size){ + if (size > datasize){ + void * tmp = realloc(data, size); + if (tmp){ + data = (char *)tmp; + datasize = size; + return true; + } else { + return false; + } + } else { + return true; + } + } + + bool frame::read(const char * newData, unsigned int length){ + if (length < 7){ + return false; + } + if ((newData[0] & 0x80)){ + return false; + } + if (checkDataSize(length)){ + memcpy(data, newData, length); + } else { + return false; + } + return true; + } + + + char frame::getNQIS(){ + return 0; + } + + char frame::getQIS(size_t index){ + if (index >= 3){ + return 0; + } + return 0; + } +*/ +/* + long long unsigned int header::parseGranuleUpper(long long unsigned int granPos){ + return granPos >> getKFGShift(); + } + + long long unsigned int header::parseGranuleLower(long long unsigned int granPos){ + return (granPos & ((1 << getKFGShift()) - 1)); + }*/ +/* + std::string frame::toPrettyString(size_t indent){ + std::stringstream result; + result << std::string(indent, ' ') << "Theora Frame" << std::endl; + result << std::string(indent + 2, ' ') << "FType: " << (int)getFTYPE() << std::endl; + return result.str(); + }*/ +} + + + + + diff --git a/lib/theora.h b/lib/theora.h new file mode 100644 index 00000000..28c066e6 --- /dev/null +++ b/lib/theora.h @@ -0,0 +1,68 @@ +#pragma once +#include +#include +#include + +namespace theora { + + bool isHeader(const char * newData, unsigned int length); + class header { + public: + ~header(); + header(char * newData, unsigned int length); // if managed is true, this class manages the data pointer + int getHeaderType(); + char getVMAJ(); + char getVMIN(); + char getVREV(); + short getFMBW(); + short getFMBH(); + long unsigned int getPICW();//movie width + long unsigned int getPICH();//movie height + char getPICX(); + char getPICY(); + long unsigned int getFRN();//frame rate numerator + long unsigned int getFRD();//frame rate denominator + long unsigned int getPARN(); + long unsigned int getPARD(); + char getCS(); + long unsigned int getNOMBR(); + char getQUAL(); + char getPF(); + char getKFGShift(); + std::string getVendor(); + long unsigned int getNComments(); + std::string getUserComment(size_t index); + char getLFLIMS(size_t index); + std::string toPrettyString(size_t indent = 0); //update this, it should check (header) type and output relevant stuff only. + //long long unsigned int parseGranuleUpper(long long unsigned int granPos); + //long long unsigned int parseGranuleLower(long long unsigned int granPos); + ///\todo put this back in pravate + unsigned int datasize; + bool isHeader(); + char getFTYPE();//for frames + protected: + uint32_t getInt32(size_t index); + uint32_t getInt24(size_t index); + uint16_t getInt16(size_t index); + uint32_t commentLen(size_t index); + private: + char * data; + bool checkDataSize(unsigned int size); + }; + /* + class frame{ // we don't need this. I hope. + public: + frame(); + ~frame(); + bool read(const char* newData, unsigned int length); + char getFTYPE(); + char getNQIS(); + char getQIS(size_t index); + std::string toPrettyString(size_t indent = 0); + private: + char * data; + unsigned int datasize; + bool checkDataSize(unsigned int size); + };*/ +} + diff --git a/lib/timing.cpp b/lib/timing.cpp new file mode 100644 index 00000000..3d7673a1 --- /dev/null +++ b/lib/timing.cpp @@ -0,0 +1,99 @@ +/// \file timing.cpp +/// Utilities for handling time and timestamps. + +#include "timing.h" +#include //for gettimeofday +#include //for time and nanosleep + +//emulate clock_gettime() for OSX compatibility +#if defined(__APPLE__) || defined(__MACH__) +#include +#include +#define CLOCK_REALTIME CALENDAR_CLOCK +#define CLOCK_MONOTONIC SYSTEM_CLOCK +void clock_gettime(int ign, struct timespec * ts) { + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), ign, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + ts->tv_sec = mts.tv_sec; + ts->tv_nsec = mts.tv_nsec; +} +#endif + +/// Sleeps for the indicated amount of milliseconds or longer. +/// Will not sleep if ms is negative. +/// Will not sleep for longer than 10 minutes (600000ms). +/// If interrupted by signal, resumes sleep until at least ms milliseconds have passed. +/// Can be slightly off (in positive direction only) depending on OS accuracy. +void Util::wait(int ms){ + if (ms < 0) { + return; + } + if (ms > 600000) { + ms = 600000; + } + long long int start = getMS(); + long long int now = start; + while (now < start+ms){ + sleep(start+ms-now); + now = getMS(); + } +} + +/// Sleeps for roughly the indicated amount of milliseconds. +/// Will not sleep if ms is negative. +/// Will not sleep for longer than 100 seconds (100000ms). +/// Can be interrupted early by a signal, no guarantee of minimum sleep time. +/// Can be slightly off depending on OS accuracy. +void Util::sleep(int ms) { + if (ms < 0) { + return; + } + if (ms > 100000) { + ms = 100000; + } + struct timespec T; + T.tv_sec = ms / 1000; + T.tv_nsec = 1000000 * (ms % 1000); + nanosleep(&T, 0); +} + +long long Util::getNTP() { + struct timespec t; + clock_gettime(CLOCK_REALTIME, &t); + return ((((long long int)t.tv_sec) + 2208988800) << 32) + (t.tv_nsec * 4.2949); +} + +/// Gets the current time in milliseconds. +long long int Util::getMS() { + struct timespec t; + clock_gettime(CLOCK_REALTIME, &t); + return ((long long int)t.tv_sec) * 1000 + t.tv_nsec / 1000000; +} + +long long int Util::bootSecs() { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return t.tv_sec; +} + +/// Gets the current time in microseconds. +long long unsigned int Util::getMicros() { + struct timespec t; + clock_gettime(CLOCK_REALTIME, &t); + return ((long long unsigned int)t.tv_sec) * 1000000 + t.tv_nsec / 1000; +} + +/// Gets the time difference in microseconds. +long long unsigned int Util::getMicros(long long unsigned int previous) { + struct timespec t; + clock_gettime(CLOCK_REALTIME, &t); + return ((long long unsigned int)t.tv_sec) * 1000000 + t.tv_nsec / 1000 - previous; +} + +/// Gets the amount of seconds since 01/01/1970. +long long int Util::epoch() { + return time(0); +} diff --git a/lib/timing.h b/lib/timing.h new file mode 100644 index 00000000..0c027a45 --- /dev/null +++ b/lib/timing.h @@ -0,0 +1,15 @@ +/// \file timing.h +/// Utilities for handling time and timestamps. + +#pragma once + +namespace Util { + void wait(int ms); ///< Sleeps for the indicated amount of milliseconds or longer. + void sleep(int ms); ///< Sleeps for roughly the indicated amount of milliseconds. + long long int getMS(); ///< Gets the current time in milliseconds. + long long int bootSecs(); ///< Gets the current system uptime in seconds. + long long unsigned int getMicros();/// +#include "tinythread.h" + +#if defined(_TTHREAD_POSIX_) +#include +#include +#elif defined(_TTHREAD_WIN32_) +#include +#endif + + +namespace tthread { + +//------------------------------------------------------------------------------ +// condition_variable +//------------------------------------------------------------------------------ +// NOTE 1: The Win32 implementation of the condition_variable class is based on +// the corresponding implementation in GLFW, which in turn is based on a +// description by Douglas C. Schmidt and Irfan Pyarali: +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// +// NOTE 2: Windows Vista actually has native support for condition variables +// (InitializeConditionVariable, WakeConditionVariable, etc), but we want to +// be portable with pre-Vista Windows versions, so TinyThread++ does not use +// Vista condition variables. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_WIN32_) +#define _CONDITION_EVENT_ONE 0 +#define _CONDITION_EVENT_ALL 1 +#endif + +#if defined(_TTHREAD_WIN32_) + condition_variable::condition_variable() : mWaitersCount(0) { + mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); + mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); + InitializeCriticalSection(&mWaitersCountLock); + } +#endif + +#if defined(_TTHREAD_WIN32_) + condition_variable::~condition_variable() { + CloseHandle(mEvents[_CONDITION_EVENT_ONE]); + CloseHandle(mEvents[_CONDITION_EVENT_ALL]); + DeleteCriticalSection(&mWaitersCountLock); + } +#endif + +#if defined(_TTHREAD_WIN32_) + void condition_variable::_wait() { + // Wait for either event to become signaled due to notify_one() or + // notify_all() being called + int result = WaitForMultipleObjects(2, mEvents, FALSE, INFINITE); + + // Check if we are the last waiter + EnterCriticalSection(&mWaitersCountLock); + -- mWaitersCount; + bool lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) && + (mWaitersCount == 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we are the last waiter to be notified to stop waiting, reset the event + if (lastWaiter) + ResetEvent(mEvents[_CONDITION_EVENT_ALL]); + } +#endif + +#if defined(_TTHREAD_WIN32_) + void condition_variable::notify_one() { + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if (haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ONE]); + } +#endif + +#if defined(_TTHREAD_WIN32_) + void condition_variable::notify_all() { + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if (haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ALL]); + } +#endif + + +//------------------------------------------------------------------------------ +// POSIX pthread_t to unique thread::id mapping logic. +// Note: Here we use a global thread safe std::map to convert instances of +// pthread_t to small thread identifier numbers (unique within one process). +// This method should be portable across different POSIX implementations. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_POSIX_) + static thread::id _pthread_t_to_ID(const pthread_t & aHandle) { + static mutex idMapLock; + static std::map idMap; + static unsigned long int idCount(1); + + lock_guard guard(idMapLock); + if (idMap.find(aHandle) == idMap.end()) + idMap[aHandle] = idCount ++; + return thread::id(idMap[aHandle]); + } +#endif // _TTHREAD_POSIX_ + + +//------------------------------------------------------------------------------ +// thread +//------------------------------------------------------------------------------ + +/// Information to pass to the new thread (what to run). + struct _thread_start_info { + void (*mFunction)(void *); ///< Pointer to the function to be executed. + void * mArg; ///< Function argument for the thread function. + thread * mThread; ///< Pointer to the thread object. + }; + +// Thread wrapper function. +#if defined(_TTHREAD_WIN32_) + unsigned WINAPI thread::wrapper_function(void * aArg) +#elif defined(_TTHREAD_POSIX_) + void * thread::wrapper_function(void * aArg) +#endif + { + // Get thread startup information + _thread_start_info * ti = (_thread_start_info *) aArg; + + try { + // Call the actual client thread function + ti->mFunction(ti->mArg); + } catch (...) { + // Uncaught exceptions will terminate the application (default behavior + // according to C++11) + std::terminate(); + } + + // The thread is no longer executing + if (ti->mThread) { + lock_guard guard(ti->mThread->mDataMutex); + ti->mThread->mNotAThread = true; + ti->mThread->ti_copy = 0; + } + + // The thread is responsible for freeing the startup information + delete ti; + + return 0; + } + + thread::thread(void (*aFunction)(void *), void * aArg) { + // Serialize access to this thread structure + lock_guard guard(mDataMutex); + + // Fill out the thread startup information (passed to the thread wrapper, + // which will eventually free it) + _thread_start_info * ti = new _thread_start_info; + ti_copy = ti; + ti->mFunction = aFunction; + ti->mArg = aArg; + ti->mThread = this; + + // The thread is now alive + mNotAThread = false; + + // Create the thread +#if defined(_TTHREAD_WIN32_) + mHandle = (HANDLE) _beginthreadex(0, 0, wrapper_function, (void *) ti, 0, &mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + if (pthread_create(&mHandle, NULL, wrapper_function, (void *) ti) != 0) + mHandle = 0; +#endif + + // Did we fail to create the thread? + if (!mHandle) { + mNotAThread = true; + ti_copy = 0; + delete ti; + } + } + + thread::~thread() { + if (ti_copy) { + ((_thread_start_info *)ti_copy)->mThread = 0; + } + if (joinable()) + std::terminate(); + } + + void thread::join() { + if (joinable()) { +#if defined(_TTHREAD_WIN32_) + WaitForSingleObject(mHandle, INFINITE); + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_join(mHandle, NULL); +#endif + } + } + + bool thread::joinable() const { + mDataMutex.lock(); + bool result = !mNotAThread; + mDataMutex.unlock(); + return result; + } + + void thread::detach() { + mDataMutex.lock(); + if (!mNotAThread) { +#if defined(_TTHREAD_WIN32_) + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_detach(mHandle); +#endif + mNotAThread = true; + } + mDataMutex.unlock(); + } + + thread::id thread::get_id() const { + if (!joinable()) + return id(); +#if defined(_TTHREAD_WIN32_) + return id((unsigned long int) mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(mHandle); +#endif + } + + unsigned thread::hardware_concurrency() { +#if defined(_TTHREAD_WIN32_) + SYSTEM_INFO si; + GetSystemInfo(&si); + return (int) si.dwNumberOfProcessors; +#elif defined(_SC_NPROCESSORS_ONLN) + return (int) sysconf(_SC_NPROCESSORS_ONLN); +#elif defined(_SC_NPROC_ONLN) + return (int) sysconf(_SC_NPROC_ONLN); +#else + // The standard requires this function to return zero if the number of + // hardware cores could not be determined. + return 0; +#endif + } + + +//------------------------------------------------------------------------------ +// this_thread +//------------------------------------------------------------------------------ + + thread::id this_thread::get_id() { +#if defined(_TTHREAD_WIN32_) + return thread::id((unsigned long int) GetCurrentThreadId()); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(pthread_self()); +#endif + } + +} diff --git a/lib/tinythread.h b/lib/tinythread.h new file mode 100644 index 00000000..fdee215f --- /dev/null +++ b/lib/tinythread.h @@ -0,0 +1,688 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _TINYTHREAD_H_ +#define _TINYTHREAD_H_ + +/// @file +/// @mainpage TinyThread++ API Reference +/// +/// @section intro_sec Introduction +/// TinyThread++ is a minimal, portable implementation of basic threading +/// classes for C++. +/// +/// They closely mimic the functionality and naming of the C++11 standard, and +/// should be easily replaceable with the corresponding std:: variants. +/// +/// @section port_sec Portability +/// The Win32 variant uses the native Win32 API for implementing the thread +/// classes, while for other systems, the POSIX threads API (pthread) is used. +/// +/// @section class_sec Classes +/// In order to mimic the threading API of the C++11 standard, subsets of +/// several classes are provided. The fundamental classes are: +/// @li tthread::thread +/// @li tthread::mutex +/// @li tthread::recursive_mutex +/// @li tthread::condition_variable +/// @li tthread::lock_guard +/// @li tthread::fast_mutex +/// +/// @section misc_sec Miscellaneous +/// The following special keywords are available: #thread_local. +/// +/// For more detailed information (including additional classes), browse the +/// different sections of this documentation. A good place to start is: +/// tinythread.h. + +// Which platform are we on? +#if !defined(_TTHREAD_PLATFORM_DEFINED_) +#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) +#define _TTHREAD_WIN32_ +#else +#define _TTHREAD_POSIX_ +#endif +#define _TTHREAD_PLATFORM_DEFINED_ +#endif + +// Platform specific includes +#if defined(_TTHREAD_WIN32_) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#define __UNDEF_LEAN_AND_MEAN +#endif +#include +#ifdef __UNDEF_LEAN_AND_MEAN +#undef WIN32_LEAN_AND_MEAN +#undef __UNDEF_LEAN_AND_MEAN +#endif +#else +#include +#include +#include +#include +#endif + +// Generic includes +#include + +/// TinyThread++ version (major number). +#define TINYTHREAD_VERSION_MAJOR 1 +/// TinyThread++ version (minor number). +#define TINYTHREAD_VERSION_MINOR 1 +/// TinyThread++ version (full version). +#define TINYTHREAD_VERSION (TINYTHREAD_VERSION_MAJOR * 100 + TINYTHREAD_VERSION_MINOR) + +// Do we have a fully featured C++11 compiler? +#if (__cplusplus > 199711L) || (defined(__STDCXX_VERSION__) && (__STDCXX_VERSION__ >= 201001L)) +#define _TTHREAD_CPP11_ +#endif + +// ...at least partial C++11? +#if defined(_TTHREAD_CPP11_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) +#define _TTHREAD_CPP11_PARTIAL_ +#endif + +// Macro for disabling assignments of objects. +#ifdef _TTHREAD_CPP11_PARTIAL_ +#define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&) = delete; \ + name& operator=(const name&) = delete; +#else +#define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&); \ + name& operator=(const name&); +#endif + +/// @def thread_local +/// Thread local storage keyword. +/// A variable that is declared with the @c thread_local keyword makes the +/// value of the variable local to each thread (known as thread-local storage, +/// or TLS). Example usage: +/// @code +/// // This variable is local to each thread. +/// thread_local int variable; +/// @endcode +/// @note The @c thread_local keyword is a macro that maps to the corresponding +/// compiler directive (e.g. @c __declspec(thread)). While the C++11 standard +/// allows for non-trivial types (e.g. classes with constructors and +/// destructors) to be declared with the @c thread_local keyword, most pre-C++11 +/// compilers only allow for trivial types (e.g. @c int). So, to guarantee +/// portable code, only use trivial types for thread local storage. +/// @note This directive is currently not supported on Mac OS X (it will give +/// a compiler error), since compile-time TLS is not supported in the Mac OS X +/// executable format. Also, some older versions of MinGW (before GCC 4.x) do +/// not support this directive. +/// @hideinitializer + +#if !defined(_TTHREAD_CPP11_) && !defined(thread_local) +#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) +#define thread_local __thread +#else +#define thread_local __declspec(thread) +#endif +#endif + + +/// Main name space for TinyThread++. +/// This namespace is more or less equivalent to the @c std namespace for the +/// C++11 thread classes. For instance, the tthread::mutex class corresponds to +/// the std::mutex class. +namespace tthread { + +/// Mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is non-recursive (i.e. a +/// program may deadlock if the thread that owns a mutex object calls lock() +/// on that object). +/// @see recursive_mutex + class mutex { + public: + /// Constructor. + mutex() +#if defined(_TTHREAD_WIN32_) + : mAlreadyLocked(false) +#endif + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutex_init(&mHandle, NULL); +#endif + } + + /// Destructor. + ~mutex() { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until @c unlock() is called. + /// @see lock_guard + inline void lock() { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); + while (mAlreadyLocked) Sleep(1000); // Simulate deadlock... + mAlreadyLocked = true; +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return @c true if the lock was acquired, or @c false if the lock could + /// not be acquired. + inline bool try_lock() { +#if defined(_TTHREAD_WIN32_) + bool ret = (TryEnterCriticalSection(&mHandle) ? true : false); + if (ret && mAlreadyLocked) { + LeaveCriticalSection(&mHandle); + ret = false; + } + return ret; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() { +#if defined(_TTHREAD_WIN32_) + mAlreadyLocked = false; + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; + bool mAlreadyLocked; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; + }; + +/// Recursive mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is recursive (i.e. a thread +/// may lock the mutex several times, as long as it unlocks the mutex the same +/// number of times). +/// @see mutex + class recursive_mutex { + public: + /// Constructor. + recursive_mutex() { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mHandle, &attr); +#endif + } + + /// Destructor. + ~recursive_mutex() { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until @c unlock() is called. + /// @see lock_guard + inline void lock() { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return @c true if the lock was acquired, or @c false if the lock could + /// not be acquired. + inline bool try_lock() { +#if defined(_TTHREAD_WIN32_) + return TryEnterCriticalSection(&mHandle) ? true : false; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() { +#if defined(_TTHREAD_WIN32_) + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(recursive_mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; + }; + +/// Lock guard class. +/// The constructor locks the mutex, and the destructor unlocks the mutex, so +/// the mutex will automatically be unlocked when the lock guard goes out of +/// scope. Example usage: +/// @code +/// mutex m; +/// int counter; +/// +/// void increment() +/// { +/// lock_guard guard(m); +/// ++ counter; +/// } +/// @endcode + + template + class lock_guard { + public: + typedef T mutex_type; + + lock_guard() : mMutex(0) {} + + /// The constructor locks the mutex. + explicit lock_guard(mutex_type & aMutex) { + mMutex = &aMutex; + mMutex->lock(); + } + + /// The destructor unlocks the mutex. + ~lock_guard() { + if (mMutex) + mMutex->unlock(); + } + + private: + mutex_type * mMutex; + }; + +/// Condition variable class. +/// This is a signalling object for synchronizing the execution flow for +/// several threads. Example usage: +/// @code +/// // Shared data and associated mutex and condition variable objects +/// int count; +/// mutex m; +/// condition_variable cond; +/// +/// // Wait for the counter to reach a certain number +/// void wait_counter(int targetCount) +/// { +/// lock_guard guard(m); +/// while(count < targetCount) +/// cond.wait(m); +/// } +/// +/// // Increment the counter, and notify waiting threads +/// void increment() +/// { +/// lock_guard guard(m); +/// ++ count; +/// cond.notify_all(); +/// } +/// @endcode + class condition_variable { + public: + /// Constructor. +#if defined(_TTHREAD_WIN32_) + condition_variable(); +#else + condition_variable() { + pthread_cond_init(&mHandle, NULL); + } +#endif + + /// Destructor. +#if defined(_TTHREAD_WIN32_) + ~condition_variable(); +#else + ~condition_variable() { + pthread_cond_destroy(&mHandle); + } +#endif + + /// Wait for the condition. + /// The function will block the calling thread until the condition variable + /// is woken by @c notify_one(), @c notify_all() or a spurious wake up. + /// @param[in] aMutex A mutex that will be unlocked when the wait operation + /// starts, an locked again as soon as the wait operation is finished. + template + inline void wait(_mutexT & aMutex) { +#if defined(_TTHREAD_WIN32_) + // Increment number of waiters + EnterCriticalSection(&mWaitersCountLock); + ++ mWaitersCount; + LeaveCriticalSection(&mWaitersCountLock); + + // Release the mutex while waiting for the condition (will decrease + // the number of waiters when done)... + aMutex.unlock(); + _wait(); + aMutex.lock(); +#else + pthread_cond_wait(&mHandle, &aMutex.mHandle); +#endif + } + + /// Notify one thread that is waiting for the condition. + /// If at least one thread is blocked waiting for this condition variable, + /// one will be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_one(); +#else + inline void notify_one() { + pthread_cond_signal(&mHandle); + } +#endif + + /// Notify all threads that are waiting for the condition. + /// All threads that are blocked waiting for this condition variable will + /// be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_all(); +#else + inline void notify_all() { + pthread_cond_broadcast(&mHandle); + } +#endif + + _TTHREAD_DISABLE_ASSIGNMENT(condition_variable) + + private: +#if defined(_TTHREAD_WIN32_) + void _wait(); + HANDLE mEvents[2]; ///< Signal and broadcast event HANDLEs. + unsigned int mWaitersCount; ///< Count of the number of waiters. + CRITICAL_SECTION mWaitersCountLock; ///< Serialize access to mWaitersCount. +#else + pthread_cond_t mHandle; +#endif + }; + + +/// Thread class. + class thread { + public: +#if defined(_TTHREAD_WIN32_) + typedef HANDLE native_handle_type; +#else + typedef pthread_t native_handle_type; +#endif + + class id; + + /// Default constructor. + /// Construct a @c thread object without an associated thread of execution + /// (i.e. non-joinable). + thread() : mHandle(0), mNotAThread(true) +#if defined(_TTHREAD_WIN32_) + , mWin32ThreadID(0) +#endif + {} + + /// Thread starting constructor. + /// Construct a @c thread object with a new thread of execution. + /// @param[in] aFunction A function pointer to a function of type: + /// void fun(void * arg) + /// @param[in] aArg Argument to the thread function. + /// @note This constructor is not fully compatible with the standard C++ + /// thread class. It is more similar to the pthread_create() (POSIX) and + /// CreateThread() (Windows) functions. + thread(void (*aFunction)(void *), void * aArg); + + /// Destructor. + /// @note If the thread is joinable upon destruction, @c std::terminate() + /// will be called, which terminates the process. It is always wise to do + /// @c join() before deleting a thread object. + ~thread(); + + /// Wait for the thread to finish (join execution flows). + /// After calling @c join(), the thread object is no longer associated with + /// a thread of execution (i.e. it is not joinable, and you may not join + /// with it nor detach from it). + void join(); + + /// Check if the thread is joinable. + /// A thread object is joinable if it has an associated thread of execution. + bool joinable() const; + + /// Detach from the thread. + /// After calling @c detach(), the thread object is no longer assicated with + /// a thread of execution (i.e. it is not joinable). The thread continues + /// execution without the calling thread blocking, and when the thread + /// ends execution, any owned resources are released. + void detach(); + + /// Return the thread ID of a thread object. + id get_id() const; + + /// Get the native handle for this thread. + /// @note Under Windows, this is a @c HANDLE, and under POSIX systems, this + /// is a @c pthread_t. + inline native_handle_type native_handle() { + return mHandle; + } + + /// Determine the number of threads which can possibly execute concurrently. + /// This function is useful for determining the optimal number of threads to + /// use for a task. + /// @return The number of hardware thread contexts in the system. + /// @note If this value is not defined, the function returns zero (0). + static unsigned hardware_concurrency(); + + _TTHREAD_DISABLE_ASSIGNMENT(thread) + + private: + native_handle_type mHandle; ///< Thread handle. + mutable mutex mDataMutex; ///< Serializer for access to the thread private data. + bool mNotAThread; ///< True if this object is not a thread of execution. + void * ti_copy; +#if defined(_TTHREAD_WIN32_) + unsigned int mWin32ThreadID; ///< Unique thread ID (filled out by _beginthreadex). +#endif + + // This is the internal thread wrapper function. +#if defined(_TTHREAD_WIN32_) + static unsigned WINAPI wrapper_function(void * aArg); +#else + static void * wrapper_function(void * aArg); +#endif + }; + +/// Thread ID. +/// The thread ID is a unique identifier for each thread. +/// @see thread::get_id() + class thread::id { + public: + /// Default constructor. + /// The default constructed ID is that of thread without a thread of + /// execution. + id() : mId(0) {}; + + id(unsigned long int aId) : mId(aId) {}; + + id(const id & aId) : mId(aId.mId) {}; + + inline id & operator=(const id & aId) { + mId = aId.mId; + return *this; + } + + inline friend bool operator==(const id & aId1, const id & aId2) { + return (aId1.mId == aId2.mId); + } + + inline friend bool operator!=(const id & aId1, const id & aId2) { + return (aId1.mId != aId2.mId); + } + + inline friend bool operator<=(const id & aId1, const id & aId2) { + return (aId1.mId <= aId2.mId); + } + + inline friend bool operator<(const id & aId1, const id & aId2) { + return (aId1.mId < aId2.mId); + } + + inline friend bool operator>=(const id & aId1, const id & aId2) { + return (aId1.mId >= aId2.mId); + } + + inline friend bool operator>(const id & aId1, const id & aId2) { + return (aId1.mId > aId2.mId); + } + + inline friend std::ostream & operator <<(std::ostream & os, const id & obj) { + os << obj.mId; + return os; + } + + private: + unsigned long int mId; + }; + + +// Related to - minimal to be able to support chrono. + typedef long long __intmax_t; + +/// Minimal implementation of the @c ratio class. This class provides enough +/// functionality to implement some basic @c chrono classes. + template <__intmax_t N, __intmax_t D = 1> class ratio { + public: + static double _as_double() { + return double(N) / double(D); + } + }; + +/// Minimal implementation of the @c chrono namespace. +/// The @c chrono namespace provides types for specifying time intervals. + namespace chrono { + /// Duration template class. This class provides enough functionality to + /// implement @c this_thread::sleep_for(). + template > class duration { + private: + _Rep rep_; + public: + typedef _Rep rep; + typedef _Period period; + + /// Construct a duration object with the given duration. + template + explicit duration(const _Rep2 & r) : rep_(r) {}; + + /// Return the value of the duration object. + rep count() const { + return rep_; + } + }; + + // Standard duration types. + typedef duration<__intmax_t, ratio<1, 1000000000> > nanoseconds; ///< Duration with the unit nanoseconds. + typedef duration<__intmax_t, ratio<1, 1000000> > microseconds; ///< Duration with the unit microseconds. + typedef duration<__intmax_t, ratio<1, 1000> > milliseconds; ///< Duration with the unit milliseconds. + typedef duration<__intmax_t> seconds; ///< Duration with the unit seconds. + typedef duration<__intmax_t, ratio<60> > minutes; ///< Duration with the unit minutes. + typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours. + } + +/// The namespace @c this_thread provides methods for dealing with the +/// calling thread. + namespace this_thread { + /// Return the thread ID of the calling thread. + thread::id get_id(); + + /// Yield execution to another thread. + /// Offers the operating system the opportunity to schedule another thread + /// that is ready to run on the current processor. + inline void yield() { +#if defined(_TTHREAD_WIN32_) + Sleep(0); +#else + sched_yield(); +#endif + } + + /// Blocks the calling thread for a period of time. + /// @param[in] aTime Minimum time to put the thread to sleep. + /// Example usage: + /// @code + /// // Sleep for 100 milliseconds + /// this_thread::sleep_for(chrono::milliseconds(100)); + /// @endcode + /// @note Supported duration types are: nanoseconds, microseconds, + /// milliseconds, seconds, minutes and hours. + template void sleep_for(const chrono::duration<_Rep, _Period> & aTime) { +#if defined(_TTHREAD_WIN32_) + Sleep(int(double(aTime.count()) * (1000.0 * _Period::_as_double()) + 0.5)); +#else + usleep(int(double(aTime.count()) * (1000000.0 * _Period::_as_double()) + 0.5)); +#endif + } + } + +} + +// Define/macro cleanup +#undef _TTHREAD_DISABLE_ASSIGNMENT + +#endif // _TINYTHREAD_H_ diff --git a/lib/ts_packet.cpp b/lib/ts_packet.cpp new file mode 100644 index 00000000..b943445b --- /dev/null +++ b/lib/ts_packet.cpp @@ -0,0 +1,1041 @@ +/// \file ts_packet.cpp +/// Holds all code for the TS namespace. + +#include +#include +#include +#include +#include +#include "ts_packet.h" +#include "defines.h" + +#ifndef FILLER_DATA +#define FILLER_DATA "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent commodo vulputate urna eu commodo. Cras tempor velit nec nulla placerat volutpat. Proin eleifend blandit quam sit amet suscipit. Pellentesque vitae tristique lorem. Maecenas facilisis consequat neque, vitae iaculis eros vulputate ut. Suspendisse ut arcu non eros vestibulum pulvinar id sed erat. Nam dictum tellus vel tellus rhoncus ut mollis tellus fermentum. Fusce volutpat consectetur ante, in mollis nisi euismod vulputate. Curabitur vitae facilisis ligula. Sed sed gravida dolor. Integer eu eros a dolor lobortis ullamcorper. Mauris interdum elit non neque interdum dictum. Suspendisse imperdiet eros sed sapien cursus pulvinar. Vestibulum ut dolor lectus, id commodo elit. Cras convallis varius leo eu porta. Duis luctus sapien nec dui adipiscing quis interdum nunc congue. Morbi pharetra aliquet mauris vitae tristique. Etiam feugiat sapien quis augue elementum id ultricies magna vulputate. Phasellus luctus, leo id egestas consequat, eros tortor commodo neque, vitae hendrerit nunc sem ut odio." +#endif + +std::set pmt_pids; +std::map stream_pids; + + +namespace TS { +/// This constructor creates an empty Packet, ready for use for either reading or writing. +/// All this constructor does is call Packet::clear(). + Packet::Packet() { + pos=0; + clear(); + } + +/// This function fills a Packet from a file. +/// It fills the content with the next 188 bytes int he file. +/// \param Data The data to be read into the packet. +/// \return true if it was possible to read in a full packet, false otherwise. + bool Packet::FromFile(FILE * data) { + long long int pos = ftell(data); + if (!fread((void *)strBuf, 188, 1, data)) { + return false; + } + pos=188; + if (strBuf[0] != 0x47){ + INFO_MSG("Failed to read a good packet on pos %lld", pos); + return false; + } + return true; + } + +///This funtion fills a Packet from +///a char array. It fills the content with +///the first 188 characters of a char array +///\param Data The char array that contains the data to be read into the packet +///\return true if successful (which always happens, or else a segmentation fault should occur) + bool Packet::FromPointer(const char * data) { + memcpy((void *)strBuf, (void *)data, 188); + pos=188; + return true; + } + +/// The deconstructor deletes all space that may be occupied by a Packet. + Packet::~Packet() { + } + + ///update position in character array (pos), + void Packet::updPos(unsigned int newPos){ + if(pos < newPos){ + pos=newPos; + } + } + +/// Sets the PID of a single Packet. +/// \param NewPID The new PID of the packet. + void Packet::setPID(int NewPID) { + strBuf[1] = (strBuf[1] & 0xE0) + ((NewPID & 0x1F00) >> 8); + strBuf[2] = (NewPID & 0x00FF); + updPos(2); + } + +/// Gets the PID of a single Packet. +/// \return The value of the PID. + unsigned int Packet::getPID() const{ + return (unsigned int)(((strBuf[1] & 0x1F) << 8) + strBuf[2]); + } + +/// Sets the Continuity Counter of a single Packet. +/// \param NewContinuity The new Continuity Counter of the packet. + void Packet::setContinuityCounter(int NewContinuity) { + strBuf[3] = (strBuf[3] & 0xF0) | (NewContinuity & 0x0F); + updPos(3); + } + +/// Gets the Continuity Counter of a single Packet. +/// \return The value of the Continuity Counter. + int Packet::getContinuityCounter() const{ + return (strBuf[3] & 0x0F); + } + +/// Gets the amount of bytes that are not written yet in a Packet. +/// \return The amount of bytes that can still be written to this packet. + unsigned int Packet::getBytesFree() const{ + if(pos > 188){ + FAIL_MSG("pos is > 188. Actual pos: %d segfaulting gracefully :)", pos); + ((char*)0)[0] = 1; + } + return 188 - pos; + } + +/// Sets the packet pos to 4, and resets the first 4 fields to defaults (including sync byte on pos 0) + void Packet::clear() { + memset(strBuf,(char)0, 188); + strBuf[0] = 0x47; + strBuf[1] = 0x00; + strBuf[2] = 0x00; + strBuf[3] = 0x10; + pos=4; + } + +/// Sets the selection value for an adaptationfield of a Packet. +/// \param NewSelector The new value of the selection bits. +/// - 1: No AdaptationField. +/// - 2: AdaptationField Only. +/// - 3: AdaptationField followed by Data. + void Packet::setAdaptationField(int NewSelector) { + strBuf[3] = (strBuf[3] & 0xCF) + ((NewSelector & 0x03) << 4); + if (NewSelector & 0x02) { + strBuf[4] = 0x00; + } else { + pos=4; + } + } + +/// Gets whether a Packet contains an adaptationfield. +/// \return The existence of an adaptationfield. +/// - 0: No adaptationfield present. +/// - 1: Adaptationfield is present. + int Packet::getAdaptationField() const{ + return ((strBuf[3] & 0x30) >> 4); + } + +/// Sets the PCR (Program Clock Reference) of a Packet. +/// \param NewVal The new PCR Value. + void Packet::setPCR(int64_t NewVal) { + updPos(12); + setAdaptationField(3); + strBuf[4] = 0x07; + strBuf[5] = (strBuf[5] | 0x10); + int64_t TmpVal = NewVal / 300; + strBuf[6] = (((TmpVal >> 1) >> 24) & 0xFF); + strBuf[7] = (((TmpVal >> 1) >> 16) & 0xFF); + strBuf[8] = (((TmpVal >> 1) >> 8) & 0xFF); + strBuf[9] = ((TmpVal >> 1) & 0xFF); + int Remainder = NewVal % 300; + strBuf[10] = 0x7E + ((TmpVal & 0x01) << 7) + ((Remainder & 0x0100) >> 8); + strBuf[11] = (Remainder & 0x00FF); + } + +/// Gets the PCR (Program Clock Reference) of a Packet. +/// \return The value of the PCR. + int64_t Packet::getPCR() const{ + if (!getAdaptationField()) { + return -1; + } + if (!(strBuf[5] & 0x10)) { + return -1; + } + int64_t Result = (((strBuf[6] << 24) | (strBuf[7] << 16) | (strBuf[8] << 8) | strBuf[9]) << 1) | (strBuf[10] >> 7); + Result *= 300; + Result |= (((strBuf[10] & 0x01) << 8) + strBuf[11]); + return Result; + } + +/// Gets the OPCR (Original Program Clock Reference) of a Packet. +/// \return The value of the OPCR. + int64_t Packet::getOPCR() const{ + if (!getAdaptationField()) { + return -1; + } + if (!(strBuf[5 + 6] & 0x10)) { + return -1; + } + int64_t Result = 0; + Result = (((strBuf[6 + 6] << 24) + (strBuf[7 + 6] << 16) + (strBuf[8 + 6] << 8) + strBuf[9 + 6]) << 1) + (strBuf[10 + 6] & 0x80 >> 7); + Result = Result * 300; + Result += (((strBuf[10 + 6] & 0x01) << 8) + strBuf[11 + 6]); + return Result; + } + +/// Gets the transport error inficator of a Packet +/// \return The transport error inficator of a Packet + bool Packet::hasTransportError() const{ + return strBuf[1] & 0x80; + } + +/// Gets the payload unit start inficator of a Packet +/// \return The payload unit start inficator of a Packet + bool Packet::getUnitStart() const{ + return strBuf[1] & 0x40; + } + +/// Gets the transport priority of a Packet +/// \return The transport priority of a Packet + bool Packet::hasPriority() const{ + return strBuf[1] & 0x20; + } + +/// Gets the transport scrambling control of a Packet +/// \return The transport scrambling control of a Packet + unsigned int Packet::getTransportScramblingControl() const{ + return (unsigned int)((strBuf[3] >> 6) & (0x03)); + } + +/// Gets the current length of the adaptationfield. +/// \return The length of the adaptationfield. + int Packet::getAdaptationFieldLen() const{ + if (!getAdaptationField()) { + return -1; + } + return (int)strBuf[4]; + } + + Packet::operator bool() const{ + return pos && strBuf[0] == 0x47; + } + +/// Prints a packet to stdout, for analyser purposes. + std::string Packet::toPrettyString(size_t indent, int detailLevel) const{ + if (!(*this)){ + return "[Invalid packet - no sync byte]"; + } + std::stringstream output; + output << std::string(indent, ' ') << "[PID " << getPID() << "|" << std::hex << getContinuityCounter() << std::dec << ": " << getDataSize() << "b "; + if (!getPID()){ + output << "PAT"; + }else{ + if (pmt_pids.count(getPID())){ + output << "PMT"; + }else{ + if (stream_pids.count(getPID())){ + output << stream_pids[getPID()]; + }else{ + output << "Unknown"; + } + } + } + output << "]"; + if (getUnitStart()){ + output << " [Start]"; + } + if (getAdaptationField() > 1 && getAdaptationFieldLen()) { + if (hasDiscontinuity()){ + output << " [Discontinuity]"; + } + if (getRandomAccess()){ + output << " [RandomXS]"; + } + if (hasPCR()) { + output << " [PCR " << (double)getPCR() / 27000000 << "s]"; + } + if (hasOPCR()) { + output<< " [OPCR: " << (double)getOPCR() / 27000000 << "s]"; + } + } + output << std::endl; + if (!getPID()) { + //PAT + if (detailLevel >= 2){ + output << ((ProgramAssociationTable *)this)->toPrettyString(indent + 2); + }else{ + ((ProgramAssociationTable *)this)->toPrettyString(indent + 2); + } + return output.str(); + } + + if (pmt_pids.count(getPID())){ + //PMT + if (detailLevel >= 2){ + output << ((ProgramMappingTable *)this)->toPrettyString(indent + 2); + }else{ + ((ProgramMappingTable *)this)->toPrettyString(indent + 2); + } + return output.str(); + } + + if (detailLevel >= 3){ + output << std::string(indent+2, ' ') << "Raw data bytes:"; + unsigned int size = getDataSize(); + + for (unsigned int i = 0; i < size; ++i){ + if (!(i % 32)){ + output << std::endl << std::string(indent + 4, ' '); + } + output << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)(strBuf+pos)[i] << " "; + if ((i % 4) == 3){ + output << " "; + } + } + output << std::endl; + } + + return output.str(); + } + /* + char * Packet::dataPointer(){ + return (char*)strBuf+pos;//.data() + 188 - dataSize(); + }*/ + + unsigned int Packet::getDataSize() const{ + return 184 - ((getAdaptationField() > 1) ? getAdaptationFieldLen() + 1 : 0); + } + + /// Returns true if this PID contains a PMT. + /// Important caveat: only works if the corresponding PAT has been pretty-printed earlier! + bool Packet::isPMT() const{ + return pmt_pids.count(getPID()); + } + +/// Sets the start of a new unit in this Packet. +/// \param NewVal The new value for the start of a unit. + void Packet::setUnitStart(bool NewVal) { + if (NewVal) { + strBuf[1] |= 0x40; + } else { + strBuf[1] &= 0xBF; + } + } + +/// Gets the elementary stream priority indicator of a Packet +/// \return The elementary stream priority indicator of a Packet + bool Packet::hasESpriority() const{ + return strBuf[5] & 0x20; + } + + bool Packet::hasDiscontinuity() const{ + return strBuf[5] & 0x80; + } + + void Packet::setDiscontinuity(bool newVal){ + updPos(6); + if (getAdaptationField() == 3) { + if (!strBuf[4]) { + strBuf[4] = 1; + } + if (newVal) { + strBuf[5] |= 0x80; + } else { + strBuf[5] &= 0x7F; + } + } else { + setAdaptationField(3); + strBuf[4] = 1; + if (newVal) { + strBuf[5] = 0x80; + } else { + strBuf[5] = 0x00; + } + } + } + +/// Gets whether this Packet can be accessed at random (indicates keyframe). +/// \return Whether or not this Packet contains a keyframe. + bool Packet::getRandomAccess() const{ + if (getAdaptationField() < 2) { + return false; + } + return strBuf[5] & 0x40; + } + +///Gets the value of the PCR flag +///\return true if there is a PCR, false otherwise + bool Packet::hasPCR() const{ + return strBuf[5] & 0x10; + } + +///Gets the value of the OPCR flag +///\return true if there is an OPCR, false otherwise + bool Packet::hasOPCR() const{ + return strBuf[5] & 0x08; + } + +///Gets the value of the splicing point flag +///\return the value of the splicing point flag + bool Packet::hasSplicingPoint() const{ + return strBuf[5] & 0x04; + } + +///Gets the value of the transport private data point flag +///\return the value of the transport private data point flag + void Packet::setRandomAccess(bool NewVal) { + updPos(6); + if (getAdaptationField() == 3) { + if (!strBuf[4]) { + strBuf[4] = 1; + } + if (NewVal) { + strBuf[5] |= 0x40; + } else { + strBuf[5] &= 0xBF; + } + } else { + setAdaptationField(3); + strBuf[4] = 1; + if (NewVal) { + strBuf[5] = 0x40; + } else { + strBuf[5] = 0x00; + } + } + } + +/// Transforms the Packet into a standard Program Association Table + void Packet::setDefaultPAT() { + static int MyCntr = 0; + memcpy((void*)strBuf, (void*)PAT, 188); + pos=188; + setContinuityCounter(MyCntr++); + MyCntr %= 0x10; + } + +/// Checks the size of the internal packet buffer (prints error if size !=188), then returns a pointer to the data. +/// \return A character pointer to the internal packet buffer data + const char * Packet::checkAndGetBuffer() const{ + if (pos != 188) { + DEBUG_MSG(DLVL_ERROR, "Size invalid (%d) - invalid data from this point on", pos); + } + return strBuf; + } + + +//BEGIN PES FUNCTIONS +//pes functons do not use the internal strBuf character buffer + ///\brief Appends the PES-encoded timestamp to a string. + ///\param strBuf The string to append to + ///\param fixedLead The "fixed" 4-bit lead value to use + ///\param time The timestamp to encode + void encodePESTimestamp(std::string & tmpBuf, char fixedLead, unsigned long long time){ + //FixedLead of 4 bits, bits 32-30 time, 1 marker bit + tmpBuf += (char)(fixedLead | ((time & 0x1C0000000LL) >> 29) | 0x01); + //Bits 29-22 time + tmpBuf += (char)((time & 0x03FC00000LL) >> 22); + //Bits 21-15 time, 1 marker bit + tmpBuf += (char)(((time & 0x0003F8000LL) >> 14) | 0x01); + //Bits 14-7 time + tmpBuf += (char)((time & 0x000007F80LL) >> 7); + //Bits 7-0 time, 1 marker bit + tmpBuf += (char)(((time & 0x00000007FLL) << 1) | 0x01); + } + +/// Generates a PES Lead-in for a video frame. +/// Prepends the lead-in to variable toSend, assumes toSend's length is all other data. +/// \param len The length of this frame. +/// \param PTS The timestamp of the frame. + std::string & Packet::getPESVideoLeadIn(unsigned int len, unsigned long long PTS, unsigned long long offset, bool isAligned) { + len += (offset ? 13 : 8); + static std::string tmpStr; + tmpStr.clear(); + tmpStr.reserve(25); + tmpStr.append("\000\000\001\340", 4); + tmpStr += (char)((len >> 8) & 0xFF); + tmpStr += (char)(len & 0xFF); + if (isAligned){ + tmpStr.append("\204", 1); + }else{ + tmpStr.append("\200", 1); + } + tmpStr += (char)(offset ? 0xC0 : 0x80) ; //PTS/DTS + Flags + tmpStr += (char)(offset ? 0x0A : 0x05); //PESHeaderDataLength + encodePESTimestamp(tmpStr, (offset ? 0x30 : 0x20), PTS + offset); + if (offset){ + encodePESTimestamp(tmpStr, 0x10, PTS); + } + return tmpStr; + } + +/// Generates a PES Lead-in for an audio frame. +/// Prepends the lead-in to variable toSend, assumes toSend's length is all other data. +/// \param len The length of this frame. +/// \param PTS The timestamp of the frame. + std::string & Packet::getPESAudioLeadIn(unsigned int len, unsigned long long PTS) { + static std::string tmpStr; + tmpStr.clear(); + tmpStr.reserve(14); + len += 8; + tmpStr.append("\000\000\001\300", 4); + tmpStr += (char)((len & 0xFF00) >> 8); //PES PacketLength + tmpStr += (char)(len & 0x00FF); //PES PacketLength (Cont) + tmpStr.append("\204\200\005", 3); + encodePESTimestamp(tmpStr, 0x20, PTS); + return tmpStr; + } +//END PES FUNCTIONS + +/// Fills the free bytes of the Packet. +/// Stores as many bytes from NewVal as possible in the packet. +/// The minimum of Packet::BytesFree and maxLen is used. +/// \param NewVal The data to store in the packet. +/// \param maxLen The maximum amount of bytes to store. + int Packet::fillFree(const char * NewVal, int maxLen) { + int toWrite = std::min((int)getBytesFree(), maxLen); + memcpy((void*)(strBuf+pos), (void*)NewVal, toWrite); + pos+=toWrite; + return toWrite; + } + +/// Adds stuffing to the Packet depending on how much content you want to send. +/// \param NumBytes the amount of non-stuffing content bytes you want to send. +/// \return The amount of content bytes that can be send. + void Packet::addStuffing() { + unsigned int numBytes = getBytesFree(); + if (!numBytes) { + return; + } + + if (getAdaptationField() == 2){ + FAIL_MSG("Can not handle adaptation field 2 - should stuff the entire packet, no data will follow after the adaptation field"); ///\todo more stuffing required + return; + } + + if (getAdaptationField() == 1){ + //Convert adaptationfield to 3, by shifting the \001 at [4] (and all after it) to [5]. + if (numBytes == 184){ + strBuf[pos++]=0x0; //strBuf.append("\000", 1); + }else{ + //strBuf.insert(4, 1, (char)0); + memmove((void*) (strBuf+5), (void*)(strBuf+4), 188-4-1); + pos++; + } + setAdaptationField(3);//sets [4] to 0 + } + + numBytes=getBytesFree(); //get the most recent strBuf len before stuffing + + if (getAdaptationField() == 3 && numBytes ) { + if (strBuf[4] == 0){//if we already have stuffing + memmove((void*) (strBuf+5+numBytes), (void*)(strBuf+5), 188-5-numBytes); + memset((void*)(strBuf+5),'$',numBytes); + pos+=numBytes; + }else{ + memmove((void*)(strBuf+5+strBuf[4]+numBytes), (void*)(strBuf+5+strBuf[4]) , 188-5-strBuf[4]-numBytes); + memset((void*)(strBuf+5+strBuf[4]),'$',numBytes); + pos+=numBytes; + } + strBuf[4] += numBytes;//add stuffing to the stuffing counter at [4] + } + if (numBytes){//if we added stuffing... + if (numBytes == strBuf[4]){//and the stuffing is ALL the stuffing... + strBuf[5] = 0x00;//set [5] to zero for some reason...? + numBytes --;//decrease the stuffing needed by one + } + //overwrite bytes [5+currStuffing-newStuffing] onward with newStuffing bytes of prettier filler data + for (int i = 0; i < numBytes; i++) { + strBuf[5+(strBuf[4] - numBytes)+i] = FILLER_DATA[i % sizeof(FILLER_DATA)]; + } + } + } + +///returns the character buffer with a std::string wrapper +///\return The raw TS data as a string + //const std::string& Packet::getStrBuf() const{ +// return std::string(strBuf); +// } + +///Gets the payload of this packet, as a raw char array +///\return The payload of this ts packet as a char pointer + const char * Packet::getPayload() const{ + return strBuf + (4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0)); + } + + ///Gets the length of the payload for this apcket + ///\return The amount of bytes payload in this packet + int Packet::getPayloadLength() const{ + return 184 - ((getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0)); + } + + + ///Retrieves the current addStuffingoffset value for a PAT + char ProgramAssociationTable::getOffset() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0); + return strBuf[loc]; + } + + ///Retrieves the ID of this table + char ProgramAssociationTable::getTableId() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 1; + return strBuf[loc]; + } + + ///Retrieves the current section length + short ProgramAssociationTable::getSectionLength() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 2; + return (short)(strBuf[loc] & 0x0F) << 8 | strBuf[loc + 1]; + } + + ///Retrieves the Transport Stream ID + short ProgramAssociationTable::getTransportStreamId() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 4; + return (short)(strBuf[loc] & 0x0F) << 8 | strBuf[loc + 1]; + } + + ///Retrieves the version number + char ProgramAssociationTable::getVersionNumber() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 6; + return (strBuf[loc] >> 1) & 0x1F; + } + + ///Retrieves the "current/next" indicator + bool ProgramAssociationTable::getCurrentNextIndicator() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 6; + return (strBuf[loc] >> 1) & 0x01; + } + + ///Retrieves the section number + char ProgramAssociationTable::getSectionNumber() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 7; + return strBuf[loc]; + } + + ///Retrieves the last section number + char ProgramAssociationTable::getLastSectionNumber() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 8; + return strBuf[loc]; + } + + ///Returns the amount of programs in this table + short ProgramAssociationTable::getProgramCount() const{ + //This is correct, not -12 since we already parsed 4 bytes here + return (getSectionLength() - 8) / 4; + } + + short ProgramAssociationTable::getProgramNumber(short index) const{ + if (index > getProgramCount()) { + return 0; + } + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 9; + return ((short)(strBuf[loc + (index * 4)]) << 8) | strBuf[loc + (index * 4) + 1]; + } + + short ProgramAssociationTable::getProgramPID(short index) const{ + if (index > getProgramCount()) { + return 0; + } + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 9; + return (((short)(strBuf[loc + (index * 4) + 2]) << 8) | strBuf[loc + (index * 4) + 3]) & 0x1FFF; + } + + int ProgramAssociationTable::getCRC() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 9 + (getProgramCount() * 4); + return ((int)(strBuf[loc]) << 24) | ((int)(strBuf[loc + 1]) << 16) | ((int)(strBuf[loc + 2]) << 8) | strBuf[loc + 3]; + } + +///This function prints a program association table, +///prints all values in a human readable format +///\param indent The indentation of the string printed as wanted by the user +///\return The string with human readable data from a PAT + std::string ProgramAssociationTable::toPrettyString(size_t indent) const{ + std::stringstream output; + output << std::string(indent, ' ') << "[Program Association Table]" << std::endl; + output << std::string(indent + 2, ' ') << "Pointer Field: " << (int)getOffset() << std::endl; + output << std::string(indent + 2, ' ') << "Table ID: " << (int)getTableId() << std::endl; + output << std::string(indent + 2, ' ') << "Sectionlen: " << getSectionLength() << std::endl; + output << std::string(indent + 2, ' ') << "Transport Stream ID: " << getTransportStreamId() << std::endl; + output << std::string(indent + 2, ' ') << "Version Number: " << (int)getVersionNumber() << std::endl; + output << std::string(indent + 2, ' ') << "Current/Next Indicator: " << (int)getCurrentNextIndicator() << std::endl; + output << std::string(indent + 2, ' ') << "Section number: " << (int)getSectionNumber() << std::endl; + output << std::string(indent + 2, ' ') << "Last Section number: " << (int)getLastSectionNumber() << std::endl; + output << std::string(indent + 2, ' ') << "Programs [" << (int)getProgramCount() << "]" << std::endl; + for (int i = 0; i < getProgramCount(); i++) { + output << std::string(indent + 4, ' ') << "[" << i + 1 << "] "; + output << "Program Number: " << getProgramNumber(i) << ", "; + output << (getProgramNumber(i) == 0 ? "Network" : "Program Map") << " PID: " << getProgramPID(i); + pmt_pids.insert(getProgramPID(i)); + output << std::endl; + } + output << std::string(indent + 2, ' ') << "CRC32: " << std::hex << std::setw(8) << std::setfill('0') << std::uppercase << getCRC() << std::dec << std::endl; + return output.str(); + + } + + ProgramMappingEntry::ProgramMappingEntry(char * begin, char * end){ + data = begin; + boundary = end; + } + + ProgramMappingEntry::operator bool() const { + return data && (data < boundary); + } + + int ProgramMappingEntry::getStreamType() const{ + return data[0]; + } + + std::string ProgramMappingEntry::getCodec() const{ + switch (getStreamType()){ + case 0x01: + case 0x03: return "MPEG1"; + case 0x02: return "MPEG1/2"; + case 0x04: + case 0x05: + case 0x06: return "MPEG2"; + case 0x07: return "MHEG"; + case 0x08: return "MPEG2 DSM CC"; + case 0x09: return "H.222.1"; + case 0x0A: return "DSM CC encapsulation"; + case 0x0B: return "DSM CC U-N"; + case 0x0C: return "DSM CC descriptor"; + case 0x0D: return "DSM CC section"; + case 0x0E: return "MPEG2 aux"; + case 0x0F: return "ADTS"; + case 0x10: return "MPEG4"; + case 0x11: return "LATM"; + case 0x12: return "SL/Flex PES"; + case 0x13: return "SL/Flex section"; + case 0x14: return "SDP"; + case 0x15: return "meta PES"; + case 0x16: return "meta section"; + case 0x1B: return "H264"; + case 0x81: return "AC3"; + default: return "unknown"; + } + } + + std::string ProgramMappingEntry::getStreamTypeString() const{ + switch (getStreamType()){ + case 0x01: + case 0x02: + case 0x09: + case 0x10: + case 0x1B: return "video"; + case 0x03: + case 0x04: + case 0x11: + case 0x81: + case 0x0F: return "audio"; + default: return "data"; + } + } + + int ProgramMappingEntry::getElementaryPid() const{ + return ((data[1] << 8) | data[2]) & 0x1FFF; + } + + int ProgramMappingEntry::getESInfoLength() const{ + return ((data[3] << 8) | data[4]) & 0x0FFF; + } + + void ProgramMappingEntry::advance(){ + if (!(*this)) { + return; + } + data += 5 + getESInfoLength(); + } + + ProgramMappingTable::ProgramMappingTable(){ + strBuf[0] = 0x47; + strBuf[1] = 0x50; + strBuf[2] = 0x00; + strBuf[3] = 0x10; + pos=4; + } + + char ProgramMappingTable::getOffset() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0); + return strBuf[loc]; + } + + void ProgramMappingTable::setOffset(char newVal) { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0); + strBuf[loc] = newVal; + } + + char ProgramMappingTable::getTableId() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 1; + return strBuf[loc]; + } + + void ProgramMappingTable::setTableId(char newVal) { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 1; + updPos(loc+1); + strBuf[loc] = newVal; + } + + short ProgramMappingTable::getSectionLength() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 2; + return (((short)strBuf[loc] & 0x0F) << 8) | strBuf[loc + 1]; + } + + void ProgramMappingTable::setSectionLength(short newVal) { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 2; + updPos(loc+2); + strBuf[loc] = (char)(newVal >> 8); + strBuf[loc+1] = (char)newVal; + } + + short ProgramMappingTable::getProgramNumber() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 4; + return (((short)strBuf[loc]) << 8) | strBuf[loc + 1]; + } + + void ProgramMappingTable::setProgramNumber(short newVal) { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 4; + updPos(loc+2); + strBuf[loc] = (char)(newVal >> 8); + strBuf[loc+1] = (char)newVal; + } + + char ProgramMappingTable::getVersionNumber() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 6; + return (strBuf[loc] >> 1) & 0x1F; + } + + void ProgramMappingTable::setVersionNumber(char newVal) { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 6; + updPos(loc+1); + strBuf[loc] = ((newVal & 0x1F) << 1) | 0xC1; + } + + ///Retrieves the "current/next" indicator + bool ProgramMappingTable::getCurrentNextIndicator() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 6; + return (strBuf[loc] >> 1) & 0x01; + } + + ///Sets the "current/next" indicator + void ProgramMappingTable::setCurrentNextIndicator(bool newVal) { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 6; + updPos(loc+1); + strBuf[loc] = (((char)newVal) << 1) | (strBuf[loc] & 0xFD) | 0xC1; + } + + ///Retrieves the section number + char ProgramMappingTable::getSectionNumber() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 7; + return strBuf[loc]; + } + + ///Sets the section number + void ProgramMappingTable::setSectionNumber(char newVal) { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 7; + updPos(loc+1); + strBuf[loc] = newVal; + } + + ///Retrieves the last section number + char ProgramMappingTable::getLastSectionNumber() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 8; + return strBuf[loc]; + } + + ///Sets the last section number + void ProgramMappingTable::setLastSectionNumber(char newVal) { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 8; + updPos(loc+1); + strBuf[loc] = newVal; + } + + short ProgramMappingTable::getPCRPID() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 9; + return (((short)strBuf[loc] & 0x1F) << 8) | strBuf[loc + 1]; + } + + void ProgramMappingTable::setPCRPID(short newVal) { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 9; + updPos(loc+2); + strBuf[loc] = (char)((newVal >> 8) & 0x1F) | 0xE0;//Note: here we set reserved bits on 1 + strBuf[loc+1] = (char)newVal; + } + + short ProgramMappingTable::getProgramInfoLength() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 11; + return (((short)strBuf[loc] & 0x0F) << 8) | strBuf[loc + 1]; + } + + void ProgramMappingTable::setProgramInfoLength(short newVal) { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 11; + updPos(loc+2); + strBuf[loc] = (char)((newVal >> 8) & 0x0F) | 0xF0;//Note: here we set reserved bits on 1 + strBuf[loc+1] = (char)newVal; + } + + short ProgramMappingTable::getProgramCount() const{ + return (getSectionLength() - 13) / 5; + } + + void ProgramMappingTable::setProgramCount(short newVal) { + setSectionLength(newVal * 5 + 13); + } + + ProgramMappingEntry ProgramMappingTable::getEntry(int index) const{ + int dataOffset = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset(); + ProgramMappingEntry res((char*)(strBuf + dataOffset + 13 + getProgramInfoLength()), (char*)(strBuf + dataOffset + getSectionLength()) ); + for (int i = 0; i < index; i++){ + res.advance(); + } + return res; + } + + char ProgramMappingTable::getStreamType(short index) const{ + if (index > getProgramCount()) { + return 0; + } + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); + return strBuf[loc + (index * 5)]; + } + + void ProgramMappingTable::setStreamType(char newVal, short index) { + if (index > getProgramCount()) { + return; + } + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); //TODO + updPos(loc+(index*5)+1); + strBuf[loc + (index * 5)] = newVal; + } + + short ProgramMappingTable::getElementaryPID(short index) const{ + if (index > getProgramCount()) { + return 0; + } + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); + return (((short)strBuf[loc + (index * 5) + 1] & 0x1F) << 8) | strBuf[loc + (index * 5) + 2]; + } + + void ProgramMappingTable::setElementaryPID(short newVal, short index) { + if (index > getProgramCount()) { + return; + } + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); + updPos(loc+(index*5)+3); + strBuf[loc + (index * 5)+1] = ((newVal >> 8) & 0x1F )| 0xE0; + strBuf[loc + (index * 5)+2] = (char)newVal; + } + + short ProgramMappingTable::getESInfoLength(short index) const{ + if (index > getProgramCount()) { + return 0; + } + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); + return (((short)strBuf[loc + (index * 5) + 3] & 0x0F) << 8) | strBuf[loc + (index * 5) + 4]; + } + + void ProgramMappingTable::setESInfoLength(short newVal, short index) { + if (index > getProgramCount()) { + return; + } + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); + updPos(loc+(index*5)+5); + strBuf[loc + (index * 5)+3] = ((newVal >> 8) & 0x0F) | 0xF0; + strBuf[loc + (index * 5)+4] = (char)newVal; + } + + int ProgramMappingTable::getCRC() const{ + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + getSectionLength(); + return ((int)(strBuf[loc]) << 24) | ((int)(strBuf[loc + 1]) << 16) | ((int)(strBuf[loc + 2]) << 8) | strBuf[loc + 3]; + } + + void ProgramMappingTable::calcCRC() { + unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + getSectionLength(); + unsigned int newVal;//this will hold the CRC32 value; + unsigned int pidLoc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 1;//location of PCRPID + newVal = checksum::crc32(-1, strBuf + pidLoc, loc - pidLoc);//calculating checksum over all the fields from table ID to the last stream element + updPos(188); + strBuf[loc + 3] = (newVal >> 24) & 0xFF; + strBuf[loc + 2] = (newVal >> 16) & 0xFF; + strBuf[loc + 1] = (newVal >> 8) & 0xFF; + strBuf[loc] = newVal & 0xFF; + memset((void*)(strBuf + loc + 4), 0xFF, 184 - loc); + } + +///Print all PMT values in a human readable format +///\param indent The indentation of the string printed as wanted by the user +///\return The string with human readable data from a PMT table + std::string ProgramMappingTable::toPrettyString(size_t indent) const{ + std::stringstream output; + output << std::string(indent, ' ') << "[Program Mapping Table]" << std::endl; + output << std::string(indent + 2, ' ') << "Pointer Field: " << (int)getOffset() << std::endl; + output << std::string(indent + 2, ' ') << "Table ID: " << (int)getTableId() << std::endl; + output << std::string(indent + 2, ' ') << "Section Length: " << getSectionLength() << std::endl; + output << std::string(indent + 2, ' ') << "Program number: " << getProgramNumber() << std::endl; + output << std::string(indent + 2, ' ') << "Version number: " << (int)getVersionNumber() << std::endl; + output << std::string(indent + 2, ' ') << "Current next indicator: " << (int)getCurrentNextIndicator() << std::endl; + output << std::string(indent + 2, ' ') << "Section number: " << (int)getSectionNumber() << std::endl; + output << std::string(indent + 2, ' ') << "Last Section number: " << (int)getLastSectionNumber() << std::endl; + output << std::string(indent + 2, ' ') << "PCR PID: " << getPCRPID() << std::endl; + output << std::string(indent + 2, ' ') << "Program Info Length: " << getProgramInfoLength() << std::endl; + ProgramMappingEntry entry = getEntry(0); + while (entry) { + output << std::string(indent + 4, ' '); + stream_pids[entry.getElementaryPid()] = entry.getCodec() + std::string(" ") + entry.getStreamTypeString(); + output << "Stream " << entry.getElementaryPid() << ": " << stream_pids[entry.getElementaryPid()] << " (" << entry.getStreamType() << "), InfoLen = " << entry.getESInfoLength() << std::endl; + entry.advance(); + } + output << std::string(indent + 2, ' ') << "CRC32: " << std::hex << std::setw(8) << std::setfill('0') << std::uppercase << getCRC() << std::dec << std::endl; + return output.str(); + } + + + /// Construct a PMT (special 188B ts packet) from a set of selected tracks and metadata. + /// This function is not part of the packet class, but it is in the TS namespace. + /// It uses an internal static TS packet for PMT storage. + ///\param selectedTracks tracks to include in PMT creation + ///\param myMeta + ///\returns character pointer to a static 188B TS packet + const char * createPMT(std::set& selectedTracks, DTSC::Meta& myMeta, int contCounter){ + static ProgramMappingTable PMT; + PMT.setPID(4096); + PMT.setTableId(2); + //section length met 2 tracks: 0xB017 + PMT.setSectionLength(0xB00D + (selectedTracks.size() * 5)); + PMT.setProgramNumber(1); + PMT.setVersionNumber(0); + PMT.setCurrentNextIndicator(0); + PMT.setSectionNumber(0); + PMT.setLastSectionNumber(0); + PMT.setContinuityCounter(contCounter); + int vidTrack = -1; + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + if (myMeta.tracks[*it].type == "video"){ + vidTrack = *it; + break; + } + } + if (vidTrack == -1){ + vidTrack = *(selectedTracks.begin()); + } + PMT.setPCRPID(0x100 + vidTrack - 1); + PMT.setProgramInfoLength(0); + short id = 0; + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + if (myMeta.tracks[*it].codec == "H264"){ + PMT.setStreamType(0x1B,id); + }else if (myMeta.tracks[*it].codec == "AAC"){ + PMT.setStreamType(0x0F,id); + }else if (myMeta.tracks[*it].codec == "MP3"){ + PMT.setStreamType(0x03,id); + } + PMT.setElementaryPID(0x100 + (*it) - 1, id); + PMT.setESInfoLength(0,id); + id++; + } + PMT.calcCRC(); + return PMT.checkAndGetBuffer(); + } + +} + + + diff --git a/lib/ts_packet.h b/lib/ts_packet.h new file mode 100644 index 00000000..7c0d370c --- /dev/null +++ b/lib/ts_packet.h @@ -0,0 +1,203 @@ +/// \file ts_packet.h +/// Holds all headers for the TS Namespace. + +#pragma once +#include +#include +#include +#include //for uint64_t +#include +#include +#include +#include +#include "dtsc.h" +#include "checksum.h" + +/// Holds all TS processing related code. +namespace TS { + + ///Class for reading and writing TS Streams. The class is capable of analyzing a packet of 188 bytes + ///and calculating key values + class Packet { + public: + //Constructors and fillers + Packet(); + ~Packet(); + bool FromPointer(const char * data); + bool FromFile(FILE * data); + + //Base properties + void setPID(int NewPID); + unsigned int getPID() const; + void setContinuityCounter(int NewContinuity); + int getContinuityCounter() const; + void setPCR(int64_t NewVal); + int64_t getPCR() const; + int64_t getOPCR() const; + void setAdaptationField(int NewVal); + int getAdaptationField() const; + int getAdaptationFieldLen() const; + unsigned int getTransportScramblingControl() const; + + //Flags + void setUnitStart(bool newVal); + bool getUnitStart() const; + void setRandomAccess(bool newVal); + bool getRandomAccess() const; + + void setDiscontinuity(bool newVal); + bool hasDiscontinuity() const; + bool hasPCR() const; + bool hasOPCR() const; + bool hasSplicingPoint() const; + bool hasTransportError() const; + bool hasPriority() const; + bool hasESpriority() const; + + //Helper functions + operator bool() const; + bool isPMT() const; + void clear(); + void setDefaultPAT(); + unsigned int getDataSize() const; + + unsigned int getBytesFree() const; + int fillFree(const char * PackageData, int maxLen); + void addStuffing(); + void updPos(unsigned int newPos); + + //PES helpers + static std::string & getPESVideoLeadIn(unsigned int len, unsigned long long PTS, unsigned long long offset, bool isAligned); + static std::string & getPESAudioLeadIn(unsigned int len, unsigned long long PTS); + + //Printers and writers + std::string toPrettyString(size_t indent = 0, int detailLevel = 3) const; + const char * getPayload() const; + int getPayloadLength() const; + const char * checkAndGetBuffer() const; + + protected: + char strBuf[189]; + unsigned int pos; + }; + + class ProgramAssociationTable : public Packet { + public: + char getOffset() const; + char getTableId() const; + short getSectionLength() const; + short getTransportStreamId() const; + char getVersionNumber() const; + bool getCurrentNextIndicator() const; + char getSectionNumber() const; + char getLastSectionNumber() const; + short getProgramCount() const; + short getProgramNumber(short index) const; + short getProgramPID(short index) const; + int getCRC() const; + std::string toPrettyString(size_t indent) const; + }; + + class ProgramMappingEntry { + public: + ProgramMappingEntry(char * begin, char * end); + + operator bool() const; + + int getStreamType() const; + std::string getCodec() const; + std::string getStreamTypeString() const; + int getElementaryPid() const; + int getESInfoLength() const; + char * getESInfo() const; + void advance(); + private: + char* data; + char* boundary; + }; + + class ProgramMappingTable : public Packet { + public: + ProgramMappingTable(); + char getOffset() const; + void setOffset(char newVal); + char getTableId() const; + void setTableId(char newVal); + short getSectionLength() const; + void setSectionLength(short newVal); + short getProgramNumber() const; + void setProgramNumber(short newVal); + char getVersionNumber() const; + void setVersionNumber(char newVal); + bool getCurrentNextIndicator() const; + void setCurrentNextIndicator(bool newVal); + char getSectionNumber() const; + void setSectionNumber(char newVal); + char getLastSectionNumber() const; + void setLastSectionNumber(char newVal); + short getPCRPID() const; + void setPCRPID(short newVal); + short getProgramInfoLength() const; + void setProgramInfoLength(short newVal); + short getProgramCount() const; + void setProgramCount(short newVal); + ProgramMappingEntry getEntry(int index) const; + void setStreamType(char newVal, short index); + char getStreamType(short index) const; + void setElementaryPID(short newVal, short index); + short getElementaryPID(short index) const; + void setESInfoLength(short newVal,short index); + short getESInfoLength(short index) const; + int getCRC() const; + void calcCRC(); + std::string toPrettyString(size_t indent) const; + }; + + /// Constructs an audio header to be used on each audio frame. + /// The length of this header will ALWAYS be 7 bytes, and has to be + /// prepended on each audio frame. + /// \param FrameLen the length of the current audio frame. + /// \param initData A string containing the initalization data for this track's codec. + static inline std::string getAudioHeader(int FrameLen, std::string initData) { + char StandardHeader[7] = {0xFF, 0xF1, 0x00, 0x00, 0x00, 0x1F, 0xFC}; + FrameLen += 7; + StandardHeader[2] = ((((initData[0] >> 3) - 1) << 6) & 0xC0); //AAC Profile - 1 ( First two bits ) + StandardHeader[2] |= ((((initData[0] & 0x07) << 1) | ((initData[1] >> 7) & 0x01)) << 2); //AAC Frequency Index + StandardHeader[2] |= ((initData[1] & 0x20) >> 5); //AAC Channel Config + StandardHeader[3] = ((initData[1] & 0x18) << 3); //AAC Channel Config (cont.) + StandardHeader[3] |= ((FrameLen & 0x00001800) >> 11); + StandardHeader[4] = ((FrameLen & 0x000007F8) >> 3); + StandardHeader[5] |= ((FrameLen & 0x00000007) << 5); + return std::string(StandardHeader, 7); + } + + + /// A standard Program Association Table, as generated by FFMPEG. + /// Seems to be independent of the stream. + //0x47 = sync byte + //0x4000 = transport error(1) = 0, payload unit start(1) = 1, priority(1) = 0, PID(13) = 0 + //0x10 = transportscrambling(2) = 0, adaptation(2) = 1, continuity(4) = 0 + //0x00 = pointer = 0 + //0x00 = table ID = 0 = PAT + //0xB00D = section syntax(1) = 1, 0(1)=0, reserved(2) = 3, section_len(12) = 13 + //0x0001 = transport stream id = 1 + //0xC1 = reserved(2) = 3, version(5)=0, curr_next_indi(1) = 1 + //0x00 = section_number = 0 + //0x00 = last_section_no = 0 + //0x0001 = ProgNo = 1 + //0xF000 = reserved(3) = 7, network pid = 4096 + //0x2AB104B2 = CRC32 + static char PAT[188] = {0x47, 0x40, 0x00, 0x10, 0x00, 0x00, 0xB0, 0x0D, 0x00, 0x01, 0xC1, 0x00, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x2A, 0xB1, 0x04, + 0xB2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + + const char * createPMT(std::set& selectedTracks, DTSC::Meta& myMeta, int contCounter=0); + +} //TS namespace + diff --git a/lib/vorbis.cpp b/lib/vorbis.cpp new file mode 100644 index 00000000..0e088c70 --- /dev/null +++ b/lib/vorbis.cpp @@ -0,0 +1,438 @@ +#include "vorbis.h" +#include "defines.h" +#include +#include +#include +#include +#include "bitstream.h" +#include + +#include +#include + +namespace vorbis{ + long long unsigned int reverseByte16(long long unsigned int input){ + return ((input & 0xFF00) >> 8) | ((input & 0xFF) << 8); + } + + long long unsigned int reverseByte24(long long unsigned int input){ + return ((input & 0xFF0000) >> 16)| (input & 0xFF00) | ((input & 0xFF) << 16); + } + + long long unsigned int reverseByte32(long long unsigned int input){ + return ((input & 0xFF000000) >> 24)| ((input & 0xFF0000) >> 8) | ((input & 0xFF00) << 8) | ((input & 0xFF) << 24); + } + + header::header(char * newData, unsigned int length){ + data = newData; + datasize = length; + } + + header::~header(){ + } + + int header::getHeaderType(){ + return (int)(data[0]); + } + + long unsigned int header::getVorbisVersion(){ + if (getHeaderType() == 1){ + return getInt32(7); + }else{ + return 0; + } + } + + char header::getAudioChannels(){ + if (getHeaderType() == 1){ + return data[11]; + }else{ + return 0; + } + } + + long unsigned int header::getAudioSampleRate(){ + if (getHeaderType() == 1){ + return ntohl(getInt32(12)); + }else{ + return 0; + } + } + + long unsigned int header::getBitrateMaximum(){ + if (getHeaderType() == 1){ + return getInt32(16); + }else{ + return 0; + } + } + + long unsigned int header::getBitrateNominal(){ + if (getHeaderType() == 1){ + return getInt32(20); + }else{ + return 0; + } + } + + long unsigned int header::getBitrateMinimum(){ + if (getHeaderType() == 1){ + return getInt32(24); + }else{ + return 0; + } + + } + + char header::getBlockSize0(){ + if (getHeaderType() == 1){ + return data[28] & 0x0F; + }else{ + return 0; + } + } + + char header::getBlockSize1(){ + if (getHeaderType() == 1){ + return (data[28]>>4) & 0x0F; + }else{ + return 0; + } + } + + + char header::getFramingFlag(){ + if (getHeaderType() == 1){ + return data[29]; + }else{ + return 0; + } + } + + bool header::checkDataSize(unsigned int size){ + if (size > datasize){ + void* tmp = realloc(data,size); + if (tmp){ + data = (char*)tmp; + datasize = size; + return true; + }else{ + return false; + } + }else{ + return true; + } + } + + bool header::validate(){ + switch(getHeaderType()){ + case 1://ID header + if (datasize!=30){ + return false; + } + if (getVorbisVersion()!=0){ + return false; + } + if (getAudioChannels()<=0){ + return false; + }; + if (getAudioSampleRate()<=0) { + return false; + } + if (getBlockSize0()>getBlockSize1()){ + return false; + }; + if (getFramingFlag()!=1){ + return false; + }; + break; + case 3://comment header + break; + case 5://init header + break; + default: + return false; + break; + } + return true; + } + + bool isHeader(const char * newData, unsigned int length){ + if (length < 7){ + return false; + } + if(memcmp(newData+1, "vorbis", 6)!=0){ + return false; + } + return true; + } + + bool header::isHeader(){ + return ::vorbis::isHeader(data, datasize); + } + + +/* + bool header::read(const char* newData, unsigned int length){ + if (length < 7){ + return false; + } + if(memcmp(newData+1, "vorbis", 6)!=0){ + return false; + } + + if (checkDataSize(length)){ + memcpy(data, newData, length); + }else{ + return false; + } + return true; + }*/ + + std::deque header::readModeDeque(char audioChannels){ + Utils::bitstreamLSBF stream; + stream.append(data,datasize); + stream.skip(28); //skipping common header part + stream.skip(28); //skipping common header part + char codebook_count = stream.get(8) + 1; + for (int i = 0; i < codebook_count; i++){ + long long unsigned int CMN = stream.get(24); + if (CMN != 0x564342){ + DEBUG_MSG(DLVL_WARN,"Is dit het? VCB != %c%c%c", (char)(CMN >> 16), (char)(CMN >> 8), (char)CMN); + exit(1); + } + unsigned short codebook_dimensions = stream.get(16); + unsigned int codebook_entries = stream.get(24); + bool orderedFlag = stream.get(1); + if (!orderedFlag){ + bool sparseFlag = stream.get(1); + if (sparseFlag){//sparse flag + //sparse handling + for (unsigned int o = 0; o < codebook_entries; o++){ + if (stream.get(1)){ + stream.skip(5); + } + } + }else{ + for (unsigned int o = 0; o < codebook_entries; o++){ + stream.skip(5); + } + } + }else{ + //ordered handling + stream.skip(5); + for (unsigned int o = 0; o < codebook_entries; o++){ + int readnow = (std::log(codebook_entries-o))/(std::log(2))+1; + o+=stream.get(readnow); + + } + } + char codebook_lookup_type = stream.get(4); + if (codebook_lookup_type != 0){ + stream.skip(32); + stream.skip(32); + char codebook_value_bits = stream.get(4) + 1; + stream.skip(1); + unsigned int codebook_lookup_value; + if (codebook_lookup_type == 1){ + codebook_lookup_value = std::pow(codebook_entries, (1.0/codebook_dimensions)); + }else{ + codebook_lookup_value = codebook_entries * codebook_dimensions; + } + for (unsigned int i = 0; i < codebook_lookup_value; i++){ + stream.skip(codebook_value_bits); + } + } + } + //end of codebooks + //time domain transforms + long long unsigned int TDT = stream.get(6) + 1; + for (unsigned int i = 0; i < TDT; i++){ + stream.skip(16); + } + //Floors + long long unsigned int floors = stream.get(6) + 1; + for (unsigned int i = 0; i < floors; i++){ + long long unsigned int floorType = stream.get(16); + switch(floorType){ + case 0:{ + DEBUG_MSG(DLVL_WARN, "FloorType 0 in vorbis setup header not tested!"); + stream.skip(8);//order + stream.skip(16);//rate + stream.skip(16);//bark_map_size + stream.skip(6);//amplitude bits + stream.skip(8);//amplitude offset + long long unsigned int numberOfBooks = stream.get(4)+1; + for (unsigned int o = 0; o < numberOfBooks; o++){ + stream.skip(8);//book list array + } + break; + } + case 1:{ + long long unsigned int floorPartitions = stream.get(5); + long long int max = -1; + std::deque partition_class; + for (unsigned int o = 0; o < floorPartitions; o++){ + long long int temp = stream.get(4); + partition_class.push_back(temp); + if (temp>max) max = temp; + } + std::deque class_dimensions; + for (int o = 0; o <= max; o++){ + class_dimensions.push_back(stream.get(3)+1);//class dimensions PUT IN ARRAY! + int class_subclass = stream.get(2); + if (class_subclass !=0){ + stream.skip(8);//class_master_books + } + for (int p = 0; p < (1< residueCascade; + long long unsigned int residueType = stream.get(16); + if(residueType<=2){ + stream.skip(24);//residue begin + stream.skip(24);//residue end + stream.skip(24);//residue partition size + long long unsigned int residueClass = stream.get(6)+1;//residue classifications + stream.skip(8);//residue classbook + for (unsigned int o = 0; o < residueClass; o++){ + char temp = stream.get(3);//low bits + bool bitFlag = stream.get(1); + if (bitFlag){ + temp += stream.get(5) << 3; + } + residueCascade.push_back(temp); + } + for (unsigned int o = 0; o < residueClass; o++){ + for (unsigned int p = 0; p < 7; p++){ + if (((residueCascade[o] >> p) & 1) == 1){ + stream.skip(8); + }else{ + } + } + } + }else{ + exit(0); + } + } + //Mappings + long long unsigned int mappings = stream.get(6) + 1; + for(unsigned int i = 0; i < mappings; i++){ + long long unsigned int mapType = stream.get(16); + if (mapType == 0){ + char mappingSubmaps = 1; + if (stream.get(1)==1){ + mappingSubmaps = stream.get(4);//vorbis mapping submaps + } + long long unsigned int coupling_steps = 0; + if (stream.get(1)==1){ + coupling_steps = stream.get(8)+1; + for (unsigned int o = 0; o0){ + stream.skip(temp);//mapping magnitude + stream.skip(temp);//mapping angle + } + } + } + char meh = stream.get(2); + if (meh != 0){ + DEBUG_MSG(DLVL_ERROR, "Sanity check ==0 : %i", (int)meh); + exit(0); + } + if (mappingSubmaps > 1){ + for (int o = 0; o < audioChannels; o++){ + stream.skip(4); + } + } + for (int o = 0; o < mappingSubmaps; o++){ + stream.skip(8);//placeholder + stream.skip(8);//vorbis Mapping subfloor + stream.skip(8);//vorbis mapping submap residue + } + + }else{ + exit(0); + } + } + //Modes + long long unsigned int modes = stream.get(6) + 1; + std::deque retVal; + for (unsigned int i = 0; i < modes; i++){ + mode temp; + temp.blockFlag = stream.get(1); + temp.windowType = stream.get(16); + temp.transformType = stream.get(16); + temp.mapping = stream.get(8); + retVal.push_back(temp); + } + stream.skip(1); + return retVal; + } + + uint32_t header::getInt32(size_t index){ + if (datasize >= (index + 3)){ + return (data[index] << 24) + (data[index + 1] << 16) + (data[index + 2] << 8) + data[index + 3]; + } + return 0; + } + + uint32_t header::getInt24(size_t index){ + if (datasize >= (index + 2)){ + return 0 + (data[index] << 16) + (data[index + 1] << 8) + data[index + 2]; + } + return 0; + } + + uint16_t header::getInt16(size_t index){ + if (datasize >= (index + 1)){ + return 0 + (data[index] << 8) + data[index + 1]; + } + return 0; + } + + std::string header::toPrettyString(size_t indent){ + std::stringstream r; + r << std::string(indent+1,' ') << "Vorbis Header" << std::endl; + r << std::string(indent+2,' ') << "Magic Number: " << std::string(data + 1,6) << std::endl; + r << std::string(indent+2,' ') << "Header Type: " << getHeaderType() << std::endl; + if (getHeaderType() == 1){ + r << std::string(indent+2,' ') << "ID Header" << std::endl; + r << std::string(indent+2,' ') << "VorbisVersion: " << getVorbisVersion() << std::endl; + r << std::string(indent+2,' ') << "AudioChannels: " << (int)getAudioChannels() << std::endl; + r << std::string(indent+2,' ') << "BitrateMaximum: " << std::hex << getBitrateMaximum() << std::dec << std::endl; + r << std::string(indent+2,' ') << "BitrateNominal: " << std::hex << getBitrateNominal() << std::dec << std::endl; + r << std::string(indent+2,' ') << "BitrateMinimum: " << std::hex << getBitrateMinimum() << std::dec << std::endl; + r << std::string(indent+2,' ') << "BlockSize0: " << (int)getBlockSize0() << std::endl; + r << std::string(indent+2,' ') << "BlockSize1: " << (int)getBlockSize1() << std::endl; + r << std::string(indent+2,' ') << "FramingFlag: " << (int)getFramingFlag() << std::endl; + } else if (getHeaderType() == 3){ + r << std::string(indent+2,' ') << "Comment Header" << std::endl; + } else if (getHeaderType() == 5){ + r << std::string(indent+2,' ') << "Setup Header" << std::endl; + } + return r.str(); + } +} diff --git a/lib/vorbis.h b/lib/vorbis.h new file mode 100644 index 00000000..a5c885c9 --- /dev/null +++ b/lib/vorbis.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace vorbis{ + struct mode{ + bool blockFlag; + unsigned short windowType; + unsigned short transformType; + char mapping; + }; + + inline unsigned int ilog(unsigned int input){ + return (std::log(input))/(std::log(2))+1; + } + + bool isHeader(const char * newData, unsigned int length); + class header{ + public: + ~header(); + header(char* newData, unsigned int length); + int getHeaderType(); + long unsigned int getVorbisVersion(); + char getAudioChannels(); + long unsigned int getAudioSampleRate(); + long unsigned int getBitrateMaximum(); + long unsigned int getBitrateNominal(); + long unsigned int getBitrateMinimum(); + char getBlockSize0(); + char getBlockSize1(); + char getFramingFlag(); + std::string toPrettyString(size_t indent = 0); + std::deque readModeDeque(char audioChannels); + bool isHeader(); + unsigned int getDataSize(){ + return datasize; + } + protected: + uint32_t getInt32(size_t index); + uint32_t getInt24(size_t index); + uint16_t getInt16(size_t index); + private: + std::deque modes; + char* data; + unsigned int datasize; + bool checkDataSize(unsigned int size); + bool validate(); + }; +} diff --git a/test/abst_test.cpp b/test/abst_test.cpp new file mode 100644 index 00000000..5a19dce7 --- /dev/null +++ b/test/abst_test.cpp @@ -0,0 +1,335 @@ +/// \file abst_test.cpp +/// Tests the bootstrap generation functions by comparing to a known good bootstrap. + +#include +#include +#include +#include +#include +#include + +/// This is a known good bootstrap retrieved from a wowza demo server. +unsigned char __data[] = { + 0x00, 0x00, 0x0c, 0xce, 0x61, 0x62, 0x73, 0x74, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x09, 0x19, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x1a, 0x61, 0x73, 0x72, 0x74, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc7, 0x01, + 0x00, 0x00, 0x0c, 0x86, 0x61, 0x66, 0x72, 0x74, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x17, 0x70, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x28, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0xe0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3a, 0x98, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x50, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x52, 0x08, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5d, 0xc0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x78, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x75, 0x30, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0xe8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0xa0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x98, 0x58, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa4, 0x10, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf, 0xc8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbb, 0x80, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc7, 0x38, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0xf0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xde, 0xa8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xea, 0x60, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0x18, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0xd0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x0d, 0x88, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x19, 0x40, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x24, 0xf8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x30, 0xb0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3c, 0x68, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x48, 0x20, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x53, 0xd8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x5f, 0x90, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x6b, 0x48, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x77, 0x00, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x82, 0xb8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x8e, 0x70, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x9a, 0x28, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xa5, 0xe0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xb1, 0x98, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xbd, 0x50, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc9, 0x08, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xd4, 0xc0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xe0, 0x78, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xec, 0x30, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xf7, 0xe8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x03, 0xa0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0f, 0x58, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x1b, 0x10, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x26, 0xc8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x32, 0x80, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x3e, 0x38, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x49, 0xf0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x55, 0xa8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x61, 0x60, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x6d, 0x18, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x78, 0xd0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x84, 0x88, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x90, 0x40, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x9b, 0xf8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0xa7, 0xb0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xb3, 0x68, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xbf, 0x20, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0xca, 0xd8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xd6, 0x90, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xe2, 0x48, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0xee, 0x00, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xf9, 0xb8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0x70, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x11, 0x28, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x1c, 0xe0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x28, 0x98, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x34, 0x50, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x40, 0x08, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4b, 0xc0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x57, 0x78, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x63, 0x30, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x6e, 0xe8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x7a, 0xa0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x86, 0x58, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x92, 0x10, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x9d, 0xc8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0xa9, 0x80, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xb5, 0x38, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0xc0, 0xf0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0xcc, 0xa8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xd8, 0x60, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0xe4, 0x18, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0xef, 0xd0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfb, 0x88, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x07, 0x40, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x12, 0xf8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x1e, 0xb0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x2a, 0x68, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x36, 0x20, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x41, 0xd8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x4d, 0x90, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x59, 0x48, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x65, 0x00, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x70, 0xb8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x7c, 0x70, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, 0x28, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x93, 0xe0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x9f, 0x98, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xab, 0x50, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0xb7, 0x08, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0xc2, 0xc0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xce, 0x78, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0xda, 0x30, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0xe5, 0xe8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xf1, 0xa0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0xfd, 0x58, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x09, 0x10, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x14, 0xc8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x20, 0x80, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x2c, 0x38, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x37, 0xf0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x43, 0xa8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x4f, 0x60, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x5b, 0x18, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x66, 0xd0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x72, 0x88, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x7e, 0x40, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x89, 0xf8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x95, 0xb0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xa1, 0x68, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0xad, 0x20, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0xb8, 0xd8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xc4, 0x90, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0xd0, 0x48, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0xdc, 0x00, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xe7, 0xb8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0xf3, 0x70, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0xff, 0x28, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0a, 0xe0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x16, 0x98, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x22, 0x50, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x2e, 0x08, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x39, 0xc0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x45, 0x78, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x51, 0x30, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x5c, 0xe8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x8d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x68, 0xa0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x74, 0x58, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x80, 0x10, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x8b, 0xc8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x97, 0x80, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0xa3, 0x38, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x93, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0xae, 0xf0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xba, 0xa8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0xc6, 0x60, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0xd2, 0x18, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xdd, 0xd0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0xe9, 0x88, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0xf5, 0x40, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0xf8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0x0c, 0xb0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x18, 0x68, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x20, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0x2f, 0xd8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0x9f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x3b, 0x90, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x47, 0x48, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0x53, 0x00, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xa2, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x5e, 0xb8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x6a, 0x70, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0x76, 0x28, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xa5, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x81, 0xe0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x8d, 0x98, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xa7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0x99, 0x50, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xa5, 0x08, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xb0, 0xc0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0xbc, 0x78, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xc8, 0x30, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xd3, 0xe8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0xdf, 0xa0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xae, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xeb, 0x58, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf7, 0x10, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x02, 0xc8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x0e, 0x80, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1a, 0x38, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x25, 0xf0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x31, 0xa8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xb5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x3d, 0x60, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x49, 0x18, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x54, 0xd0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x60, 0x88, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x6c, 0x40, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x77, 0xf8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x83, 0xb0, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x8f, 0x68, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xbd, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x9b, 0x20, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xa6, 0xd8, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0xb2, 0x90, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0xbe, 0x48, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xca, 0x00, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0xd5, 0xb8, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0xe1, 0x70, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xed, 0x28, 0x00, 0x00, + 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0xf8, 0xe0, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, 0x00, 0xc6, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x09, 0x04, 0x98, 0x00, 0x00, 0x0b, 0xb8, 0x00, 0x00, + 0x00, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x10, 0x50, 0x00, 0x00, + 0x09, 0x9a +}; +unsigned int __data_len = 3278; + +/// Generates a bootstrap equal to the above file, then compares. +/// \returns Zero if they are equal (test success), other values otherwise. +int main(int argc, char ** argv) { + std::string temp; + + MP4::ASRT asrt; + asrt.setVersion(1); + asrt.setQualityEntry(temp, 0); + asrt.setSegmentRun(1, 199, 0); + + MP4::AFRT afrt; + afrt.setVersion(1); + afrt.setTimeScale(1000); + afrt.setQualityEntry(temp, 0); + MP4::afrt_runtable afrtrun; + for (int i = 0; i < 198; i++){ + afrtrun.firstFragment = i+1; + afrtrun.firstTimestamp = 3000*i; + afrtrun.duration = 3000; + afrt.setFragmentRun(afrtrun, i); + } + afrtrun.firstFragment = 199; + afrtrun.firstTimestamp = 594000; + afrtrun.duration = 2458; + afrt.setFragmentRun(afrtrun, 198); + + MP4::ABST abst; + abst.setVersion(1); + abst.setBootstrapinfoVersion(1); + abst.setProfile(0); + abst.setLive(false); + abst.setUpdate(false); + abst.setTimeScale(1000); + abst.setCurrentMediaTime(596458); + abst.setSmpteTimeCodeOffset(0); + abst.setMovieIdentifier(temp); + abst.setServerEntry(temp, 0); + abst.setQualityEntry(temp, 0); + abst.setDrmData(temp); + abst.setMetaData(temp); + abst.setSegmentRunTable(asrt, 0); + abst.setFragmentRunTable(afrt, 0); + + if (abst.boxedSize() != __data_len){return 42;} + return memcmp(abst.asBox(), __data, __data_len); +}