Initial drop from DIAL-1.0.2-1676049.tar.gz
diff --git a/Changelog b/Changelog
new file mode 100644
index 0000000..1b47bfc
--- /dev/null
+++ b/Changelog
@@ -0,0 +1,40 @@
+---------------------------------------------------------------------
+Version 1.0.2 (1673785)
+---------------------------------------------------------------------
+Server changes:
+ - Fix an application state bug when the application is launched
+ without using DIAL.
+ - Remove unnecessary functionality from Mongoose.
+ - Use constant values for DIAL port.
+ - Propery reap child processes.
+
+Client (test tool) changes:
+ - None
+
+---------------------------------------------------------------------
+Version 1.0.1 (1601607)
+---------------------------------------------------------------------
+General Changes:
+ - Simplified directory structure.
+ - Version.txt changed to Version.h.
+ - moved main Makefile into src dir.
+
+Server changes:
+ - Server configuration options moved into dial_options.h.
+ - Added logic to walk/proc to see if the named app (eg Netflix)
+ is running before attempting to start/kill the app.
+ - Improved payload handling:
+ - Explicitly define payload size.
+ - Added logic to check validity of payload, and send HTTP 400
+ "bad request" responses to requests with improper payloads.
+ - Added URL encoding for payloads.
+ - Added logic to compare launch payload on subsequent launch
+ requests.
+
+Client (test tool) changes:
+ - small change to printed format of server names.
+
+---------------------------------------------------------------------
+Version 1.0 (1416215)
+---------------------------------------------------------------------
+Initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cfc42fa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Netflix, Inc.
+All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+· Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+· Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README b/README
new file mode 100644
index 0000000..be020f5
--- /dev/null
+++ b/README
@@ -0,0 +1,89 @@
+--------------------------------------------------------------------------------
+Building the DIAL server
+--------------------------------------------------------------------------------
+1) Define the TARGET environment variable to point to the CC compiler prefix
+ for your target platform.
+
+ For example, using the NRDP-3.2 x86 desktop toolchain, the CC is located at:
+ /usr/local/i686-netflix-linux-gnu-3.2/bin/i686-netflix-linux-gnu-gcc
+
+2) Run make, passing in your TARGET value.
+
+ For example:
+ TARGET=/usr/local/i686-netflix-linux-gnu-3.2/bin/i686-netflix-linux-gnu- make
+
+--------------------------------------------------------------------------------
+Running the DIAL server
+--------------------------------------------------------------------------------
+The DIAL server should be started as a service, after the platform's networking
+has been initialized, and it should remain running at all times (a daemon
+process in the system).
+
+Sample SysV init scripts are available on the Netflix NRD Partner portal.
+
+--------------------------------------------------------------------------------
+Building the DIAL client
+--------------------------------------------------------------------------------
+The DIAL client is a standalone C++ console application you can use to test
+a running DIAL server implementation on your device. Unlike the server, which
+is built for, and meant to run on your device, the client is meant to run on
+your desktop (development) machine.
+
+The DIAL client uses CURL to send HTTP REST commands to the DIAL server, so to
+build the client, you need to ensure that the CURL dependecies are
+defined properly. If you are a Netflix NRD partner and have the NRD Platform
+desktop toolchain installed, it includes libcurl. To build against that version,
+use the following build command:
+
+TARGET=/usr/local/i686-netflix-linux-gnu-3.2/bin/i686-netflix-linux-gnu- \
+LDFLAGS="-L/usr/local/i686-netflix-linux-gnu-3.2/netflix/lib \
+-Wl,-rpath,/usr/local/i686-netflix-linux-gnu-3.2/netflix/lib" \
+INCLUDES=-I/usr/local/i686-netflix-linux-gnu-3.2/netflix/include \
+make
+
+Alternatively, you can build against a different, current version of libcurl.
+Adjust the INCLUDES and LDFLAGS definitions to point to your actual libcurl
+header and library locations. In most cases, you can omit the TARGET define.
+
+Note: the -rpath argument passed to LDFLAGS specifies the libcurl location
+to the runtime linker.
+
+--------------------------------------------------------------------------------
+Running the DIAL client in interactive (menu) mode
+--------------------------------------------------------------------------------
+1) The DIAL client application must be running in the same subnet as the
+ DIAL server.
+
+2) Start the client: ./dialclient (or ./dialclient -m)
+ The on-screen menu will list all available actions.
+
+--------------------------------------------------------------------------------
+Running the DIAL client in conformance test (non-interactive) mode
+--------------------------------------------------------------------------------
+1) The DIAL client application must be running in the same subnet as the
+ DIAL server.
+
+2) Start the client:
+ ./dialclient -i [input-file] [-o output-file] [-a server-IP-addr]
+
+ In script-driven mode, the client reads in an input-file, executes the
+ instructions in the input-file, and generates a
+ report. The default file locations (which can be overridden) are:
+ ./dialclient_input.txt
+ ./report.html
+
+--------------------------------------------------------------------------------
+DIAL client Usage
+--------------------------------------------------------------------------------
+When running the DIAL client, you have the following options
+usage: dialclient <option>
+
+Option Parameter Description
+ -h none Usage menu
+ -m none Use menu
+ -o filename Reporter output file (./report.html)
+ -i filename Input File (./dialclient_input.txt)
+ -a ip_address IP addr of DIAL server (used for conformance testing)
+
+If you do not provide an ip_address and multiple servers are discovered, the
+client will prompt you to select a server.
diff --git a/Version.h b/Version.h
new file mode 100644
index 0000000..5c8762c
--- /dev/null
+++ b/Version.h
@@ -0,0 +1,38 @@
+/*
+ * (c) 1997-2012 Netflix, Inc. All content herein is protected by
+ * U.S. copyright and other applicable intellectual property laws and
+ * may not be copied without the express permission of Netflix, Inc.,
+ * which reserves all rights. Reuse of any of this content for any
+ * purpose without the permission of Netflix, Inc. is strictly
+ * prohibited.
+ */
+
+#ifndef DIAL_VERSION_H
+#define DIAL_VERSION_H
+
+#undef DIAL_VERSION_MAJOR
+#define DIAL_VERSION_MAJOR 1
+
+#undef DIAL_VERSION_MINOR
+#define DIAL_VERSION_MINOR 0
+
+#undef DIAL_VERSION_PATCH
+#define DIAL_VERSION_PATCH 2
+
+#undef DIAL_VERSION_NUMBER_STR2
+#define DIAL_VERSION_NUMBER_STR2(M) #M
+#undef DIAL_VERSION_NUMBER_STR
+#define DIAL_VERSION_NUMBER_STR(M) DIAL_VERSION_NUMBER_STR2(M)
+
+#undef DIAL_VERSION_NUMBER
+#define DIAL_VERSION_NUMBER DIAL_VERSION_NUMBER_STR(DIAL_VERSION_MAJOR) "." DIAL_VERSION_NUMBER_STR(DIAL_VERSION_MINOR) "." DIAL_VERSION_NUMBER_STR(DIAL_VERSION_PATCH)
+
+#undef DIAL_VERSION_STRING
+#ifdef DIAL_VERSION_SUFFIX
+# define DIAL_VERSION_STRING DIAL_VERSION_NUMBER "-" DIAL_VERSION_SUFFIX
+#else
+# define DIAL_VERSION_STRING DIAL_VERSION_NUMBER
+#endif
+
+
+#endif
diff --git a/src/client/DialClientInput.cpp b/src/client/DialClientInput.cpp
new file mode 100644
index 0000000..4192b86
--- /dev/null
+++ b/src/client/DialClientInput.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "DialClientInput.h"
+#include "DialServer.h"
+#include <iostream>
+#include <fstream>
+
+using namespace std;
+
+bool DialClientInput::init(std::string file)
+{
+ if( file.empty() ) file = DialClientInput::getDefaultFilename();
+ ATRACE("DialClientInput::%s, opening %s\n", __FUNCTION__, file.c_str());
+ string line;
+ ifstream myfile (file.c_str());
+ if (myfile.is_open())
+ {
+ // first fill _actions with commands from the input file
+ while ( myfile.good() )
+ {
+ getline (myfile,line);
+ if( line[0] != '#' && !line.empty() )
+ {
+ if( line.find("addApplication") != line.npos )
+ {
+ // add an application
+ size_t pos = line.find_first_of('=');
+ _applist.push_back( line.substr(pos+1, line.length()));
+ ATRACE("Adding app: %s\n", line.substr(pos+1, line.length()).c_str());
+ }
+ else if( line.find("addErrorApplication") != line.npos )
+ {
+ // add an application
+ size_t pos = line.find_first_of('=');
+ _errorapplist.push_back( line.substr(pos+1, line.length()));
+ ATRACE("Adding error app: %s\n", line.substr(pos+1, line.length()).c_str());
+ }
+ else
+ {
+ // add a command
+ size_t pos = line.find_first_of(" ");
+ std::string params = line.substr(pos+1, line.length());
+ std::pair<std::string, std::string>
+ action_to_push(line.substr(0, pos),
+ pos == line.npos ? "":params);
+ _actions.push_back(action_to_push);
+ ATRACE("command: %s params: %s\n",
+ line.substr(0, pos).c_str(), params.c_str() );
+ }
+ }
+#ifdef DEBUG
+ //else ATRACE("COMMENT: %s\n", line.c_str());
+#endif
+ }
+ myfile.close();
+ }
+ else
+ {
+ fprintf(stderr, "Unable to open file\n");
+ return false;
+ }
+ return true;
+}
+
+bool DialClientInput::addApplication( string& application )
+{
+ // see if the application exits
+ vector<string>::iterator it;
+ for( it = _applist.begin(); it < _applist.end(); it ++ )
+ if( !((*it).compare( application )) ) break;
+
+ // if not, add it
+ if( it < _applist.end() ) _applist.push_back(application);
+ else return false; // already in the list
+
+ return true;
+}
+
+bool DialClientInput::getNextAction( string& command, vector<string>& parameters )
+{
+ if (_actions.empty()) return false;
+
+ pair<string, string> action = _actions.front();
+ _actions.erase(_actions.begin());
+
+ command = action.first;
+ string params = action.second;
+ size_t pos = params.find_first_of(" "), start = 0;
+ // Parse out the parameters from the string
+ while (1)
+ {
+ parameters.push_back(params.substr( start, pos-start ));
+ if( pos == params.npos ) break;
+ start = pos+1;
+ pos = params.find_first_of(" ", start);
+ }
+
+ return true;
+}
+
+void DialClientInput::getApplicationList( vector<string> &list )
+{
+ list = _applist;
+}
+
+
+void DialClientInput::getErrorApplicationList(vector<string> &list)
+{
+ list = _errorapplist;
+}
+
diff --git a/src/client/DialClientInput.h b/src/client/DialClientInput.h
new file mode 100644
index 0000000..b31f546
--- /dev/null
+++ b/src/client/DialClientInput.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIALCLIENTINPUT_H
+#define DIALCLIENTINPUT_H
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+class DialClientInput
+{
+public:
+ DialClientInput() {}
+ ~DialClientInput() {}
+
+ /**
+ * Called to initialize
+ *
+ * @param[in] file Name of the file to load
+ * @return true if successful, false otherwise
+ */
+ bool init( string file );
+
+ /**
+ * The DialClient application can use this to add applications passed on
+ * the command line. If the application is already in the list, it will
+ * drop the add.
+ *
+ * @param[in] application Name of the application to add.
+ *
+ * @return true if successful, false otherwise
+ */
+ bool addApplication( string &application );
+
+ /**
+ * Get the next action to execute
+ *
+ * @param[out] command Command to execute
+ * @param[out] parameters Parameters for the command
+ */
+ bool getNextAction( string& command, vector<string>& parameters );
+
+ /**
+ * Get a list of valid applications defined in the input file.
+ *
+ * @param[out] list List of valid applications
+ */
+ void getApplicationList(vector<string> &list);
+
+ /**
+ * Get a list of applications used for error tests (should not exist).
+ *
+ * @param[out] list List of error applications
+ */
+ void getErrorApplicationList(vector<string> &list);
+
+ static string getDefaultFilename() { return "./dialclient_input.txt"; }
+
+private:
+ vector<string> _applist;
+ vector<string> _errorapplist;
+ vector< pair<string,string> > _actions;
+};
+
+#endif // DIALCLIENTINPUT_H
diff --git a/src/client/DialConformance.cpp b/src/client/DialConformance.cpp
new file mode 100644
index 0000000..cb18290
--- /dev/null
+++ b/src/client/DialConformance.cpp
@@ -0,0 +1,586 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+#include "DialConformance.h"
+#include <assert.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+// Number of milliseconds to sleep between launches when doing a launch=ALL
+#define LAUNCH_SLEEP 6000
+#define MAX_PARAMETER_LENGTH 60
+
+using namespace std;
+
+/*
+ * This class will be reponsible for reporting the conformance test results.
+ * Requirements:
+ * * Results should be in HTML
+ * * Results should be easily readable
+ * * Results should clearly distinguish between pass and fail
+ * * If there is a failure, there should be ample information provided
+ * for debugging
+ */
+class OutputWriter
+{
+public:
+ OutputWriter( string fileName ) :
+ _filename(fileName),
+ _pCurrentTest(NULL),
+ _isComplete(false)
+ {
+ }
+ ~OutputWriter()
+ { }
+
+ void setOutputFile( string fileName )
+ {
+ _filename = fileName;
+ }
+
+ void setTest( string testName )
+ {
+ assert( _pCurrentTest == NULL );
+ _pCurrentTest = new Test(testName);
+ _tests.push_back(_pCurrentTest);
+ }
+ void clearTest()
+ {
+ // still holding a ref in the test vector. That will get deleted
+ // when the test is complete
+ _pCurrentTest = NULL;
+ }
+
+ void setError( string error ) { _pCurrentTest->addError(error); }
+ void setResult( bool result ) { _pCurrentTest->setResult( result ); }
+
+ void start( string& friendlyname, string& uuid, string ipaddress )
+ {
+ _file.open(_filename.c_str());
+ _file << "<html>\n<body>\n";
+ _file << "<font size=\"6\"><b>FriendlyName: " << friendlyname;
+ _file << "<br>\nIP: " << ipaddress;
+ _file << "<br>\nUUID: " << uuid;
+
+ {
+ time_t rawtime;
+ struct tm * timeinfo;
+ char buffer [80];
+
+ time ( &rawtime );
+ timeinfo = localtime ( &rawtime );
+
+ //strftime (buffer,80,"%I:%M%p.",timeinfo);
+ strftime (buffer,80,"%c",timeinfo);
+ _file << "<br>\nTime of Run: " << buffer << "</b></font>";
+ }
+ _file << "<table border=\"10\">\n";
+ _file << "<tr>\n";
+ _file << "<th> RESULT </th>"; //header 1
+ _file << "<th> Test Executed </th>"; //header 2
+ _file << "<th> Errors </th>"; //header 2
+ _file << "</tr>\n";
+ }
+
+ void complete()
+ {
+ vector<Test*>::iterator it;
+ for( it = _tests.begin(); it < _tests.end(); it++ )
+ {
+ write("<tr>");
+ // write the name and the result
+ stringstream result;
+ bool testResult = (*it)->getResult();
+ result << "<td><b> <font color=\"" << (testResult ? "green" : "red") << "\"> ";
+ result << (testResult ? "SUCCESS" : "FAIL") << " </b></font></td>\n<td>" << (*it)->getName() << "</td>";
+ write( result.str() );
+
+ // write errors if they exist
+ write("<td>");
+ vector<string> errors = (*it)->getErrors();
+ if( !errors.empty() )
+ {
+ vector<string>::iterator it2;
+ write("<b>");
+ for( it2 = errors.begin(); it2 < errors.end(); it2++ )
+ write( (*it2) );
+ write("</b>");
+ }
+ else
+ write("NO ERROR");
+ write("</td>\n</tr>");
+
+ // delete the test
+ delete (*it);
+ }
+ _file << "</table></body>\n</html>\n";
+ _file.close();
+
+ _isComplete = true;
+ }
+
+private:
+ // Abstraction of a test
+ class Test
+ {
+ public:
+ Test(string& name) :
+ _testname(name),
+ _testPassed(false) {}
+ ~Test() {}
+
+ string getName() { return _testname; }
+ void setResult( bool result ) { _testPassed = result; }
+ bool getResult() { return _testPassed; }
+
+ void addError( string error ) { _errors.push_back(error); }
+ vector<string> getErrors() { return _errors; }
+
+ private:
+ string _testname;
+ bool _testPassed;
+ vector<string> _errors;
+ };
+
+ string _filename;
+ ofstream _file;
+ vector<Test*> _tests;
+ Test* _pCurrentTest;
+ bool _isComplete;
+
+ int write( string line )
+ {
+ _file << line.c_str() << "\n";
+ return line.length()+1;
+ }
+};
+
+static const string defaultOutputFile = "./report.html";
+static OutputWriter gWriter(defaultOutputFile);
+
+DialConformance *DialConformance::sConformance = 0;
+
+DialConformance* DialConformance::create(void)
+{
+ assert( DialConformance::sConformance == 0 );
+ return new DialConformance();
+}
+
+DialConformance* DialConformance::instance(void)
+{
+ return DialConformance::sConformance;
+}
+
+DialConformance::DialConformance()
+{
+ assert( DialConformance::sConformance == 0 );
+ DialConformance::sConformance = this;
+}
+
+DialConformance::~DialConformance()
+{
+ assert( DialConformance::sConformance == this );
+ DialConformance::sConformance = 0;
+}
+
+void DialConformance::extractParamValue(
+ string& param, string& value )
+{
+ // TODO: Add support for quotes after the equals
+ size_t posBegin = param.find("=");
+ value = param.substr(posBegin+1, param.length()-posBegin);
+}
+
+bool DialConformance::validateParams( vector<string>& params,
+ string& responseHeaders, string& responseBody )
+{
+ bool testPassed = true;
+ vector<string>::iterator it;
+ for( it = params.begin(); it < params.end() && testPassed == true; it++ )
+ {
+ ATRACE("%s:: params:%s \n", __FUNCTION__, (*it).c_str());
+ if( (*it).find("httpresponse=") != (*it).npos )
+ testPassed = validateHttpResponse( responseHeaders, *it );
+ if( (*it).find("httpresponseheader=") != (*it).npos )
+ testPassed = validateHttpHeaders( responseHeaders, *it );
+ if( (*it).find("resultbody=") != (*it).npos )
+ testPassed = validateResponseBody( responseBody, *it );
+ }
+ return testPassed;
+}
+
+
+bool DialConformance::validateHttpResponse(
+ string& headers, string& param )
+{
+ bool retval = false;
+ string responseCode;
+ extractParamValue( param, responseCode );
+
+ ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
+ if( headers.find(responseCode) != headers.npos )
+ {
+ ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
+ retval = true;
+ }
+ else
+ {
+ ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
+ stringstream error;
+ error << responseCode << " not found\n";
+ gWriter.setError( error.str() );
+ }
+ return retval;
+}
+
+bool DialConformance::validateHttpHeaders(
+ string& headers, string& param )
+{
+ bool retval = false;
+ string responseCode;
+ extractParamValue( param, responseCode );
+
+ ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
+ if( headers.find(responseCode) != headers.npos )
+ {
+ ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
+ retval = true;
+ }
+ else
+ {
+ ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
+ stringstream error;
+ error << responseCode << " not found\n";
+ gWriter.setError( error.str() );
+ }
+ return retval;
+}
+
+bool DialConformance::validateResponseBody(
+ string& body, string& param )
+{
+ bool retval = false;
+ string responseCode;
+ extractParamValue( param, responseCode );
+
+ ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
+ if( body.find(responseCode) != body.npos )
+ {
+ ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
+ retval = true;
+ }
+ else
+ {
+ ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
+ stringstream error;
+ error << responseCode << " not found\n";
+ error << "response: " << body;
+ gWriter.setError( error.str() );
+ }
+ return retval;
+}
+
+void DialConformance::getPayload(vector<string>& params, string& payload )
+{
+ vector<string>::iterator it;
+ for( it = params.begin(); it < params.end(); it++ )
+ {
+ if( (*it).find("param") != (*it).npos )
+ {
+ // Check to see if the param is using quotes. If it is, split on
+ // that. If not, split on a space
+ size_t posEnd, pos = (*it).find( "=" );
+ if( (*it)[pos+1] == '\"' )
+ {
+ posEnd = (*it).find( "\"", pos+2 );
+ payload = (*it).substr( pos+2, posEnd-(pos+2) );
+ }
+ else
+ {
+ posEnd = (*it).find( " ", pos );
+ payload = (*it).substr( pos+1, posEnd );
+ }
+ ATRACE("payload = %s\n", payload.c_str());
+ }
+ }
+}
+
+bool DialConformance::execute_launch( Application* pApp, vector<string>& params )
+{
+ string payload, responseHeaders, responseBody;
+ getPayload( params, payload );
+ pApp->launch( payload, responseHeaders, responseBody );
+ return (validateParams( params, responseHeaders, responseBody ));
+}
+
+bool DialConformance::execute_status( Application* pApp, vector<string>& params )
+{
+ ATRACE("%s::%d\n", __FUNCTION__, __LINE__);
+ string responseHeaders, responseBody;
+ pApp->status( responseHeaders, responseBody );
+
+ bool retval = validateParams( params, responseHeaders, responseBody );
+ if( retval && (responseHeaders.find("200") != responseHeaders.npos) )
+ {
+ // if the status was successful
+ // Ensure the response body contains the application name
+ if( responseBody.find( pApp->getName() ) == responseBody.npos )
+ {
+ retval = false;
+ stringstream error;
+ error << "Reponse XML did not contain application name: " << pApp->getName();
+ gWriter.setError( error.str() );
+ retval = false;
+ }
+ if( responseBody.find( "urn:dial-multiscreen-org:schemas:dial" )
+ == responseBody.npos )
+ {
+ gWriter.setError( "Response Body did not contain DIAL service "
+ "string: urn:dial-multiscreen-org:schemas:dial" );
+ retval = false;
+ }
+ }
+ return retval;
+}
+
+bool DialConformance::execute_stop( Application* pApp, vector<string>& params )
+{
+ ATRACE("%s::%d\n", __FUNCTION__, __LINE__);
+ string responseHeaders, responseBody;
+ pApp->stop(responseHeaders);
+ return (validateParams( params, responseHeaders, responseBody ));
+}
+
+DialConformance::Application* DialConformance::getApplication( string& command )
+{
+ Application* pApp = NULL;
+
+ // extract the application
+ string app;
+ size_t pos = command.find("=");
+ app = command.substr(pos+1, command.length());
+ assert( app.length() > 0 );
+
+ // find the application
+ vector<Application*>::iterator it;
+ for( it = _apps.begin(); it < _apps.end(); it++ )
+ {
+ ATRACE("Comparing: %s.compare(%s)\n", (*it)->getName().c_str(), app.c_str());
+ if( (*it)->getName().compare(app) == 0)
+ {
+ pApp = (*it);
+ break;
+ }
+ }
+
+ assert(pApp != NULL);
+ return pApp;
+}
+
+bool DialConformance::execute_command( string& command, vector<string>& params )
+{
+ ATRACE("%s::%s\n", __FUNCTION__, command.c_str());
+ bool retval = false;
+ if( command.find("launch") != command.npos )
+ retval = execute_launch(getApplication(command), params);
+ else if ( command.find("status") != command.npos )
+ retval = execute_status(getApplication(command), params);
+ else if ( command.find("stop") != command.npos )
+ retval = execute_stop(getApplication(command), params);
+ else if ( command.find("sleep") != command.npos )
+ {
+ string time;
+ size_t pos = command.find("=");
+ time = command.substr(pos+1, command.length());
+ unsigned long milliseconds = atoi(time.c_str());
+ ATRACE("Sleeping %ld milliseconds \n", milliseconds);
+ usleep(milliseconds*1000);
+ retval = true;
+ }
+ else ATRACE("Can't execute command: %s\n", command.c_str());
+ return retval;
+}
+
+void DialConformance::run_internal( DialServer* pServer )
+{
+ string command;
+ vector<string> params;
+ bool retval;
+ string friendlyName, uuid;
+
+ // only execute the test if we have a friendly name and UUID
+ retval = pServer->getFriendlyName( friendlyName );
+ if (retval) pServer->getUuid( uuid );
+ if (retval) gWriter.start( friendlyName, uuid, pServer->getIpAddress() );
+ while( retval && _input.getNextAction(command, params) )
+ {
+ vector<string> commandlist;
+
+ // Check the command here, if it is command=ALL, then we need to loop through
+ // all of the valid applications
+ size_t pos;
+ if( (pos = command.find("=ALL")) != command.npos )
+ {
+ vector<Application*>::iterator it;
+ for( it = _apps.begin(); it < _apps.end(); it++ )
+ {
+ // strip off ALL and append the application name
+ string newCommand = command.substr(0, pos+1);
+ newCommand.append( (*it)->getName() );
+ Application *pApp = getApplication( newCommand );
+ if( pApp != NULL && (!pApp->isErrorApp()) )
+ {
+ ATRACE("Adding command %s\n", newCommand.c_str());
+ commandlist.push_back( newCommand );
+ }
+
+ if( newCommand.find("launch") != newCommand.npos )
+ {
+ // If we are adding launch commands, insert a sleep so that we don't overwhelm
+ // the target
+ stringstream sleep;
+ sleep << "sleep=" << LAUNCH_SLEEP;
+ commandlist.push_back( sleep.str() );
+ }
+ }
+ }
+ else
+ {
+ ATRACE("Adding command %s\n", command.c_str());
+ commandlist.push_back(command);
+ }
+
+ vector<string>::iterator it;
+ for( it = commandlist.begin(); it < commandlist.end(); it++ )
+ {
+ // construct the name of the test
+ ATRACE("***********************" "\n%s: Command: %s \n",
+ __FUNCTION__, (*it).c_str());
+ stringstream test;
+ test << "Command:" << (*it);
+
+ // The input code will always push a parameter. If there are no
+ // parameters, it will push an empty string, so test for that here.
+ if( params.size() > 0 && !params[0].empty() ) test << " Params:";
+
+ vector<string>::iterator it2;
+ for( it2 = params.begin(); it2 < params.end(); it2++)
+ {
+ ATRACE("param: %s ", (*it2).c_str());
+
+ // check to see if we have a really large parameter, if we do,
+ // truncate it to a reasonable length
+ string param = (*it2);
+ if( param.size() > MAX_PARAMETER_LENGTH )
+ {
+ param = (*it2).substr(0, MAX_PARAMETER_LENGTH-3 );
+ param.append("...");
+ }
+ test << param << " ";
+ }
+
+ if( (*it).find("sleep") == (*it).npos )
+ {
+ // set the test name
+ gWriter.setTest( test.str() );
+
+ // run the test
+ bool retval = execute_command( *it, params );
+ printf("%s\n", retval ? "SUCCESS":"FAILED");
+
+ // set the result and prep for the next test
+ gWriter.setResult( retval );
+ gWriter.clearTest();
+ }
+ else
+ {
+ //just sleep here
+ string time;
+ size_t pos = (*it).find("=");
+ time = (*it).substr(pos+1, command.length());
+ unsigned long milliseconds = atoi(time.c_str());
+ ATRACE("Sleeping %ld milliseconds \n", milliseconds);
+ usleep(milliseconds*1000);
+ }
+ }
+
+ // clear the parameters for the next loop
+ params.clear();
+ }
+ if( retval ) gWriter.complete();
+}
+
+int DialConformance::run(
+ DialServer* pServer,
+ vector<string>& appList,
+ string &inputFile,
+ string &outputFile )
+{
+ printf("Running conformance tests from %s and printing report to %s\n\n",
+ inputFile.empty() ?
+ DialClientInput::getDefaultFilename().c_str() :
+ inputFile.c_str(),
+ outputFile.empty() ?
+ defaultOutputFile.c_str() :
+ outputFile.c_str() );
+ //read the input file
+ _input.init(inputFile);
+ if( !outputFile.empty() ) gWriter.setOutputFile( outputFile );
+
+ // Add applications passed by the client (command line)
+ vector<string>::iterator it;
+ for( it = appList.begin(); it < appList.end(); it++ ) _input.addApplication(*it);
+
+ // build the application list
+ vector<string> listOfApps;
+ _input.getApplicationList(listOfApps); // get the list of apps
+ for( it = listOfApps.begin(); it < listOfApps.end(); it++ )
+ {
+ _apps.push_back( new Application(*it, pServer, false) );
+ ATRACE("Adding application: %s\n", (*it).c_str());
+ }
+
+ _input.getErrorApplicationList(listOfApps); // get the list of apps
+ for( it = listOfApps.begin(); it < listOfApps.end(); it++ )
+ {
+ _apps.push_back( new Application(*it, pServer, true) );
+ ATRACE("Adding Error application: %s\n", (*it).c_str());
+ }
+
+ // run the test
+ run_internal( pServer );
+
+ // clean up the application list
+ vector<Application*>::iterator appit;
+ for ( appit = _apps.begin() ; appit < _apps.end(); appit++ ) delete (*appit);
+
+ return 0;
+}
+
diff --git a/src/client/DialConformance.h b/src/client/DialConformance.h
new file mode 100644
index 0000000..49c7c97
--- /dev/null
+++ b/src/client/DialConformance.h
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIALCONFORMANCE_H
+#define DIALCONFORMANCE_H
+
+#include <memory>
+#include "DialServer.h"
+#include "DialClientInput.h"
+
+using namespace std;
+
+class DialConformance
+{
+public:
+ /**
+ * Create a singleton
+ */
+ static DialConformance* create();
+
+ /**
+ * Get a pointer to the singleton
+ */
+ static DialConformance* instance(void);
+
+ ~DialConformance();
+
+ /**
+ * Run the test
+ *
+ * @param[in] pServer DIAL server to test
+ * @param[in] appList List of applications from the command line
+ * @param[in] inputFile Input file used to drive tests
+ * @param[in] outputFile File for writing the report. Use an
+ * empty string to use the default (report.html).
+ *
+ * @return 0 if successful, !0 otherwise
+ */
+ int run(
+ DialServer* pServer,
+ vector<string>& appList,
+ string &inputFile,
+ string &outputFile );
+private:
+ DialConformance();
+ DialServer* _pServer;
+ static DialConformance* sConformance;
+ DialClientInput _input;
+
+ /**
+ * Class to manage state for each application supported by the DIAL
+ * server. The application list is managed by the input file.
+ */
+ class Application{
+ public:
+ enum State
+ {
+ STOPPED,
+ LAUNCHING, // application has just been launched, state
+ // has not been confirmed
+ LAUNCHED,
+ STOPPING // application has been stopped, state has not
+ // been confirmed
+ };
+
+ Application( string& name, DialServer *pServer, bool isErrorApp ) :
+ _isErrorApp(isErrorApp),
+ _pServer(pServer),
+ _state(STOPPED),
+ _stopurl(""),
+ _name(name) {}
+ ~Application(){}
+
+ /**
+ * State Getter/Setters
+ */
+ string getName() { return _name; }
+ State getState() { return _state; }
+
+ /**
+ * Returns true if the application is not a valid application
+ */
+ bool isErrorApp() { return _isErrorApp; }
+
+ /**
+ * Launch an this application using the server that was stored when
+ * the class was created.
+ *
+ * @param[in] payload Data that is put into the POST data.
+ * @param[out] responseHeaders HTML response headers
+ * @param[out] responseBody HTML response body
+ */
+ void launch(
+ string& payload,
+ string& responseHeaders,
+ string& responseBody )
+ {
+ _pServer->launchApplication(
+ _name, payload, responseHeaders, responseBody );
+ // TODO: Set state
+
+ // find Location in the header, store the stop url
+ if( !responseHeaders.empty() )
+ {
+ size_t pos, tmp = responseHeaders.find("Location");
+ if( tmp != responseHeaders.npos )
+ {
+ pos = responseHeaders.find("http", tmp);
+ size_t posEnd = responseHeaders.find("\n", pos+1);
+ if( posEnd != responseHeaders.npos )
+ _stopurl = responseHeaders.substr( pos, posEnd );
+
+ // chomp off the \r\n chars
+ DialConformance::chomp( _stopurl );
+ ATRACE("StopURL = %s********\n", _stopurl.c_str());
+ }
+ }
+ }
+
+ /**
+ * Get the status of this application
+ *
+ * @param[out] responseHeaders HTML response headers
+ * @param[out] responseBody HTML response body
+ */
+ void status(
+ string& responseHeaders,
+ string& responseBody )
+ { _pServer->getStatus( _name, responseHeaders, responseBody ); }
+
+ /**
+ * Stop the application
+ *
+ * @param[out] responseHeaders HTML response headers
+ */
+ void stop(
+ string& responseHeaders )
+ { stop(_stopurl, responseHeaders ); }
+
+ /**
+ * Stop the application using a custom stop URL.
+ *
+ * @param[out] responseHeaders HTML response headers
+ */
+ void stop( string& stopurl, string& responseHeaders)
+ {
+ if( !stopurl.empty() )
+ {
+ _pServer->stopApplication( stopurl, responseHeaders );
+ }
+#ifdef DEBUG
+ else ATRACE("%s: Not sending stop, stop URL is empty\n", __FUNCTION__);
+#endif
+ }
+
+ private:
+ bool _isErrorApp;
+ DialServer* _pServer;
+ State _state;
+ string _stopurl;
+ string _name;
+ };
+ // list of applications
+ vector<Application*> _apps;
+
+ // Internal helpers
+ void run_internal( DialServer* pServer );
+ bool execute_command( string& command, vector<string>& params );
+
+ // Get the Application pointer from an application string
+ Application* getApplication( string& command );
+
+ // extract the payload from a list of parameters
+ void getPayload(vector<string>& params, string& payload );
+
+ // Command execution functions
+ bool execute_launch( Application* pApp, vector<string>& params );
+ bool execute_status( Application* pApp, vector<string>& params );
+ bool execute_stop( Application* pApp, vector<string>& params );
+
+ // Helper function to extract a parameter.
+ void extractParamValue( string& param, string& value );
+
+ // Validation functions
+ bool validateParams( vector<string>& params,
+ string& responseHeaders, string& responseBody );
+ bool validateHttpResponse( string& headers, string& params );
+ bool validateHttpHeaders( string& headers, string& params );
+ bool validateResponseBody( string& headers, string& params );
+
+public:
+ // Helper function to chomp off the carriage return line feed.
+ static void chomp(string& str)
+ {
+ string crlf("\r\n");
+ size_t pos = str.find_last_not_of( crlf );
+ if( pos != str.npos )
+ {
+ ATRACE("CHOMP\n");
+ str.erase(pos+1);
+ }
+ }
+};
+
+#endif // DIALCONFORMANCE_H
diff --git a/src/client/DialDiscovery.cpp b/src/client/DialDiscovery.cpp
new file mode 100644
index 0000000..bfaa301
--- /dev/null
+++ b/src/client/DialDiscovery.cpp
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <string>
+#include <map>
+#include <curl/curl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <iostream>
+#include <vector>
+#include "DialDiscovery.h"
+#include <assert.h>
+
+#define SSDP_TIMEOUT 30
+#define SSDP_RESPONSE_TIMEOUT 3
+
+using namespace std;
+
+DialDiscovery *DialDiscovery::sDiscovery = 0;
+
+static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
+static int my_port = 0;
+static struct sockaddr_in saddr;
+typedef struct
+{
+ struct sockaddr_in saddr;
+ int sock;
+ socklen_t addrlen;
+}search_conn;
+
+static pthread_mutex_t list_locker = PTHREAD_MUTEX_INITIALIZER;
+class ScopeLocker
+{
+public:
+ ScopeLocker(pthread_mutex_t &m) : mLock(m)
+ { pthread_mutex_lock(&mLock); }
+ virtual ~ScopeLocker()
+ { pthread_mutex_unlock(&mLock); }
+private:
+ pthread_mutex_t mLock;
+};
+
+static const char ssdp_msearch[] =
+ "M-SEARCH * HTTP/1.1\r\n"
+ "HOST: 239.255.255.250:1900\r\n"
+ "MAN: \"ssdp:discover\"\r\n"
+ "MX: 10\r\n"
+ "ST: urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
+
+static string getLocation(char *pResponse)
+{
+ string loc(pResponse);
+ size_t prev = 0, index = loc.find("\r\n");
+ string locUrl, retUrl;
+ while( index != string::npos ) {
+ locUrl = loc.substr(prev, index-prev);
+ ATRACE("locUrl: ##%s## prev: %d index %d\n", locUrl.c_str(), (int)prev, (int)index );
+ if( locUrl.find("LOCATION: ") != string::npos ) {
+ index = locUrl.find("\r\n");
+ retUrl = locUrl.substr( 10 );
+ break;
+ }
+
+ prev = index+2; // move past the "\r\n" token
+ index = loc.find("\r\n", prev);
+ }
+ return retUrl;
+}
+
+static size_t header_cb(void* ptr, size_t size, size_t nmemb, void* userdata)
+{
+ if ((size * nmemb) != 0) {
+ string parse((char*)ptr);
+ if( parse.find("Application-URL: ") != string::npos ) {
+ size_t index_start = parse.find(":");
+ index_start += 2;
+ size_t index_end = parse.find("\r\n");
+ string *header = static_cast<string*>(userdata);
+ header->append(parse.substr(index_start, index_end-index_start));
+ ATRACE("Apps URL set to %s\n", header->c_str());
+#ifndef DEBUG
+ }
+#else
+ } else {
+ ATRACE("%s: Dropping %s\n", __FUNCTION__, (char*)ptr);
+ }
+#endif
+ }
+ return (size * nmemb);
+}
+
+static size_t receiveData(void *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ if ((size * nmemb) != 0) {
+ string *url = static_cast<string*>(userdata);
+ url->append((char*)ptr);
+ }
+ return (size * nmemb);
+}
+
+static void getServerInfo( const string &server, string& appsUrl, string& ddxml )
+{
+ CURL *curl;
+ CURLcode res = CURLE_OK;
+
+ if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
+ fprintf(stderr, "curl_global_init() failed\n");
+ return;
+ }
+
+ if ((curl = curl_easy_init()) == NULL) {
+ fprintf(stderr, "curl_easy_init() failed\n");
+ curl_global_cleanup();
+ return;
+ }
+
+ ATRACE("Sending ##%s##\n", server.c_str());
+ curl_easy_setopt(curl, CURLOPT_URL, server.c_str());
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, &appsUrl);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ddxml);
+ res = curl_easy_perform(curl);
+
+ curl_easy_cleanup(curl);
+ //curl_global_cleanup();
+}
+
+void DialDiscovery::updateServerList(string& server)
+{
+ ServerMap::const_iterator it;
+ it = mServerMap.find(server);
+ if( it == mServerMap.end() ) {
+ // not found, add it
+ string appsUrl, ddxml;
+ getServerInfo(server, appsUrl, ddxml);
+ mServerMap[server] = new DialServer(server, appsUrl, ddxml);
+ } else {
+ // just mark that we found it
+ (*it).second->setFound(true);
+ }
+}
+
+void DialDiscovery::processServer(char *pResponse)
+{
+ if (strstr(pResponse, "ST: urn:dial-multiscreen-org:service:dial:1")) {
+ string server;
+
+ ScopeLocker s(list_locker);
+ // parse for LOCATION header
+ server = getLocation(pResponse);
+ ATRACE("FOUND server: %s\n", server.c_str());
+
+ // save the appURL in the server
+ updateServerList(server);
+#ifndef DEBUG
+ }
+#else
+ } else {
+ ATRACE("Dropping Server\n");
+ }
+#endif
+}
+
+void *DialDiscovery::receiveResponses(void *p)
+{
+ search_conn *pConn = (search_conn*)p;
+ int bytes;
+ char buf[4096] = {0,};
+ while (1) {
+ if (-1 == (bytes = recvfrom(pConn->sock, buf, sizeof(buf) - 1, 0,
+ (struct sockaddr *)&pConn->saddr, &pConn->addrlen))) {
+ perror("recvfrom");
+ break;
+ }
+ buf[bytes] = 0;
+ ATRACE("Received [%s:%d] %s\n", __FUNCTION__, __LINE__, buf);
+ DialDiscovery::instance()->processServer(buf);
+ }
+ return 0;
+}
+
+void DialDiscovery::cleanServerList(void)
+{
+ ScopeLocker s(list_locker);
+
+ ServerMap::const_iterator it;
+ vector<string> removal;
+ for( it = mServerMap.begin(); it != mServerMap.end(); it++ )
+ {
+ if( !(*it).second->isFound() ) {
+ removal.push_back((*it).second->getLocation());
+ }
+ }
+
+ // now remove and delete
+ vector<string>::iterator iter;
+ for( iter = removal.begin(); iter != removal.end(); iter ++)
+ {
+ ATRACE("Removing Server: %s\n", (*iter).c_str());
+ DialServer *p = mServerMap[(*iter)];
+ delete (p);
+ mServerMap.erase((*iter));
+ }
+}
+
+void DialDiscovery::resetDiscovery(void)
+{
+ ScopeLocker s(list_locker);
+ ServerMap::const_iterator it;
+ for( it = mServerMap.begin(); it != mServerMap.end(); it++ ) {
+ (*it).second->setFound(false);
+ }
+}
+
+void * DialDiscovery::send_mcast(void *p)
+{
+ int one = 1, my_sock;
+ socklen_t addrlen;
+ //struct ip_mreq mreq;
+ char send_buf[strlen((char*)ssdp_msearch) + INET_ADDRSTRLEN + 256] = {0,};
+ int send_size;
+ pthread_attr_t attr;
+ search_conn connection;
+
+ send_size = snprintf(send_buf, sizeof(send_buf), ssdp_msearch, ip_addr, my_port);
+ ATRACE("[%s:%d] %s\n", __FUNCTION__, __LINE__, send_buf);
+
+ if (-1 == (my_sock = socket(AF_INET, SOCK_DGRAM, 0))) {
+ perror("socket");
+ exit(1);
+ }
+ if (-1 == setsockopt(my_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
+ perror("reuseaddr");
+ exit(1);
+ }
+ saddr.sin_family = AF_INET;
+ saddr.sin_addr.s_addr = inet_addr("239.255.255.250");
+ saddr.sin_port = htons(1900);
+
+ while (1) {
+ addrlen = sizeof(saddr);
+ ATRACE("Sending SSDP M-SEARCH to %s:%d\n",
+ inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
+ if (-1 == sendto(my_sock, send_buf, send_size, 0, (struct sockaddr *)&saddr, addrlen)) {
+ perror("sendto");
+ continue;
+ }
+
+ // set all servers to not found
+ DialDiscovery::instance()->resetDiscovery();
+
+ // spawn a response thread
+ connection.saddr = saddr;
+ connection.sock = my_sock;
+ connection.addrlen = addrlen;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ pthread_create(&DialDiscovery::instance()->_responseThread, &attr, DialDiscovery::receiveResponses, &connection);
+
+ // sleep SSDP_RESPONSE_TIMEOUT seconds to allow clients to response
+ sleep(SSDP_RESPONSE_TIMEOUT);
+ DialDiscovery::instance()->cleanServerList();
+
+ sleep(SSDP_TIMEOUT-SSDP_RESPONSE_TIMEOUT);
+ pthread_cancel(DialDiscovery::instance()->_responseThread);
+ }
+}
+
+void DialDiscovery::init()
+{
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ pthread_create(&_mcastThread, &attr, DialDiscovery::send_mcast, (void*)ssdp_msearch );
+}
+
+void DialDiscovery::getServerList( vector<DialServer*>& list )
+{
+ for( ServerMap::iterator it = mServerMap.begin(); it != mServerMap.end(); ++it ) {
+ list.push_back( it->second );
+ }
+}
+
+bool DialDiscovery::getServer( const string& friendlyName, DialServer &server )
+{
+ return true;
+}
+
+DialDiscovery::DialDiscovery()
+{
+ assert( DialDiscovery::sDiscovery == 0 );
+ DialDiscovery::sDiscovery = this;
+}
+
+DialDiscovery::~DialDiscovery()
+{
+ assert( sDiscovery == this );
+ sDiscovery = 0;
+}
+
+DialDiscovery * DialDiscovery::create()
+{
+ assert( sDiscovery == 0 );
+ return new DialDiscovery();
+}
+
+DialDiscovery * DialDiscovery::instance()
+{
+ return DialDiscovery::sDiscovery;
+}
+
diff --git a/src/client/DialDiscovery.h b/src/client/DialDiscovery.h
new file mode 100644
index 0000000..81d67a5
--- /dev/null
+++ b/src/client/DialDiscovery.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIALDISCOVERY_H
+#define DIALDISCOVERY_H
+
+#include "DialServer.h"
+#include <map>
+
+using namespace std;
+
+class DialDiscovery
+{
+public:
+ /**
+ * Create a singleton
+ */
+ static DialDiscovery* create(void);
+
+ /**
+ * Get a pointer to the singleton
+ */
+ static DialDiscovery* instance(void);
+
+ ~DialDiscovery();
+
+ /**
+ * Initialize the discover object. This will kick off a periodic
+ * worker thread that will poll for DIAL servers.
+ *
+ */
+ void init();
+
+ /**
+ * Get the list of servers that have been discovered.
+ *
+ * @param[out] list List of DIAL servers. Returns an empty list if there
+ * are no servers.
+ */
+ void getServerList(vector<DialServer*>& list);
+
+ /**
+ * Get a DIAL server based on friendly name
+ *
+ * @param[in] friendlyName Friendly name of DIAL server
+ * @param[out] server Server object (if successful)
+ *
+ * @return true if successful, false otherwise
+ */
+ bool getServer(
+ const string& friendlyName,
+ DialServer &server );
+
+private:
+ DialDiscovery();
+ void updateServerList(string& server);
+ static void *receiveResponses(void *p);
+ static void *send_mcast(void *p);
+ void processServer(char *pResponse);
+ void cleanServerList();
+ void resetDiscovery();
+
+ pthread_t _mcastThread;
+ pthread_t _responseThread;
+
+ typedef map<string, DialServer*> ServerMap;
+ ServerMap mServerMap;
+
+ static DialDiscovery* sDiscovery;
+};
+
+#endif // DIALDISCOVERY_H
diff --git a/src/client/DialServer.cpp b/src/client/DialServer.cpp
new file mode 100644
index 0000000..c0306b8
--- /dev/null
+++ b/src/client/DialServer.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DialServer.h"
+#include <curl/curl.h>
+
+using namespace std;
+
+enum DIAL_COMMAND{
+ COMMAND_LAUNCH,
+ COMMAND_STATUS,
+ COMMAND_KILL
+};
+
+static size_t header_cb(void* ptr, size_t size, size_t nmemb, void* userdata)
+{
+ if ((size * nmemb) != 0) {
+ string newHeader((char*)ptr);
+ string *header = static_cast<string*>(userdata);
+ header->append(newHeader);
+ ATRACE("%s: Adding header: %s", __FUNCTION__, newHeader.c_str());
+ }
+ return (size * nmemb);
+}
+
+static size_t receiveData(void *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ if ((size * nmemb) != 0) {
+ string *body= static_cast<string*>(userdata);
+ body->append((char*)ptr);
+ ATRACE("%s: Adding to Body: %s", __FUNCTION__, (char*)ptr);
+ }
+ return (size * nmemb);
+}
+
+int DialServer::sendCommand(
+ string &url,
+ int command,
+ string &payload,
+ string &responseHeaders,
+ string &responseBody )
+{
+ CURL *curl;
+ CURLcode res = CURLE_OK;
+
+ if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
+ {
+ fprintf(stderr, "curl_global_init() failed\n");
+ return 0;
+ }
+
+ if ((curl = curl_easy_init()) == NULL)
+ {
+ fprintf(stderr, "curl_easy_init() failed\n");
+ curl_global_cleanup();
+ return 0;
+ }
+
+ if (command == COMMAND_LAUNCH)
+ {
+ curl_easy_setopt(curl, CURLOPT_POST, true);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, payload.size());
+ if( payload.size() )
+ {
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
+ }
+#ifdef DEBUG
+ else ATRACE("Sending empty POST\n");
+#endif
+ }
+ else if (command == COMMAND_KILL)
+ {
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
+ }
+ ATRACE("Sending %s:%s\n",
+ command == COMMAND_LAUNCH ? "LAUNCH" :
+ (command == COMMAND_KILL ? "KILL" : "STATUS"),
+ url.c_str());
+
+ curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, &responseHeaders);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBody);
+ res = curl_easy_perform(curl);
+
+ curl_easy_cleanup(curl);
+ curl_global_cleanup();
+ return (res == CURLE_OK);
+}
+
+string DialServer::getIpAddress()
+{
+ // m_appsUrl=http://192.168.1.103:36269/apps/
+ if( m_ipAddr.empty() )
+ {
+ size_t begin = m_appsUrl.find("//");
+ if( begin != m_appsUrl.npos )
+ {
+ begin += 2; // move to the start of the IP address
+ size_t end = m_appsUrl.find(":", begin);
+ if( end != m_appsUrl.npos )
+ {
+ m_ipAddr = m_appsUrl.substr( begin, end-begin );
+ ATRACE("IP ADDRESS: %s\n", m_ipAddr.c_str() );
+ }
+ }
+ }
+ return m_ipAddr;
+}
+
+bool DialServer::getFriendlyName( string& name )
+{
+ bool retval = false;
+ if( !m_ddxml.empty() )
+ {
+ string friendlyName = "<friendlyName>";
+ size_t pos;
+ if( ( pos = m_ddxml.find( friendlyName ) ) != m_ddxml.npos )
+ {
+ string friendlyNameEnd = "</friendlyName>";
+ size_t end = m_ddxml.find( friendlyNameEnd );
+ name = m_ddxml.substr( pos + friendlyName.size(),
+ (end - (pos + friendlyName.size())) );
+ ATRACE("***Friendly name=%s***", name.c_str());
+ retval = true;
+ }
+#ifdef DEBUG
+ else
+ {
+ ATRACE("Friendly name not found\n%s\n", m_ddxml.c_str());
+ }
+#endif
+ }
+
+ return retval;
+}
+
+bool DialServer::getUuid( string& uuid )
+{
+ bool retval = false;
+ if( !m_ddxml.empty() )
+ {
+ string udn = "<UDN>";
+ size_t pos;
+ if( ( pos = m_ddxml.find( udn ) ) != m_ddxml.npos )
+ {
+ string udnEnd = "</UDN>";
+ size_t end = m_ddxml.find( udnEnd );
+ uuid = m_ddxml.substr( pos + udn.size(),
+ (end - (pos + udn.size())) );
+ ATRACE("***UUID=%s***", uuid.c_str() );
+ retval = true;
+ }
+#ifdef DEBUG
+ else
+ {
+ ATRACE("Friendly name not found\n%s\n", m_ddxml.c_str());
+ }
+#endif
+ }
+
+ return retval;
+}
+
+int DialServer::launchApplication(
+ string &application,
+ string &payload,
+ string &responseHeaders,
+ string &responseBody )
+{
+ ATRACE("%s: Launch %s\n", __FUNCTION__, application.c_str());
+ string appUrl = m_appsUrl;
+ sendCommand( appUrl.append(application), COMMAND_LAUNCH, payload, responseHeaders, responseBody);
+ return 0;
+}
+
+int DialServer::getStatus(
+ string &application,
+ string &responseHeaders,
+ string &responseBody )
+{
+ ATRACE("%s: GetStatus %s\n", __FUNCTION__, application.c_str());
+ string emptyPayload;
+ string appUrl = m_appsUrl;
+ sendCommand( appUrl.append(application), COMMAND_STATUS, emptyPayload, responseHeaders, responseBody );
+ return 0;
+}
+
+int DialServer::stopApplication(
+ string &application,
+ string &endPoint,
+ string &responseHeaders )
+{
+ ATRACE("%s: Quit %s\n", __FUNCTION__, application.c_str());
+ string emptyPayload, responseBody; // dropping this
+ string appUrl = m_appsUrl;
+ sendCommand(
+ (appUrl.append(application)).append(endPoint),
+ COMMAND_KILL, emptyPayload, responseHeaders, responseBody );
+ return 0;
+}
+
+
+int DialServer::stopApplication(
+ string &stopurl,
+ string &responseHeaders )
+{
+ ATRACE("%s: Quit URL: %s\n", __FUNCTION__, stopurl.c_str());
+ string emptyPayload, responseBody; // dropping this
+ sendCommand( stopurl, COMMAND_KILL,
+ emptyPayload, responseHeaders, responseBody );
+ return 0;
+}
+
+int DialServer::getHttpResponseHeader(
+ string &responseHeaders,
+ string &header,
+ string &value )
+{
+ return 0;
+}
diff --git a/src/client/DialServer.h b/src/client/DialServer.h
new file mode 100644
index 0000000..ce81e9f
--- /dev/null
+++ b/src/client/DialServer.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIALSERVER_H
+#define DIALSERVER_H
+
+//#define DEBUG
+#ifdef DEBUG
+#define ATRACE(...) printf(__VA_ARGS__)
+#else
+#define ATRACE(...)
+#endif
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+class DialServer
+{
+public:
+ /**
+ * Dial Server ctor
+ *
+ * @param[in] location dd.xml LOCATION header
+ * @param[in] appsUrl Parsed out Application URL
+ * @param[in] location dd.xml LOCATION header
+ * empty string to find any server
+ *
+ */
+ DialServer( string location, string appsUrl, string dd_xml ) :
+ m_location(location),
+ m_appsUrl(appsUrl),
+ found(true),
+ m_ddxml(dd_xml)
+ {}
+
+ ~DialServer() { ATRACE("%s\n", __FUNCTION__); }
+
+ /**
+ * Get the DIAL Server location
+ *
+ * @return Location of the server (http://<IP_ADDR>:<PORT>/dd.xml)
+ */
+ string getLocation() { return m_location; }
+
+ /**
+ * Get the DIAL Server IP address
+ *
+ * @return IP address of the server (X.X.X.X)
+ */
+ string getIpAddress();
+
+ /**
+ * Get the DIAL REST endpoint
+ *
+ * @return Location of the server (http://<IP_ADDR>:<PORT>/apps)
+ */
+ string getAppsUrl() { return m_appsUrl; }
+
+ /**
+ * Get the DIAL friendly name
+ *
+ * @return true if successful, false otherwise
+ */
+ bool getFriendlyName( string& name );
+
+ /**
+ * Get the DIAL UUID
+ *
+ * @return true if successful, false otherwise
+ */
+ bool getUuid( string& uuid );
+
+ /**
+ * Launch a DIAL application
+ *
+ * @param[in] application Name of the application to launch
+ * @param[in] payload launch POST data
+ * @param[out] responseHeaders Returns the HTTP response headers
+ * @param[out] responseBody Returns the HTTP response body
+ *
+ * @return 0 if successful, !0 otherwise
+ */
+ int launchApplication(
+ string &application,
+ string &payload,
+ string &responseHeaders,
+ string &responseBody );
+
+ /**
+ * Get the status of a DIAL application
+ *
+ * @param[in] application Name of the application to query
+ * @param[out] responseHeaders Returns the HTTP response headers
+ * @param[out] responseBody Returns the HTTP response body
+ *
+ * @return 0 if successful, !0 otherwise
+ */
+ int getStatus(
+ string &application,
+ string &responseHeaders,
+ string &responseBody );
+
+ /**
+ * Stop an application. Client *must* provide the endPoint
+ *
+ * @param[in] application Name of the application to stop
+ * @param[in] endPoint URL of the REST endpoint to stop
+ * @param[out] responseHeaders Returns the HTTP response headers
+ *
+ * @return 0 if successful, !0 otherwise
+ */
+ int stopApplication(
+ string &application,
+ string &endPoint,
+ string &responseHeaders );
+
+ /**
+ * Stop an application using a fully formed URL.
+ *
+ * @param[in] stopurl Full stop URL
+ * @param[out] responseHeaders Returns the HTTP response headers
+ *
+ * @return 0 if successful, !0 otherwise
+ */
+ int stopApplication(
+ string &stopurl,
+ string &responseHeaders );
+
+
+ /** ********************* **/
+ /** Convenience functions **/
+ /** ********************* **/
+
+ /**
+ * Extract a header from the response
+ *
+ * @param[in] responseHeaders Response headers
+ * @param[in] header Header value to extract
+ * @param[out] value Value of the header provided
+ *
+ * @return 0 if successful, !0 otherwise
+ */
+ int getHttpResponseHeader(
+ string &responseHeaders,
+ string &header,
+ string &value );
+
+ /**
+ * Returns true if the server has been recently found
+ *
+ * @return true if successful, false otherwise
+ */
+ bool isFound() { return found; }
+
+ /**
+ * Sets the "found status" of this server
+ */
+ void setFound(bool b) { found = b; }
+
+private:
+ int sendCommand( string &url, int command, string &payload,
+ string &responseHeaders, string &responseBody );
+ string m_location;
+ string m_appsUrl;
+ string m_ipAddr;
+ bool found;
+ string m_ddxml; // information about the device
+};
+
+#endif // DIALSERVER_H
diff --git a/src/client/dialclient_input.txt b/src/client/dialclient_input.txt
new file mode 100644
index 0000000..01e783f
--- /dev/null
+++ b/src/client/dialclient_input.txt
@@ -0,0 +1,80 @@
+# Input file for DIAL client
+
+# List the applications defined.
+# Valid applications that should exist on your DIAL server implementation
+addApplication=Netflix
+addApplication=YouTube
+
+# Error applications that should not exist on your DIAL server implementation
+addErrorApplication=netflix
+addErrorApplication=Netflix1
+
+# Launch the Netflix application
+# HTTP response should be 201
+# HTTP response header should contain a Location header
+launch=Netflix httpresponse=201 httpresponse=Created httpresponseheader=Location
+sleep=6000
+
+# Get the status of the application, it should be running
+status=Netflix httpresponse=200 resultbody=state resultbody=running
+
+# Stop the Netflix application, wait for 1 second while it shuts down.
+stop=Netflix
+sleep=1000
+
+# Call stop again and ensure the implementation returns the proper response
+stop=Netflix httpresponse=404
+
+# Get the status of the Netflix application and ensure it returns stopped
+status=Netflix httpresponse=200 resultbody=state resultbody=stopped
+sleep=2000
+
+# Launch the application with a parameter
+# NOTE: This test can not ensure that the parameter was taken properly
+launch=Netflix param="NETFLIX&WAS+HERE" httpresponse=201 httpresponse=Created httpresponseheader=Location
+sleep=6000
+
+# Ensure the application is running
+status=Netflix httpresponse=200 resultbody=state resultbody=running
+
+# This will launch Netflix with the same parameter. *Netflix should not relaunch*
+launch=Netflix param="NETFLIX&WAS*HERE" httpresponse=201 httpresponse=Created httpresponseheader=Location
+sleep=6000
+
+# Ensure the application is running
+status=Netflix httpresponse=200 resultbody=state resultbody=running
+
+# This will launch Netflix with a different parameter. *Netflix should not relaunch*
+launch=Netflix param="&&&%%%ThisShouldRelaunch%%%" httpresponse=201 httpresponse=Created httpresponseheader=Location
+sleep=6000
+
+# Ensure the application is running
+status=Netflix httpresponse=200 resultbody=state resultbody=running
+
+# Stop Netflix
+stop=Netflix
+sleep=1500
+
+# Make sure netflix is reported as stopped
+status=Netflix httpresponse=200 resultbody=state resultbody=stopped
+
+# Test a parameter with over 4096 bytes and ensure 413 is returned.
+launch=Netflix param="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" httpresponse=413
+
+# Test for case sensitivity
+launch=netflix httpresponse=404
+sleep=200
+
+# Ensure the server is checking the full application name
+launch=Netflix1 httpresponse=404
+sleep=200
+
+# This executes correctly, but fails the XML
+status=Netflix1 httpresponse=404
+sleep=200
+
+# Currently can't run this test, the code won't execute a stop without a stop URL.
+#stop=Netflix1 httpresponse=404
+
+# Test ALL valid applications
+status=ALL httpresponse=200 resultbody=state resultbody=stopped
diff --git a/src/client/main.cpp b/src/client/main.cpp
new file mode 100644
index 0000000..ffce725
--- /dev/null
+++ b/src/client/main.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "DialDiscovery.h"
+#include "DialConformance.h"
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+using namespace std;
+
+static DialDiscovery* gpDiscovery;
+static bool gUseMenu = true;
+
+// TODO: Make it possible to pass applications from the command line
+static vector<string> gAppList;
+static string gOutputFile;
+static string gInputFile;
+
+// IP address of the DIAL server
+static string gIpAddress;
+
+static void printServerList( vector<DialServer*> list )
+{
+ int i;
+ vector<DialServer*>::iterator it;
+ for( i = 0, it = list.begin(); it < list.end(); it++, i++ )
+ {
+ string uuid, name;
+ (*it)->getFriendlyName( name );
+ (*it)->getUuid( uuid );
+ printf("%Zu: Server IP[%s] UUID[%s] FriendlyName[%s] \n",
+ i+1, (*it)->getIpAddress().c_str(),
+ uuid.c_str(), name.c_str() );
+ }
+}
+
+static DialServer* getServerFromUser( vector<DialServer*> list )
+{
+ DialServer* pServer;
+ // show a list to the user
+ if( list.size() > 1 )
+ {
+ char buf[80] = {0,};
+ vector<DialServer*>::iterator it;
+
+ printf("Found Multiple servers\n");
+ printServerList(list);
+ printf("Enter server: ");
+ scanf("%s", buf);
+ unsigned int server = atoi(buf);
+ assert( server > 0 && server <= list.size() );
+ pServer = list[server-1];
+ }
+ else
+ {
+ pServer = list.front();
+ }
+ return pServer;
+}
+
+static void runConformance()
+{
+ vector<DialServer*> list;
+ gpDiscovery->getServerList(list);
+
+ if( list.size() )
+ {
+ DialServer *pServer = NULL;
+ if( !gIpAddress.empty() )
+ {
+ pServer = NULL;
+ vector<DialServer*>::iterator it;
+ for( it = list.begin(); it < list.end(); it ++ )
+ {
+ if( gIpAddress.compare((*it)->getIpAddress()) == 0 )
+ {
+ ATRACE("Found server %s in the list of servers\n",
+ gIpAddress.c_str() );
+ pServer = (*it);
+ break;
+ }
+ }
+ }
+ else
+ {
+ pServer = getServerFromUser( list );
+ }
+
+ if( pServer )
+ {
+ string name;
+ bool serverExists = pServer->getFriendlyName(name);
+ assert( serverExists );
+ string uuid;
+ pServer->getUuid( uuid );
+ printf("\nRunning conformance against: IP[%s] UUID[%s] FriendlyName[%s] \n",
+ pServer->getIpAddress().c_str(),
+ uuid.c_str(), name.c_str() );
+ DialConformance::instance()->run(
+ pServer,
+ gAppList,
+ gInputFile,
+ gOutputFile );
+ }
+ else
+ {
+ printf("DIAL server not found\n");
+ printf("%Zu available server(s): \n", list.size());
+ printServerList(list);
+ }
+ }
+ else
+ {
+ printf("No servers available\n");
+ }
+}
+
+int handleUser(DialDiscovery *pDial) {
+ int processInput = 1;
+ char buf[80];
+ vector<DialServer*> list;
+
+ pDial->getServerList(list);
+ if( list.size() == 0 )
+ {
+ printf("No servers available\n");
+ return 1;
+ }
+ DialServer* pServer = getServerFromUser( list );
+
+ while(processInput)
+ {
+ string responseHeaders, responseBody, payload;
+ string endpoint = "/run";
+ string netflix = "Netflix";
+ string youtube = "YouTube";
+
+ memset(buf, 0, 80);
+ printf("0. List DIAL servers\n");
+ printf("1. Launch Netflix\n");
+ printf("2. Kill Netflix\n");
+ printf("3. Netflix status\n");
+ printf("4. Launch YouTube\n");
+ printf("5. Kill YouTube\n");
+ printf("6. YouTube status\n");
+ printf("7. Run conformance tests\n");
+ printf("8. QUIT\n");
+ printf("Command (0:1:2:3:4:5:6:7:8): ");
+ scanf("%s", buf);
+ switch( atoi(buf) )
+ {
+ case 0:
+ {
+ printf("\n\n******** %Zu servers found ********\n\n", list.size());
+ for( unsigned int i = 0; i < list.size(); i++ )
+ {
+ string name;
+ list[i]->getFriendlyName(name);
+ printf("Server %Zu: %s\n", i+1, name.c_str());
+ }
+ printf("\n*********************************\n\n");
+ }break;
+ case 1:
+ printf("Launch Netflix\n");
+ pServer->launchApplication( netflix, payload, responseHeaders, responseBody );
+ break;
+ case 2:
+ printf("Kill Netflix\n");
+ pServer->stopApplication( netflix, endpoint, responseHeaders );
+ break;
+ case 3:
+ printf("Netflix Status: \n");
+ pServer->getStatus( netflix, responseHeaders, responseBody );
+ printf("RESPONSE: \n%s\n", responseBody.c_str());
+ break;
+ case 4:
+ printf("Launch YouTube\n");
+ pServer->launchApplication( youtube, payload, responseHeaders, responseBody );
+ break;
+ case 5:
+ printf("Kill YouTube\n");
+ pServer->stopApplication( youtube, endpoint, responseHeaders );
+ break;
+ case 6:
+ printf("YouTube Status: \n");
+ pServer->getStatus( youtube, responseHeaders, responseBody );
+ break;
+ case 7:
+ runConformance();
+ break;
+ case 8:
+ processInput = 0;
+ break;
+ default:
+ printf("Invalid, try again\n");
+ }
+ }
+ return 0;
+}
+
+static const char usage[] = "\n"
+" If no option is specified, the program will run a conformance test.\n"
+"\n"
+"usage: dialclient <option>\n"
+" Option Parameter Description\n"
+" -h none Usage menu\n"
+" -s filename (optional) Run conformance test. Use filename as\n"
+" the input, if provided\n"
+" -o filename Reporter output file (./report.html)\n"
+" -a ip_address IP address of DIAL server (used for conformance\n"
+" testing)\n"
+"\n";
+
+inline void notSupported( string s )
+{
+ printf( "%s not supported", s.c_str() );
+ printf( "%s\n", usage );
+ exit(0);
+}
+
+int parseArgs( int argc, char* argv[] )
+{
+ for( int i = 1; i < argc; i++ )
+ {
+ string input(argv[i]);
+ if( input[0] != '-' ) notSupported(input);
+ switch(input[1])
+ {
+ case 's':
+ gUseMenu = false;
+ if( argv[i+1] != NULL && argv[i+1][0] != '-' )
+ {
+ //filename provided
+ gInputFile = argv[++i];
+ }
+ break;
+ case 'o':
+ gOutputFile = argv[++i];
+ break;
+ case 'a':
+ gIpAddress = argv[++i];
+ break;
+ case 'h':
+ printf("%s", usage);
+ exit(0);
+ break;
+ default:
+ notSupported(input);
+ }
+ }
+ return 0;
+}
+
+int main(int argc, char* argv[]) {
+ parseArgs(argc, argv);
+
+ gpDiscovery = DialDiscovery::create();
+ gpDiscovery->init();
+ DialConformance::create();
+
+ // Sleep for 2 seconds to allow DIAL servers to response to MSEARCH.
+ sleep(2);
+
+ if ( gUseMenu )
+ {
+ return handleUser(gpDiscovery);
+ }
+
+ // not using the menu, just run the conformance test.
+ runConformance();
+ return 0;
+}
+
diff --git a/src/client/makefile b/src/client/makefile
new file mode 100644
index 0000000..68cf150
--- /dev/null
+++ b/src/client/makefile
@@ -0,0 +1,14 @@
+CC=$(TARGET)g++
+
+.PHONY: clean
+.DEFAULT_GOAL=dialclient
+
+includes = $(wildcard *.h)
+OBJS := main.cpp DialServer.cpp DialDiscovery.cpp DialConformance.cpp DialClientInput.cpp
+
+# You may not need all these libraries. This example uses a build of curl that needs crypto, ssl, cares, and zlib
+dialclient: $(OBJS) ${includes}
+ $(CC) -Wall -Werror -g $(OBJS) $(INCLUDES) $(LDFLAGS) -ldl -lpthread -lcurl -lz -lcrypto -lssl -lcares -m32 -o dialclient
+
+clean:
+ rm -f *.o dialclient
diff --git a/src/makefile b/src/makefile
new file mode 100644
index 0000000..e934e46
--- /dev/null
+++ b/src/makefile
@@ -0,0 +1,8 @@
+DIRS = client
+DIRS += server
+
+all:
+ for dir in $(DIRS); do (make -C $$dir || exit 1) || exit 1; done
+clean:
+ for dir in $(DIRS); do (make clean -C $$dir || exit 1) || exit 1; done
+
diff --git a/src/server/dial_options.h b/src/server/dial_options.h
new file mode 100644
index 0000000..9d67392
--- /dev/null
+++ b/src/server/dial_options.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIAL_OPTIONS_H
+#define DIAL_OPTIONS_H
+
+#define DATA_PATH_OPTION "-D"
+#define DATA_PATH_OPTION_LONG "--data-path"
+#define DATA_PATH_DESCRIPTION "Path to netflix secure store"
+
+#define NETFLIX_PATH_OPTION "-N"
+#define NETFLIX_PATH_OPTION_LONG "--netflix-path"
+#define NETFLIX_PATH_DESCRIPTION "Path to Netflix application"
+
+#define FRIENDLY_NAME_OPTION "-F"
+#define FRIENDLY_NAME_OPTION_LONG "--friendly-name"
+#define FRIENDLY_NAME_DESCRIPTION "Device Friendly Name"
+
+#define MODELNAME_OPTION "-M"
+#define MODELNAME_OPTION_LONG "--model-name"
+#define MODELNAME_DESCRIPTION "Model name of the device"
+
+#define UUID_OPTION "-U"
+#define UUID_OPTION_LONG "--uuid-name"
+#define UUID_DESCRIPTION "UUID of the device"
+
+struct dial_options
+{
+ const char * pOption;
+ const char * pLongOption;
+ const char * pOptionDescription;
+}dial_options_t;
+
+struct dial_options gDialOptions[] =
+{
+ {
+ DATA_PATH_OPTION,
+ DATA_PATH_OPTION_LONG,
+ DATA_PATH_DESCRIPTION
+ },
+ {
+ NETFLIX_PATH_OPTION,
+ NETFLIX_PATH_OPTION_LONG,
+ NETFLIX_PATH_DESCRIPTION
+ },
+ {
+ FRIENDLY_NAME_OPTION,
+ FRIENDLY_NAME_OPTION_LONG,
+ FRIENDLY_NAME_DESCRIPTION,
+ },
+ {
+ MODELNAME_OPTION,
+ MODELNAME_OPTION_LONG,
+ MODELNAME_DESCRIPTION
+ },
+ {
+ UUID_OPTION,
+ UUID_OPTION_LONG,
+ UUID_DESCRIPTION
+ }
+};
+
+#endif
+
diff --git a/src/server/dial_server.c b/src/server/dial_server.c
new file mode 100644
index 0000000..8929594
--- /dev/null
+++ b/src/server/dial_server.c
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dial_server.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include "mongoose.h"
+
+// TODO: Partners should define this port
+#define DIAL_PORT (56789)
+
+struct DIALApp_ {
+ struct DIALApp_ *next;
+ struct DIALAppCallbacks callbacks;
+ void *callback_data;
+ DIAL_run_t run_id;
+ DIALStatus state;
+ char *name;
+ char payload[DIAL_MAX_PAYLOAD];
+};
+
+typedef struct DIALApp_ DIALApp;
+
+struct DIALServer_ {
+ struct mg_context *ctx;
+ struct DIALApp_ *apps;
+ pthread_mutex_t mux;
+};
+
+static void ds_lock(DIALServer *ds) {
+ pthread_mutex_lock(&ds->mux);
+}
+
+static void ds_unlock(DIALServer *ds) {
+ pthread_mutex_unlock(&ds->mux);
+}
+
+// finds an app and returns a pointer to the previous element's next pointer
+// if not found, return a pointer to the last element's next pointer
+static DIALApp **find_app(DIALServer *ds, const char *app_name) {
+ DIALApp *app;
+ DIALApp **ret = &ds->apps;
+
+ for (app = ds->apps; app != NULL; ret = &app->next, app = app->next) {
+ if (!strcmp(app_name, app->name)) {
+ break;
+ }
+ }
+ return ret;
+}
+
+/*
+ * A bad payload is defined to be an unprintable character or a
+ * non-ascii character.
+ */
+static int isBadPayload( const char* pPayload, int numBytes )
+{
+ int i = 0;
+ fprintf( stderr, "Checking %d bytes\n", numBytes );
+ for( ; i < numBytes; i++)
+ {
+ // High order bit should not be set
+ // 0x7F is DEL (non-printable)
+ // Anything under 32 is non-printable
+ if( ((pPayload[i] & 0x80) == 0x80) ||
+ (pPayload[i] == 0x7F) ||
+ (pPayload[i] <= 0x1F) )
+ return 1;
+ }
+ return 0;
+}
+
+static void handle_app_start(struct mg_connection *conn,
+ const struct mg_request_info *request_info,
+ const char *app_name) {
+ char body[DIAL_MAX_PAYLOAD+2] = {0,};
+ DIALApp *app;
+ DIALServer *ds = request_info->user_data;
+ int nread;
+
+ ds_lock(ds);
+ app = *find_app(ds, app_name);
+ if (!app) {
+ mg_send_http_error(conn, 404, "Not Found", "Not Found");
+ } else {
+ nread = mg_read(conn, body, sizeof(body));
+ // NUL-terminate it just in case
+ if( nread > DIAL_MAX_PAYLOAD ) {
+ mg_send_http_error(conn, 413, "413 Request Entity Too Large",
+ "413 Request Entity Too Large");
+ }
+ else if( isBadPayload( body, nread ) ){
+ mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request");
+ }
+ else {
+ app->state = app->callbacks.start_cb(ds, app_name, body, nread,
+ &app->run_id, app->callback_data);
+ if (app->state == kDIALStatusRunning) {
+ char laddr[INET6_ADDRSTRLEN];
+ const struct sockaddr_in *addr =
+ (struct sockaddr_in *)&request_info->local_addr;
+ inet_ntop(addr->sin_family, &addr->sin_addr, laddr, sizeof(laddr));
+ mg_printf(conn, "HTTP/1.1 201 Created\r\n"
+ "Content-Type: text/plain\r\n"
+ "Location: http://%s:%d/apps/%s/run\r\n"
+ "\r\n", laddr, DIAL_get_port(ds), app_name);
+ // copy the payload into the application struct
+ memset( app->payload, 0, DIAL_MAX_PAYLOAD );
+ memcpy( app->payload, body, nread );
+ } else {
+ mg_send_http_error(conn, 503, "Service Unavailable",
+ "Service Unavailable");
+ }
+ }
+ }
+ ds_unlock(ds);
+}
+
+static void handle_app_status(struct mg_connection *conn,
+ const struct mg_request_info *request_info,
+ const char *app_name) {
+ DIALApp *app;
+ int canStop = 0;
+ DIALServer *ds = request_info->user_data;
+
+ ds_lock(ds);
+ app = *find_app(ds, app_name);
+ if (!app) {
+ mg_send_http_error(conn, 404, "Not Found", "Not Found");
+ } else {
+ app->state = app->callbacks.status_cb(ds, app_name, app->run_id,
+ &canStop, app->callback_data);
+ mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+ "Content-Type: application/xml\r\n"
+ "\r\n"
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
+ "<service xmlns=\"urn:dial-multiscreen-org:schemas:dial\">\r\n"
+ " <name>%s</name>\r\n"
+ " <options allowStop=\"%s\"/>\r\n"
+ " <state>%s</state>\r\n"
+ "%s"
+ "</service>\r\n", app->name, canStop ? "true":"false",
+ app->state ? "running" : "stopped",
+ app->state == kDIALStatusStopped ?
+ "" : " <link rel=\"run\" href=\"run\"/>\r\n" );
+ }
+ ds_unlock(ds);
+}
+
+static void handle_app_stop(struct mg_connection *conn,
+ const struct mg_request_info *request_info,
+ const char *app_name) {
+ DIALApp *app;
+ DIALServer *ds = request_info->user_data;
+ int canStop = 0;
+
+ ds_lock(ds);
+ app = *find_app(ds, app_name);
+
+ // update the application state
+ if( !app ) {
+ app->state = app->callbacks.status_cb(ds, app_name, app->run_id,
+ &canStop, app->callback_data);
+ }
+
+ if (!app || app->state != kDIALStatusRunning) {
+ mg_send_http_error(conn, 404, "Not Found", "Not Found");
+ } else {
+ app->callbacks.stop_cb(ds, app_name, app->run_id, app->callback_data);
+ app->state = kDIALStatusStopped;
+ mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n");
+ }
+ ds_unlock(ds);
+}
+
+#define APPS_URI "/apps/"
+#define RUN_URI "/run"
+
+static void *request_handler(enum mg_event event,
+ struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ if (event == MG_NEW_REQUEST) {
+ // URL ends with run
+ if (!strncmp(request_info->uri + strlen(request_info->uri)-4, RUN_URI, strlen(RUN_URI))) {
+ char app_name[256] = {0,}; // assuming the application name is not over 256 chars.
+ strncpy( app_name, request_info->uri + strlen(APPS_URI),
+ ((strlen(request_info->uri)-4) - (sizeof(APPS_URI)-1)) );
+
+ // DELETE non-empty app name
+ if (app_name[0] != '\0' &&
+ !strcmp(request_info->request_method, "DELETE")) {
+ handle_app_stop(conn, request_info, app_name);
+ } else {
+ mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented");
+ }
+ }
+ // URI starts with "/apps/"
+ else if (!strncmp(request_info->uri, APPS_URI, sizeof(APPS_URI) - 1)) {
+ const char *app_name;
+ app_name = request_info->uri + sizeof(APPS_URI) - 1;
+ // start app
+ if (!strcmp(request_info->request_method, "POST")) {
+ handle_app_start(conn, request_info, app_name);
+ // get app status
+ } else if (!strcmp(request_info->request_method, "GET")) {
+ handle_app_status(conn, request_info, app_name);
+ } else {
+ mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented");
+ }
+ } else {
+ mg_send_http_error(conn, 404, "Not Found", "Not Found");
+ }
+ return "done";
+ } else if (event == MG_EVENT_LOG) {
+ fprintf( stderr, "MG: %s\n", request_info->log_message);
+ return "done";
+ }
+ return NULL;
+}
+
+DIALServer *DIAL_start() {
+ DIALServer *ds = calloc(1, sizeof(DIALServer));
+ struct mg_context *ctx;
+
+ pthread_mutex_init(&ds->mux, NULL);
+ ctx = mg_start(&request_handler, ds, DIAL_PORT);
+ if (!ctx) {
+ free(ds);
+ return NULL;
+ }
+ ds->ctx = ctx;
+ return ds;
+}
+
+void DIAL_stop(DIALServer *ds) {
+ mg_stop(ds->ctx);
+ pthread_mutex_destroy(&ds->mux);
+}
+
+in_port_t DIAL_get_port(DIALServer *ds) {
+ struct sockaddr sa;
+ socklen_t len = sizeof(sa);
+ if (!mg_get_listen_addr(ds->ctx, &sa, &len)) {
+ return 0;
+ }
+ return ntohs(((struct sockaddr_in *)&sa)->sin_port);
+}
+
+int DIAL_register_app(DIALServer *ds, const char *app_name,
+ struct DIALAppCallbacks *callbacks,
+ void *user_data) {
+ DIALApp **ptr, *app;
+ int ret;
+
+ ds_lock(ds);
+ ptr = find_app(ds, app_name);
+ if (*ptr != NULL) { // app already registered
+ ds_unlock(ds);
+ ret = 0;
+ } else {
+ app = malloc(sizeof(DIALApp));
+ app->callbacks = *callbacks;
+ app->name = strdup(app_name);
+ app->next = *ptr;
+ app->state = kDIALStatusStopped;
+ app->callback_data = user_data;
+ *ptr = app;
+ ret = 1;
+ }
+
+ ds_unlock(ds);
+ return ret;
+}
+
+int DIAL_unregister_app(DIALServer *ds, const char *app_name) {
+ DIALApp **ptr, *app;
+ int ret;
+
+ ds_lock(ds);
+ ptr = find_app(ds, app_name);
+ if (*ptr == NULL) { // no such app
+ ret = 0;
+ } else {
+ app = *ptr;
+ *ptr = app->next;
+ free(app->name);
+ free(app);
+ ret = 1;
+ }
+
+ ds_unlock(ds);
+ return ret;
+}
+
+const char * DIAL_get_payload(DIALServer *ds, const char *app_name)
+{
+ const char * pPayload = NULL;
+ DIALApp **ptr, *app;
+
+ // NOTE: Don't grab the mutex as we are calling this function from
+ // inside the application callback which already has the lock.
+ //ds_lock(ds);
+ ptr = find_app(ds, app_name);
+ if (*ptr != NULL)
+ {
+ app = *ptr;
+ pPayload = app->payload;
+ }
+ //ds_unlock(ds);
+ return pPayload;
+}
+
diff --git a/src/server/dial_server.h b/src/server/dial_server.h
new file mode 100644
index 0000000..fa309ad
--- /dev/null
+++ b/src/server/dial_server.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIAL_SERVER_H_
+#define DIAL_SERVER_H_
+
+#include <netinet/in.h>
+
+/*
+ * Dial application states
+ */
+typedef enum {
+ kDIALStatusStopped,
+ kDIALStatusRunning
+} DIALStatus;
+
+/*
+ * The maximum DIAL payload accepted per the DIAL 1.6.1 specification.
+ */
+#define DIAL_MAX_PAYLOAD (4096)
+
+/*
+ * Opaque DIAL server handle
+ */
+struct DIALServer_;
+typedef struct DIALServer_ DIALServer;
+
+/*
+ * Opaque run id that can be system specific
+ */
+typedef void * DIAL_run_t;
+
+/*
+ * DIAL start callback
+ */
+typedef DIALStatus (*DIAL_app_start_cb)(DIALServer *ds, const char *app_name,
+ const char *args, size_t arglen,
+ DIAL_run_t *run_id, void *callback_data);
+/*
+ * DIAL stop callback
+ */
+typedef void (*DIAL_app_stop_cb)(DIALServer *ds, const char *app_name,
+ DIAL_run_t run_id, void *callback_data);
+/*
+ * DIAL status callback
+ */
+typedef DIALStatus (*DIAL_app_status_cb)(DIALServer *ds, const char *app_name,
+ DIAL_run_t run_id, int* pCanStop,
+ void *callback_data);
+
+/*
+ * DIAL callbacks
+ */
+struct DIALAppCallbacks {
+ DIAL_app_start_cb start_cb;
+ DIAL_app_stop_cb stop_cb;
+ DIAL_app_status_cb status_cb;
+};
+
+/*
+ * Start the DIAL server. Returns a handle to the DIAL server.
+ */
+DIALServer *DIAL_start();
+
+/*
+ * Stop the DIAL server. Returns a handle to the DIAL server.
+ *
+ * @param[in] ds DIAL server handle
+ */
+void DIAL_stop(DIALServer *ds);
+
+/*
+ * Register a DIAL application
+ *
+ * @param[in] ds DIAL server handle
+ * @param[in] app_name Name of the application. This should match the DIAL
+ * application end point.
+ * @param[in] callbacks Structure with application callbacks
+ * @param[in] callback_data Client user data
+ *
+ * @return 1 if successful, 0 otherwise
+ */
+int DIAL_register_app(DIALServer *ds, const char *app_name,
+ struct DIALAppCallbacks *callbacks,
+ void *callback_data);
+
+/*
+ * Unregsiter an application
+ *
+ * @param[in] ds DIAL server handle
+ * @param[in] app_name Name of the DIAL application
+ *
+ * @return 1 if successful, 0 otherwise
+ */
+int DIAL_unregister_app(DIALServer *ds, const char *app_name);
+
+/*
+ * Get the DIAL REST endpoint
+ *
+ * @return Port number of the DIAL rest endpoint. Returns 0 on error.
+ */
+in_port_t DIAL_get_port(DIALServer *ds);
+
+/*
+ * Get the last payload delivered to an application. This can be used
+ * by application clients to see if the payload changed between lauches.
+ *
+ * @param[in] ds DIAL server handle
+ * @param[in] app_name Name of the application
+ *
+ * @return Pointer to a NULL terminated string.
+ */
+const char * DIAL_get_payload(DIALServer *ds, const char *app_name);
+
+#endif // DIAL_SERVER_H_
diff --git a/src/server/main.c b/src/server/main.c
new file mode 100644
index 0000000..aac852e
--- /dev/null
+++ b/src/server/main.c
@@ -0,0 +1,451 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <regex.h>
+
+#include "dial_server.h"
+#include "dial_options.h"
+
+#define BUFSIZE 256
+
+static char *spAppNetflix = "netflix"; // name of the netflix executable
+static char *spSecret = "12345678910shhhNFLXwashereman";
+static char *spDefaultNetflix = "../../../src/platform/qt/netflix";
+static char *spDefaultData="../../../src/platform/qt/data";
+static char *spNfDataDir = "NF_DATA_DIR=";
+static char *defaultLaunchParam = "source_type=12";
+static char *spDefaultFriendlyName = "DIAL server sample";
+static char *spDefaultModelName = "NOT A VALID MODEL NAME";
+static char *spDefaultUuid = "deadbeef-dead-beef-dead-beefdeadbeef";
+static char spDataDir[BUFSIZE];
+static char spNetflix[BUFSIZE];
+static char spFriendlyName[BUFSIZE];
+static char spModelName[BUFSIZE];
+static char spUuid[BUFSIZE];
+static int gDialPort;
+
+static char *spAppYouTube = "chrome";
+static char *spAppYouTubeMatch = "chrome.*google-chrome-dial";
+static char *spAppYouTubeExecutable = "/opt/google/chrome/google-chrome";
+static char *spYouTubePS3UserAgent = "--user-agent="
+ "Mozilla/5.0 (PS3; Leanback Shell) AppleWebKit/535.22 (KHTML, like Gecko) "
+ "Chrome/19.0.1048.0 LeanbackShell/01.00.01.73 QA Safari/535.22 Sony PS3/ "
+ "(PS3, , no, CH)";
+
+// Adding 20 bytes for prepended source_type for Netflix
+static char sQueryParam[DIAL_MAX_PAYLOAD+20];
+
+static int doesMatch( char* pzExp, char* pzStr)
+{
+ regex_t exp;
+ int ret;
+ int match = 0;
+ if ((ret = regcomp( &exp, pzExp, REG_EXTENDED ))) {
+ char errbuf[1024] = {0,};
+ regerror(ret, &exp, errbuf, sizeof(errbuf));
+ fprintf( stderr, "regexp error: %s", errbuf );
+ } else {
+ regmatch_t matches[1];
+ if( regexec( &exp, pzStr, 1, matches, 0 ) == 0 ) {
+ match = 1;
+ }
+ }
+ regfree(&exp);
+ return match;
+}
+
+
+/* The URL encoding source code was obtained here:
+ * http://www.geekhideout.com/urlcode.shtml
+ */
+
+/* Converts a hex character to its integer value */
+char from_hex(char ch) {
+ return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
+}
+
+/* Converts an integer value to its hex character*/
+char to_hex(char code) {
+ static char hex[] = "0123456789abcdef";
+ return hex[code & 15];
+}
+
+/* Returns a url-encoded version of str */
+/* IMPORTANT: be sure to free() the returned string after use */
+char *url_encode(const char *str) {
+ const char *pstr;
+ char *buf, *pbuf;
+ pstr = str;
+ buf = malloc(strlen(str) * 3 + 1);
+ pbuf = buf;
+ if( buf )
+ {
+ while (*pstr) {
+ if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
+ *pbuf++ = *pstr;
+ else if (*pstr == ' ')
+ *pbuf++ = '+';
+ else
+ *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
+ pstr++;
+ }
+ *pbuf = '\0';
+ }
+ return buf;
+}
+
+/*
+ * End of URL ENCODE source
+ */
+
+/*
+ * This function will walk /proc and look for the application in
+ * /proc/<PID>/comm. and /proc/<PID>/cmdline to find it's command (executable
+ * name) and command line (if needed).
+ * Implementors can override this function with an equivalent.
+ */
+static int isAppRunning( char *pzName, char *pzCommandPattern ) {
+ DIR* proc_fd = opendir("/proc");
+ if( proc_fd != NULL ) {
+ struct dirent* procEntry;
+ while((procEntry=readdir(proc_fd)) != NULL) {
+ if( doesMatch( "^[0-9][0-9]*$", procEntry->d_name ) ) {
+ char exePath[64] = {0,};
+ char link[256] = {0,};
+ char cmdlinePath[64] = {0,};
+ char buffer[1024] = {0,};
+ int len;
+ sprintf( exePath, "/proc/%s/exe", procEntry->d_name);
+ sprintf( cmdlinePath, "/proc/%s/cmdline", procEntry->d_name);
+
+ if( (len = readlink( exePath, link, sizeof(link)-1)) != -1 ) {
+ char executable[256] = {0,};
+ strcat( executable, pzName );
+ strcat( executable, "$" );
+ // TODO: Make this search for EOL to prevent false positivies
+ if( !doesMatch( executable, link ) ) {
+ continue;
+ }
+ // else //fall through, we found it
+ }
+ else continue;
+
+ if (pzCommandPattern != NULL) {
+ FILE *cmdline = fopen(cmdlinePath, "r");
+ if (!cmdline) {
+ continue;
+ }
+ if (fgets(buffer, 1024, cmdline) == NULL) {
+ fclose(cmdline);
+ continue;
+ }
+ fclose(cmdline);
+
+ if (!doesMatch( pzCommandPattern, buffer )) {
+ continue;
+ }
+ }
+
+ closedir(proc_fd);
+ return atoi(procEntry->d_name);
+ }
+ }
+
+ closedir(proc_fd);
+ } else {
+ printf("/proc failed to open\n");
+ }
+ return 0;
+}
+
+static pid_t runApplication( const char * const args[], DIAL_run_t *run_id ) {
+ pid_t pid = fork();
+ if (pid != -1) {
+ if (!pid) { // child
+ putenv(spDataDir);
+ printf("Execute:\n");
+ for(int i = 0; args[i]; ++i) {
+ printf(" %d) %s\n", i, args[i]);
+ }
+ execv(*args, (char * const *) args);
+ } else {
+ *run_id = (void *)(long)pid; // parent PID
+ }
+ return kDIALStatusRunning;
+ } else {
+ return kDIALStatusStopped;
+ }
+}
+
+
+/* Compare the applications last launch parameters with the new parameters.
+ * If they match, return false
+ * If they don't match, return true
+ */
+static int shouldRelaunch(
+ DIALServer *pServer,
+ const char *pAppName,
+ const char *args )
+{
+ return ( strncmp( DIAL_get_payload(pServer, pAppName), args, DIAL_MAX_PAYLOAD ) != 0 );
+}
+
+static DIALStatus youtube_start(DIALServer *ds, const char *appname,
+ const char *args, size_t arglen,
+ DIAL_run_t *run_id, void *callback_data) {
+ printf("\n\n ** LAUNCH YouTube **\n\n");
+
+ // TODO(steineldar): Verify that the args string is a valid URL query param.
+ for (int n = 0; args[n] != 0; ++n) {
+ char c = args[n];
+ if (!(( '0' <= c && c <= '9' ) ||
+ ( 'a' <= c && c <= 'z' ) ||
+ ( 'A' <= c && c <= 'Z' ) ||
+ ( c == '=' || c == '-' || c == '_' || c == '&' ))) {
+ printf("Invalid char [%d] = %c", (int) c, c);
+ args = "";
+ break;
+ }
+ }
+
+ char url[512] = {0,}, data[512] = {0,};
+ sprintf( url, "https://www.youtube.com/tv?%s", args);
+ sprintf( data, "--user-data-dir=%s/.config/google-chrome-dial", getenv("HOME") );
+
+ const char * const youtube_args[] = { spAppYouTubeExecutable,
+ spYouTubePS3UserAgent,
+ data, "--app", url, NULL
+ };
+ runApplication( youtube_args, run_id );
+
+ return kDIALStatusRunning;
+}
+
+static DIALStatus youtube_status(DIALServer *ds, const char *appname,
+ DIAL_run_t run_id, int *pCanStop, void *callback_data) {
+ // YouTube can stop
+ *pCanStop = 1;
+ return isAppRunning( spAppYouTube, spAppYouTubeMatch ) ? kDIALStatusRunning : kDIALStatusStopped;
+}
+
+static void youtube_stop(DIALServer *ds, const char *appname, DIAL_run_t run_id,
+ void *callback_data) {
+ printf("\n\n ** KILL YouTube **\n\n");
+ pid_t pid;
+ if ((pid = isAppRunning( spAppYouTube, spAppYouTubeMatch ))) {
+ kill(pid, SIGTERM);
+ }
+}
+
+static DIALStatus netflix_start(DIALServer *ds, const char *appname,
+ const char *args, size_t arglen,
+ DIAL_run_t *run_id, void *callback_data) {
+ int shouldRelaunchApp = 0;
+ int payloadLen = 0;
+ int appPid = 0;
+
+ // only launch Netflix if it isn't running
+ appPid = isAppRunning( spAppNetflix, NULL );
+ shouldRelaunchApp = shouldRelaunch( ds, appname, args );
+
+ // construct the payload to determine if it has changed from the previous launch
+ payloadLen = strlen(args);
+ memset( sQueryParam, 0, DIAL_MAX_PAYLOAD );
+ strcat( sQueryParam, defaultLaunchParam );
+ if( payloadLen )
+ {
+ char * pUrlEncodedParams;
+ pUrlEncodedParams = url_encode( args );
+ if( pUrlEncodedParams )
+ {
+ strcat( sQueryParam, "&dial=");
+ strcat( sQueryParam, pUrlEncodedParams );
+ free( pUrlEncodedParams );
+ }
+ }
+
+ printf("appPid = %s, shouldRelaunch = %s queryParams = %s\n",
+ appPid?"TRUE":"FALSE",
+ shouldRelaunchApp?"TRUE":"FALSE",
+ sQueryParam );
+
+ // if its not running, launch it. The Netflix application should
+ // never be relaunched
+ if( !appPid )
+ {
+ const char * const netflix_args[] = {spNetflix, "-s", spSecret, "-Q", sQueryParam, 0};
+ return runApplication( netflix_args, run_id );
+ }
+ else return kDIALStatusRunning;
+}
+
+static DIALStatus netflix_status(DIALServer *ds, const char *appname,
+ DIAL_run_t run_id, int* pCanStop, void *callback_data) {
+ // Netflix application can stop
+ *pCanStop = 1;
+
+ waitpid((pid_t)run_id, NULL, WNOHANG); // reap child
+ return isAppRunning( spAppNetflix, NULL ) ? kDIALStatusRunning : kDIALStatusStopped;
+}
+
+static void netflix_stop(DIALServer *ds, const char *appname, DIAL_run_t run_id,
+ void *callback_data) {
+ int pid;
+ pid = isAppRunning( spAppNetflix, NULL );
+ if( pid )
+ {
+ printf("Killing pid %d\n", pid);
+ kill((pid_t)pid, SIGKILL);
+ waitpid((pid_t)pid, NULL, 0); // reap child
+ }
+}
+
+void run_ssdp(int port, const char *pFriendlyName, const char * pModelName, const char *pUuid);
+
+static void printUsage()
+{
+ int i, numberOfOptions = sizeof(gDialOptions) / sizeof(dial_options_t);
+ printf("usage: dialserver <options>\n");
+ printf("options:\n");
+ for( i = 0; i < numberOfOptions; i++ )
+ {
+ printf(" %s|%s [value]: %s\n",
+ gDialOptions[i].pOption,
+ gDialOptions[i].pLongOption,
+ gDialOptions[i].pOptionDescription );
+ }
+}
+
+static void setValue( char * pSource, char dest[] )
+{
+ // Destination is always one of our static buffers with size BUFSIZE
+ memset( dest, 0, BUFSIZE );
+ memcpy( dest, pSource, strlen(pSource) );
+}
+
+static void setDataDir(char *pData)
+{
+ setValue( spNfDataDir, spDataDir );
+ strcat(spDataDir, pData);
+}
+
+void runDial(void)
+{
+ DIALServer *ds;
+ ds = DIAL_start();
+ struct DIALAppCallbacks cb_nf = {netflix_start, netflix_stop, netflix_status};
+ struct DIALAppCallbacks cb_yt = {youtube_start, youtube_stop, youtube_status};
+
+ DIAL_register_app(ds, "Netflix", &cb_nf, NULL);
+ DIAL_register_app(ds, "YouTube", &cb_yt, NULL);
+ gDialPort = DIAL_get_port(ds);
+ printf("launcher listening on gDialPort %d\n", gDialPort);
+ run_ssdp(gDialPort, spFriendlyName, spModelName, spUuid);
+
+ DIAL_stop(ds);
+}
+
+static void processOption( int index, char * pOption )
+{
+ switch(index)
+ {
+ case 0: // Data path
+ memset( spDataDir, 0, sizeof(spDataDir) );
+ setDataDir( pOption );
+ break;
+ case 1: // Netflix path
+ setValue( pOption, spNetflix );
+ break;
+ case 2: // Friendly name
+ setValue( pOption, spFriendlyName );
+ break;
+ case 3: // Model Name
+ setValue( pOption, spModelName );
+ break;
+ case 4: // UUID
+ setValue( pOption, spUuid );
+ break;
+ default:
+ // Should not get here
+ fprintf( stderr, "Option %d not valid\n", index);
+ exit(1);
+ }
+}
+
+int main(int argc, char* argv[])
+{
+ int i;
+ i = isAppRunning(spAppNetflix, NULL );
+ printf("Netflix is %s\n", i ? "Running":"Not Running");
+ i = isAppRunning( spAppYouTube, spAppYouTubeMatch );
+ printf("YouTube is %s\n", i ? "Running":"Not Running");
+
+ // set all defaults
+ setValue(spDefaultFriendlyName, spFriendlyName );
+ setValue(spDefaultModelName, spModelName );
+ setValue(spDefaultUuid, spUuid );
+ setValue(spDefaultNetflix, spNetflix );
+ setDataDir(spDefaultData);
+
+ // Process command line options
+ // Loop through pairs of command line options.
+ for( i = 1; i < argc; i+=2 )
+ {
+ int numberOfOptions = sizeof(gDialOptions) / sizeof(dial_options_t);
+ while( --numberOfOptions >= 0 )
+ {
+ int shortLen, longLen;
+ shortLen = strlen(gDialOptions[numberOfOptions].pOption);
+ longLen = strlen(gDialOptions[numberOfOptions].pLongOption);
+ if( ( ( strncmp( argv[i], gDialOptions[numberOfOptions].pOption, shortLen ) == 0 ) ||
+ ( strncmp( argv[i], gDialOptions[numberOfOptions].pLongOption, longLen ) == 0 ) ) &&
+ ( (i+1) < argc ) )
+ {
+ processOption( numberOfOptions, argv[i+1] );
+ break;
+ }
+ }
+ // if we don't find an option in our list, bail out.
+ if( numberOfOptions < 0 )
+ {
+ printUsage();
+ exit(1);
+ }
+ }
+ runDial();
+
+ return 0;
+}
+
diff --git a/src/server/makefile b/src/server/makefile
new file mode 100644
index 0000000..c269204
--- /dev/null
+++ b/src/server/makefile
@@ -0,0 +1,18 @@
+CC=$(TARGET)gcc
+
+.PHONY: clean
+.DEFAULT_GOAL=dialserver
+
+OBJS := main.o dial_server.o mongoose.o quick_ssdp.o
+HEADERS := $(wildcard *.h)
+
+%.c: $(HEADERS)
+
+%.o: %.c $(HEADERS)
+ $(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o
+
+dialserver: $(OBJS)
+ $(CC) -Wall -Werror -g $(OBJS) -ldl -lpthread -o dialserver
+
+clean:
+ rm -f *.o dialserver
diff --git a/src/server/mongoose.c b/src/server/mongoose.c
new file mode 100644
index 0000000..14b4980
--- /dev/null
+++ b/src/server/mongoose.c
@@ -0,0 +1,952 @@
+// Copyright (c) 2004-2010 Sergey Lyubka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#ifndef BUFSIZ
+#define BUFSIZ 4096
+#endif
+
+#define MAX_REQUEST_SIZE 4096
+#define NUM_THREADS 4
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <pthread.h>
+
+
+#define ERRNO errno
+#define INVALID_SOCKET (-1)
+
+typedef int SOCKET;
+
+#include "mongoose.h"
+
+#define MONGOOSE_VERSION "3.0"
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
+
+#if defined(DEBUG)
+#define DEBUG_TRACE(x) do { \
+ flockfile(stdout); \
+ printf("*** %lu.%p.%s.%d: ", \
+ (unsigned long) time(NULL), (void *) pthread_self(), \
+ __func__, __LINE__); \
+ printf x; \
+ putchar('\n'); \
+ fflush(stdout); \
+ funlockfile(stdout); \
+} while (0)
+#else
+#define DEBUG_TRACE(x)
+#endif // DEBUG
+
+typedef void * (*mg_thread_func_t)(void *);
+
+
+// Describes a socket which was accept()-ed by the master thread and queued for
+// future handling by the worker thread.
+struct socket {
+ SOCKET sock; // Listening socket
+ struct sockaddr_in local_addr; // Local socket address
+ struct sockaddr_in remote_addr; // Remote socket address
+};
+
+struct mg_context {
+ volatile int stop_flag; // Should we stop event loop
+ mg_callback_t user_callback; // User-defined callback function
+ void *user_data; // User-defined data
+
+ SOCKET local_socket;
+ struct sockaddr_in local_address;
+
+ volatile int num_threads; // Number of threads
+ pthread_mutex_t mutex; // Protects (max|num)_threads
+ pthread_cond_t cond; // Condvar for tracking workers terminations
+
+ struct socket queue[20]; // Accepted sockets
+ volatile int sq_head; // Head of the socket queue
+ volatile int sq_tail; // Tail of the socket queue
+ pthread_cond_t sq_full; // Singaled when socket is produced
+ pthread_cond_t sq_empty; // Signaled when socket is consumed
+};
+
+struct mg_connection {
+ struct mg_request_info request_info;
+ struct mg_context *ctx;
+ struct socket client; // Connected client
+ time_t birth_time; // Time connection was accepted
+ int64_t num_bytes_sent; // Total bytes sent to client
+ int64_t content_len; // Content-Length header value
+ int64_t consumed_content; // How many bytes of content is already read
+ char *buf; // Buffer for received data
+ int buf_size; // Buffer size
+ int request_len; // Size of the request + headers in a buffer
+ int data_len; // Total size of data in a buffer
+};
+
+static void *call_user(struct mg_connection *conn, enum mg_event event) {
+ conn->request_info.user_data = conn->ctx->user_data;
+ return conn->ctx->user_callback == NULL ? NULL :
+ conn->ctx->user_callback(event, conn, &conn->request_info);
+}
+
+// Print error message to the opened error log stream.
+static void cry(struct mg_connection *conn, const char *fmt, ...) {
+ char buf[BUFSIZ];
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void) vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ // Do not lock when getting the callback value, here and below.
+ // I suppose this is fine, since function cannot disappear in the
+ // same way string option can.
+ conn->request_info.log_message = buf;
+ if (call_user(conn, MG_EVENT_LOG) == NULL) {
+ DEBUG_TRACE((buf));
+ }
+ conn->request_info.log_message = NULL;
+}
+
+// Return fake connection structure. Used for logging, if connection
+// is not applicable at the moment of logging.
+static struct mg_connection *fc(struct mg_context *ctx) {
+ static struct mg_connection fake_connection;
+ fake_connection.ctx = ctx;
+ return &fake_connection;
+}
+
+const char *mg_version(void) {
+ return MONGOOSE_VERSION;
+}
+
+static int lowercase(const char *s) {
+ return tolower(* (const unsigned char *) s);
+}
+
+static int mg_strcasecmp(const char *s1, const char *s2) {
+ int diff;
+
+ do {
+ diff = lowercase(s1++) - lowercase(s2++);
+ } while (diff == 0 && s1[-1] != '\0');
+
+ return diff;
+}
+
+// Like snprintf(), but never returns negative value, or the value
+// that is larger than a supplied buffer.
+// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability
+// in his audit report.
+static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen,
+ const char *fmt, va_list ap) {
+ int n;
+
+ if (buflen == 0)
+ return 0;
+
+ n = vsnprintf(buf, buflen, fmt, ap);
+
+ if (n < 0) {
+ cry(conn, "vsnprintf error");
+ n = 0;
+ } else if (n >= (int) buflen) {
+ cry(conn, "truncating vsnprintf buffer: [%.*s]",
+ n > 200 ? 200 : n, buf);
+ n = (int) buflen - 1;
+ }
+ buf[n] = '\0';
+
+ return n;
+}
+
+static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen,
+ const char *fmt, ...) {
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = mg_vsnprintf(conn, buf, buflen, fmt, ap);
+ va_end(ap);
+
+ return n;
+}
+
+// Skip the characters until one of the delimiters characters found.
+// 0-terminate resulting word. Skip the delimiter and following whitespaces if any.
+// Advance pointer to buffer to the next word. Return found 0-terminated word.
+// Delimiters can be quoted with quotechar.
+static char *skip_quoted(char **buf, const char *delimiters, const char *whitespace, char quotechar) {
+ char *p, *begin_word, *end_word, *end_whitespace;
+
+ begin_word = *buf;
+ end_word = begin_word + strcspn(begin_word, delimiters);
+
+ /* Check for quotechar */
+ if (end_word > begin_word) {
+ p = end_word - 1;
+ while (*p == quotechar) {
+ /* If there is anything beyond end_word, copy it */
+ if (*end_word == '\0') {
+ *p = '\0';
+ break;
+ } else {
+ size_t end_off = strcspn(end_word + 1, delimiters);
+ memmove (p, end_word, end_off + 1);
+ p += end_off; /* p must correspond to end_word - 1 */
+ end_word += end_off + 1;
+ }
+ }
+ for (p++; p < end_word; p++) {
+ *p = '\0';
+ }
+ }
+
+ if (*end_word == '\0') {
+ *buf = end_word;
+ } else {
+ end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace);
+
+ for (p = end_word; p < end_whitespace; p++) {
+ *p = '\0';
+ }
+
+ *buf = end_whitespace;
+ }
+
+ return begin_word;
+}
+
+// Simplified version of skip_quoted without quote char
+// and whitespace == delimiters
+static char *skip(char **buf, const char *delimiters) {
+ return skip_quoted(buf, delimiters, delimiters, 0);
+}
+
+
+// Return HTTP header value, or NULL if not found.
+static const char *get_header(const struct mg_request_info *ri,
+ const char *name) {
+ int i;
+
+ for (i = 0; i < ri->num_headers; i++)
+ if (!mg_strcasecmp(name, ri->http_headers[i].name))
+ return ri->http_headers[i].value;
+
+ return NULL;
+}
+
+const char *mg_get_header(const struct mg_connection *conn, const char *name) {
+ return get_header(&conn->request_info, name);
+}
+
+static const char *suggest_connection_header(const struct mg_connection *conn) {
+ return "close";
+}
+
+void mg_send_http_error(struct mg_connection *conn, int status,
+ const char *reason, const char *fmt, ...) {
+ char buf[BUFSIZ];
+ va_list ap;
+ int len;
+
+ conn->request_info.status_code = status;
+
+ buf[0] = '\0';
+ len = 0;
+
+ /* Errors 1xx, 204 and 304 MUST NOT send a body */
+ if (status > 199 && status != 204 && status != 304) {
+ len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason);
+ cry(conn, "%s", buf);
+ buf[len++] = '\n';
+
+ va_start(ap, fmt);
+ len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap);
+ va_end(ap);
+ }
+ DEBUG_TRACE(("[%s]", buf));
+
+ mg_printf(conn, "HTTP/1.1 %d %s\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: %d\r\n"
+ "Connection: %s\r\n\r\n", status, reason, len,
+ suggest_connection_header(conn));
+ conn->num_bytes_sent += mg_printf(conn, "%s", buf);
+}
+
+static int start_thread(struct mg_context *ctx, mg_thread_func_t func,
+ void *param) {
+ pthread_t thread_id;
+ pthread_attr_t attr;
+ int retval;
+
+ (void) pthread_attr_init(&attr);
+ (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ // TODO(lsm): figure out why mongoose dies on Linux if next line is enabled
+ // (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5);
+
+ if ((retval = pthread_create(&thread_id, &attr, func, param)) != 0) {
+ cry(fc(ctx), "%s: %s", __func__, strerror(retval));
+ }
+
+ return retval;
+}
+
+static int set_non_blocking_mode(SOCKET sock) {
+ int flags;
+
+ flags = fcntl(sock, F_GETFL, 0);
+ (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);
+
+ return 0;
+}
+
+// Write data to the IO channel - opened file descriptor, socket or SSL
+// descriptor. Return number of bytes written.
+static int64_t push(FILE *fp, SOCKET sock, const char *buf, int64_t len) {
+ int64_t sent;
+ int n, k;
+
+ sent = 0;
+ while (sent < len) {
+
+ /* How many bytes we send in this iteration */
+ k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
+
+ if (fp != NULL) {
+ n = fwrite(buf + sent, 1, (size_t)k, fp);
+ if (ferror(fp))
+ n = -1;
+ } else {
+ n = send(sock, buf + sent, (size_t)k, 0);
+ }
+
+ if (n < 0)
+ break;
+
+ sent += n;
+ }
+
+ return sent;
+}
+
+// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
+// Return number of bytes read.
+static int pull(SOCKET sock, char *buf, int len) {
+ int nread;
+
+ nread = recv(sock, buf, (size_t) len, 0);
+
+ return nread;
+}
+
+int mg_read(struct mg_connection *conn, void *buf, size_t len) {
+ int n, buffered_len, nread;
+ const char *buffered;
+
+ assert((conn->content_len == -1 && conn->consumed_content == 0) ||
+ conn->consumed_content <= conn->content_len);
+ DEBUG_TRACE(("%p %zu %lld %lld", buf, len,
+ conn->content_len, conn->consumed_content));
+ nread = 0;
+ if (conn->consumed_content < conn->content_len) {
+
+ // Adjust number of bytes to read.
+ int64_t to_read = conn->content_len - conn->consumed_content;
+ if (to_read < (int64_t) len) {
+ len = (int) to_read;
+ }
+
+ // How many bytes of data we have buffered in the request buffer?
+ buffered = conn->buf + conn->request_len + conn->consumed_content;
+ buffered_len = conn->data_len - conn->request_len;
+ assert(buffered_len >= 0);
+
+ // Return buffered data back if we haven't done that yet.
+ if (conn->consumed_content < (int64_t) buffered_len) {
+ buffered_len -= (int) conn->consumed_content;
+ if (len < (size_t) buffered_len) {
+ buffered_len = len;
+ }
+ memcpy(buf, buffered, (size_t)buffered_len);
+ len -= buffered_len;
+ buf = (char *) buf + buffered_len;
+ conn->consumed_content += buffered_len;
+ nread = buffered_len;
+ }
+
+ // We have returned all buffered data. Read new data from the remote socket.
+ while (len > 0) {
+ n = pull(conn->client.sock, (char *) buf, (int) len);
+ if (n <= 0) {
+ break;
+ }
+ buf = (char *) buf + n;
+ conn->consumed_content += n;
+ nread += n;
+ len -= n;
+ }
+ }
+ return nread;
+}
+
+int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
+ return (int) push(NULL, conn->client.sock, (const char *) buf, (int64_t) len);
+}
+
+int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
+ char buf[BUFSIZ];
+ int len;
+ va_list ap;
+
+ va_start(ap, fmt);
+ len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ return mg_write(conn, buf, (size_t)len);
+}
+
+// URL-decode input buffer into destination buffer.
+// 0-terminate the destination buffer. Return the length of decoded data.
+// form-url-encoded data differs from URI encoding in a way that it
+// uses '+' as character for space, see RFC 1866 section 8.2.1
+// http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt
+static size_t url_decode(const char *src, size_t src_len, char *dst,
+ size_t dst_len, int is_form_url_encoded) {
+ size_t i, j;
+ int a, b;
+#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
+
+ for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
+ if (src[i] == '%' &&
+ isxdigit(* (const unsigned char *) (src + i + 1)) &&
+ isxdigit(* (const unsigned char *) (src + i + 2))) {
+ a = tolower(* (const unsigned char *) (src + i + 1));
+ b = tolower(* (const unsigned char *) (src + i + 2));
+ dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
+ i += 2;
+ } else if (is_form_url_encoded && src[i] == '+') {
+ dst[j] = ' ';
+ } else {
+ dst[j] = src[i];
+ }
+ }
+
+ dst[j] = '\0'; /* Null-terminate the destination */
+
+ return j;
+}
+
+// Check whether full request is buffered. Return:
+// -1 if request is malformed
+// 0 if request is not yet fully buffered
+// >0 actual request length, including last \r\n\r\n
+static int get_request_len(const char *buf, int buflen) {
+ const char *s, *e;
+ int len = 0;
+
+ DEBUG_TRACE(("buf: %p, len: %d", buf, buflen));
+ for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)
+ // Control characters are not allowed but >=128 is.
+ if (!isprint(* (const unsigned char *) s) && *s != '\r' &&
+ *s != '\n' && * (const unsigned char *) s < 128) {
+ len = -1;
+ } else if (s[0] == '\n' && s[1] == '\n') {
+ len = (int) (s - buf) + 2;
+ } else if (s[0] == '\n' && &s[1] < e &&
+ s[1] == '\r' && s[2] == '\n') {
+ len = (int) (s - buf) + 3;
+ }
+
+ return len;
+}
+
+// Protect against directory disclosure attack by removing '..',
+// excessive '/' and '\' characters
+static void remove_double_dots_and_double_slashes(char *s) {
+ char *p = s;
+
+ while (*s != '\0') {
+ *p++ = *s++;
+ if (s[-1] == '/' || s[-1] == '\\') {
+ // Skip all following slashes and backslashes
+ while (*s == '/' || *s == '\\') {
+ s++;
+ }
+
+ // Skip all double-dots
+ while (*s == '.' && s[1] == '.') {
+ s += 2;
+ }
+ }
+ }
+ *p = '\0';
+}
+
+// Parse HTTP headers from the given buffer, advance buffer to the point
+// where parsing stopped.
+static void parse_http_headers(char **buf, struct mg_request_info *ri) {
+ int i;
+
+ for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {
+ ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0);
+ ri->http_headers[i].value = skip(buf, "\r\n");
+ if (ri->http_headers[i].name[0] == '\0')
+ break;
+ ri->num_headers = i + 1;
+ }
+}
+
+static int is_valid_http_method(const char *method) {
+ return !strcmp(method, "GET") || !strcmp(method, "POST") ||
+ !strcmp(method, "DELETE");
+}
+
+// Parse HTTP request, fill in mg_request_info structure.
+static int parse_http_request(char *buf, struct mg_request_info *ri) {
+ int status = 0;
+
+ // RFC says that all initial whitespaces should be ingored
+ while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
+ buf++;
+ }
+
+ ri->request_method = skip(&buf, " ");
+ ri->uri = skip(&buf, " ");
+ ri->http_version = skip(&buf, "\r\n");
+
+ if (is_valid_http_method(ri->request_method) &&
+ strncmp(ri->http_version, "HTTP/", 5) == 0) {
+ ri->http_version += 5; /* Skip "HTTP/" */
+ parse_http_headers(&buf, ri);
+ status = 1;
+ }
+
+ return status;
+}
+
+// Keep reading the input from socket sock
+// into buffer buf, until \r\n\r\n appears in the buffer (which marks the end
+// of HTTP request). Buffer buf may already have some data. The length of the
+// data is stored in nread. Upon every read operation, increase nread by the
+// number of bytes read.
+static int read_request(SOCKET sock, char *buf, int bufsiz,
+ int *nread) {
+ int n, request_len;
+
+ request_len = 0;
+ while (*nread < bufsiz && request_len == 0) {
+ n = pull(sock, buf + *nread, bufsiz - *nread);
+ if (n <= 0) {
+ break;
+ } else {
+ *nread += n;
+ request_len = get_request_len(buf, *nread);
+ }
+ }
+
+ return request_len;
+}
+
+// This is the heart of the Mongoose's logic.
+// This function is called when the request is read, parsed and validated,
+// and Mongoose must decide what action to take: serve a file, or
+// a directory, or call embedded function, etcetera.
+static void handle_request(struct mg_connection *conn) {
+ struct mg_request_info *ri = &conn->request_info;
+ int uri_len;
+
+ if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
+ * conn->request_info.query_string++ = '\0';
+ }
+ uri_len = strlen(ri->uri);
+ (void) url_decode(ri->uri, (size_t)uri_len, ri->uri, (size_t)(uri_len + 1), 0);
+ remove_double_dots_and_double_slashes(ri->uri);
+
+ DEBUG_TRACE(("%s", ri->uri));
+ if (call_user(conn, MG_NEW_REQUEST) == NULL) {
+ mg_send_http_error(conn, 404, "Not Found", "%s", "File not found");
+ }
+}
+
+static void close_all_listening_sockets(struct mg_context *ctx) {
+ (void) close(ctx->local_socket);
+}
+
+// only reports address of the first listening socket
+int mg_get_listen_addr(struct mg_context *ctx,
+ struct sockaddr *addr, socklen_t *addrlen) {
+ size_t len = sizeof(ctx->local_address);
+ if (*addrlen < len) return 0;
+ *addrlen = len;
+ memcpy(addr, &ctx->local_address, len);
+ return 1;
+}
+
+static int set_ports_option(struct mg_context *ctx, int port) {
+ int reuseaddr = 1, success = 1;
+ socklen_t sock_len = sizeof(ctx->local_address);
+ // MacOS needs that. If we do not zero it, subsequent bind() will fail.
+ memset(&ctx->local_address, 0, sock_len);
+ ctx->local_address.sin_family = AF_INET;
+ ctx->local_address.sin_port = htons((uint16_t) port);
+ ctx->local_address.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 500 * 1000;
+
+ if ((ctx->local_socket = socket(PF_INET, SOCK_STREAM, 6)) == INVALID_SOCKET ||
+ setsockopt(ctx->local_socket, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) != 0 ||
+ setsockopt(ctx->local_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0 ||
+ bind(ctx->local_socket, (const struct sockaddr *) &ctx->local_address, sock_len) != 0 ||
+ // TODO(steineldar): Replace 20 (max socket backlog len in connections).
+ listen(ctx->local_socket, 20) != 0) {
+ close(ctx->local_socket);
+ cry(fc(ctx), "%s: cannot bind to port %d: %s", __func__,
+ port, strerror(ERRNO));
+ success = 0;
+ } else if (getsockname(ctx->local_socket, (struct sockaddr *) &ctx->local_address, &sock_len)) {
+ close(ctx->local_socket);
+ cry(fc(ctx), "%s: %s", __func__, strerror(ERRNO));
+ success = 0;
+ }
+
+ if (!success) {
+ ctx->local_socket = INVALID_SOCKET;
+ close_all_listening_sockets(ctx);
+ }
+
+ return success;
+}
+
+
+static void reset_per_request_attributes(struct mg_connection *conn) {
+ struct mg_request_info *ri = &conn->request_info;
+
+ ri->request_method = ri->uri = ri->http_version = NULL;
+ ri->num_headers = 0;
+ ri->status_code = -1;
+
+ conn->num_bytes_sent = conn->consumed_content = 0;
+ conn->content_len = -1;
+ conn->request_len = conn->data_len = 0;
+}
+
+static void close_socket_gracefully(SOCKET sock) {
+ char buf[BUFSIZ];
+ int n;
+
+ // Send FIN to the client
+ (void) shutdown(sock, SHUT_WR);
+ set_non_blocking_mode(sock);
+
+ // Read and discard pending data. If we do not do that and close the
+ // socket, the data in the send buffer may be discarded. This
+ // behaviour is seen on Windows, when client keeps sending data
+ // when server decide to close the connection; then when client
+ // does recv() it gets no data back.
+ do {
+ n = pull(sock, buf, sizeof(buf));
+ } while (n > 0);
+
+ // Now we know that our FIN is ACK-ed, safe to close
+ (void) close(sock);
+}
+
+static void close_connection(struct mg_connection *conn) {
+ if (conn->client.sock != INVALID_SOCKET) {
+ close_socket_gracefully(conn->client.sock);
+ }
+}
+
+static void discard_current_request_from_buffer(struct mg_connection *conn) {
+ int buffered_len, body_len;
+
+ buffered_len = conn->data_len - conn->request_len;
+ assert(buffered_len >= 0);
+
+ if (conn->content_len == -1) {
+ body_len = 0;
+ } else if (conn->content_len < (int64_t) buffered_len) {
+ body_len = (int) conn->content_len;
+ } else {
+ body_len = buffered_len;
+ }
+
+ conn->data_len -= conn->request_len + body_len;
+ memmove(conn->buf, conn->buf + conn->request_len + body_len,
+ (size_t) conn->data_len);
+}
+
+static void process_new_connection(struct mg_connection *conn) {
+ struct mg_request_info *ri = &conn->request_info;
+ const char *cl;
+
+ reset_per_request_attributes(conn);
+
+ // If next request is not pipelined, read it in
+ if ((conn->request_len = get_request_len(conn->buf, conn->data_len)) == 0) {
+ conn->request_len = read_request(conn->client.sock,
+ conn->buf, conn->buf_size, &conn->data_len);
+ }
+ assert(conn->data_len >= conn->request_len);
+ if (conn->request_len == 0 && conn->data_len == conn->buf_size) {
+ mg_send_http_error(conn, 413, "Request Too Large", "");
+ return;
+ } if (conn->request_len <= 0) {
+ return; // Remote end closed the connection
+ }
+
+ // Nul-terminate the request cause parse_http_request() uses sscanf
+ conn->buf[conn->request_len - 1] = '\0';
+ if (!parse_http_request(conn->buf, ri)) {
+ // Do not put garbage in the access log, just send it back to the client
+ mg_send_http_error(conn, 400, "Bad Request",
+ "Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf);
+ } else if (strcmp(ri->http_version, "1.0") && strcmp(ri->http_version, "1.1")) {
+ // Request seems valid, but HTTP version is strange
+ mg_send_http_error(conn, 505, "HTTP version not supported", "");
+ } else {
+ // Request is valid, handle it
+ cl = get_header(ri, "Content-Length");
+ conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10);
+ conn->birth_time = time(NULL);
+ handle_request(conn);
+ discard_current_request_from_buffer(conn);
+ }
+}
+
+// Worker threads take accepted socket from the queue
+static int consume_socket(struct mg_context *ctx, struct socket *sp) {
+ (void) pthread_mutex_lock(&ctx->mutex);
+ DEBUG_TRACE(("going idle"));
+
+ // If the queue is empty, wait. We're idle at this point.
+ while (ctx->sq_head == ctx->sq_tail && ctx->stop_flag == 0) {
+ pthread_cond_wait(&ctx->sq_full, &ctx->mutex);
+ }
+ // Master thread could wake us up without putting a socket.
+ // If this happens, it is time to exit.
+ if (ctx->stop_flag) {
+ (void) pthread_mutex_unlock(&ctx->mutex);
+ return 0;
+ }
+ assert(ctx->sq_head > ctx->sq_tail);
+
+ // Copy socket from the queue and increment tail
+ *sp = ctx->queue[ctx->sq_tail % ARRAY_SIZE(ctx->queue)];
+ ctx->sq_tail++;
+ DEBUG_TRACE(("grabbed socket %d, going busy", sp->sock));
+
+ // Wrap pointers if needed
+ while (ctx->sq_tail > (int) ARRAY_SIZE(ctx->queue)) {
+ ctx->sq_tail -= ARRAY_SIZE(ctx->queue);
+ ctx->sq_head -= ARRAY_SIZE(ctx->queue);
+ }
+
+ (void) pthread_cond_signal(&ctx->sq_empty);
+ (void) pthread_mutex_unlock(&ctx->mutex);
+
+ return 1;
+}
+
+
+static void worker_thread(struct mg_context *ctx) {
+ struct mg_connection *conn;
+ // This is the specified request size limit for DIAL requests. Note that
+ // this will effectively make the request limit one byte *smaller* than the
+ // required in the DIAL specification.
+ int buf_size = MAX_REQUEST_SIZE;
+
+ conn = (struct mg_connection *) calloc(1, sizeof(*conn) + buf_size);
+ conn->buf_size = buf_size;
+ conn->buf = (char *) (conn + 1);
+ assert(conn != NULL);
+
+ while (ctx->stop_flag == 0 && consume_socket(ctx, &conn->client)) {
+ conn->birth_time = time(NULL);
+ conn->ctx = ctx;
+
+ // Fill in IP, port info early so even if SSL setup below fails,
+ // error handler would have the corresponding info.
+ // Thanks to Johannes Winkelmann for the patch.
+ memcpy(&conn->request_info.remote_addr,
+ &conn->client.remote_addr, sizeof(conn->client.remote_addr));
+
+ // Fill in local IP info
+ socklen_t addr_len = sizeof(conn->request_info.local_addr);
+ getsockname(conn->client.sock,
+ (struct sockaddr *) &conn->request_info.local_addr, &addr_len);
+
+ process_new_connection(conn);
+
+ close_connection(conn);
+ }
+ free(conn);
+
+ // Signal master that we're done with connection and exiting
+ (void) pthread_mutex_lock(&ctx->mutex);
+ ctx->num_threads--;
+ (void) pthread_cond_signal(&ctx->cond);
+ assert(ctx->num_threads >= 0);
+ (void) pthread_mutex_unlock(&ctx->mutex);
+
+ DEBUG_TRACE(("exiting"));
+}
+
+// Master thread adds accepted socket to a queue
+static void produce_socket(struct mg_context *ctx, const struct socket *sp) {
+ (void) pthread_mutex_lock(&ctx->mutex);
+
+ // If the queue is full, wait
+ while (ctx->sq_head - ctx->sq_tail >= (int) ARRAY_SIZE(ctx->queue)) {
+ (void) pthread_cond_wait(&ctx->sq_empty, &ctx->mutex);
+ }
+ assert(ctx->sq_head - ctx->sq_tail < (int) ARRAY_SIZE(ctx->queue));
+
+ // Copy socket to the queue and increment head
+ ctx->queue[ctx->sq_head % ARRAY_SIZE(ctx->queue)] = *sp;
+ ctx->sq_head++;
+ DEBUG_TRACE(("queued socket %d", sp->sock));
+
+ (void) pthread_cond_signal(&ctx->sq_full);
+ (void) pthread_mutex_unlock(&ctx->mutex);
+}
+
+
+static void master_thread(struct mg_context *ctx) {
+ struct socket accepted;
+
+ socklen_t sock_len = sizeof(accepted.local_addr);
+ memcpy(&accepted.local_addr, &ctx->local_address, sock_len);
+
+ while (ctx->stop_flag == 0) {
+ memset(&accepted.remote_addr, 0, sock_len);
+
+ accepted.sock = accept(ctx->local_socket,
+ (struct sockaddr *) &accepted.remote_addr, &sock_len);
+
+ if (accepted.sock != INVALID_SOCKET) {
+ // Put accepted socket structure into the queue.
+ DEBUG_TRACE(("accepted socket %d", accepted.sock));
+ produce_socket(ctx, &accepted);
+ }
+ }
+ DEBUG_TRACE(("stopping workers"));
+
+ // Stop signal received: somebody called mg_stop. Quit.
+ close_all_listening_sockets(ctx);
+
+ // Wakeup workers that are waiting for connections to handle.
+ pthread_cond_broadcast(&ctx->sq_full);
+
+ // Wait until all threads finish
+ (void) pthread_mutex_lock(&ctx->mutex);
+ while (ctx->num_threads > 0) {
+ (void) pthread_cond_wait(&ctx->cond, &ctx->mutex);
+ }
+ (void) pthread_mutex_unlock(&ctx->mutex);
+
+ // All threads exited, no sync is needed. Destroy mutex and condvars
+ (void) pthread_mutex_destroy(&ctx->mutex);
+ (void) pthread_cond_destroy(&ctx->cond);
+ (void) pthread_cond_destroy(&ctx->sq_empty);
+ (void) pthread_cond_destroy(&ctx->sq_full);
+
+ // Signal mg_stop() that we're done
+ ctx->stop_flag = 2;
+
+ DEBUG_TRACE(("exiting"));
+}
+
+static void free_context(struct mg_context *ctx) {
+ // Deallocate context itself
+ free(ctx);
+}
+
+void mg_stop(struct mg_context *ctx) {
+ ctx->stop_flag = 1;
+
+ // Wait until mg_fini() stops
+ while (ctx->stop_flag != 2) {
+ // TODO(steineldar): Avoid busy waiting.
+ (void) sleep(0);
+ }
+ free_context(ctx);
+}
+
+struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, int port) {
+ struct mg_context *ctx;
+
+ // Allocate context and initialize reasonable general case defaults.
+ // TODO(lsm): do proper error handling here.
+ ctx = (struct mg_context *) calloc(1, sizeof(*ctx));
+ ctx->user_callback = user_callback;
+ ctx->user_data = user_data;
+
+ if (!set_ports_option(ctx, port)) {
+ free_context(ctx);
+ return NULL;
+ }
+ // Ignore SIGPIPE signal, so if browser cancels the request, it
+ // won't kill the whole process.
+ (void) signal(SIGPIPE, SIG_IGN);
+ (void) pthread_mutex_init(&ctx->mutex, NULL);
+ (void) pthread_cond_init(&ctx->cond, NULL);
+ (void) pthread_cond_init(&ctx->sq_empty, NULL);
+ (void) pthread_cond_init(&ctx->sq_full, NULL);
+
+ // Start master (listening) thread
+ start_thread(ctx, (mg_thread_func_t) master_thread, ctx);
+
+ // Start worker threads
+ for (int i = 0; i < NUM_THREADS; i++) {
+ if (start_thread(ctx, (mg_thread_func_t) worker_thread, ctx) != 0) {
+ cry(fc(ctx), "Cannot start worker thread: %d", ERRNO);
+ } else {
+ ctx->num_threads++;
+ }
+ }
+
+ return ctx;
+}
diff --git a/src/server/mongoose.h b/src/server/mongoose.h
new file mode 100644
index 0000000..7842aac
--- /dev/null
+++ b/src/server/mongoose.h
@@ -0,0 +1,160 @@
+// Copyright (c) 2004-2010 Sergey Lyubka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+
+// NOTE: This is a SEVERELY stripped down version of mongoose, which only
+// supports GET, POST and DELETE HTTP commands, no CGI, no file or directory
+// access, no ACLs or authentication, and no proxying and no SSL. HTTP Header
+// limit is 16 instead of 64, as it's not supposed to be called from standard
+// browsers. And most options are removed.
+
+#ifndef MONGOOSE_HEADER_INCLUDED
+#define MONGOOSE_HEADER_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+struct mg_context; // Handle for the HTTP service itself
+struct mg_connection; // Handle for the individual connection
+
+
+// This structure contains information about the HTTP request.
+struct mg_request_info {
+ void *user_data; // User-defined pointer passed to mg_start()
+ char *request_method; // "GET", "POST", etc
+ char *uri; // URL-decoded URI
+ char *http_version; // E.g. "1.0", "1.1"
+ char *query_string; // \0 - terminated
+ char *request_body; // \0 - terminated
+ char *log_message; // Mongoose error log message
+ struct sockaddr_in local_addr; // Our server's address for this connection
+ struct sockaddr_in remote_addr; // The remote address for this connection
+ int status_code; // HTTP reply status code
+ int num_headers; // Number of headers
+ struct mg_header {
+ char *name; // HTTP header name
+ char *value; // HTTP header value
+ } http_headers[16]; // Maximum 16 headers
+};
+
+// Various events on which user-defined function is called by Mongoose.
+enum mg_event {
+ MG_NEW_REQUEST, // New HTTP request has arrived from the client
+ MG_HTTP_ERROR, // HTTP error must be returned to the client
+ MG_EVENT_LOG, // Mongoose logs an event, request_info.log_message
+};
+
+// Prototype for the user-defined function. Mongoose calls this function
+// on every event mentioned above.
+//
+// Parameters:
+// event: which event has been triggered.
+// conn: opaque connection handler. Could be used to read, write data to the
+// client, etc. See functions below that accept "mg_connection *".
+// request_info: Information about HTTP request.
+//
+// Return:
+// If handler returns non-NULL, that means that handler has processed the
+// request by sending appropriate HTTP reply to the client. Mongoose treats
+// the request as served.
+// If callback returns NULL, that means that callback has not processed
+// the request. Handler must not send any data to the client in this case.
+// Mongoose proceeds with request handling as if nothing happened.
+typedef void * (*mg_callback_t)(enum mg_event event,
+ struct mg_connection *conn,
+ const struct mg_request_info *request_info);
+
+
+// Start web server.
+//
+// Parameters:
+// callback: user defined event handling function or NULL.
+//
+// Example:
+// struct mg_context *ctx = mg_start(&my_func, NULL);
+//
+// Please refer to http://code.google.com/p/mongoose/wiki/MongooseManual
+// for the list of valid option and their possible values.
+//
+// Return:
+// web server context, or NULL on error.
+struct mg_context *mg_start(mg_callback_t callback, void *user_data, int port);
+
+
+// Stop the web server.
+//
+// Must be called last, when an application wants to stop the web server and
+// release all associated resources. This function blocks until all Mongoose
+// threads are stopped. Context pointer becomes invalid.
+void mg_stop(struct mg_context *);
+
+
+// Send data to the client.
+int mg_write(struct mg_connection *, const void *buf, size_t len);
+
+
+// Send data to the browser using printf() semantics.
+//
+// Works exactly like mg_write(), but allows to do message formatting.
+// Note that mg_printf() uses internal buffer of size IO_BUF_SIZE
+// (8 Kb by default) as temporary message storage for formatting. Do not
+// print data that is bigger than that, otherwise it will be truncated.
+int mg_printf(struct mg_connection *, const char *fmt, ...);
+
+
+// Read data from the remote end, return number of bytes read.
+int mg_read(struct mg_connection *, void *buf, size_t len);
+
+
+// Get the value of particular HTTP header.
+//
+// This is a helper function. It traverses request_info->http_headers array,
+// and if the header is present in the array, returns its value. If it is
+// not present, NULL is returned.
+const char *mg_get_header(const struct mg_connection *, const char *name);
+
+
+// Return Mongoose version.
+const char *mg_version(void);
+
+
+// MD5 hash given strings.
+// Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of
+// asciiz strings. When function returns, buf will contain human-readable
+// MD5 hash. Example:
+// char buf[33];
+// mg_md5(buf, "aa", "bb", NULL);
+void mg_md5(char *buf, ...);
+
+void mg_send_http_error(struct mg_connection *conn, int status,
+ const char *reason, const char *fmt, ...);
+int mg_get_listen_addr(struct mg_context *ctx, struct sockaddr *addr,
+ socklen_t *addrlen);
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // MONGOOSE_HEADER_INCLUDED
diff --git a/src/server/quick_ssdp.c b/src/server/quick_ssdp.c
new file mode 100644
index 0000000..fa1d84a
--- /dev/null
+++ b/src/server/quick_ssdp.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "mongoose.h"
+
+// TODO: Partners should define this port
+#define SSDP_PORT (56790)
+
+
+static char gBuf[4096];
+
+// TODO: Partners should get the friendlyName from the system and insert here.
+// TODO: Partners should ensure the friendlyName is the same string returned
+// in the ISsystem::getFriendlyName DPI function.
+// TODO: Partners should get the UUID from the system and insert here.
+// TODO: Partners should ensure the modelName is identifiably similar to the
+// model name returned in the ISystem::getDeviceModel DPI function
+static const char ddxml[] = ""
+"<?xml version=\"1.0\"?>"
+"<root"
+" xmlns=\"urn:schemas-upnp-org:device-1-0\""
+" xmlns:r=\"urn:restful-tv-org:schemas:upnp-dd\">"
+" <specVersion>"
+" <major>1</major>"
+" <minor>0</minor>"
+" </specVersion>"
+" <device>"
+" <deviceType>urn:schemas-upnp-org:device:tvdevice:1</deviceType>"
+" <friendlyName>%s</friendlyName>"
+" <manufacturer> </manufacturer>"
+" <modelName>%s</modelName>"
+" <UDN>uuid:%s</UDN>"
+" </device>"
+"</root>";
+
+// TODO: Partners should get the UUID from the system and insert here.
+static const char ssdp_reply[] = "HTTP/1.1 200 OK\r\n"
+ "LOCATION: http://%s:%d/dd.xml\r\n"
+ "CACHE-CONTROL: max-age=1800\r\n"
+ "EXT:\r\n"
+ "BOOTID.UPNP.ORG: 1\r\n"
+ "SERVER: Linux/2.6 UPnP/1.0 quick_ssdp/1.0\r\n"
+ "ST: urn:dial-multiscreen-org:service:dial:1\r\n"
+ "USN: uuid:%s::"
+ "urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
+
+static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
+static int dial_port = 0;
+static int my_port = 0;
+static char friendly_name[256];
+static char uuid[256];
+static char model_name[256];
+static struct mg_context *ctx;
+
+static void *request_handler(enum mg_event event,
+ struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ if (event == MG_NEW_REQUEST) {
+ if (!strcmp(request_info->uri, "/dd.xml") &&
+ !strcmp(request_info->request_method, "GET")) {
+ mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+ "Content-Type: application/xml\r\n"
+ "Application-URL: http://%s:%d/apps/\r\n"
+ "\r\n", ip_addr, dial_port);
+ mg_printf(conn, ddxml, friendly_name, model_name, uuid);
+ } else {
+ mg_send_http_error(conn, 404, "Not Found", "Not Found");
+ }
+ return "done";
+ }
+ return NULL;
+}
+
+static void get_local_address() {
+ struct ifconf ifc;
+ char buf[4096];
+ int s, i;
+ if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) {
+ perror("socket");
+ exit(1);
+ }
+ ifc.ifc_len = sizeof(buf);
+ ifc.ifc_buf = buf;
+ if (0 > ioctl(s, SIOCGIFCONF, &ifc)) {
+ perror("SIOCGIFCONF");
+ exit(1);
+ }
+ if (ifc.ifc_len == sizeof(buf)) {
+ fprintf(stderr, "SIOCGIFCONF output too long");
+ exit(1);
+ }
+ close(s);
+ for (i = 0; i < ifc.ifc_len/sizeof(ifc.ifc_req[0]); i++) {
+ strcpy(ip_addr,
+ inet_ntoa(((struct sockaddr_in *)(&ifc.ifc_req[i].ifr_addr))->sin_addr));
+ // exit if we found a non-loopback address
+ if (strcmp("127.0.0.1", ip_addr)) {
+ break;
+ }
+ }
+}
+
+static void handle_mcast() {
+ int s, one = 1, bytes;
+ socklen_t addrlen;
+ struct sockaddr_in saddr;
+ struct ip_mreq mreq;
+ char send_buf[sizeof(ssdp_reply) + INET_ADDRSTRLEN + 256 + 256] = {0,};
+ int send_size;
+
+ send_size = snprintf(send_buf, sizeof(send_buf), ssdp_reply, ip_addr, my_port, uuid);
+
+ if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) {
+ perror("socket");
+ exit(1);
+ }
+ if (-1 == setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
+ perror("reuseaddr");
+ exit(1);
+ }
+ saddr.sin_family = AF_INET;
+ saddr.sin_addr.s_addr = inet_addr("239.255.255.250");
+ saddr.sin_port = htons(1900);
+ if (-1 == bind(s, (struct sockaddr *)&saddr, sizeof(saddr))) {
+ perror("bind");
+ exit(1);
+ }
+ mreq.imr_multiaddr.s_addr = inet_addr("239.255.255.250");
+ mreq.imr_interface.s_addr = inet_addr(ip_addr);
+ if (-1 == setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq))) {
+ perror("add_membership");
+ exit(1);
+ }
+ //printf("Starting Multicast handling on 239.255.255.250\n");
+ while (1) {
+ addrlen = sizeof(saddr);
+ if (-1 == (bytes = recvfrom(s, gBuf, sizeof(gBuf) - 1, 0,
+ (struct sockaddr *)&saddr, &addrlen))) {
+ perror("recvfrom");
+ continue;
+ }
+ gBuf[bytes] = 0;
+
+ // sophisticated SSDP parsing algorithm
+ if (!strstr(gBuf, "ST: urn:dial-multiscreen-org:service:dial:1"))
+ {
+#if 0 // use for debugging
+ printf("Dropping: \n");
+ {
+ int i;
+ for (i = 0; i < bytes; i++)
+ {
+ putchar(gBuf[i]);
+ }
+ }
+ printf("\n##### End of DROP #######\n");
+#endif
+ continue;
+ }
+ printf("Sending SSDP reply to %s:%d\n",
+ inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
+ if (-1 == sendto(s, send_buf, send_size, 0, (struct sockaddr *)&saddr, addrlen)) {
+ perror("sendto");
+ continue;
+ }
+ }
+}
+
+void run_ssdp(int port, const char *pFriendlyName, const char * pModelName, const char *pUuid) {
+ struct sockaddr sa;
+ socklen_t len = sizeof(sa);
+
+ if(pFriendlyName) {
+ strncpy(friendly_name, pFriendlyName, sizeof(friendly_name));
+ friendly_name[255] = '\0';
+ } else {
+ strcpy(friendly_name, "DIAL server sample");
+ }
+ if(pModelName) {
+ strncpy(model_name, pModelName, sizeof(model_name));
+ uuid[255] = '\0';
+ } else {
+ strcpy(model_name, "deadbeef-dead-beef-dead-beefdeadbeef");
+ }
+ if(pUuid) {
+ strncpy(uuid, pUuid, sizeof(uuid));
+ uuid[255] = '\0';
+ } else {
+ strcpy(uuid, "deadbeef-dead-beef-dead-beefdeadbeef");
+ }
+
+ dial_port = port;
+ get_local_address();
+ ctx = mg_start(&request_handler, NULL, SSDP_PORT);
+
+ if (mg_get_listen_addr(ctx, &sa, &len)) {
+ my_port = ntohs(((struct sockaddr_in *)&sa)->sin_port);
+ }
+
+ printf("SSDP listening on %s:%d\n", ip_addr, my_port);
+ handle_mcast();
+}