dial spec 1.7

This commit is contained in:
jcli
2015-10-20 16:26:28 -07:00
commit 3454a59e99
41 changed files with 6174 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.o
client/dialclient
server/dialserver
server/tests/run_tests

55
Changelog Normal file
View File

@@ -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.

19
LICENSE Normal file
View File

@@ -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.

78
README Normal file
View File

@@ -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>
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.

38
build/Version.h Normal file
View File

@@ -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

66
build/dial_build.sh Executable file
View File

@@ -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 *

133
client/DialClientInput.cpp Normal file
View File

@@ -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 <iostream>
#include <fstream>
using namespace std;
bool DialClientInput::init(std::string file)
{
if( file.empty() ) file = DialClientInput::getDefaultFilename();
ATRACE("DialClientInput::%s, opening %s\n", __FUNCTION__, file.c_str());
string line;
ifstream myfile (file.c_str());
if (myfile.is_open())
{
// first fill _actions with commands from the input file
while ( myfile.good() )
{
getline (myfile,line);
if( line[0] != '#' && !line.empty() )
{
if( line.find("addApplication") != line.npos )
{
// add an application
size_t pos = line.find_first_of('=');
_applist.push_back( line.substr(pos+1, line.length()));
ATRACE("Adding app: %s\n", line.substr(pos+1, line.length()).c_str());
}
else if( line.find("addErrorApplication") != line.npos )
{
// add an application
size_t pos = line.find_first_of('=');
_errorapplist.push_back( line.substr(pos+1, line.length()));
ATRACE("Adding error app: %s\n", line.substr(pos+1, line.length()).c_str());
}
else
{
// add a command
size_t pos = line.find_first_of(" ");
std::string params = line.substr(pos+1, line.length());
std::pair<std::string, std::string>
action_to_push(line.substr(0, pos),
pos == line.npos ? "":params);
_actions.push_back(action_to_push);
ATRACE("command: %s params: %s\n",
line.substr(0, pos).c_str(), params.c_str() );
}
}
#ifdef DEBUG
//else ATRACE("COMMENT: %s\n", line.c_str());
#endif
}
myfile.close();
}
else
{
fprintf(stderr, "Unable to open file\n");
return false;
}
return true;
}
bool DialClientInput::addApplication( string& application )
{
// see if the application exits
vector<string>::iterator it;
for( it = _applist.begin(); it < _applist.end(); it ++ )
if( !((*it).compare( application )) ) break;
// if not, add it
if( it < _applist.end() ) _applist.push_back(application);
else return false; // already in the list
return true;
}
bool DialClientInput::getNextAction( string& command, vector<string>& parameters )
{
if (_actions.empty()) return false;
pair<string, string> action = _actions.front();
_actions.erase(_actions.begin());
command = action.first;
string params = action.second;
size_t pos = params.find_first_of(" "), start = 0;
// Parse out the parameters from the string
while (1)
{
parameters.push_back(params.substr( start, pos-start ));
if( pos == params.npos ) break;
start = pos+1;
pos = params.find_first_of(" ", start);
}
return true;
}
void DialClientInput::getApplicationList( vector<string> &list )
{
list = _applist;
}
void DialClientInput::getErrorApplicationList(vector<string> &list)
{
list = _errorapplist;
}

89
client/DialClientInput.h Normal file
View File

@@ -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 <string>
#include <vector>
using namespace std;
class DialClientInput
{
public:
DialClientInput() {}
~DialClientInput() {}
/**
* Called to initialize
*
* @param[in] file Name of the file to load
* @return true if successful, false otherwise
*/
bool init( string file );
/**
* The DialClient application can use this to add applications passed on
* the command line. If the application is already in the list, it will
* drop the add.
*
* @param[in] application Name of the application to add.
*
* @return true if successful, false otherwise
*/
bool addApplication( string &application );
/**
* Get the next action to execute
*
* @param[out] command Command to execute
* @param[out] parameters Parameters for the command
*/
bool getNextAction( string& command, vector<string>& parameters );
/**
* Get a list of valid applications defined in the input file.
*
* @param[out] list List of valid applications
*/
void getApplicationList(vector<string> &list);
/**
* Get a list of applications used for error tests (should not exist).
*
* @param[out] list List of error applications
*/
void getErrorApplicationList(vector<string> &list);
static string getDefaultFilename() { return "./dialclient_input.txt"; }
private:
vector<string> _applist;
vector<string> _errorapplist;
vector< pair<string,string> > _actions;
};
#endif // DIALCLIENTINPUT_H

587
client/DialConformance.cpp Normal file
View File

@@ -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 <iostream>
#include <fstream>
#include <sstream>
#include "DialConformance.h"
#include <assert.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// Number of milliseconds to sleep between launches when doing a launch=ALL
#define LAUNCH_SLEEP 6000
#define MAX_PARAMETER_LENGTH 60
using namespace std;
/*
* This class will be reponsible for reporting the conformance test results.
* Requirements:
* * Results should be in HTML
* * Results should be easily readable
* * Results should clearly distinguish between pass and fail
* * If there is a failure, there should be ample information provided
* for debugging
*/
class OutputWriter
{
public:
OutputWriter( string fileName ) :
_filename(fileName),
_pCurrentTest(NULL),
_isComplete(false)
{
}
~OutputWriter()
{ }
void setOutputFile( string fileName )
{
_filename = fileName;
}
void setTest( string testName )
{
assert( _pCurrentTest == NULL );
_pCurrentTest = new Test(testName);
_tests.push_back(_pCurrentTest);
}
void clearTest()
{
// still holding a ref in the test vector. That will get deleted
// when the test is complete
_pCurrentTest = NULL;
}
void setError( string error ) { _pCurrentTest->addError(error); }
void setResult( bool result ) { _pCurrentTest->setResult( result ); }
void start( string& friendlyname, string& uuid, string ipaddress )
{
_file.open(_filename.c_str());
_file << "<html>\n<body>\n";
_file << "<font size=\"6\"><b>FriendlyName: " << friendlyname;
_file << "<br>\nIP: " << ipaddress;
_file << "<br>\nUUID: " << uuid;
{
time_t rawtime;
struct tm * timeinfo;
char buffer [80];
time ( &rawtime );
timeinfo = localtime ( &rawtime );
//strftime (buffer,80,"%I:%M%p.",timeinfo);
strftime (buffer,80,"%c",timeinfo);
_file << "<br>\nTime of Run: " << buffer << "</b></font>";
}
_file << "<table border=\"10\">\n";
_file << "<tr>\n";
_file << "<th> RESULT </th>"; //header 1
_file << "<th> Test Executed </th>"; //header 2
_file << "<th> Errors </th>"; //header 2
_file << "</tr>\n";
}
void complete()
{
vector<Test*>::iterator it;
for( it = _tests.begin(); it < _tests.end(); it++ )
{
write("<tr>");
// write the name and the result
stringstream result;
bool testResult = (*it)->getResult();
result << "<td><b> <font color=\"" << (testResult ? "green" : "red") << "\"> ";
result << (testResult ? "SUCCESS" : "FAIL") << " </b></font></td>\n<td>" << (*it)->getName() << "</td>";
write( result.str() );
// write errors if they exist
write("<td>");
vector<string> errors = (*it)->getErrors();
if( !errors.empty() )
{
vector<string>::iterator it2;
write("<b>");
for( it2 = errors.begin(); it2 < errors.end(); it2++ )
write( (*it2) );
write("</b>");
}
else
write("NO ERROR");
write("</td>\n</tr>");
// delete the test
delete (*it);
}
_file << "</table></body>\n</html>\n";
_file.close();
_isComplete = true;
}
private:
// Abstraction of a test
class Test
{
public:
Test(string& name) :
_testname(name),
_testPassed(false) {}
~Test() {}
string getName() { return _testname; }
void setResult( bool result ) { _testPassed = result; }
bool getResult() { return _testPassed; }
void addError( string error ) { _errors.push_back(error); }
vector<string> getErrors() { return _errors; }
private:
string _testname;
bool _testPassed;
vector<string> _errors;
};
string _filename;
ofstream _file;
vector<Test*> _tests;
Test* _pCurrentTest;
bool _isComplete;
int write( string line )
{
_file << line.c_str() << "\n";
return line.length()+1;
}
};
static const string defaultOutputFile = "./report.html";
static OutputWriter gWriter(defaultOutputFile);
DialConformance *DialConformance::sConformance = 0;
DialConformance* DialConformance::create(void)
{
assert( DialConformance::sConformance == 0 );
return new DialConformance();
}
DialConformance* DialConformance::instance(void)
{
return DialConformance::sConformance;
}
DialConformance::DialConformance()
{
assert( DialConformance::sConformance == 0 );
DialConformance::sConformance = this;
}
DialConformance::~DialConformance()
{
assert( DialConformance::sConformance == this );
DialConformance::sConformance = 0;
}
void DialConformance::extractParamValue(
string& param, string& value )
{
// TODO: Add support for quotes after the equals
size_t posBegin = param.find("=");
value = param.substr(posBegin+1, param.length()-posBegin);
}
bool DialConformance::validateParams( vector<string>& params,
string& responseHeaders, string& responseBody )
{
bool testPassed = true;
vector<string>::iterator it;
for( it = params.begin(); it < params.end() && testPassed == true; it++ )
{
ATRACE("%s:: params:%s \n", __FUNCTION__, (*it).c_str());
if( (*it).find("httpresponse=") != (*it).npos )
testPassed = validateHttpResponse( responseHeaders, *it );
if( (*it).find("httpresponseheader=") != (*it).npos )
testPassed = validateHttpHeaders( responseHeaders, *it );
if( (*it).find("resultbody=") != (*it).npos )
testPassed = validateResponseBody( responseBody, *it );
}
return testPassed;
}
bool DialConformance::validateHttpResponse(
string& headers, string& param )
{
bool retval = false;
string responseCode;
extractParamValue( param, responseCode );
ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
if( headers.find(responseCode) != headers.npos )
{
ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
retval = true;
}
else
{
ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
stringstream error;
error << responseCode << " not found\n";
gWriter.setError( error.str() );
}
return retval;
}
bool DialConformance::validateHttpHeaders(
string& headers, string& param )
{
bool retval = false;
string responseCode;
extractParamValue( param, responseCode );
ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
if( headers.find(responseCode) != headers.npos )
{
ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
retval = true;
}
else
{
ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
stringstream error;
error << responseCode << " not found\n";
gWriter.setError( error.str() );
}
return retval;
}
bool DialConformance::validateResponseBody(
string& body, string& param )
{
bool retval = false;
string responseCode;
extractParamValue( param, responseCode );
ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
if( body.find(responseCode) != body.npos )
{
ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
retval = true;
}
else
{
ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
stringstream error;
error << responseCode << " not found\n";
error << "response: " << body;
gWriter.setError( error.str() );
}
return retval;
}
void DialConformance::getPayload(vector<string>& params, string& payload )
{
vector<string>::iterator it;
for( it = params.begin(); it < params.end(); it++ )
{
if( (*it).find("param") != (*it).npos )
{
// Check to see if the param is using quotes. If it is, split on
// that. If not, split on a space
size_t posEnd, pos = (*it).find( "=" );
if( (*it)[pos+1] == '\"' )
{
posEnd = (*it).find( "\"", pos+2 );
payload = (*it).substr( pos+2, posEnd-(pos+2) );
}
else
{
posEnd = (*it).find( " ", pos );
payload = (*it).substr( pos+1, posEnd );
}
ATRACE("payload = %s\n", payload.c_str());
}
}
}
bool DialConformance::execute_launch( Application* pApp, vector<string>& params )
{
string payload, responseHeaders, responseBody;
getPayload( params, payload );
pApp->launch( payload, responseHeaders, responseBody );
return (validateParams( params, responseHeaders, responseBody ));
}
bool DialConformance::execute_status( Application* pApp, vector<string>& params )
{
ATRACE("%s::%d\n", __FUNCTION__, __LINE__);
string responseHeaders, responseBody;
pApp->status( responseHeaders, responseBody );
bool retval = validateParams( params, responseHeaders, responseBody );
if( retval && (responseHeaders.find("200") != responseHeaders.npos) )
{
// if the status was successful
// Ensure the response body contains the application name
if( responseBody.find( pApp->getName() ) == responseBody.npos )
{
retval = false;
stringstream error;
error << "Reponse XML did not contain application name: " << pApp->getName();
gWriter.setError( error.str() );
retval = false;
}
if( responseBody.find( "urn:dial-multiscreen-org:schemas:dial" )
== responseBody.npos )
{
gWriter.setError( "Response Body did not contain DIAL service "
"string: urn:dial-multiscreen-org:schemas:dial" );
retval = false;
}
}
return retval;
}
bool DialConformance::execute_stop( Application* pApp, vector<string>& params )
{
ATRACE("%s::%d\n", __FUNCTION__, __LINE__);
string responseHeaders, responseBody;
pApp->stop(responseHeaders);
return (validateParams( params, responseHeaders, responseBody ));
}
DialConformance::Application* DialConformance::getApplication( string& command )
{
Application* pApp = NULL;
// extract the application
string app;
size_t pos = command.find("=");
app = command.substr(pos+1, command.length());
assert( app.length() > 0 );
// find the application
vector<Application*>::iterator it;
for( it = _apps.begin(); it < _apps.end(); it++ )
{
ATRACE("Comparing: %s.compare(%s)\n", (*it)->getName().c_str(), app.c_str());
if( (*it)->getName().compare(app) == 0)
{
pApp = (*it);
break;
}
}
assert(pApp != NULL);
return pApp;
}
bool DialConformance::execute_command( string& command, vector<string>& params )
{
ATRACE("%s::%s\n", __FUNCTION__, command.c_str());
bool retval = false;
if( command.find("launch") != command.npos )
retval = execute_launch(getApplication(command), params);
else if ( command.find("status") != command.npos )
retval = execute_status(getApplication(command), params);
else if ( command.find("stop") != command.npos )
retval = execute_stop(getApplication(command), params);
else if ( command.find("sleep") != command.npos )
{
string time;
size_t pos = command.find("=");
time = command.substr(pos+1, command.length());
unsigned long milliseconds = atoi(time.c_str());
ATRACE("Sleeping %ld milliseconds \n", milliseconds);
usleep(milliseconds*1000);
retval = true;
}
else ATRACE("Can't execute command: %s\n", command.c_str());
return retval;
}
void DialConformance::run_internal( DialServer* pServer )
{
string command;
vector<string> params;
bool retval;
string friendlyName, uuid;
// only execute the test if we have a friendly name and UUID
retval = pServer->getFriendlyName( friendlyName );
if (retval) pServer->getUuid( uuid );
if (retval) gWriter.start( friendlyName, uuid, pServer->getIpAddress() );
while( retval && _input.getNextAction(command, params) )
{
vector<string> commandlist;
// Check the command here, if it is command=ALL, then we need to loop through
// all of the valid applications
size_t pos;
if( (pos = command.find("=ALL")) != command.npos )
{
vector<Application*>::iterator it;
for( it = _apps.begin(); it < _apps.end(); it++ )
{
// strip off ALL and append the application name
string newCommand = command.substr(0, pos+1);
newCommand.append( (*it)->getName() );
Application *pApp = getApplication( newCommand );
if( pApp != NULL && (!pApp->isErrorApp()) )
{
ATRACE("Adding command %s\n", newCommand.c_str());
commandlist.push_back( newCommand );
}
if( newCommand.find("launch") != newCommand.npos )
{
// If we are adding launch commands, insert a sleep so that we don't overwhelm
// the target
stringstream sleep;
sleep << "sleep=" << LAUNCH_SLEEP;
commandlist.push_back( sleep.str() );
}
}
}
else
{
ATRACE("Adding command %s\n", command.c_str());
commandlist.push_back(command);
}
vector<string>::iterator it;
for( it = commandlist.begin(); it < commandlist.end(); it++ )
{
// construct the name of the test
ATRACE("***********************" "\n%s: Command: %s \n",
__FUNCTION__, (*it).c_str());
stringstream test;
test << "Command:" << (*it);
// The input code will always push a parameter. If there are no
// parameters, it will push an empty string, so test for that here.
if( params.size() > 0 && !params[0].empty() ) test << " Params:";
vector<string>::iterator it2;
for( it2 = params.begin(); it2 < params.end(); it2++)
{
ATRACE("param: %s ", (*it2).c_str());
// check to see if we have a really large parameter, if we do,
// truncate it to a reasonable length
string param = (*it2);
if( param.size() > MAX_PARAMETER_LENGTH )
{
param = (*it2).substr(0, MAX_PARAMETER_LENGTH-3 );
param.append("...");
}
test << param << " ";
}
if( (*it).find("sleep") == (*it).npos )
{
// set the test name
gWriter.setTest( test.str() );
// run the test
bool retval = execute_command( *it, params );
printf("%s\n", retval ? "SUCCESS":"FAILED");
// set the result and prep for the next test
gWriter.setResult( retval );
gWriter.clearTest();
}
else
{
//just sleep here
string time;
size_t pos = (*it).find("=");
time = (*it).substr(pos+1, command.length());
unsigned long milliseconds = atoi(time.c_str());
ATRACE("Sleeping %ld milliseconds \n", milliseconds);
usleep(milliseconds*1000);
}
}
// clear the parameters for the next loop
params.clear();
}
if( retval ) gWriter.complete();
}
int DialConformance::run(
DialServer* pServer,
vector<string>& appList,
string &inputFile,
string &outputFile )
{
printf("Running conformance tests from %s and printing report to %s\n\n",
inputFile.empty() ?
DialClientInput::getDefaultFilename().c_str() :
inputFile.c_str(),
outputFile.empty() ?
defaultOutputFile.c_str() :
outputFile.c_str() );
//read the input file
_input.init(inputFile);
if( !outputFile.empty() ) gWriter.setOutputFile( outputFile );
// Add applications passed by the client (command line)
vector<string>::iterator it;
for( it = appList.begin(); it < appList.end(); it++ ) _input.addApplication(*it);
// build the application list
vector<string> listOfApps;
_input.getApplicationList(listOfApps); // get the list of apps
for( it = listOfApps.begin(); it < listOfApps.end(); it++ )
{
_apps.push_back( new Application(*it, pServer, false) );
ATRACE("Adding application: %s\n", (*it).c_str());
}
_input.getErrorApplicationList(listOfApps); // get the list of apps
for( it = listOfApps.begin(); it < listOfApps.end(); it++ )
{
_apps.push_back( new Application(*it, pServer, true) );
ATRACE("Adding Error application: %s\n", (*it).c_str());
}
// run the test
run_internal( pServer );
// clean up the application list
vector<Application*>::iterator appit;
for ( appit = _apps.begin() ; appit < _apps.end(); appit++ ) delete (*appit);
return 0;
}

227
client/DialConformance.h Normal file
View File

@@ -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 <memory>
#include "DialServer.h"
#include "DialClientInput.h"
using namespace std;
class DialConformance
{
public:
/**
* Create a singleton
*/
static DialConformance* create();
/**
* Get a pointer to the singleton
*/
static DialConformance* instance(void);
~DialConformance();
/**
* Run the test
*
* @param[in] pServer DIAL server to test
* @param[in] appList List of applications from the command line
* @param[in] inputFile Input file used to drive tests
* @param[in] outputFile File for writing the report. Use an
* empty string to use the default (report.html).
*
* @return 0 if successful, !0 otherwise
*/
int run(
DialServer* pServer,
vector<string>& appList,
string &inputFile,
string &outputFile );
private:
DialConformance();
DialServer* _pServer;
static DialConformance* sConformance;
DialClientInput _input;
/**
* Class to manage state for each application supported by the DIAL
* server. The application list is managed by the input file.
*/
class Application{
public:
enum State
{
STOPPED,
LAUNCHING, // application has just been launched, state
// has not been confirmed
LAUNCHED,
STOPPING // application has been stopped, state has not
// been confirmed
};
Application( string& name, DialServer *pServer, bool isErrorApp ) :
_isErrorApp(isErrorApp),
_pServer(pServer),
_state(STOPPED),
_stopurl(""),
_name(name) {}
~Application(){}
/**
* State Getter/Setters
*/
string getName() { return _name; }
State getState() { return _state; }
/**
* Returns true if the application is not a valid application
*/
bool isErrorApp() { return _isErrorApp; }
/**
* Launch an this application using the server that was stored when
* the class was created.
*
* @param[in] payload Data that is put into the POST data.
* @param[out] responseHeaders HTML response headers
* @param[out] responseBody HTML response body
*/
void launch(
string& payload,
string& responseHeaders,
string& responseBody )
{
_pServer->launchApplication(
_name, payload, responseHeaders, responseBody );
// TODO: Set state
// find Location in the header, store the stop url
if( !responseHeaders.empty() )
{
size_t pos, tmp = responseHeaders.find("Location");
if( tmp != responseHeaders.npos )
{
pos = responseHeaders.find("http", tmp);
size_t posEnd = responseHeaders.find("\n", pos+1);
if( posEnd != responseHeaders.npos )
_stopurl = responseHeaders.substr( pos, posEnd );
// chomp off the \r\n chars
DialConformance::chomp( _stopurl );
ATRACE("StopURL = %s********\n", _stopurl.c_str());
}
}
}
/**
* Get the status of this application
*
* @param[out] responseHeaders HTML response headers
* @param[out] responseBody HTML response body
*/
void status(
string& responseHeaders,
string& responseBody )
{ _pServer->getStatus( _name, responseHeaders, responseBody ); }
/**
* Stop the application
*
* @param[out] responseHeaders HTML response headers
*/
void stop(
string& responseHeaders )
{ stop(_stopurl, responseHeaders ); }
/**
* Stop the application using a custom stop URL.
*
* @param[out] responseHeaders HTML response headers
*/
void stop( string& stopurl, string& responseHeaders)
{
if( !stopurl.empty() )
{
_pServer->stopApplication( stopurl, responseHeaders );
}
#ifdef DEBUG
else ATRACE("%s: Not sending stop, stop URL is empty\n", __FUNCTION__);
#endif
}
private:
bool _isErrorApp;
DialServer* _pServer;
State _state;
string _stopurl;
string _name;
};
// list of applications
vector<Application*> _apps;
// Internal helpers
void run_internal( DialServer* pServer );
bool execute_command( string& command, vector<string>& params );
// Get the Application pointer from an application string
Application* getApplication( string& command );
// extract the payload from a list of parameters
void getPayload(vector<string>& params, string& payload );
// Command execution functions
bool execute_launch( Application* pApp, vector<string>& params );
bool execute_status( Application* pApp, vector<string>& params );
bool execute_stop( Application* pApp, vector<string>& params );
// Helper function to extract a parameter.
void extractParamValue( string& param, string& value );
// Validation functions
bool validateParams( vector<string>& params,
string& responseHeaders, string& responseBody );
bool validateHttpResponse( string& headers, string& params );
bool validateHttpHeaders( string& headers, string& params );
bool validateResponseBody( string& headers, string& params );
public:
// Helper function to chomp off the carriage return line feed.
static void chomp(string& str)
{
string crlf("\r\n");
size_t pos = str.find_last_not_of( crlf );
if( pos != str.npos )
{
ATRACE("CHOMP\n");
str.erase(pos+1);
}
}
};
#endif // DIALCONFORMANCE_H

344
client/DialDiscovery.cpp Normal file
View File

@@ -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 <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string>
#include <map>
#include <curl/curl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <vector>
#include "DialDiscovery.h"
#include <assert.h>
#define SSDP_TIMEOUT 30
#define SSDP_RESPONSE_TIMEOUT 3
using namespace std;
DialDiscovery *DialDiscovery::sDiscovery = 0;
//static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
//static int my_port = 0;
static struct sockaddr_in saddr;
typedef struct
{
struct sockaddr_in saddr;
int sock;
socklen_t addrlen;
}search_conn;
static pthread_mutex_t list_locker = PTHREAD_MUTEX_INITIALIZER;
class ScopeLocker
{
public:
ScopeLocker(pthread_mutex_t &m) : mLock(m)
{ pthread_mutex_lock(&mLock); }
virtual ~ScopeLocker()
{ pthread_mutex_unlock(&mLock); }
private:
pthread_mutex_t mLock;
};
static const char ssdp_msearch[] =
"M-SEARCH * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"MAN: \"ssdp:discover\"\r\n"
"MX: 10\r\n"
"ST: urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
static string getLocation(char *pResponse)
{
string loc(pResponse);
size_t prev = 0, index = loc.find("\r\n");
string locUrl, retUrl;
while( index != string::npos ) {
locUrl = loc.substr(prev, index-prev);
ATRACE("locUrl: ##%s## prev: %d index %d\n", locUrl.c_str(), (int)prev, (int)index );
if( locUrl.find("LOCATION: ") != string::npos ) {
index = locUrl.find("\r\n");
retUrl = locUrl.substr( 10 );
break;
}
prev = index+2; // move past the "\r\n" token
index = loc.find("\r\n", prev);
}
return retUrl;
}
static size_t header_cb(void* ptr, size_t size, size_t nmemb, void* userdata)
{
if ((size * nmemb) != 0) {
string parse((char*)ptr);
if( parse.find("Application-URL: ") != string::npos ) {
size_t index_start = parse.find(":");
index_start += 2;
size_t index_end = parse.find("\r\n");
string *header = static_cast<string*>(userdata);
header->append(parse.substr(index_start, index_end-index_start));
ATRACE("Apps URL set to %s\n", header->c_str());
#ifndef DEBUG
}
#else
} else {
ATRACE("%s: Dropping %s\n", __FUNCTION__, (char*)ptr);
}
#endif
}
return (size * nmemb);
}
static size_t receiveData(void *ptr, size_t size, size_t nmemb, void *userdata)
{
if ((size * nmemb) != 0) {
string *url = static_cast<string*>(userdata);
url->append((char*)ptr);
}
return (size * nmemb);
}
static void getServerInfo( const string &server, string& appsUrl, string& ddxml )
{
CURL *curl;
CURLcode res = CURLE_OK;
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
fprintf(stderr, "curl_global_init() failed\n");
return;
}
if ((curl = curl_easy_init()) == NULL) {
fprintf(stderr, "curl_easy_init() failed\n");
curl_global_cleanup();
return;
}
ATRACE("Sending ##%s##\n", server.c_str());
curl_easy_setopt(curl, CURLOPT_URL, server.c_str());
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &appsUrl);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ddxml);
res = curl_easy_perform(curl);
(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<string> removal;
for( it = mServerMap.begin(); it != mServerMap.end(); it++ )
{
if( !(*it).second->isFound() ) {
removal.push_back((*it).second->getLocation());
}
}
// now remove and delete
vector<string>::iterator iter;
for( iter = removal.begin(); iter != removal.end(); iter ++)
{
ATRACE("Removing Server: %s\n", (*iter).c_str());
DialServer *p = mServerMap[(*iter)];
delete (p);
mServerMap.erase((*iter));
}
}
void DialDiscovery::resetDiscovery(void)
{
ScopeLocker s(list_locker);
ServerMap::const_iterator it;
for( it = mServerMap.begin(); it != mServerMap.end(); it++ ) {
(*it).second->setFound(false);
}
}
void * DialDiscovery::send_mcast(void *p)
{
int one = 1, my_sock;
socklen_t addrlen;
//struct ip_mreq mreq;
char send_buf[strlen((char*)ssdp_msearch) + INET_ADDRSTRLEN + 256] = {0,};
int send_size;
pthread_attr_t attr;
search_conn connection;
// send_size = snprintf(send_buf, sizeof(send_buf), ssdp_msearch, ip_addr, my_port);
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<DialServer*>& list )
{
for( ServerMap::iterator it = mServerMap.begin(); it != mServerMap.end(); ++it ) {
list.push_back( it->second );
}
}
bool DialDiscovery::getServer( const string& friendlyName, DialServer &server )
{
return true;
}
DialDiscovery::DialDiscovery()
{
assert( DialDiscovery::sDiscovery == 0 );
DialDiscovery::sDiscovery = this;
}
DialDiscovery::~DialDiscovery()
{
assert( sDiscovery == this );
sDiscovery = 0;
}
DialDiscovery * DialDiscovery::create()
{
assert( sDiscovery == 0 );
return new DialDiscovery();
}
DialDiscovery * DialDiscovery::instance()
{
return DialDiscovery::sDiscovery;
}

94
client/DialDiscovery.h Normal file
View File

@@ -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 <map>
using namespace std;
class DialDiscovery
{
public:
/**
* Create a singleton
*/
static DialDiscovery* create(void);
/**
* Get a pointer to the singleton
*/
static DialDiscovery* instance(void);
~DialDiscovery();
/**
* Initialize the discover object. This will kick off a periodic
* worker thread that will poll for DIAL servers.
*
*/
void init();
/**
* Get the list of servers that have been discovered.
*
* @param[out] list List of DIAL servers. Returns an empty list if there
* are no servers.
*/
void getServerList(vector<DialServer*>& list);
/**
* Get a DIAL server based on friendly name
*
* @param[in] friendlyName Friendly name of DIAL server
* @param[out] server Server object (if successful)
*
* @return true if successful, false otherwise
*/
bool getServer(
const string& friendlyName,
DialServer &server );
private:
DialDiscovery();
void updateServerList(string& server);
static void *receiveResponses(void *p);
static void *send_mcast(void *p);
void processServer(char *pResponse);
void cleanServerList();
void resetDiscovery();
pthread_t _mcastThread;
pthread_t _responseThread;
typedef map<string, DialServer*> ServerMap;
ServerMap mServerMap;
static DialDiscovery* sDiscovery;
};
#endif // DIALDISCOVERY_H

249
client/DialServer.cpp Normal file
View File

@@ -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 <curl/curl.h>
using namespace std;
enum DIAL_COMMAND{
COMMAND_LAUNCH,
COMMAND_STATUS,
COMMAND_KILL
};
static size_t header_cb(void* ptr, size_t size, size_t nmemb, void* userdata)
{
if ((size * nmemb) != 0) {
string newHeader((char*)ptr);
string *header = static_cast<string*>(userdata);
header->append(newHeader);
ATRACE("%s: Adding header: %s", __FUNCTION__, newHeader.c_str());
}
return (size * nmemb);
}
static size_t receiveData(void *ptr, size_t size, size_t nmemb, void *userdata)
{
if ((size * nmemb) != 0) {
string *body= static_cast<string*>(userdata);
body->append((char*)ptr);
ATRACE("%s: Adding to Body: %s", __FUNCTION__, (char*)ptr);
}
return (size * nmemb);
}
int DialServer::sendCommand(
string &url,
int command,
string &payload,
string &responseHeaders,
string &responseBody )
{
CURL *curl;
CURLcode res = CURLE_OK;
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
{
fprintf(stderr, "curl_global_init() failed\n");
return 0;
}
if ((curl = curl_easy_init()) == NULL)
{
fprintf(stderr, "curl_easy_init() failed\n");
curl_global_cleanup();
return 0;
}
if (command == COMMAND_LAUNCH)
{
curl_easy_setopt(curl, CURLOPT_POST, true);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, payload.size());
if( payload.size() )
{
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
}
#ifdef DEBUG
else ATRACE("Sending empty POST\n");
#endif
}
else if (command == COMMAND_KILL)
{
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
}
ATRACE("Sending %s:%s\n",
command == COMMAND_LAUNCH ? "LAUNCH" :
(command == COMMAND_KILL ? "KILL" : "STATUS"),
url.c_str());
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &responseHeaders);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBody);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
curl_global_cleanup();
return (res == CURLE_OK);
}
string DialServer::getIpAddress()
{
// m_appsUrl=http://192.168.1.103:36269/apps/
if( m_ipAddr.empty() )
{
size_t begin = m_appsUrl.find("//");
if( begin != m_appsUrl.npos )
{
begin += 2; // move to the start of the IP address
size_t end = m_appsUrl.find(":", begin);
if( end != m_appsUrl.npos )
{
m_ipAddr = m_appsUrl.substr( begin, end-begin );
ATRACE("IP ADDRESS: %s\n", m_ipAddr.c_str() );
}
}
}
return m_ipAddr;
}
bool DialServer::getFriendlyName( string& name )
{
bool retval = false;
if( !m_ddxml.empty() )
{
string friendlyName = "<friendlyName>";
size_t pos;
if( ( pos = m_ddxml.find( friendlyName ) ) != m_ddxml.npos )
{
string friendlyNameEnd = "</friendlyName>";
size_t end = m_ddxml.find( friendlyNameEnd );
name = m_ddxml.substr( pos + friendlyName.size(),
(end - (pos + friendlyName.size())) );
ATRACE("***Friendly name=%s***", name.c_str());
retval = true;
}
#ifdef DEBUG
else
{
ATRACE("Friendly name not found\n%s\n", m_ddxml.c_str());
}
#endif
}
return retval;
}
bool DialServer::getUuid( string& uuid )
{
bool retval = false;
if( !m_ddxml.empty() )
{
string udn = "<UDN>";
size_t pos;
if( ( pos = m_ddxml.find( udn ) ) != m_ddxml.npos )
{
string udnEnd = "</UDN>";
size_t end = m_ddxml.find( udnEnd );
uuid = m_ddxml.substr( pos + udn.size(),
(end - (pos + udn.size())) );
ATRACE("***UUID=%s***", uuid.c_str() );
retval = true;
}
#ifdef DEBUG
else
{
ATRACE("Friendly name not found\n%s\n", m_ddxml.c_str());
}
#endif
}
return retval;
}
int DialServer::launchApplication(
string &application,
string &payload,
string &responseHeaders,
string &responseBody )
{
ATRACE("%s: Launch %s\n", __FUNCTION__, application.c_str());
string appUrl = m_appsUrl;
sendCommand( appUrl.append(application), COMMAND_LAUNCH, payload, responseHeaders, responseBody);
return 0;
}
int DialServer::getStatus(
string &application,
string &responseHeaders,
string &responseBody )
{
ATRACE("%s: GetStatus %s\n", __FUNCTION__, application.c_str());
string emptyPayload;
string appUrl = m_appsUrl;
sendCommand( appUrl.append(application), COMMAND_STATUS, emptyPayload, responseHeaders, responseBody );
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;
}

181
client/DialServer.h Normal file
View File

@@ -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 <string>
#include <vector>
using namespace std;
class DialServer
{
public:
/**
* Dial Server ctor
*
* @param[in] location dd.xml LOCATION header
* @param[in] appsUrl Parsed out Application URL
* @param[in] location dd.xml LOCATION header
* empty string to find any server
*
*/
DialServer( string location, string appsUrl, string dd_xml ) :
m_location(location),
m_appsUrl(appsUrl),
found(true),
m_ddxml(dd_xml)
{}
~DialServer() { ATRACE("%s\n", __FUNCTION__); }
/**
* Get the DIAL Server location
*
* @return Location of the server (http://<IP_ADDR>:<PORT>/dd.xml)
*/
string getLocation() { return m_location; }
/**
* Get the DIAL Server IP address
*
* @return IP address of the server (X.X.X.X)
*/
string getIpAddress();
/**
* Get the DIAL REST endpoint
*
* @return Location of the server (http://<IP_ADDR>:<PORT>/apps)
*/
string getAppsUrl() { return m_appsUrl; }
/**
* Get the DIAL friendly name
*
* @return true if successful, false otherwise
*/
bool getFriendlyName( string& name );
/**
* Get the DIAL UUID
*
* @return true if successful, false otherwise
*/
bool getUuid( string& uuid );
/**
* Launch a DIAL application
*
* @param[in] application Name of the application to launch
* @param[in] payload launch POST data
* @param[out] responseHeaders Returns the HTTP response headers
* @param[out] responseBody Returns the HTTP response body
*
* @return 0 if successful, !0 otherwise
*/
int launchApplication(
string &application,
string &payload,
string &responseHeaders,
string &responseBody );
/**
* Get the status of a DIAL application
*
* @param[in] application Name of the application to query
* @param[out] responseHeaders Returns the HTTP response headers
* @param[out] responseBody Returns the HTTP response body
*
* @return 0 if successful, !0 otherwise
*/
int getStatus(
string &application,
string &responseHeaders,
string &responseBody );
/**
* Stop an application.
*
* @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

5
client/build.sh Executable file
View File

@@ -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

View File

@@ -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

298
client/main.cpp Normal file
View File

@@ -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 <string>
#include "DialDiscovery.h"
#include "DialConformance.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
using namespace std;
static DialDiscovery* gpDiscovery;
static bool gUseMenu = true;
// TODO: Make it possible to pass applications from the command line
static vector<string> gAppList;
static string gOutputFile;
static string gInputFile;
// IP address of the DIAL server
static string gIpAddress;
static void printServerList( vector<DialServer*> list )
{
int i;
vector<DialServer*>::iterator it;
for( i = 0, it = list.begin(); it < list.end(); it++, i++ )
{
string uuid, name;
(*it)->getFriendlyName( name );
(*it)->getUuid( uuid );
printf("%Zu: Server IP[%s] UUID[%s] FriendlyName[%s] \n",
i+1, (*it)->getIpAddress().c_str(),
uuid.c_str(), name.c_str() );
}
}
static DialServer* getServerFromUser( vector<DialServer*> list )
{
DialServer* pServer;
// show a list to the user
if( list.size() > 1 )
{
char buf[80] = {0,};
vector<DialServer*>::iterator it;
printf("Found Multiple servers\n");
printServerList(list);
printf("Enter server: ");
scanf("%s", buf);
unsigned int server = atoi(buf);
assert( server > 0 && server <= list.size() );
pServer = list[server-1];
}
else
{
pServer = list.front();
}
return pServer;
}
static void runConformance()
{
vector<DialServer*> list;
gpDiscovery->getServerList(list);
if( list.size() )
{
DialServer *pServer = NULL;
if( !gIpAddress.empty() )
{
pServer = NULL;
vector<DialServer*>::iterator it;
for( it = list.begin(); it < list.end(); it ++ )
{
if( gIpAddress.compare((*it)->getIpAddress()) == 0 )
{
ATRACE("Found server %s in the list of servers\n",
gIpAddress.c_str() );
pServer = (*it);
break;
}
}
}
else
{
pServer = getServerFromUser( list );
}
if( pServer )
{
string name;
bool serverExists = pServer->getFriendlyName(name);
assert( serverExists );
string uuid;
pServer->getUuid( uuid );
printf("\nRunning conformance against: IP[%s] UUID[%s] FriendlyName[%s] \n",
pServer->getIpAddress().c_str(),
uuid.c_str(), name.c_str() );
DialConformance::instance()->run(
pServer,
gAppList,
gInputFile,
gOutputFile );
}
else
{
printf("DIAL server not found\n");
printf("%Zu available server(s): \n", list.size());
printServerList(list);
}
}
else
{
printf("No servers available\n");
}
}
int handleUser(DialDiscovery *pDial) {
int processInput = 1;
char buf[80];
vector<DialServer*> list;
pDial->getServerList(list);
if( list.size() == 0 )
{
printf("No servers available\n");
return 1;
}
DialServer* pServer = getServerFromUser( list );
while(processInput)
{
string responseHeaders, responseBody, payload;
string 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 <option>\n"
" Option Parameter Description\n"
" -h none Usage menu\n"
" -s filename (optional) Run conformance test. Use filename as\n"
" the input, if provided\n"
" -o filename Reporter output file (./report.html)\n"
" -a ip_address IP address of DIAL server (used for conformance\n"
" testing)\n"
"\n";
inline void notSupported( string s )
{
printf( "%s not supported", s.c_str() );
printf( "%s\n", usage );
exit(0);
}
int parseArgs( int argc, char* argv[] )
{
for( int i = 1; i < argc; i++ )
{
string input(argv[i]);
if( input[0] != '-' ) notSupported(input);
switch(input[1])
{
case 's':
gUseMenu = false;
if( argv[i+1] != NULL && argv[i+1][0] != '-' )
{
//filename provided
gInputFile = argv[++i];
}
break;
case 'o':
gOutputFile = argv[++i];
break;
case 'a':
gIpAddress = argv[++i];
break;
case 'h':
printf("%s", usage);
exit(0);
break;
default:
notSupported(input);
}
}
return 0;
}
int main(int argc, char* argv[]) {
parseArgs(argc, argv);
gpDiscovery = DialDiscovery::create();
gpDiscovery->init();
DialConformance::create();
// Sleep for 2 seconds to allow DIAL servers to response to MSEARCH.
sleep(2);
if ( gUseMenu )
{
return handleUser(gpDiscovery);
}
// not using the menu, just run the conformance test.
runConformance();
return 0;
}

14
client/makefile Normal file
View File

@@ -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

8
makefile Normal file
View File

@@ -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

2
server/build.sh Executable file
View File

@@ -0,0 +1,2 @@
export TARGET=/usr/local/i686-netflix-linux-gnu-4.2/bin/i686-netflix-linux-gnu-
make

87
server/dial_data.c Normal file
View File

@@ -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 <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
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;
}

63
server/dial_data.h Normal file
View File

@@ -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_ */

86
server/dial_options.h Normal file
View File

@@ -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

627
server/dial_server.c Normal file
View File

@@ -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 <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#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"
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
"<service xmlns=\"urn:dial-multiscreen-org:schemas:dial\" dialVer=%s>\r\n"
" <name>%s</name>\r\n"
" <options allowStop=\"%s\"/>\r\n"
" <state>%s</state>\r\n"
"%s"
" <additionalData>\n"
"%s"
"\n </additionalData>\n"
"</service>\r\n",
origin_header,
DIAL_VERSION,
app->name,
canStop ? "true" : "false",
app->state ? "running" : "stopped",
app->state == kDIALStatusStopped ?
"" : " <link rel=\"run\" href=\"run\"/>\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;
}

158
server/dial_server.h Normal file
View File

@@ -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 <netinet/in.h>
/*
* 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_

470
server/main.c Normal file
View File

@@ -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 <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <dirent.h>
#include <regex.h>
#include "dial_server.h"
#include "dial_options.h"
#include <signal.h>
#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/<PID>/comm. and /proc/<PID>/cmdline to find it's command (executable
* name) and command line (if needed).
* Implementors can override this function with an equivalent.
*/
static int isAppRunning( char *pzName, char *pzCommandPattern ) {
DIR* proc_fd = opendir("/proc");
if( proc_fd != NULL ) {
struct dirent* procEntry;
while((procEntry=readdir(proc_fd)) != NULL) {
if( doesMatch( "^[0-9][0-9]*$", procEntry->d_name ) ) {
char exePath[64] = {0,};
char link[256] = {0,};
char cmdlinePath[64] = {0,};
char buffer[1024] = {0,};
int len;
sprintf( exePath, "/proc/%s/exe", procEntry->d_name);
sprintf( cmdlinePath, "/proc/%s/cmdline", procEntry->d_name);
if( (len = readlink( exePath, link, sizeof(link)-1)) != -1 ) {
char executable[256] = {0,};
strcat( executable, pzName );
strcat( executable, "$" );
// TODO: Make this search for EOL to prevent false positivies
if( !doesMatch( executable, link ) ) {
continue;
}
// else //fall through, we found it
}
else continue;
if (pzCommandPattern != NULL) {
FILE *cmdline = fopen(cmdlinePath, "r");
if (!cmdline) {
continue;
}
if (fgets(buffer, 1024, cmdline) == NULL) {
fclose(cmdline);
continue;
}
fclose(cmdline);
if (!doesMatch( pzCommandPattern, buffer )) {
continue;
}
}
closedir(proc_fd);
return atoi(procEntry->d_name);
}
}
closedir(proc_fd);
} else {
printf("/proc failed to open\n");
}
return 0;
}
static pid_t runApplication( const char * const args[], DIAL_run_t *run_id ) {
pid_t pid = fork();
if (pid != -1) {
if (!pid) { // child
putenv(spDataDir);
printf("Execute:\n");
for(int i = 0; args[i]; ++i) {
printf(" %d) %s\n", i, args[i]);
}
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 <options>\n");
printf("options:\n");
for( i = 0; i < numberOfOptions; i++ )
{
printf(" %s|%s [value]: %s\n",
gDialOptions[i].pOption,
gDialOptions[i].pLongOption,
gDialOptions[i].pOptionDescription );
}
}
static void setValue( char * pSource, char dest[] )
{
// Destination is always one of our static buffers with size BUFSIZE
memset( dest, 0, BUFSIZE );
memcpy( dest, pSource, strlen(pSource) );
}
static void setDataDir(char *pData)
{
setValue( spNfDataDir, spDataDir );
strcat(spDataDir, pData);
}
void runDial(void)
{
DIALServer *ds;
ds = DIAL_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;
}

25
server/makefile Normal file
View File

@@ -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

956
server/mongoose.c Normal file
View File

@@ -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 <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#ifndef BUFSIZ
#define BUFSIZ 4096
#endif
#define MAX_REQUEST_SIZE 4096
#define NUM_THREADS 4
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <stdint.h>
#include <inttypes.h>
#include <netdb.h>
#include <unistd.h>
#include <pthread.h>
#define ERRNO errno
#define INVALID_SOCKET (-1)
typedef int SOCKET;
#include "mongoose.h"
#define MONGOOSE_VERSION "3.0"
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#if defined(DEBUG)
#define DEBUG_TRACE(x) do { \
flockfile(stdout); \
printf("*** %lu.%p.%s.%d: ", \
(unsigned long) time(NULL), (void *) pthread_self(), \
__func__, __LINE__); \
printf x; \
putchar('\n'); \
fflush(stdout); \
funlockfile(stdout); \
} while (0)
#else
#define DEBUG_TRACE(x)
#endif // DEBUG
typedef void * (*mg_thread_func_t)(void *);
// Describes a socket which was accept()-ed by the master thread and queued for
// future handling by the worker thread.
struct socket {
SOCKET sock; // Listening socket
struct sockaddr_in local_addr; // Local socket address
struct sockaddr_in remote_addr; // Remote socket address
};
struct mg_context {
volatile int stop_flag; // Should we stop event loop
mg_callback_t user_callback; // User-defined callback function
void *user_data; // User-defined data
SOCKET local_socket;
struct sockaddr_in local_address;
volatile int num_threads; // Number of threads
pthread_mutex_t mutex; // Protects (max|num)_threads
pthread_cond_t cond; // Condvar for tracking workers terminations
struct socket queue[20]; // Accepted sockets
volatile int sq_head; // Head of the socket queue
volatile int sq_tail; // Tail of the socket queue
pthread_cond_t sq_full; // Singaled when socket is produced
pthread_cond_t sq_empty; // Signaled when socket is consumed
};
struct mg_connection {
struct mg_request_info request_info;
struct mg_context *ctx;
struct socket client; // Connected client
time_t birth_time; // Time connection was accepted
int64_t num_bytes_sent; // Total bytes sent to client
int64_t content_len; // Content-Length header value
int64_t consumed_content; // How many bytes of content is already read
char *buf; // Buffer for received data
int buf_size; // Buffer size
int request_len; // Size of the request + headers in a buffer
int data_len; // Total size of data in a buffer
};
static void *call_user(struct mg_connection *conn, enum mg_event event) {
conn->request_info.user_data = conn->ctx->user_data;
return conn->ctx->user_callback == NULL ? NULL :
conn->ctx->user_callback(event, conn, &conn->request_info);
}
// Print error message to the opened error log stream.
static void cry(struct mg_connection *conn, const char *fmt, ...) {
char buf[BUFSIZ];
va_list ap;
va_start(ap, fmt);
(void) vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
// Do not lock when getting the callback value, here and below.
// I suppose this is fine, since function cannot disappear in the
// same way string option can.
conn->request_info.log_message = buf;
if (call_user(conn, MG_EVENT_LOG) == NULL) {
DEBUG_TRACE(("[%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;
}

160
server/mongoose.h Normal file
View File

@@ -0,0 +1,160 @@
// Copyright (c) 2004-2010 Sergey Lyubka
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// NOTE: This is a SEVERELY stripped down version of mongoose, which only
// supports GET, POST and DELETE HTTP commands, no CGI, no file or directory
// access, no ACLs or authentication, and no proxying and no SSL. HTTP Header
// limit is 16 instead of 64, as it's not supposed to be called from standard
// browsers. And most options are removed.
#ifndef MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_HEADER_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
struct mg_context; // Handle for the HTTP service itself
struct mg_connection; // Handle for the individual connection
// This structure contains information about the HTTP request.
struct mg_request_info {
void *user_data; // User-defined pointer passed to mg_start()
char *request_method; // "GET", "POST", etc
char *uri; // URL-decoded URI
char *http_version; // E.g. "1.0", "1.1"
char *query_string; // \0 - terminated
char *request_body; // \0 - terminated
char *log_message; // Mongoose error log message
struct sockaddr_in local_addr; // Our server's address for this connection
struct sockaddr_in remote_addr; // The remote address for this connection
int status_code; // HTTP reply status code
int num_headers; // Number of headers
struct mg_header {
char *name; // HTTP header name
char *value; // HTTP header value
} http_headers[16]; // Maximum 16 headers
};
// Various events on which user-defined function is called by Mongoose.
enum mg_event {
MG_NEW_REQUEST, // New HTTP request has arrived from the client
MG_HTTP_ERROR, // HTTP error must be returned to the client
MG_EVENT_LOG, // Mongoose logs an event, request_info.log_message
};
// Prototype for the user-defined function. Mongoose calls this function
// on every event mentioned above.
//
// Parameters:
// event: which event has been triggered.
// conn: opaque connection handler. Could be used to read, write data to the
// client, etc. See functions below that accept "mg_connection *".
// request_info: Information about HTTP request.
//
// Return:
// If handler returns non-NULL, that means that handler has processed the
// request by sending appropriate HTTP reply to the client. Mongoose treats
// the request as served.
// If callback returns NULL, that means that callback has not processed
// the request. Handler must not send any data to the client in this case.
// Mongoose proceeds with request handling as if nothing happened.
typedef void * (*mg_callback_t)(enum mg_event event,
struct mg_connection *conn,
const struct mg_request_info *request_info);
// Start web server.
//
// Parameters:
// callback: user defined event handling function or NULL.
//
// Example:
// struct mg_context *ctx = mg_start(&my_func, NULL);
//
// Please refer to http://code.google.com/p/mongoose/wiki/MongooseManual
// for the list of valid option and their possible values.
//
// Return:
// web server context, or NULL on error.
struct mg_context *mg_start(mg_callback_t callback, void *user_data, int port);
// Stop the web server.
//
// Must be called last, when an application wants to stop the web server and
// release all associated resources. This function blocks until all Mongoose
// threads are stopped. Context pointer becomes invalid.
void mg_stop(struct mg_context *);
// Send data to the client.
int mg_write(struct mg_connection *, const void *buf, size_t len);
// Send data to the browser using printf() semantics.
//
// Works exactly like mg_write(), but allows to do message formatting.
// Note that mg_printf() uses internal buffer of size IO_BUF_SIZE
// (8 Kb by default) as temporary message storage for formatting. Do not
// print data that is bigger than that, otherwise it will be truncated.
int mg_printf(struct mg_connection *, const char *fmt, ...);
// Read data from the remote end, return number of bytes read.
int mg_read(struct mg_connection *, void *buf, size_t len);
// Get the value of particular HTTP header.
//
// This is a helper function. It traverses request_info->http_headers array,
// and if the header is present in the array, returns its value. If it is
// not present, NULL is returned.
const char *mg_get_header(const struct mg_connection *, const char *name);
// Return Mongoose version.
const char *mg_version(void);
// MD5 hash given strings.
// Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of
// asciiz strings. When function returns, buf will contain human-readable
// MD5 hash. Example:
// char buf[33];
// mg_md5(buf, "aa", "bb", NULL);
void mg_md5(char *buf, ...);
void mg_send_http_error(struct mg_connection *conn, int status,
const char *reason, const char *fmt, ...);
int mg_get_listen_addr(struct mg_context *ctx, struct sockaddr *addr,
socklen_t *addrlen);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // MONGOOSE_HEADER_INCLUDED

242
server/quick_ssdp.c Normal file
View File

@@ -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 <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include "mongoose.h"
// TODO: Partners should define this port
#define SSDP_PORT (56790)
static 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[] = ""
"<?xml version=\"1.0\"?>"
"<root"
" xmlns=\"urn:schemas-upnp-org:device-1-0\""
" xmlns:r=\"urn:restful-tv-org:schemas:upnp-dd\">"
" <specVersion>"
" <major>1</major>"
" <minor>0</minor>"
" </specVersion>"
" <device>"
" <deviceType>urn:schemas-upnp-org:device:tvdevice:1</deviceType>"
" <friendlyName>%s</friendlyName>"
" <manufacturer> </manufacturer>"
" <modelName>%s</modelName>"
" <UDN>uuid:%s</UDN>"
" </device>"
"</root>";
// TODO: Partners should get the UUID from the system and insert here.
static const char ssdp_reply[] = "HTTP/1.1 200 OK\r\n"
"LOCATION: http://%s:%d/dd.xml\r\n"
"CACHE-CONTROL: max-age=1800\r\n"
"EXT:\r\n"
"BOOTID.UPNP.ORG: 1\r\n"
"SERVER: Linux/2.6 UPnP/1.0 quick_ssdp/1.0\r\n"
"ST: urn:dial-multiscreen-org:service:dial:1\r\n"
"USN: uuid:%s::"
"urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
static int dial_port = 0;
static int my_port = 0;
static char friendly_name[256];
static char uuid[256];
static char model_name[256];
static struct mg_context *ctx;
static void *request_handler(enum mg_event event,
struct mg_connection *conn,
const struct mg_request_info *request_info) {
if (event == MG_NEW_REQUEST) {
if (!strcmp(request_info->uri, "/dd.xml") &&
!strcmp(request_info->request_method, "GET")) {
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
"Content-Type: application/xml\r\n"
"Application-URL: http://%s:%d/apps/\r\n"
"\r\n", ip_addr, dial_port);
mg_printf(conn, ddxml, friendly_name, model_name, uuid);
} else {
mg_send_http_error(conn, 404, "Not Found", "Not Found");
}
return "done";
}
return NULL;
}
static void get_local_address() {
struct ifconf ifc;
char buf[4096];
int s, i;
if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) {
perror("socket");
exit(1);
}
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (0 > ioctl(s, SIOCGIFCONF, &ifc)) {
perror("SIOCGIFCONF");
exit(1);
}
if (ifc.ifc_len == sizeof(buf)) {
fprintf(stderr, "SIOCGIFCONF output too long");
exit(1);
}
close(s);
for (i = 0; i < ifc.ifc_len/sizeof(ifc.ifc_req[0]); i++) {
strcpy(ip_addr,
inet_ntoa(((struct sockaddr_in *)(&ifc.ifc_req[i].ifr_addr))->sin_addr));
// exit if we found a non-loopback address
if (strcmp("127.0.0.1", ip_addr)) {
break;
}
}
}
static void handle_mcast() {
int s, one = 1, bytes;
socklen_t addrlen;
struct sockaddr_in saddr;
struct ip_mreq mreq;
char send_buf[sizeof(ssdp_reply) + INET_ADDRSTRLEN + 256 + 256] = {0,};
int send_size;
send_size = snprintf(send_buf, sizeof(send_buf), ssdp_reply, ip_addr, my_port, uuid);
if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) {
perror("socket");
exit(1);
}
if (-1 == setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
perror("reuseaddr");
exit(1);
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = inet_addr("239.255.255.250");
saddr.sin_port = htons(1900);
if (-1 == bind(s, (struct sockaddr *)&saddr, sizeof(saddr))) {
perror("bind");
exit(1);
}
mreq.imr_multiaddr.s_addr = inet_addr("239.255.255.250");
mreq.imr_interface.s_addr = inet_addr(ip_addr);
if (-1 == setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mreq, sizeof(mreq))) {
perror("add_membership");
exit(1);
}
//printf("Starting Multicast handling on 239.255.255.250\n");
while (!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);
}

18
server/tests/makefile Normal file
View File

@@ -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

42
server/tests/run_tests.c Normal file
View File

@@ -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 <stdio.h>
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;
}

53
server/tests/test.h Normal file
View File

@@ -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_ */

View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
<head>
<title>DIAL Security Test CORS</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
<script>
$(document).ready(function(){
var makePost=function(app){
console.log("make dial post...");
var ip = $("#ipAddress").val();
var port = $("#dialPort").val();
var urlStr = "http://"+ip+":"+port+"/apps/"+app;
console.log(urlStr);
$("#status").text("posted to "+urlStr);
$.ajax({
type: "POST",
url: urlStr,
data: "v=QH2-TGUlwu4",
dataType: "text/plain; charset=\"utf-8\""
});
};
$( "#testNetflix" ).click(function(){
console.log("testing Netflix");
makePost("Netflix");
});
$( "#testYoutube" ).click(function(){
console.log("testing Youtube");
makePost("Youtube");
});
});
</script>
</head>
<body>
<input id="ipAddress" type="text" name="ip" value="192.168.1.100">
<input id="dialPort" type="text" name="port" value="8060">
<p>
<button id="testNetflix">Test Netflix</button>
<button id="testYoutube">Test Youtube</button>
</p>
<p>
<div id="status"></div>
</p>
</body>
</html>

71
server/tests/test_cors.sh Executable file
View File

@@ -0,0 +1,71 @@
#!/bin/bash
if [ $# -eq 0 ]; then
echo "usage: `basename $0`: <DIAL server ip address> <port> "
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."

View File

@@ -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 <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#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();
}

View File

@@ -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_ */

113
server/tests/test_url_lib.c Normal file
View File

@@ -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 <stdio.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#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();
}

View File

@@ -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_ */

183
server/url_lib.c Normal file
View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
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, "&amp;", max_size - current_size);
current_size += 5;
break;
case '\"':
dst = smartstrcat(dst, "&quot;", max_size - current_size);
current_size += 6;
break;
case '\'':
dst = smartstrcat(dst, "&apos;", max_size - current_size);
current_size += 6;
break;
case '<':
dst = smartstrcat(dst, "&lt;", max_size - current_size);
current_size += 4;
break;
case '>':
dst = smartstrcat(dst, "&gt;", 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;
}

61
server/url_lib.h Normal file
View File

@@ -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 <stddef.h>
/**
* 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/<app_name>/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_