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

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