commit 3454a59e99f9106018876a4039f97301a302ea60
Author: jcli
Date: Tue Oct 20 16:26:28 2015 -0700
dial spec 1.7
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cb029e8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.o
+client/dialclient
+server/dialserver
+server/tests/run_tests
diff --git a/Changelog b/Changelog
new file mode 100644
index 0000000..0906af8
--- /dev/null
+++ b/Changelog
@@ -0,0 +1,55 @@
+---------------------------------------------------------------------
+Version 1.0.5 (06edbce)
+---------------------------------------------------------------------
+Server changes:
+ - Update to DIAL specificiation 1.7.2
+ - CORE compliance security patch
+ - Added CORS compliance test scripts
+
+---------------------------------------------------------------------
+Version 1.0.4 (1673785)
+---------------------------------------------------------------------
+Server changes:
+ - Update to DIAL specificiation 1.7
+ - Catch SIGTERM so that we don't self terminate.
+
+---------------------------------------------------------------------
+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..0ab73c0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 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..befd198
--- /dev/null
+++ b/README
@@ -0,0 +1,78 @@
+--------------------------------------------------------------------------------
+Building the DIAL server
+--------------------------------------------------------------------------------
+1) Define the TARGET environment variable to point to the CC compiler prefix
+ for your target platform.
+
+
+2) Run make, passing in your TARGET value.
+
+ For example:
+ TARGET=/usr/local/i686-DIAL-EXAMPLE/bin/i686-DIAL-EXAMPLE 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).
+
+
+--------------------------------------------------------------------------------
+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 dependencies are
+defined properly.
+
+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 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/build/Version.h b/build/Version.h
new file mode 100644
index 0000000..39b021d
--- /dev/null
+++ b/build/Version.h
@@ -0,0 +1,38 @@
+/*
+ * (c) 1997-2015 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 5
+
+#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/build/dial_build.sh b/build/dial_build.sh
new file mode 100755
index 0000000..4667f05
--- /dev/null
+++ b/build/dial_build.sh
@@ -0,0 +1,66 @@
+#!/bin/bash -e
+#
+# (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.
+
+BUILD_PREFIX="DIAL"
+BUILD_MAJOR=
+BUILD_MINOR=
+BUILD_PATCH=
+BUILD_SUFFIX=
+BUILD_NUMBER=
+COPY_SOURCE_VERSION_H=
+
+if [ -n "${P4_CHANGELIST}" ]; then
+ #echo "Using information from Jenkins environment to determine RELEASE number: $P4_CHANGELIST"
+ BUILD_NUMBER="${P4_CHANGELIST}"
+fi
+
+if [ -z "$COPY_SOURCE_VERSION_H" ]; then
+ VERSION_H="Version.h"
+ COPY_SOURCE_VERSION_H=`find ${COPY_SOURCE_DIR} -name ${VERSION_H}`
+ if [ -z "$COPY_SOURCE_VERSION_H" ]; then
+ echo "${VERSION_H} cannot be found!"
+ exit 1
+ elif echo "$COPY_VERSION" | grep --quiet ' '; then
+ echo "Too many ${VERSION_H} found!"
+ exit 1
+ fi
+fi
+
+BUILD_MAJOR=`grep -m1 -i "^# *define *${BUILD_PREFIX}_VERSION_MAJOR" "$COPY_SOURCE_VERSION_H" | sed "s,/[/*].*$,," | awk '{print $(NF)}'`
+if [ -z "$BUILD_MAJOR" ]; then
+ echo "Failed to determine version major of current source." >&2
+ exit 1
+fi
+
+if [ -z "$BUILD_MINOR" ]; then
+ BUILD_MINOR=`grep -m1 -i "^# *define *${BUILD_PREFIX}_VERSION_MINOR" "$COPY_SOURCE_VERSION_H" | sed "s,/[/*].*$,," | awk '{print $(NF)}'`
+ if [ -z "$BUILD_MINOR" ]; then
+ echo "Failed to determine version minor of current source." >&2
+ exit 1
+ fi
+fi
+
+if [ -z "$BUILD_PATCH" ]; then
+ BUILD_PATCH=`grep -m1 -i "^# *define *${BUILD_PREFIX}_VERSION_PATCH" "$COPY_SOURCE_VERSION_H" | sed "s,/[/*].*$,," | awk '{print $(NF)}'`
+ if [ -z "$BUILD_PATCH" ]; then
+ echo "Failed to determine version minor of current source." >&2
+ exit 1
+ fi
+fi
+
+BUILD_STRING="${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_PATCH}-${BUILD_NUMBER}"
+echo "${BUILD_STRING}"
+
+tar czvhf ${WORKSPACE}/DIAL-$BUILD_STRING.tar.gz *
+
+# Package the binaries
+cp ${WORKSPACE}/src/dial/client/dialclient ${WORKSPACE}/build
+cp ${WORKSPACE}/src/dial/server/dialserver ${WORKSPACE}/build
+cd ${WORKSPACE}/build
+tar czvhf ${WORKSPACE}/DIAL-$BUILD_STRING-binaries.tar.gz *
diff --git a/client/DialClientInput.cpp b/client/DialClientInput.cpp
new file mode 100644
index 0000000..0ab4779
--- /dev/null
+++ b/client/DialClientInput.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2014 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
+#include
+
+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
+ 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::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& parameters )
+{
+ if (_actions.empty()) return false;
+
+ pair 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 &list )
+{
+ list = _applist;
+}
+
+
+void DialClientInput::getErrorApplicationList(vector &list)
+{
+ list = _errorapplist;
+}
+
diff --git a/client/DialClientInput.h b/client/DialClientInput.h
new file mode 100644
index 0000000..e53d260
--- /dev/null
+++ b/client/DialClientInput.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2014 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
+#include
+
+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& parameters );
+
+ /**
+ * Get a list of valid applications defined in the input file.
+ *
+ * @param[out] list List of valid applications
+ */
+ void getApplicationList(vector &list);
+
+ /**
+ * Get a list of applications used for error tests (should not exist).
+ *
+ * @param[out] list List of error applications
+ */
+ void getErrorApplicationList(vector &list);
+
+ static string getDefaultFilename() { return "./dialclient_input.txt"; }
+
+private:
+ vector _applist;
+ vector _errorapplist;
+ vector< pair > _actions;
+};
+
+#endif // DIALCLIENTINPUT_H
diff --git a/client/DialConformance.cpp b/client/DialConformance.cpp
new file mode 100644
index 0000000..1aa8080
--- /dev/null
+++ b/client/DialConformance.cpp
@@ -0,0 +1,587 @@
+/*
+ * Copyright (c) 2014 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
+#include
+#include
+
+#include "DialConformance.h"
+#include
+#include
+#include
+#include
+#include
+
+// 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 << "\n\n";
+ _file << "FriendlyName: " << friendlyname;
+ _file << " \nIP: " << ipaddress;
+ _file << " \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 << " \nTime of Run: " << buffer << " ";
+ }
+ _file << "\n";
+ _file << "\n";
+ _file << " RESULT "; //header 1
+ _file << " Test Executed "; //header 2
+ _file << " Errors "; //header 2
+ _file << " \n";
+ }
+
+ void complete()
+ {
+ vector::iterator it;
+ for( it = _tests.begin(); it < _tests.end(); it++ )
+ {
+ write("");
+ // write the name and the result
+ stringstream result;
+ bool testResult = (*it)->getResult();
+ result << " ";
+ result << (testResult ? "SUCCESS" : "FAIL") << " \n" << (*it)->getName() << " ";
+ write( result.str() );
+
+ // write errors if they exist
+ write("");
+ vector errors = (*it)->getErrors();
+ if( !errors.empty() )
+ {
+ vector::iterator it2;
+ write("");
+ for( it2 = errors.begin(); it2 < errors.end(); it2++ )
+ write( (*it2) );
+ write(" ");
+ }
+ else
+ write("NO ERROR");
+ write(" \n ");
+
+ // delete the test
+ delete (*it);
+ }
+ _file << "
\n\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 getErrors() { return _errors; }
+
+ private:
+ string _testname;
+ bool _testPassed;
+ vector _errors;
+ };
+
+ string _filename;
+ ofstream _file;
+ vector _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& params,
+ string& responseHeaders, string& responseBody )
+{
+ bool testPassed = true;
+ vector::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& params, string& payload )
+{
+ vector::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& 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& 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& 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::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& 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 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 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::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::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::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& 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::iterator it;
+ for( it = appList.begin(); it < appList.end(); it++ ) _input.addApplication(*it);
+
+ // build the application list
+ vector 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::iterator appit;
+ for ( appit = _apps.begin() ; appit < _apps.end(); appit++ ) delete (*appit);
+
+ return 0;
+}
+
diff --git a/client/DialConformance.h b/client/DialConformance.h
new file mode 100644
index 0000000..b6c4298
--- /dev/null
+++ b/client/DialConformance.h
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2014 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
+#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& 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 _apps;
+
+ // Internal helpers
+ void run_internal( DialServer* pServer );
+ bool execute_command( string& command, vector& params );
+
+ // Get the Application pointer from an application string
+ Application* getApplication( string& command );
+
+ // extract the payload from a list of parameters
+ void getPayload(vector& params, string& payload );
+
+ // Command execution functions
+ bool execute_launch( Application* pApp, vector& params );
+ bool execute_status( Application* pApp, vector& params );
+ bool execute_stop( Application* pApp, vector& params );
+
+ // Helper function to extract a parameter.
+ void extractParamValue( string& param, string& value );
+
+ // Validation functions
+ bool validateParams( vector& 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/client/DialDiscovery.cpp b/client/DialDiscovery.cpp
new file mode 100644
index 0000000..efb37af
--- /dev/null
+++ b/client/DialDiscovery.cpp
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2014 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "DialDiscovery.h"
+#include
+
+#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(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(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);
+
+ (void)(res);
+ 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 removal;
+ for( it = mServerMap.begin(); it != mServerMap.end(); it++ )
+ {
+ if( !(*it).second->isFound() ) {
+ removal.push_back((*it).second->getLocation());
+ }
+ }
+
+ // now remove and delete
+ vector::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);
+ send_size = snprintf(send_buf, sizeof(send_buf), ssdp_msearch);
+ 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& 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/client/DialDiscovery.h b/client/DialDiscovery.h
new file mode 100644
index 0000000..c6ba01f
--- /dev/null
+++ b/client/DialDiscovery.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2014 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
+
+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& 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 ServerMap;
+ ServerMap mServerMap;
+
+ static DialDiscovery* sDiscovery;
+};
+
+#endif // DIALDISCOVERY_H
diff --git a/client/DialServer.cpp b/client/DialServer.cpp
new file mode 100644
index 0000000..951727d
--- /dev/null
+++ b/client/DialServer.cpp
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2014 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
+
+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(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(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 = "";
+ size_t pos;
+ if( ( pos = m_ddxml.find( friendlyName ) ) != m_ddxml.npos )
+ {
+ string friendlyNameEnd = " ";
+ 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 = "";
+ size_t pos;
+ if( ( pos = m_ddxml.find( udn ) ) != m_ddxml.npos )
+ {
+ string udnEnd = " ";
+ 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 );
+
+ ATRACE("Body: %s\n", responseBody.c_str());
+ unsigned found = responseBody.find("href=");
+ if( found != string::npos )
+ {
+ // The start of href is after href= and the quote
+ unsigned href_start = found + 5 + 1;
+
+ // get the body from the start of href to the end, then find
+ // the last quote delimiter.
+ string tmp = responseBody.substr( href_start );
+ unsigned href_end = tmp.find("\"");
+ m_stopEndPoint = responseBody.substr( href_start, href_end );
+ }
+ return 0;
+}
+
+int DialServer::stopApplication(
+ string &application,
+ string &responseHeaders )
+{
+ ATRACE("%s: Quit %s\n", __FUNCTION__, application.c_str());
+ string emptyPayload, responseBody; // dropping this
+ string appUrl = m_appsUrl;
+
+ // just call status to update the run endpoint
+ getStatus( application, responseHeaders, responseBody );
+
+ sendCommand(
+ (appUrl.append(application)).append("/"+m_stopEndPoint),
+ COMMAND_KILL, emptyPayload, responseHeaders, responseBody );
+ return 0;
+}
+
+int DialServer::getHttpResponseHeader(
+ string &responseHeaders,
+ string &header,
+ string &value )
+{
+ return 0;
+}
diff --git a/client/DialServer.h b/client/DialServer.h
new file mode 100644
index 0000000..59b5aa7
--- /dev/null
+++ b/client/DialServer.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2014 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
+#include
+
+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://:/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://:/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.
+ *
+ * @param[in] application Name of the application to stop
+ * @param[out] responseHeaders Returns the HTTP response headers
+ *
+ * @return 0 if successful, !0 otherwise
+ */
+ int stopApplication(
+ string &application,
+ 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
+ string m_stopEndPoint;
+};
+
+#endif // DIALSERVER_H
diff --git a/client/build.sh b/client/build.sh
new file mode 100755
index 0000000..b7e2c4f
--- /dev/null
+++ b/client/build.sh
@@ -0,0 +1,5 @@
+TARGET=/usr/local/i686-netflix-linux-gnu-4.2/bin/i686-netflix-linux-gnu- \
+LDFLAGS="-L/usr/local/i686-netflix-linux-gnu-4.2/netflix/lib \
+-Wl,-rpath,/usr/local/i686-netflix-linux-gnu-4.2/netflix/lib" \
+INCLUDES=-I/usr/local/i686-netflix-linux-gnu-4.2/netflix/include \
+make
diff --git a/client/dialclient_input.txt b/client/dialclient_input.txt
new file mode 100644
index 0000000..01e783f
--- /dev/null
+++ b/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/client/main.cpp b/client/main.cpp
new file mode 100644
index 0000000..c882207
--- /dev/null
+++ b/client/main.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2014 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
+#include "DialDiscovery.h"
+#include "DialConformance.h"
+#include
+#include
+#include
+#include
+#include
+
+using namespace std;
+
+static DialDiscovery* gpDiscovery;
+static bool gUseMenu = true;
+
+// TODO: Make it possible to pass applications from the command line
+static vector gAppList;
+static string gOutputFile;
+static string gInputFile;
+
+// IP address of the DIAL server
+static string gIpAddress;
+
+static void printServerList( vector list )
+{
+ int i;
+ vector::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 list )
+{
+ DialServer* pServer;
+ // show a list to the user
+ if( list.size() > 1 )
+ {
+ char buf[80] = {0,};
+ vector::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 list;
+ gpDiscovery->getServerList(list);
+
+ if( list.size() )
+ {
+ DialServer *pServer = NULL;
+ if( !gIpAddress.empty() )
+ {
+ pServer = NULL;
+ vector::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 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 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, 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, 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 \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/client/makefile b/client/makefile
new file mode 100644
index 0000000..68cf150
--- /dev/null
+++ b/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/makefile b/makefile
new file mode 100644
index 0000000..e934e46
--- /dev/null
+++ b/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/server/build.sh b/server/build.sh
new file mode 100755
index 0000000..6490e9b
--- /dev/null
+++ b/server/build.sh
@@ -0,0 +1,2 @@
+export TARGET=/usr/local/i686-netflix-linux-gnu-4.2/bin/i686-netflix-linux-gnu-
+make
diff --git a/server/dial_data.c b/server/dial_data.c
new file mode 100644
index 0000000..b31b5f9
--- /dev/null
+++ b/server/dial_data.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2014 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.
+ */
+/*
+ * Functions related to storing/retrieving and manipulating DIAL data.
+ */
+#include "dial_data.h"
+#include
+#include
+#include
+#include
+
+
+char dial_data_dir[256] = DIAL_DATA_DIR;
+
+void set_dial_data_dir(const char *data_dir) {
+ strncpy(dial_data_dir, data_dir, 255);
+}
+
+/**
+ * Returns the path where data is stored for the given app.
+ */
+static char* getAppPath(char app_name[]) {
+ size_t name_size = strlen(app_name) + sizeof(dial_data_dir) + 1;
+ char* filename = (char*) malloc(name_size);
+ filename[0] = 0;
+ strncat(filename, dial_data_dir, name_size);
+ strncat(filename, app_name, name_size - sizeof(dial_data_dir));
+ return filename;
+}
+
+void store_dial_data(char app_name[], DIALData *data) {
+ char* filename = getAppPath(app_name);
+ FILE *f = fopen(filename, "w");
+ if (f == NULL) {
+ printf("Cannot open DIAL data output file: %s\n", filename);
+ exit(1);
+ }
+ for (DIALData *first = data; first != NULL; first = first->next) {
+ fprintf(f, "%s %s\n", first->key, first->value);
+ }
+ fclose(f);
+}
+
+DIALData *retrieve_dial_data(char *app_name) {
+ char* filename = getAppPath(app_name);
+ FILE *f = fopen(filename, "r");
+ if (f == NULL) {
+ return NULL; // no dial data found, that's fine
+ }
+ DIALData *result = NULL;
+ char key[256];
+ char value[256];
+ while (fscanf(f, "%255s %255s\n", key, value) != EOF) {
+ DIALData *newNode = (DIALData *) malloc(sizeof(DIALData));
+ newNode->key = (char *) malloc(strlen(key));
+ strcpy(newNode->key, key);
+ newNode->value = (char *) malloc(strlen(value));
+ strcpy(newNode->value, value);
+ newNode->next = result;
+ result = newNode;
+ }
+ fclose(f);
+ return result;
+}
+
diff --git a/server/dial_data.h b/server/dial_data.h
new file mode 100644
index 0000000..e7fd9fe
--- /dev/null
+++ b/server/dial_data.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2014 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.
+ */
+/*
+ * Defines functions for persisting and retrieving DIAL data.
+ */
+
+#ifndef SRC_SERVER_DIAL_DATA_H_
+#define SRC_SERVER_DIAL_DATA_H_
+
+/*
+ * Slash-terminated directory of where to persist the DIAL data.
+ */
+#define DIAL_DATA_DIR "/tmp/"
+
+/*
+ * The maximum DIAL data payload accepted per the 'DIAL extension for smooth
+ * pairing' specification'.
+ */
+#define DIAL_DATA_MAX_PAYLOAD (4096) /* 4 KB */
+
+/*
+ * Url path where DIAL data should be posted according to the 'DIAL extension
+ * for smooth pairing' specification.
+ */
+#define DIAL_DATA_URI "/dial_data"
+
+struct DIALData_ {
+ struct DIALData_ *next;
+ char *key;
+ char *value;
+};
+
+typedef struct DIALData_ DIALData;
+
+void store_dial_data(char *app_name, DIALData *data);
+
+DIALData *retrieve_dial_data(char *app_name);
+
+void set_dial_data_dir(const char *data_dir);
+
+#endif /* SRC_SERVER_DIAL_DATA_H_ */
diff --git a/server/dial_options.h b/server/dial_options.h
new file mode 100644
index 0000000..52bd052
--- /dev/null
+++ b/server/dial_options.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2014 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/server/dial_server.c b/server/dial_server.c
new file mode 100644
index 0000000..7c28a69
--- /dev/null
+++ b/server/dial_server.c
@@ -0,0 +1,627 @@
+/*
+ * Copyright (c) 2014 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_data.h"
+#include "dial_server.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "mongoose.h"
+#include "url_lib.h"
+
+// TODO: Partners should define this port
+#define DIAL_PORT (56789)
+#define DIAL_DATA_SIZE (8*1024)
+
+static const char *gLocalhost = "127.0.0.1";
+
+struct DIALApp_ {
+ struct DIALApp_ *next;
+ struct DIALAppCallbacks callbacks;
+ struct DIALData_ *dial_data;
+ void *callback_data;
+ DIAL_run_t run_id;
+ DIALStatus state;
+ char *name;
+ char payload[DIAL_MAX_PAYLOAD];
+ int useAdditionalData;
+ char corsAllowedOrigin[256];
+
+};
+
+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;
+}
+
+static void url_decode_xml_encode(char *dst, char *src, size_t src_size) {
+ char *url_decoded_key = (char *) malloc(src_size + 1);
+ urldecode(url_decoded_key, src, src_size);
+ xmlencode(dst, url_decoded_key, 2 * src_size);
+ free(url_decoded_key);
+}
+
+/*
+ * 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, "Payload: 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,
+ const char *origin_header) {
+ char additional_data_param[256] = {0, };
+ char body[DIAL_MAX_PAYLOAD + sizeof(additional_data_param) + 2] = {0, };
+ DIALApp *app;
+ DIALServer *ds = request_info->user_data;
+ int body_size;
+
+ ds_lock(ds);
+ app = *find_app(ds, app_name);
+ if (!app) {
+ mg_send_http_error(conn, 404, "Not Found", "Not Found");
+ } else {
+ body_size = mg_read(conn, body, sizeof(body));
+ // NUL-terminate it just in case
+ if (body_size > DIAL_MAX_PAYLOAD) {
+ mg_send_http_error(conn, 413, "413 Request Entity Too Large",
+ "413 Request Entity Too Large");
+ } else if (isBadPayload(body, body_size)) {
+ mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request");
+ } else {
+ 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));
+ in_port_t dial_port = DIAL_get_port(ds);
+
+ if (app->useAdditionalData) {
+ // Construct additionalDataUrl=http://host:port/apps/app_name/dial_data
+ sprintf(additional_data_param,
+ "additionalDataUrl=http%%3A%%2F%%2Flocalhost%%3A%d%%2Fapps%%2F%s%%2Fdial_data%%3F",
+ dial_port, app_name);
+ }
+ fprintf(stderr, "Starting the app with params %s\n", body);
+ app->state = app->callbacks.start_cb(ds, app_name, body,
+ additional_data_param,
+ &app->run_id,
+ app->callback_data);
+ if (app->state == kDIALStatusRunning) {
+ 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"
+ "Access-Control-Allow-Origin: %s\r\n"
+ "\r\n",
+ laddr, dial_port, app_name, origin_header);
+ // copy the payload into the application struct
+ memset(app->payload, 0, DIAL_MAX_PAYLOAD);
+ memcpy(app->payload, body, body_size);
+ } 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,
+ const char *origin_header) {
+ 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");
+ ds_unlock(ds);
+ return;
+ }
+
+ char dial_data[DIAL_DATA_SIZE] = {0,};
+ char *end = dial_data + DIAL_DATA_SIZE;
+ char *p = dial_data;
+
+ for (DIALData* first = app->dial_data; first != NULL; first = first->next) {
+ p = smartstrcat(p, " <", end - p);
+ size_t key_length = strlen(first->key);
+ char *encoded_key = (char *) malloc(2 * key_length + 1);
+ url_decode_xml_encode(encoded_key, first->key, key_length);
+
+ size_t value_length = strlen(first->value);
+ char *encoded_value = (char *) malloc(2 * value_length + 1);
+ url_decode_xml_encode(encoded_value, first->value, value_length);
+
+ p = smartstrcat(p, encoded_key, end - p);
+ p = smartstrcat(p, ">", end - p);
+ p = smartstrcat(p, encoded_value, end - p);
+ p = smartstrcat(p, "", end - p);
+ p = smartstrcat(p, encoded_key, end - p);
+ p = smartstrcat(p, ">", end - p);
+ free(encoded_key);
+ free(encoded_value);
+ }
+ 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"
+ "Access-Control-Allow-Origin: %s\r\n"
+ "\r\n"
+ "\r\n"
+ "\r\n"
+ " %s \r\n"
+ " \r\n"
+ " %s \r\n"
+ "%s"
+ " \n"
+ "%s"
+ "\n \n"
+ " \r\n",
+ origin_header,
+ DIAL_VERSION,
+ app->name,
+ canStop ? "true" : "false",
+ app->state ? "running" : "stopped",
+ app->state == kDIALStatusStopped ?
+ "" : " \r\n",
+ dial_data);
+ ds_unlock(ds);
+}
+
+static void handle_app_stop(struct mg_connection *conn,
+ const struct mg_request_info *request_info,
+ const char *app_name,
+ const char *origin_header) {
+ 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"
+ "Access-Control-Allow-Origin: %s\r\n"
+ "\r\n",
+ origin_header);
+ }
+ ds_unlock(ds);
+}
+
+static void handle_dial_data(struct mg_connection *conn,
+ const struct mg_request_info *request_info,
+ const char *app_name,
+ const char *origin_header,
+ int use_payload) {
+ char body[DIAL_DATA_MAX_PAYLOAD + 2] = {0, };
+
+ DIALApp *app;
+ 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");
+ ds_unlock(ds);
+ return;
+ }
+ int nread;
+ if (!use_payload) {
+ if (request_info->query_string) {
+ strncpy(body, request_info->query_string, DIAL_DATA_MAX_PAYLOAD);
+ nread = strlen(body);
+ } else {
+ nread = 0;
+ }
+ } else {
+ nread = mg_read(conn, body, DIAL_DATA_MAX_PAYLOAD);
+ body[nread] = '\0';
+ }
+ if (nread > DIAL_DATA_MAX_PAYLOAD) {
+ mg_send_http_error(conn, 413, "413 Request Entity Too Large",
+ "413 Request Entity Too Large");
+ ds_unlock(ds);
+ return;
+ }
+
+ if (isBadPayload(body, nread)) {
+ mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request");
+ ds_unlock(ds);
+ return;
+ }
+
+ app->dial_data = parse_params(body);
+ store_dial_data(app->name, app->dial_data);
+
+ mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+ "Access-Control-Allow-Origin: %s\r\n"
+ "\r\n",
+ origin_header);
+
+ ds_unlock(ds);
+}
+
+static int ends_with(const char *str, const char *suffix) {
+ if (!str || !suffix)
+ return 0;
+ size_t lenstr = strlen(str);
+ size_t lensuffix = strlen(suffix);
+ if (lensuffix > lenstr)
+ return 0;
+ return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
+}
+
+
+// str contains a white space separated list of strings (only supports SPACE characters for now)
+static int ends_with_in_list (const char *str, const char *list) {
+ if (!str || !list)
+ return 0;
+
+ const char * scanPointer=list;
+ const char * spacePointer;
+ unsigned int substringSize = 257;
+ char *substring = (char *)malloc(substringSize);
+ if (!substring){
+ return 0;
+ }
+ while ( (spacePointer =strchr(scanPointer, ' ')) != NULL) {
+ int copyLength = spacePointer - scanPointer;
+
+ // protect against buffer overflow
+ if (copyLength>=substringSize){
+ substringSize=copyLength+1;
+ free(substring);
+ substring=(char *)malloc(substringSize);
+ if (!substring){
+ return 0;
+ }
+ }
+
+ memcpy(substring, scanPointer, copyLength);
+ substring[copyLength] = '\0';
+ //printf("found %s \n", substring);
+ if (ends_with(str, substring)) {
+ free(substring);
+ return 1;
+ }
+ scanPointer = scanPointer + copyLength + 1; // assumption: only 1 character
+ }
+ free(substring);
+ return ends_with(str, scanPointer);
+}
+
+static int should_check_for_origin( char * origin ) {
+ const char * const CHECK_PROTOS[] = { "http:", "https:", "file:" };
+ for (int i = 0; i < 3; ++i) {
+ if (!strncmp(origin, CHECK_PROTOS[i], strlen(CHECK_PROTOS[i]) - 1)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int is_allowed_origin(DIALServer* ds, char * origin, const char * app_name) {
+ if (!origin || strlen(origin)==0 || !should_check_for_origin(origin)) {
+ return 1;
+ }
+
+ ds_lock(ds);
+ DIALApp *app;
+ int result = 0;
+ for (app = ds->apps; app != NULL; app = app->next) {
+ if (!strcmp(app->name, app_name)) {
+ if (!app->corsAllowedOrigin[0] ||
+ ends_with_in_list(origin, app->corsAllowedOrigin)) {
+ result = 1;
+ break;
+ }
+ }
+ }
+ ds_unlock(ds);
+
+ return result;
+}
+
+#define APPS_URI "/apps/"
+#define RUN_URI "/run"
+
+static void *options_response(DIALServer *ds, struct mg_connection *conn, char *host_header, char *origin_header, const char* app_name, const char* methods)
+{
+ if (host_header && is_allowed_origin(ds, origin_header, app_name)) {
+ mg_printf(
+ conn,
+ "HTTP/1.1 204 No Content\r\n"
+ "Access-Control-Allow-Methods: %s\r\n"
+ "Access-Control-Max-Age: 86400\r\n"
+ "Access-Control-Allow-Origin: %s\r\n"
+ "Content-Length: 0"
+ "\r\n",
+ methods,
+ origin_header);
+ return "done";
+ }
+ mg_send_http_error(conn, 403, "Forbidden", "Forbidden");
+ return "done";
+}
+
+static void *request_handler(enum mg_event event, struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ DIALServer *ds = request_info->user_data;
+
+ fprintf(stderr, "Received request %s\n", request_info->uri);
+ char *host_header = {0,};
+ char *origin_header = {0,};
+ for (int i = 0; i < request_info->num_headers; ++i) {
+ if (!strcmp(request_info->http_headers[i].name, "Host")) {
+ host_header = request_info->http_headers[i].value;
+ } else if (!strcmp(request_info->http_headers[i].name,
+ "Origin")) {
+ origin_header = request_info->http_headers[i].value;
+ }
+ }
+ fprintf(stderr, "Origin %s, Host: %s\n", origin_header, host_header);
+ 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)));
+
+ if (!strcmp(request_info->request_method, "OPTIONS")) {
+ return options_response(ds, conn, host_header, origin_header, app_name, "DELETE, OPTIONS");
+ }
+
+ // DELETE non-empty app name
+ if (app_name[0] != '\0'
+ && !strcmp(request_info->request_method, "DELETE")) {
+ if (host_header && is_allowed_origin(ds, origin_header, app_name)) {
+ handle_app_stop(conn, request_info, app_name, origin_header);
+ } else {
+ mg_send_http_error(conn, 403, "Forbidden", "Forbidden");
+ return "done";
+ }
+ } else {
+ mg_send_http_error(conn, 501, "Not Implemented",
+ "Not Implemented");
+ }
+ }
+ // URI starts with "/apps/" and is followed by an app name
+ else if (!strncmp(request_info->uri, APPS_URI, sizeof(APPS_URI) - 1)
+ && !strchr(request_info->uri + strlen(APPS_URI), '/')) {
+ const char *app_name;
+ app_name = request_info->uri + sizeof(APPS_URI) - 1;
+
+ if (!strcmp(request_info->request_method, "OPTIONS")) {
+ return options_response(ds, conn, host_header, origin_header, app_name, "GET, POST, OPTIONS");
+ }
+
+ // start app
+ if (!strcmp(request_info->request_method, "POST")) {
+ if (host_header && is_allowed_origin(ds, origin_header, app_name)) {
+ handle_app_start(conn, request_info, app_name, origin_header);
+ } else {
+ mg_send_http_error(conn, 403, "Forbidden", "Forbidden");
+ return "done";
+ }
+ // get app status
+ } else if (!strcmp(request_info->request_method, "GET")) {
+ handle_app_status(conn, request_info, app_name, origin_header);
+ } else {
+ mg_send_http_error(conn, 501, "Not Implemented",
+ "Not Implemented");
+ }
+ // URI is of the form */app_name/dial_data
+ } else if (strstr(request_info->uri, DIAL_DATA_URI)) {
+ char laddr[INET6_ADDRSTRLEN];
+ const struct sockaddr_in *addr =
+ (struct sockaddr_in *) &request_info->remote_addr;
+ inet_ntop(addr->sin_family, &addr->sin_addr, laddr, sizeof(laddr));
+ if ( !strncmp(laddr, gLocalhost, strlen(gLocalhost)) ) {
+ char *app_name = parse_app_name(request_info->uri);
+
+ if (!strcmp(request_info->request_method, "OPTIONS")) {
+ void *ret = options_response(ds, conn, host_header, origin_header, app_name, "POST, OPTIONS");
+ free(app_name);
+ return ret;
+ }
+
+ int use_payload =
+ strcmp(request_info->request_method, "POST") ? 0 : 1;
+ handle_dial_data(conn, request_info, app_name, origin_header,
+ use_payload);
+
+ free(app_name);
+ } else {
+ // If the request is not from local host, return an error
+ mg_send_http_error(conn, 403, "Forbidden", "Forbidden");
+ }
+ } 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_create() {
+ DIALServer *ds = calloc(1, sizeof(DIALServer));
+ pthread_mutex_init(&ds->mux, NULL);
+ return ds;
+}
+
+void DIAL_start(DIALServer *ds) {
+ ds->ctx = mg_start(&request_handler, ds, DIAL_PORT);
+}
+
+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,
+ int useAdditionalData,
+ const char* corsAllowedOrigin) {
+ 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;
+ app->dial_data = retrieve_dial_data(app->name);
+ app->useAdditionalData = useAdditionalData;
+ app->corsAllowedOrigin[0] = '\0';
+ if (corsAllowedOrigin &&
+ strlen(corsAllowedOrigin) < sizeof(app->corsAllowedOrigin)) {
+ strcpy(app->corsAllowedOrigin, corsAllowedOrigin);
+ }
+ *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/server/dial_server.h b/server/dial_server.h
new file mode 100644
index 0000000..41310f7
--- /dev/null
+++ b/server/dial_server.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2014 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
+
+/*
+ * Dial application states
+ */
+typedef enum {
+ kDIALStatusStopped,
+ kDIALStatusRunning
+} DIALStatus;
+
+/*
+ * DIAL version that is reported via in the status response.
+ */
+#define DIAL_VERSION ("\"1.7\"")
+
+/*
+ * The maximum DIAL payload accepted per the DIAL 1.6.1 specification.
+ */
+#define DIAL_MAX_PAYLOAD (4096)
+
+/*
+ * The maximum additionalDataUrl length
+ */
+
+#define DIAL_MAX_ADDITIONALURL (1024)
+
+/*
+ * 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 *payload, const char *additionalDataUrl,
+ 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;
+};
+
+/*
+ * Creates the DIAL server. Returns a handle to the DIAL server.
+ */
+DIALServer *DIAL_create();
+
+/*
+ * Starts the DIAL server.
+ *
+ * @param[in] ds DIAL server handle
+ */
+void DIAL_start(DIALServer *ds);
+
+/*
+ * Stop 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
+ * @param[in] if non-0, the app supports DIALadditionalDataURL.
+ * @param[in] if non-NULL, specifies the CORS allowed origin for this app.
+ *
+ * @return 1 if successful, 0 otherwise
+ */
+int DIAL_register_app(DIALServer *ds, const char *app_name,
+ struct DIALAppCallbacks *callbacks,
+ void *callback_data, int useAdditionalData,
+ const char* corsAllowedOrigin);
+
+/*
+ * 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/server/main.c b/server/main.c
new file mode 100644
index 0000000..311561c
--- /dev/null
+++ b/server/main.c
@@ -0,0 +1,470 @@
+/*
+ * Copyright (c) 2014 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "dial_server.h"
+#include "dial_options.h"
+#include
+
+#define BUFSIZE 256
+
+static char *spAppNetflix = "netflix"; // name of the netflix executable
+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+DIAL_MAX_ADDITIONALURL+40];
+
+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;
+}
+
+void signalHandler(int signal)
+{
+ switch(signal)
+ {
+ case SIGTERM:
+ // just ignore this, we don't want to die
+ break;
+ }
+}
+
+/* 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//comm. and /proc//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]);
+ }
+ if( execv(*args, (char * const *) args) == -1) {
+ printf("%s failed to launch\n", *args);
+ perror("Failed to Launch \n");
+ }
+ } 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 *payload, const char *additionalDataUrl,
+ DIAL_run_t *run_id, void *callback_data) {
+ printf("\n\n ** LAUNCH YouTube ** with payload %s\n\n", payload);
+
+ char url[512] = {0,}, data[512] = {0,};
+ if (strlen(payload) && strlen(additionalDataUrl)){
+ sprintf( url, "https://www.youtube.com/tv?%s&%s", payload, additionalDataUrl);
+ }else if (strlen(payload)){
+ sprintf( url, "https://www.youtube.com/tv?%s", payload);
+ }else{
+ sprintf( url, "https://www.youtube.com/tv");
+ }
+ 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 *payload, const char *additionalDataUrl,
+ DIAL_run_t *run_id, void *callback_data) {
+ int shouldRelaunchApp = 0;
+ int appPid = 0;
+
+ // only launch Netflix if it isn't running
+ appPid = isAppRunning( spAppNetflix, NULL );
+ shouldRelaunchApp = shouldRelaunch( ds, appname, payload );
+
+ // construct the payload to determine if it has changed from the previous launch
+ memset( sQueryParam, 0, sizeof(sQueryParam) );
+ strcat( sQueryParam, defaultLaunchParam );
+ if(strlen(payload))
+ {
+ char * pUrlEncodedParams;
+ pUrlEncodedParams = url_encode( payload );
+ if( pUrlEncodedParams )
+ {
+ strcat( sQueryParam, "&dial=");
+ strcat( sQueryParam, pUrlEncodedParams );
+ free( pUrlEncodedParams );
+ }
+ }
+
+ if(strlen(additionalDataUrl)){
+ strcat(sQueryParam, "&");
+ strcat(sQueryParam, additionalDataUrl);
+ }
+
+ 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, "-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)(long)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, SIGTERM);
+ 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 \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_create();
+ 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, 1, ".netflix.com");
+ DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, ".youtube.com");
+ DIAL_start(ds);
+
+ gDialPort = DIAL_get_port(ds);
+ printf("launcher listening on gDialPort %d\n", gDialPort);
+ run_ssdp(gDialPort, spFriendlyName, spModelName, spUuid);
+
+ DIAL_stop(ds);
+ free(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[])
+{
+ struct sigaction action;
+ action.sa_handler = signalHandler;
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+ sigaction(SIGTERM, &action, NULL);
+
+ srand(time(NULL));
+ 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/server/makefile b/server/makefile
new file mode 100644
index 0000000..8b4d663
--- /dev/null
+++ b/server/makefile
@@ -0,0 +1,25 @@
+CC=$(TARGET)gcc
+
+.PHONY: clean
+.DEFAULT_GOAL=all
+
+OBJS := main.o dial_server.o mongoose.o quick_ssdp.o url_lib.o dial_data.o
+HEADERS := $(wildcard *.h)
+
+%.c: $(HEADERS)
+
+%.o: %.c $(HEADERS)
+ $(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o
+
+all: dialserver test
+
+dialserver: $(OBJS)
+ $(CC) -Wall -Werror -g $(OBJS) -ldl -lpthread -o dialserver
+
+test:
+ make -C tests
+ ./tests/run_tests
+
+clean:
+ rm -f *.o dialserver
+ make -C tests clean
diff --git a/server/mongoose.c b/server/mongoose.c
new file mode 100644
index 0000000..cee543e
--- /dev/null
+++ b/server/mongoose.c
@@ -0,0 +1,956 @@
+// 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.
+#define _GNU_SOURCE 1
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef BUFSIZ
+#define BUFSIZ 4096
+#endif
+
+#define MAX_REQUEST_SIZE 4096
+#define NUM_THREADS 4
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#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(("[%s]", 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 %" PRId64 " %" PRId64, 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) {
+ fprintf(stderr, "Received HTTP method %s\n", method);
+ return !strcmp(method, "GET") || !strcmp(method, "POST") ||
+ !strcmp(method, "DELETE") || !strcmp(method, "OPTIONS");
+}
+
+// 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;
+
+ pthread_setname_np( pthread_self(), __func__);
+ 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;
+
+ pthread_setname_np( pthread_self(), __func__);
+ 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/server/mongoose.h b/server/mongoose.h
new file mode 100644
index 0000000..7842aac
--- /dev/null
+++ b/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
+#include
+#include
+
+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/server/quick_ssdp.c b/server/quick_ssdp.c
new file mode 100644
index 0000000..ddaa3e3
--- /dev/null
+++ b/server/quick_ssdp.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2014 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "mongoose.h"
+
+
+// TODO: Partners should define this port
+#define SSDP_PORT (56790)
+
+static int exit_ssdp = 0;
+void stop_ssdp()
+{
+ exit_ssdp = 1;
+};
+
+static char gBuf[4096];
+
+// TODO: Partners should get the friendlyName from the system and insert here.
+// TODO: Partners should get the UUID from the system and insert here.
+static const char ddxml[] = ""
+""
+""
+" "
+" 1 "
+" 0 "
+" "
+" "
+" urn:schemas-upnp-org:device:tvdevice:1 "
+" %s "
+" "
+" %s "
+" uuid:%s "
+" "
+" ";
+
+// 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 (!exit_ssdp) {
+ 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, "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;
+ }
+ }
+
+ close(s);
+ exit_ssdp = 0;
+}
+
+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();
+ mg_stop(ctx);
+}
diff --git a/server/tests/makefile b/server/tests/makefile
new file mode 100644
index 0000000..7cdfe01
--- /dev/null
+++ b/server/tests/makefile
@@ -0,0 +1,18 @@
+CC=$(TARGET)gcc
+
+.PHONY: clean
+.DEFAULT_GOAL=test
+
+OBJS := test_dial_data.o test_url_lib.o ../url_lib.o ../dial_data.o run_tests.o
+HEADERS := $(wildcard ../*.h)
+
+%.c: $(HEADERS)
+
+%.o: %.c $(HEADERS)
+ $(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o
+
+test: $(OBJS)
+ $(CC) -Wall -Werror -g $(OBJS) -ldl -lpthread -o run_tests
+
+clean:
+ rm -f *.o run_tests
diff --git a/server/tests/run_tests.c b/server/tests/run_tests.c
new file mode 100644
index 0000000..dedafd0
--- /dev/null
+++ b/server/tests/run_tests.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2014 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 "test_dial_data.h"
+#include "test_url_lib.h"
+
+#include
+
+
+int main(int argc, char** argv) {
+ printf("====\n");
+ test_smartstrcat();
+ test_urldecode();
+ test_parse_app_name();
+ test_parse_params();
+ printf("====\n");
+ test_read_dial_data();
+ test_write_dial_data();
+ printf("====\n");
+ return 0;
+}
diff --git a/server/tests/test.h b/server/tests/test.h
new file mode 100644
index 0000000..1cb9295
--- /dev/null
+++ b/server/tests/test.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2014 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.
+ */
+// Macros to simplify testing functions.
+
+#ifndef SRC_SERVER_TESTS_TEST_H_
+#define SRC_SERVER_TESTS_TEST_H_
+
+#define EXPECT(a, m) \
+ do { \
+ if (!a) { \
+ printf("[%s] failed: %s\n", #a, m); \
+ printf("%s -> FAILED\n", __func__); \
+ return; \
+ } \
+ } while (0)
+
+#define EXPECT_STREQ(a, b) \
+ do { \
+ if (strcmp(a, b)) { \
+ printf("expected [%s == %s]\n", #a, #b); \
+ printf(" a = \"%s\"\n", a); \
+ printf(" b = \"%s\"\n", b); \
+ printf("%s -> FAILED\n", __func__); \
+ return; \
+ } \
+ } while (0)
+
+#define DONE() \
+ printf("%s -> OK\n", __func__)
+
+#endif /* SRC_SERVER_TESTS_TEST_H_ */
diff --git a/server/tests/test_cors.html b/server/tests/test_cors.html
new file mode 100644
index 0000000..451a96e
--- /dev/null
+++ b/server/tests/test_cors.html
@@ -0,0 +1,57 @@
+
+
+
+
+ DIAL Security Test CORS
+
+
+
+
+
+
+
+
+
+ Test Netflix
+ Test Youtube
+
+
+
+
+
+
+
+
diff --git a/server/tests/test_cors.sh b/server/tests/test_cors.sh
new file mode 100755
index 0000000..088fe02
--- /dev/null
+++ b/server/tests/test_cors.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+
+if [ $# -eq 0 ]; then
+ echo "usage: `basename $0`: "
+ exit 1
+fi
+
+ip_address=$1
+port=$2
+
+#Testing all the positive cases
+origins="http://www4.netflix.com http://1.netflix.com https://www.netflix.com https://www4.netflix.com ftp://this.is.fine"
+for origin in $origins; do
+curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/Netflix || echo "failed: $origin should be accepted"
+curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix || echo "failed: $origin should be accepted"
+curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix/run || echo "failed: $origin should be accepted"
+if [ $ip_address == "localhost" ];
+then
+ echo "testing dial_data OPTIONS on $ip_address from origin $origin"
+ curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix/dial_data || echo "failed: $origin should be accepted"
+fi
+done
+
+origins="http://www4.youtube.com http://1.youtube.com https://www.youtube.com https://www4.youtube.com ftp://this.is.fine"
+for origin in $origins; do
+curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/YouTube || echo "failed: $origin should be accepted"
+curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube || echo "failed: $origin should be accepted"
+curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube/run || echo "failed: $origin should be accepted"
+if [ $ip_address == "localhost" ];
+then
+ echo "testing dial_data OPTIONS on $ip_address from origin $origin"
+ curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube/dial_data || echo "failed: $origin should be accepted"
+fi
+done
+
+#Testing all the negative cases
+origins="http://www.netflix-a.com http://www.netflix.com4 http://a-netflix.com https://ww.netflix-a.com https://www.netflix.com4 https://a-netflix.com http://netflix.com http://www.attack.com https://www.attack.com file://www.attack.com"
+for origin in $origins; do
+curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/Netflix && echo "failed: $origin should be rejected"
+curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix && echo "failed: $origin should be rejected"
+curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix/run && echo "failed: $origin should be rejected"
+if [ $ip_address == "localhost" ];
+then
+ echo "testing dial_data OPTIONS on $ip_address from origin $origin"
+ curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/Netflix/dial_data && echo "failed: $origin should be rejected"
+fi
+done
+
+origins="http://www.youtube-a.com http://www.youtube.com4 http://a-youtube.com https://ww.youtube-a.com https://www.youtube.com4 https://a-youtube.com http://youtube.com https://youtube.com http://www.attack.com https://www.attack.com file://www.attack.com"
+for origin in $origins; do
+curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/YouTube && echo "failed: $origin should be rejected"
+curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube && echo "failed: $origin should be rejected"
+curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube/run && echo "failed: $origin should be rejected"
+if [ $ip_address == "localhost" ];
+then
+ echo "testing dial_data OPTIONS on $ip_address from origin $origin"
+ curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube/dial_data && echo "failed: $origin should be rejected"
+fi
+done
+
+#Finally test with no header
+curl --fail --silent --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/YouTube || echo "failed: request without an Origin should be accepted"
+curl --fail --silent -X OPTIONS http://$ip_address:$port/apps/YouTube || echo "failed: request without an Origin should be accepted"
+curl --fail --silent -X OPTIONS http://$ip_address:$port/apps/YouTube/run || echo "failed: request without an Origin should be accepted"
+if [ $ip_address == "localhost" ];
+then
+ echo "testing dial_data OPTIONS on $ip_address with no origin"
+ curl --fail --silent -X OPTIONS http://$ip_address:$port/apps/YouTube/dial_data || echo "failed: request without an Origin should be accepted"
+fi
+
+echo "Done."
diff --git a/server/tests/test_dial_data.c b/server/tests/test_dial_data.c
new file mode 100644
index 0000000..b383d5d
--- /dev/null
+++ b/server/tests/test_dial_data.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2014 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_data.h"
+
+#include
+#include
+#include
+#include
+
+#include "test.h"
+
+int key_value_pairs = 3;
+char *keys[] = {"key1", "key2", "key3"};
+char *values[] = {"value1", "value2", "value3"};
+
+void test_read_dial_data() {
+ DIALData *data = retrieve_dial_data("dial_data");
+ for (int i = 0; data != NULL; data = data->next, i++) {
+ EXPECT_STREQ(data->key, keys[2 - i]);
+ EXPECT_STREQ(data->value, values[2 - i]);
+ }
+ DONE();
+}
+
+void test_write_dial_data() {
+ DIALData *result = NULL;
+ for (int i = 0; i < key_value_pairs; ++i) {
+ DIALData *node = (DIALData *) malloc(sizeof(DIALData));
+ node->key = keys[i];
+ node->value = values[i];
+ node->next = result;
+ result = node;
+ }
+ store_dial_data("YouTube", result);
+
+ DIALData *readBack = retrieve_dial_data("YouTube");
+
+ for (int i = 0; readBack != NULL; readBack = readBack->next, i++) {
+ EXPECT_STREQ(readBack->key, keys[i]);
+ EXPECT_STREQ(readBack->value, values[i]);
+ }
+
+ DONE();
+}
diff --git a/server/tests/test_dial_data.h b/server/tests/test_dial_data.h
new file mode 100644
index 0000000..d2b579d
--- /dev/null
+++ b/server/tests/test_dial_data.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2014 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 SRC_SERVER_TESTS_TEST_DIAL_DATA_H_
+#define SRC_SERVER_TESTS_TEST_DIAL_DATA_H_
+
+void test_read_dial_data();
+void test_write_dial_data();
+
+#endif /* SRC_SERVER_TESTS_TEST_DIAL_DATA_H_ */
diff --git a/server/tests/test_url_lib.c b/server/tests/test_url_lib.c
new file mode 100644
index 0000000..95d6e6d
--- /dev/null
+++ b/server/tests/test_url_lib.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2014 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 "../url_lib.h"
+#include "../dial_data.h"
+
+#include
+#include
+#include
+#include
+#include "test.h"
+
+void test_smartstrcat() {
+ char* src1 = "Hello ";
+ char* src2 = "world!";
+ char* src3 = " Trunc ated";
+ char dest[128] = {0, };
+
+ char* p = (char *) dest;
+ p = smartstrcat(p, src1, 128);
+ EXPECT_STREQ(dest, "Hello ");
+ p = smartstrcat(p, src2, dest + 128 - p);
+
+ EXPECT_STREQ(dest, "Hello world!");
+
+ p = smartstrcat(p, src3, 6);
+ EXPECT_STREQ(dest, "Hello world! Trunc");
+
+ DONE();
+}
+
+void test_urldecode() {
+ char* param = "%26bla+r";
+ char dest[128] = {0, };
+
+ EXPECT(urldecode(dest, param, 128), "Failed to decode.");
+ EXPECT_STREQ(dest, "&bla r");
+ DONE();
+}
+
+void test_parse_app_name() {
+ char *app_name;
+ EXPECT((app_name = parse_app_name(NULL)), "Failed to extract app_name");
+ EXPECT_STREQ(app_name, "unknown");
+ EXPECT((app_name = parse_app_name("")), "Failed to extract app_name");
+ EXPECT_STREQ(app_name, "unknown");
+ EXPECT((app_name = parse_app_name("/")), "Failed to extract app_name");
+ EXPECT_STREQ(app_name, "unknown");
+ EXPECT((app_name = parse_app_name("/apps/YouTube/DialData")),
+ "Failed to extract app_name");
+ EXPECT_STREQ(app_name, "YouTube");
+ EXPECT((app_name = parse_app_name("//")), "Failed to extract app_name");
+ EXPECT_STREQ(app_name, "");
+ EXPECT((app_name = parse_app_name("/invalid")),
+ "Failed to extract app_name");
+ EXPECT_STREQ(app_name, "unknown");
+
+ DONE();
+}
+
+void test_parse_params() {
+ EXPECT(!parse_params(""), "Empty query string should generate no params");
+ EXPECT(!parse_params(NULL), "Null query, should generate no params");
+
+ DIALData *result = parse_params("a=b");
+ EXPECT_STREQ(result->key, "a");
+ EXPECT_STREQ(result->value, "b");
+
+ result = parse_params("?a=b");
+ EXPECT_STREQ(result->key, "a");
+ EXPECT_STREQ(result->value, "b");
+
+ result = parse_params("?a=b&c=d");
+ EXPECT_STREQ(result->key, "c");
+ EXPECT_STREQ(result->value, "d");
+ EXPECT_STREQ(result->next->key, "a");
+ EXPECT_STREQ(result->next->value, "b");
+
+ char query_string[1024] = {0, };
+ char *current = query_string;
+ for (int i = 0; i < 25; ++i) {
+ current = smartstrcat(current, "a=b&", 256);
+ }
+ result = parse_params(query_string);
+ int length = 0;
+ for (; result != NULL; result = result->next) {
+ length++;
+ }
+ EXPECT((length == 25), "25 params should have been parsed");
+
+ DONE();
+}
diff --git a/server/tests/test_url_lib.h b/server/tests/test_url_lib.h
new file mode 100644
index 0000000..1cbe2e7
--- /dev/null
+++ b/server/tests/test_url_lib.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2014 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 SRC_SERVER_TESTS_TEST_URL_LIB_H_
+#define SRC_SERVER_TESTS_TEST_URL_LIB_H_
+
+void test_smartstrcat();
+void test_urldecode();
+void test_parse_app_name();
+void test_parse_param();
+void test_parse_params();
+
+#endif /* SRC_SERVER_TESTS_TEST_URL_LIB_H_ */
diff --git a/server/url_lib.c b/server/url_lib.c
new file mode 100644
index 0000000..1ad45a5
--- /dev/null
+++ b/server/url_lib.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2014 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 "url_lib.h"
+#include "dial_data.h"
+
+#include
+#include
+#include
+#include
+
+char* smartstrcat(char* dest, char* src, size_t max_chars) {
+ size_t copied = 0;
+ while ((*(dest++) = *(src++)) && copied++ < max_chars) {
+ }
+ // In case we over-stepped the size, end the string nicely.
+ *(--dest) = '\0';
+ return dest;
+}
+
+static int append_char_from_hex(char* dest, char a, char b) {
+ if ('a' <= a && a <= 'f')
+ a = 10 + a - 'a';
+ else if ('A' <= a && a <= 'F')
+ a = 10 + a - 'A';
+ else if ('0' <= a && a <= '9')
+ a = a - '0';
+ else
+ return 0;
+
+ if ('a' <= b && b <= 'f')
+ b = 10 + b - 'a';
+ else if ('A' <= b && b <= 'F')
+ b = 10 + b - 'A';
+ else if ('0' <= b && b <= '9')
+ b = b - '0';
+ else
+ return 0;
+
+ *dest = (char) (16 * a) + b;
+ return 1;
+}
+
+int urldecode(char *dst, const char *src, size_t max_size) {
+ size_t len = 0;
+
+ while (len < max_size && *src) {
+ if (*src == '+') {
+ *dst = ' ';
+ } else if (*src == '%') {
+ if (!*(++src) || !*(++src) || !append_char_from_hex(dst, *(src - 1), *src)) {
+ *dst = '\0';
+ return 0;
+ }
+ } else {
+ *dst = *src;
+ }
+ ++dst;
+ ++src;
+ ++len;
+ }
+ *dst = '\0';
+ return len;
+}
+
+void xmlencode(char *dst, const char *src, size_t max_size) {
+ size_t current_size = 0;
+ while (*src && current_size < max_size) {
+ switch (*src) {
+ case '&':
+ dst = smartstrcat(dst, "&", max_size - current_size);
+ current_size += 5;
+ break;
+ case '\"':
+ dst = smartstrcat(dst, """, max_size - current_size);
+ current_size += 6;
+ break;
+ case '\'':
+ dst = smartstrcat(dst, "'", max_size - current_size);
+ current_size += 6;
+ break;
+ case '<':
+ dst = smartstrcat(dst, "<", max_size - current_size);
+ current_size += 4;
+ break;
+ case '>':
+ dst = smartstrcat(dst, ">", max_size - current_size);
+ current_size += 4;
+ break;
+ default:
+ *dst++ = *src;
+ current_size++;
+ break;
+ }
+ src++;
+ }
+ *dst = '\0';
+}
+
+char *parse_app_name(const char *uri) {
+ if (uri == NULL) {
+ return "unknown";
+ }
+ char *slash = strrchr(uri, '/');
+ if (slash == NULL || slash == uri) {
+ return "unknown";
+ }
+ char *begin = slash;
+ while ((begin != uri) && (*--begin != '/'))
+ ;
+ begin++; // skip the slash
+ char *result = (char *) calloc(1, slash - begin+1);
+ strncpy(result, begin, slash - begin);
+ result[slash-begin]='\0';
+ return result;
+}
+
+char *parse_param(char *query_string, char *param_name) {
+ if (query_string == NULL) {
+ return NULL;
+ }
+ char * start;
+ if ((start = strstr(query_string, param_name)) == NULL) {
+ return NULL;
+ }
+ while (*start && (*start++ != '='))
+ ;
+ char *end = start;
+ while (*end && (*end != '&'))
+ end++;
+ int result_size = end - start;
+ char *result = malloc(result_size + 1);
+ result[0] = '\0';
+ strncpy(result, start, result_size);
+ result[result_size] = '\0';
+ return result;
+}
+
+DIALData *parse_params(char * query_string) {
+ if (query_string == NULL || strlen(query_string) <= 2) {
+ return NULL;
+ }
+ if (query_string[0] == '?') {
+ query_string++; // skip leading question mark
+ }
+ DIALData *result = NULL;
+ char *query_string_dup = strdup(query_string);
+ char * name_value = strtok(query_string_dup, "&");
+ while (name_value != NULL) {
+ DIALData *tmp = (DIALData *) malloc(sizeof(DIALData));
+ size_t name_value_length = strlen(name_value);
+ tmp->key = (char *) malloc(name_value_length);
+ tmp->value = (char *) malloc(name_value_length);
+ sscanf(name_value, "%[^=]=%s", tmp->key, tmp->value);
+ tmp->next = result;
+ result = tmp;
+
+ name_value = strtok(NULL, "&"); // read next token
+ }
+ free(query_string_dup);
+ return result;
+}
diff --git a/server/url_lib.h b/server/url_lib.h
new file mode 100644
index 0000000..209dfb3
--- /dev/null
+++ b/server/url_lib.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014 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.
+ */
+/* Utility functions for dealing with URLs */
+
+#ifndef URLLIB_H_
+#define URLLIB_H_
+
+#include "dial_data.h"
+#include
+
+/**
+ * Concatenate a maxim of max_chars characters from src into dest,
+ * and return a pointer to the last character in dest.
+ */
+char* smartstrcat(char* dest, char* src, size_t max_chars);
+
+int urldecode(char *dst, const char *src, size_t max_size);
+
+void xmlencode(char *dst, const char *src, size_t max_size);
+
+/**
+ * Parse the value of the parameter with the given name from a query string.
+ */
+char *parse_param(char *query_string, char *param_name);
+
+/*
+ * Parse the application name out of a URI, such as /app/YouTube/dial_data.
+ * Note: this parser assumes that the dial_data url is of the form
+ * /apps//dial_data. If your DIAL server uses a different url-path,
+ * you will need to adapt the method below.
+ */
+char *parse_app_name(const char *uri);
+
+/*
+ * Parse a list of DIALData params out of a query string.
+ */
+DIALData *parse_params(char * query_string);
+
+#endif // URLLIB_H_