Initial drop from DIAL-1.0.2-1676049.tar.gz
diff --git a/Changelog b/Changelog
new file mode 100644
index 0000000..1b47bfc
--- /dev/null
+++ b/Changelog
@@ -0,0 +1,40 @@
+---------------------------------------------------------------------
+Version 1.0.2 (1673785)
+---------------------------------------------------------------------
+Server changes:
+    - Fix an application state bug when the application is launched
+      without using DIAL.
+    - Remove unnecessary functionality from Mongoose.
+    - Use constant values for DIAL port.
+    - Propery reap child processes.
+
+Client (test tool) changes:
+   - None
+
+---------------------------------------------------------------------
+Version 1.0.1 (1601607)
+---------------------------------------------------------------------
+General Changes: 
+    - Simplified directory structure. 
+    - Version.txt changed to Version.h.
+    - moved main Makefile into src dir.
+
+Server changes: 
+    - Server configuration options moved into dial_options.h.
+    - Added logic to walk/proc to see if the named app (eg Netflix) 
+      is running before attempting to start/kill the app.  
+    - Improved payload handling: 
+        - Explicitly define payload size. 
+        - Added logic to check validity of payload, and send HTTP 400
+          "bad request" responses to requests with improper payloads.
+        - Added URL encoding for payloads. 
+    - Added logic to compare launch payload on subsequent launch 
+      requests. 
+
+Client (test tool) changes: 
+    - small change to printed format of server names. 
+
+---------------------------------------------------------------------
+Version 1.0 (1416215)
+---------------------------------------------------------------------
+Initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cfc42fa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Netflix, Inc.
+All rights reserved.
+Redistribution and use in source and binary forms, with or without 
+modification, are permitted provided that the following conditions are met:
+·         Redistributions of source code must retain the above copyright 
+          notice, this list of conditions and the following disclaimer.
+·         Redistributions in binary form must reproduce the above copyright 
+          notice, this list of conditions and the following disclaimer in the 
+          documentation and/or other materials provided with the distribution.
+THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY 
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README b/README
new file mode 100644
index 0000000..be020f5
--- /dev/null
+++ b/README
@@ -0,0 +1,89 @@
+--------------------------------------------------------------------------------
+Building the DIAL server
+--------------------------------------------------------------------------------
+1) Define the TARGET environment variable to point to the CC compiler prefix
+   for your target platform. 
+
+   For example, using the NRDP-3.2 x86 desktop toolchain, the CC is located at: 
+   /usr/local/i686-netflix-linux-gnu-3.2/bin/i686-netflix-linux-gnu-gcc
+
+2) Run make, passing in your TARGET value. 
+
+   For example:  
+   TARGET=/usr/local/i686-netflix-linux-gnu-3.2/bin/i686-netflix-linux-gnu- make
+
+--------------------------------------------------------------------------------
+Running the DIAL server
+--------------------------------------------------------------------------------
+The DIAL server should be started as a service, after the platform's networking
+has been initialized, and it should remain running at all times (a daemon 
+process in the system).
+
+Sample SysV init scripts are available on the Netflix NRD Partner portal. 
+
+--------------------------------------------------------------------------------
+Building the DIAL client
+--------------------------------------------------------------------------------
+The DIAL client is a standalone C++ console application you can use to test 
+a running DIAL server implementation on your device. Unlike the server, which
+is built for, and meant to run on your device, the client is meant to run on 
+your desktop (development) machine. 
+
+The DIAL client uses CURL to send HTTP REST commands to the DIAL server, so to
+build the client, you need to ensure that the CURL dependecies are 
+defined properly. If you are a Netflix NRD partner and have the NRD Platform
+desktop toolchain installed, it includes libcurl. To build against that version,
+use the following build command:
+
+TARGET=/usr/local/i686-netflix-linux-gnu-3.2/bin/i686-netflix-linux-gnu- \
+LDFLAGS="-L/usr/local/i686-netflix-linux-gnu-3.2/netflix/lib \
+-Wl,-rpath,/usr/local/i686-netflix-linux-gnu-3.2/netflix/lib" \
+INCLUDES=-I/usr/local/i686-netflix-linux-gnu-3.2/netflix/include \
+make
+
+Alternatively, you can build against a different, current version of libcurl. 
+Adjust the INCLUDES and LDFLAGS definitions to point to your actual libcurl
+header and library locations. In most cases, you can omit the TARGET define. 
+
+Note: the -rpath argument passed to LDFLAGS specifies the libcurl location
+to the runtime linker. 
+
+--------------------------------------------------------------------------------
+Running the DIAL client in interactive (menu) mode
+--------------------------------------------------------------------------------
+1) The DIAL client application must be running in the same subnet as the 
+   DIAL server.
+
+2) Start the client: ./dialclient (or ./dialclient -m)  
+   The on-screen menu will list all available actions.
+
+--------------------------------------------------------------------------------
+Running the DIAL client in conformance test (non-interactive) mode
+--------------------------------------------------------------------------------
+1) The DIAL client application must be running in the same subnet as the 
+   DIAL server.
+
+2) Start the client: 
+   ./dialclient -i [input-file] [-o output-file] [-a server-IP-addr]   
+
+   In script-driven mode, the client reads in an input-file, executes the
+   instructions in the input-file, and generates a 
+   report. The default file locations (which can be overridden) are: 
+      ./dialclient_input.txt 
+      ./report.html
+
+--------------------------------------------------------------------------------
+DIAL client Usage
+--------------------------------------------------------------------------------
+When running the DIAL client, you have the following options
+usage: dialclient <option>
+
+Option Parameter          Description
+ -h     none               Usage menu
+ -m     none               Use menu
+ -o     filename           Reporter output file (./report.html)
+ -i     filename           Input File (./dialclient_input.txt)
+ -a     ip_address         IP addr of DIAL server (used for conformance testing)
+
+If you do not provide an ip_address and multiple servers are discovered, the 
+client will prompt you to select a server.
diff --git a/Version.h b/Version.h
new file mode 100644
index 0000000..5c8762c
--- /dev/null
+++ b/Version.h
@@ -0,0 +1,38 @@
+/*
+ * (c) 1997-2012 Netflix, Inc.  All content herein is protected by
+ * U.S. copyright and other applicable intellectual property laws and
+ * may not be copied without the express permission of Netflix, Inc.,
+ * which reserves all rights.  Reuse of any of this content for any
+ * purpose without the permission of Netflix, Inc. is strictly
+ * prohibited.
+ */
+
+#ifndef DIAL_VERSION_H
+#define DIAL_VERSION_H
+
+#undef DIAL_VERSION_MAJOR
+#define DIAL_VERSION_MAJOR 1
+
+#undef DIAL_VERSION_MINOR
+#define DIAL_VERSION_MINOR 0
+
+#undef DIAL_VERSION_PATCH
+#define DIAL_VERSION_PATCH 2
+
+#undef DIAL_VERSION_NUMBER_STR2
+#define DIAL_VERSION_NUMBER_STR2(M) #M
+#undef DIAL_VERSION_NUMBER_STR
+#define DIAL_VERSION_NUMBER_STR(M) DIAL_VERSION_NUMBER_STR2(M)
+
+#undef DIAL_VERSION_NUMBER
+#define DIAL_VERSION_NUMBER DIAL_VERSION_NUMBER_STR(DIAL_VERSION_MAJOR) "." DIAL_VERSION_NUMBER_STR(DIAL_VERSION_MINOR) "." DIAL_VERSION_NUMBER_STR(DIAL_VERSION_PATCH)
+
+#undef DIAL_VERSION_STRING
+#ifdef DIAL_VERSION_SUFFIX
+# define DIAL_VERSION_STRING DIAL_VERSION_NUMBER "-" DIAL_VERSION_SUFFIX
+#else
+# define DIAL_VERSION_STRING DIAL_VERSION_NUMBER
+#endif
+
+
+#endif
diff --git a/src/client/DialClientInput.cpp b/src/client/DialClientInput.cpp
new file mode 100644
index 0000000..4192b86
--- /dev/null
+++ b/src/client/DialClientInput.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "DialClientInput.h"
+#include "DialServer.h"
+#include <iostream>
+#include <fstream>
+
+using namespace std;
+
+bool DialClientInput::init(std::string file)
+{
+    if( file.empty() ) file = DialClientInput::getDefaultFilename();
+    ATRACE("DialClientInput::%s, opening %s\n", __FUNCTION__, file.c_str());
+    string line;
+    ifstream myfile (file.c_str());
+    if (myfile.is_open())
+    {
+        // first fill _actions with commands from the input file
+        while ( myfile.good() )
+        {
+            getline (myfile,line);
+            if( line[0] != '#' && !line.empty() )
+            {
+                if( line.find("addApplication") != line.npos )
+                {
+                    // add an application
+                    size_t pos = line.find_first_of('=');
+                    _applist.push_back( line.substr(pos+1, line.length()));
+                    ATRACE("Adding app: %s\n", line.substr(pos+1, line.length()).c_str());
+                }
+                else if( line.find("addErrorApplication") != line.npos )
+                {
+                    // add an application
+                    size_t pos = line.find_first_of('=');
+                    _errorapplist.push_back( line.substr(pos+1, line.length()));
+                    ATRACE("Adding error app: %s\n", line.substr(pos+1, line.length()).c_str());
+                }
+                else
+                {
+                    // add a command
+                    size_t pos = line.find_first_of(" ");
+                    std::string params = line.substr(pos+1, line.length());
+                    std::pair<std::string, std::string> 
+                            action_to_push(line.substr(0, pos), 
+                            pos == line.npos ? "":params);
+                    _actions.push_back(action_to_push);
+                    ATRACE("command: %s params: %s\n", 
+                            line.substr(0, pos).c_str(), params.c_str() );
+                }
+            }
+#ifdef DEBUG
+            //else ATRACE("COMMENT: %s\n", line.c_str());
+#endif
+        }
+        myfile.close();
+    }
+    else 
+    {
+        fprintf(stderr, "Unable to open file\n");
+        return false;
+    }
+    return true;
+}
+
+bool DialClientInput::addApplication( string& application )
+{
+    // see if the application exits
+    vector<string>::iterator it;
+    for( it = _applist.begin(); it < _applist.end(); it ++ )
+        if( !((*it).compare( application )) ) break;
+
+    // if not, add it
+    if( it < _applist.end() ) _applist.push_back(application);
+    else return false;  // already in the list
+
+    return true;
+}
+
+bool DialClientInput::getNextAction( string& command, vector<string>& parameters )
+{
+    if (_actions.empty()) return false;
+
+    pair<string, string> action = _actions.front();
+    _actions.erase(_actions.begin());
+
+    command = action.first;
+    string params = action.second;
+    size_t pos = params.find_first_of(" "), start = 0;
+    // Parse out the parameters from the string
+    while (1)
+    {
+        parameters.push_back(params.substr( start, pos-start ));
+        if( pos == params.npos ) break;
+        start = pos+1;
+        pos = params.find_first_of(" ", start);
+    }
+
+    return true;
+}
+
+void DialClientInput::getApplicationList( vector<string> &list )
+{
+    list = _applist;
+}
+
+
+void DialClientInput::getErrorApplicationList(vector<string> &list)
+{
+    list = _errorapplist;
+}
+
diff --git a/src/client/DialClientInput.h b/src/client/DialClientInput.h
new file mode 100644
index 0000000..b31f546
--- /dev/null
+++ b/src/client/DialClientInput.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIALCLIENTINPUT_H
+#define DIALCLIENTINPUT_H
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+class DialClientInput
+{
+public:
+    DialClientInput() {}
+    ~DialClientInput() {}
+
+    /**
+     * Called to initialize
+     * 
+     * @param[in] file Name of the file to load
+     * @return true if successful, false otherwise
+     */
+    bool init( string file );
+
+    /**
+     * The DialClient application can use this to add applications passed on 
+     * the command line. If the application is already in the list, it will 
+     * drop the add.
+     * 
+     * @param[in] application Name of the application to add.
+     *
+     * @return true if successful, false otherwise
+     */
+    bool addApplication( string &application );
+
+    /**
+     * Get the next action to execute
+     * 
+     * @param[out] command Command to execute
+     * @param[out] parameters Parameters for the command
+     */
+    bool getNextAction( string& command, vector<string>& parameters );
+
+    /**
+     * Get a list of valid applications defined in the input file.
+     * 
+     * @param[out] list List of valid applications
+     */
+    void getApplicationList(vector<string> &list);
+
+    /**
+     * Get a list of applications used for error tests (should not exist).
+     * 
+     * @param[out] list List of error applications
+     */
+    void getErrorApplicationList(vector<string> &list);
+
+    static string getDefaultFilename() { return "./dialclient_input.txt"; }
+
+private:
+    vector<string>                  _applist;
+    vector<string>                  _errorapplist;
+    vector< pair<string,string> >   _actions;
+};
+
+#endif // DIALCLIENTINPUT_H
diff --git a/src/client/DialConformance.cpp b/src/client/DialConformance.cpp
new file mode 100644
index 0000000..cb18290
--- /dev/null
+++ b/src/client/DialConformance.cpp
@@ -0,0 +1,586 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+#include "DialConformance.h"
+#include <assert.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+// Number of milliseconds to sleep between launches when doing a launch=ALL
+#define LAUNCH_SLEEP 6000
+#define MAX_PARAMETER_LENGTH 60
+
+using namespace std;
+
+/* 
+ * This class will be reponsible for reporting the conformance test results.
+ * Requirements:
+ *      * Results should be in HTML
+ *      * Results should be easily readable
+ *      * Results should clearly distinguish between pass and fail
+ *      * If there is a failure, there should be ample information provided 
+ *      for debugging
+ */
+class OutputWriter
+{
+public:
+    OutputWriter( string fileName ) :
+    _filename(fileName),
+    _pCurrentTest(NULL),
+    _isComplete(false)
+    {
+    }
+    ~OutputWriter()
+    { }
+
+    void setOutputFile( string fileName )
+    {
+        _filename = fileName;
+    }
+
+    void setTest( string testName )
+    {
+        assert( _pCurrentTest == NULL );
+        _pCurrentTest = new Test(testName);
+        _tests.push_back(_pCurrentTest);
+    }
+    void clearTest() 
+    { 
+        // still holding a ref in the test vector.  That will get deleted
+        // when the test is complete
+        _pCurrentTest = NULL; 
+    }
+
+    void setError( string error ) { _pCurrentTest->addError(error); }
+    void setResult( bool result ) { _pCurrentTest->setResult( result ); }
+
+    void start( string& friendlyname, string& uuid, string ipaddress )
+    {
+        _file.open(_filename.c_str());
+        _file << "<html>\n<body>\n";
+        _file << "<font size=\"6\"><b>FriendlyName: " << friendlyname;
+        _file << "<br>\nIP: " << ipaddress;
+        _file << "<br>\nUUID: " << uuid;
+
+        {
+            time_t rawtime;
+            struct tm * timeinfo;
+            char buffer [80];
+            
+            time ( &rawtime );
+            timeinfo = localtime ( &rawtime );
+            
+            //strftime (buffer,80,"%I:%M%p.",timeinfo);
+            strftime (buffer,80,"%c",timeinfo);
+            _file << "<br>\nTime of Run: " << buffer << "</b></font>";
+        }
+        _file << "<table border=\"10\">\n";
+        _file << "<tr>\n";
+        _file << "<th> RESULT </th>";           //header 1
+        _file << "<th> Test Executed </th>";    //header 2
+        _file << "<th> Errors </th>";    //header 2
+        _file << "</tr>\n";
+    }
+
+    void complete()
+    {
+        vector<Test*>::iterator it;
+        for( it = _tests.begin(); it < _tests.end(); it++ )
+        {
+            write("<tr>");
+            // write the name and the result
+            stringstream result;
+            bool testResult = (*it)->getResult();
+            result << "<td><b> <font color=\"" << (testResult ? "green" : "red") << "\"> ";
+            result << (testResult ? "SUCCESS" : "FAIL") << " </b></font></td>\n<td>" << (*it)->getName() << "</td>";
+            write( result.str() );
+
+            // write errors if they exist
+            write("<td>");
+            vector<string> errors = (*it)->getErrors();
+            if( !errors.empty() )
+            {
+                vector<string>::iterator it2;
+                write("<b>");
+                for( it2 = errors.begin(); it2 < errors.end(); it2++ )
+                    write( (*it2) );
+                write("</b>");
+            }
+            else
+                write("NO ERROR");
+            write("</td>\n</tr>");
+
+            // delete the test
+            delete (*it);
+        }
+        _file << "</table></body>\n</html>\n";
+        _file.close();
+
+        _isComplete = true;
+    }
+
+private:
+    // Abstraction of a test
+    class Test
+    {
+    public:
+        Test(string& name) :
+            _testname(name),
+            _testPassed(false) {}
+        ~Test() {}
+
+        string getName()                {   return _testname; }
+        void setResult( bool result )   {   _testPassed = result; }
+        bool getResult()                {   return _testPassed; }
+
+        void addError( string error )   {   _errors.push_back(error); }
+        vector<string> getErrors()      {   return _errors; }
+
+    private:
+        string _testname;
+        bool _testPassed;
+        vector<string> _errors;
+    };
+
+    string _filename;
+    ofstream _file;
+    vector<Test*> _tests;
+    Test* _pCurrentTest;
+    bool _isComplete;
+
+    int write( string line )
+    {
+        _file << line.c_str() << "\n";
+        return line.length()+1;
+    }
+};
+
+static const string defaultOutputFile = "./report.html";
+static OutputWriter gWriter(defaultOutputFile);
+
+DialConformance *DialConformance::sConformance = 0;
+
+DialConformance* DialConformance::create(void)
+{
+    assert( DialConformance::sConformance == 0 );
+    return new DialConformance();
+}
+
+DialConformance* DialConformance::instance(void)
+{
+    return DialConformance::sConformance;
+}
+
+DialConformance::DialConformance() 
+{
+    assert( DialConformance::sConformance == 0 );
+    DialConformance::sConformance = this;
+}
+
+DialConformance::~DialConformance()
+{
+    assert( DialConformance::sConformance == this );
+    DialConformance::sConformance = 0;
+}
+
+void DialConformance::extractParamValue( 
+    string& param, string& value )
+{
+    // TODO: Add support for quotes after the equals
+    size_t posBegin = param.find("=");
+    value = param.substr(posBegin+1, param.length()-posBegin);
+}
+
+bool DialConformance::validateParams( vector<string>& params, 
+            string& responseHeaders, string& responseBody )
+{
+    bool testPassed = true;
+    vector<string>::iterator it;
+    for( it = params.begin(); it < params.end() && testPassed == true; it++ )
+    {
+        ATRACE("%s:: params:%s \n", __FUNCTION__, (*it).c_str());
+        if( (*it).find("httpresponse=") != (*it).npos )
+            testPassed = validateHttpResponse( responseHeaders, *it );
+        if( (*it).find("httpresponseheader=") != (*it).npos )
+            testPassed = validateHttpHeaders( responseHeaders, *it );
+        if( (*it).find("resultbody=") != (*it).npos )
+            testPassed = validateResponseBody( responseBody, *it );
+    }
+    return testPassed;
+}
+
+
+bool DialConformance::validateHttpResponse( 
+    string& headers, string& param )
+{
+    bool retval = false;
+    string responseCode;
+    extractParamValue( param, responseCode );
+
+    ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
+    if( headers.find(responseCode) != headers.npos )
+    {
+        ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
+        retval = true;
+    }
+    else
+    {
+        ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
+        stringstream error;
+        error << responseCode << " not found\n";
+        gWriter.setError( error.str() );
+    }
+    return retval;
+}
+
+bool DialConformance::validateHttpHeaders( 
+    string& headers, string& param )
+{
+    bool retval = false;
+    string responseCode;
+    extractParamValue( param, responseCode ); 
+
+    ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
+    if( headers.find(responseCode) != headers.npos )
+    {
+        ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
+        retval = true;
+    }
+    else
+    {
+        ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
+        stringstream error;
+        error << responseCode << " not found\n";
+        gWriter.setError( error.str() );
+    }
+    return retval;
+}
+
+bool DialConformance::validateResponseBody( 
+    string& body, string& param )
+{
+    bool retval = false;
+    string responseCode;
+    extractParamValue( param, responseCode ); 
+
+    ATRACE("%s: Searching for %s\n", __FUNCTION__, responseCode.c_str());
+    if( body.find(responseCode) != body.npos )
+    {
+        ATRACE("%s: %s FOUND\n", __FUNCTION__, responseCode.c_str());
+        retval = true;
+    }
+    else
+    {
+        ATRACE("%s: %s not found\n", __FUNCTION__, responseCode.c_str());
+        stringstream error;
+        error << responseCode << " not found\n";
+        error << "response: " << body;
+        gWriter.setError( error.str() );
+    }
+    return retval;
+}
+
+void DialConformance::getPayload(vector<string>& params, string& payload )
+{
+    vector<string>::iterator it;
+    for( it = params.begin(); it < params.end(); it++ )
+    {
+        if( (*it).find("param") != (*it).npos )
+        {
+            // Check to see if the param is using quotes.  If it is, split on
+            // that.  If not, split on a space
+            size_t posEnd, pos = (*it).find( "=" );
+            if( (*it)[pos+1] == '\"' )
+            {
+                posEnd = (*it).find( "\"", pos+2 );
+                payload = (*it).substr( pos+2, posEnd-(pos+2) );
+            }
+            else
+            {
+                posEnd = (*it).find( " ", pos );
+                payload = (*it).substr( pos+1, posEnd );
+            }
+            ATRACE("payload = %s\n", payload.c_str());
+        }
+    }
+}
+
+bool DialConformance::execute_launch( Application* pApp, vector<string>& params )
+{
+    string payload, responseHeaders, responseBody;
+    getPayload( params, payload );
+    pApp->launch( payload, responseHeaders, responseBody );
+    return (validateParams( params, responseHeaders, responseBody ));
+}
+
+bool DialConformance::execute_status( Application* pApp, vector<string>& params )
+{
+    ATRACE("%s::%d\n", __FUNCTION__, __LINE__);
+    string responseHeaders, responseBody;
+    pApp->status( responseHeaders, responseBody );
+
+    bool retval = validateParams( params, responseHeaders, responseBody );
+    if( retval && (responseHeaders.find("200") != responseHeaders.npos) )
+    {
+        // if the status was successful
+        // Ensure the response body contains the application name
+        if( responseBody.find( pApp->getName() ) == responseBody.npos )
+        {
+            retval = false;
+            stringstream error;
+            error << "Reponse XML did not contain application name: " << pApp->getName();
+            gWriter.setError( error.str() );
+            retval = false;
+        }
+        if( responseBody.find( "urn:dial-multiscreen-org:schemas:dial" ) 
+                    == responseBody.npos )
+        {
+            gWriter.setError( "Response Body did not contain DIAL service "
+                    "string: urn:dial-multiscreen-org:schemas:dial" );
+            retval = false;
+        }
+    }
+    return retval;
+}
+
+bool DialConformance::execute_stop( Application* pApp, vector<string>& params )
+{
+    ATRACE("%s::%d\n", __FUNCTION__, __LINE__);
+    string responseHeaders, responseBody;
+    pApp->stop(responseHeaders);
+    return (validateParams( params, responseHeaders, responseBody ));
+}
+
+DialConformance::Application* DialConformance::getApplication( string& command )
+{
+    Application* pApp = NULL;
+
+    // extract the application
+    string app;
+    size_t pos = command.find("=");
+    app = command.substr(pos+1, command.length());
+    assert( app.length() > 0 );
+
+    // find the application
+    vector<Application*>::iterator it;
+    for( it = _apps.begin(); it < _apps.end(); it++ )
+    {
+        ATRACE("Comparing: %s.compare(%s)\n", (*it)->getName().c_str(), app.c_str());
+        if( (*it)->getName().compare(app) == 0)
+        {
+            pApp = (*it);
+            break;
+        }
+    }
+
+    assert(pApp != NULL);
+    return pApp;
+}
+
+bool DialConformance::execute_command( string& command, vector<string>& params )
+{
+    ATRACE("%s::%s\n", __FUNCTION__, command.c_str());
+    bool retval = false;
+    if( command.find("launch") != command.npos )
+        retval = execute_launch(getApplication(command), params);
+    else if ( command.find("status") != command.npos )
+        retval = execute_status(getApplication(command), params);
+    else if ( command.find("stop") != command.npos )
+        retval = execute_stop(getApplication(command), params);
+    else if ( command.find("sleep") != command.npos ) 
+    {
+        string time;
+        size_t pos = command.find("=");
+        time = command.substr(pos+1, command.length());
+        unsigned long milliseconds = atoi(time.c_str());
+        ATRACE("Sleeping %ld milliseconds \n", milliseconds);
+        usleep(milliseconds*1000);
+        retval = true;
+    }
+    else ATRACE("Can't execute command: %s\n", command.c_str());
+    return retval;
+}
+
+void DialConformance::run_internal( DialServer* pServer )
+{
+    string command;
+    vector<string> params;
+    bool retval;
+    string friendlyName, uuid;
+
+    // only execute the test if we have a friendly name and UUID
+    retval = pServer->getFriendlyName( friendlyName );
+    if (retval) pServer->getUuid( uuid );
+    if (retval) gWriter.start( friendlyName, uuid, pServer->getIpAddress() );
+    while( retval && _input.getNextAction(command, params) )
+    {
+        vector<string> commandlist;
+
+        // Check the command here, if it is command=ALL, then we need to loop through
+        // all of the valid applications
+        size_t pos;
+        if( (pos = command.find("=ALL")) != command.npos )
+        {
+            vector<Application*>::iterator it;
+            for( it = _apps.begin(); it < _apps.end(); it++ )
+            {
+                // strip off ALL and append the application name
+                string newCommand = command.substr(0, pos+1);
+                newCommand.append( (*it)->getName() );
+                Application *pApp = getApplication( newCommand );
+                if( pApp != NULL && (!pApp->isErrorApp()) )
+                {
+                    ATRACE("Adding command %s\n", newCommand.c_str());
+                    commandlist.push_back( newCommand );
+                }
+
+                if( newCommand.find("launch") != newCommand.npos )
+                {
+                    // If we are adding launch commands, insert a sleep so that we don't overwhelm
+                    // the target
+                    stringstream sleep;
+                    sleep << "sleep=" << LAUNCH_SLEEP;
+                    commandlist.push_back( sleep.str() );
+                }
+            }
+        }
+        else
+        {
+            ATRACE("Adding command %s\n", command.c_str());
+            commandlist.push_back(command);
+        }
+
+        vector<string>::iterator it;
+        for( it = commandlist.begin(); it < commandlist.end(); it++ )
+        {
+            // construct the name of the test
+            ATRACE("***********************" "\n%s: Command: %s \n", 
+                    __FUNCTION__, (*it).c_str());
+            stringstream test;
+            test << "Command:" << (*it);
+    
+            // The input code will always push a parameter.  If there are no
+            // parameters, it will push an empty string, so test for that here.
+            if( params.size() > 0 && !params[0].empty() ) test << " Params:";
+    
+            vector<string>::iterator it2;
+            for( it2 = params.begin(); it2 < params.end(); it2++)
+            {
+                ATRACE("param: %s ", (*it2).c_str());
+
+                // check to see if we have a really large parameter, if we do, 
+                // truncate it to a reasonable length
+                string param = (*it2);
+                if( param.size() > MAX_PARAMETER_LENGTH )
+                {
+                    param = (*it2).substr(0, MAX_PARAMETER_LENGTH-3 );
+                    param.append("...");
+                }
+                test << param << " ";
+            }
+
+            if( (*it).find("sleep") == (*it).npos )
+            {
+                // set the test name
+                gWriter.setTest( test.str() );
+    
+                // run the test
+                bool retval = execute_command( *it, params );
+                printf("%s\n", retval ? "SUCCESS":"FAILED");
+        
+                // set the result and prep for the next test
+                gWriter.setResult( retval );
+                gWriter.clearTest();
+            }
+            else
+            {
+                //just sleep here
+                string time;
+                size_t pos = (*it).find("=");
+                time = (*it).substr(pos+1, command.length());
+                unsigned long milliseconds = atoi(time.c_str());
+                ATRACE("Sleeping %ld milliseconds \n", milliseconds);
+                usleep(milliseconds*1000);
+            }
+        }
+
+        // clear the parameters for the next loop
+        params.clear();
+    }
+    if( retval ) gWriter.complete();
+}
+
+int DialConformance::run(
+        DialServer* pServer,
+        vector<string>& appList,
+        string &inputFile,
+        string &outputFile )
+{
+    printf("Running conformance tests from %s and printing report to %s\n\n",
+                    inputFile.empty() ? 
+                              DialClientInput::getDefaultFilename().c_str() : 
+                              inputFile.c_str(), 
+                    outputFile.empty() ? 
+                              defaultOutputFile.c_str() : 
+                              outputFile.c_str() );
+    //read the input file
+    _input.init(inputFile);
+    if( !outputFile.empty() ) gWriter.setOutputFile( outputFile );
+
+    // Add applications passed by the client (command line)
+    vector<string>::iterator it;
+    for( it = appList.begin(); it < appList.end(); it++ ) _input.addApplication(*it);
+
+    // build the application list
+    vector<string> listOfApps;
+    _input.getApplicationList(listOfApps); // get the list of apps
+    for( it = listOfApps.begin(); it < listOfApps.end(); it++ )
+    {
+        _apps.push_back( new Application(*it, pServer, false) );
+        ATRACE("Adding application: %s\n", (*it).c_str());
+    }
+
+    _input.getErrorApplicationList(listOfApps); // get the list of apps
+    for( it = listOfApps.begin(); it < listOfApps.end(); it++ )
+    {
+        _apps.push_back( new Application(*it, pServer, true) );
+        ATRACE("Adding Error application: %s\n", (*it).c_str());
+    }
+
+    // run the test
+    run_internal( pServer );
+
+    // clean up the application list
+    vector<Application*>::iterator appit;
+    for ( appit = _apps.begin() ; appit < _apps.end(); appit++ ) delete (*appit);
+
+    return 0;
+}
+
diff --git a/src/client/DialConformance.h b/src/client/DialConformance.h
new file mode 100644
index 0000000..49c7c97
--- /dev/null
+++ b/src/client/DialConformance.h
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIALCONFORMANCE_H
+#define DIALCONFORMANCE_H
+
+#include <memory>
+#include "DialServer.h"
+#include "DialClientInput.h"
+
+using namespace std;
+
+class DialConformance
+{
+public:
+    /**
+     * Create a singleton
+     */
+    static DialConformance* create();
+
+    /**
+     * Get a pointer to the singleton
+     */
+    static DialConformance* instance(void);
+
+    ~DialConformance();
+
+    /**
+     * Run the test
+     *
+     * @param[in] pServer DIAL server to test
+     * @param[in] appList List of applications from the command line
+     * @param[in] inputFile Input file used to drive tests
+     * @param[in] outputFile File for writing the report.  Use an
+     * empty string to use the default (report.html).
+     *
+     * @return 0 if successful, !0 otherwise
+     */
+    int run( 
+        DialServer* pServer,
+        vector<string>& appList,
+        string &inputFile,
+        string &outputFile );
+private:
+    DialConformance();
+    DialServer*                 _pServer;
+    static DialConformance*     sConformance;
+    DialClientInput             _input;
+
+    /**
+     * Class to manage state for each application supported by the DIAL 
+     * server.  The application list is managed by the input file.
+     */
+    class Application{
+    public:
+        enum State
+        {
+            STOPPED,
+            LAUNCHING,  // application has just been launched, state 
+                        // has not been confirmed
+            LAUNCHED,
+            STOPPING    // application has been stopped, state has not
+                        // been confirmed
+        };
+
+        Application( string& name, DialServer *pServer, bool isErrorApp ) :
+        _isErrorApp(isErrorApp),
+        _pServer(pServer),
+        _state(STOPPED),
+        _stopurl(""),
+        _name(name) {}
+        ~Application(){}       
+        
+        /**
+         * State Getter/Setters
+         */
+        string getName()    {   return _name; }
+        State getState()    {   return _state; }
+
+        /**
+         * Returns true if the application is not a valid application
+         */
+        bool isErrorApp()   {   return _isErrorApp; }
+
+        /**
+         * Launch an this application using the server that was stored when
+         * the class was created.
+         *
+         * @param[in] payload Data that is put into the POST data.
+         * @param[out] responseHeaders HTML response headers
+         * @param[out] responseBody HTML response body
+         */
+        void launch(
+            string& payload,
+            string& responseHeaders,
+            string& responseBody )
+        {
+            _pServer->launchApplication( 
+                        _name, payload, responseHeaders, responseBody );
+            // TODO: Set state
+
+            // find Location in the header, store the stop url
+            if( !responseHeaders.empty() )
+            {
+                size_t pos, tmp = responseHeaders.find("Location");
+                if( tmp != responseHeaders.npos )
+                {
+                    pos = responseHeaders.find("http", tmp);
+                    size_t posEnd = responseHeaders.find("\n", pos+1);
+                    if( posEnd != responseHeaders.npos )
+                        _stopurl = responseHeaders.substr( pos, posEnd );
+        
+                    // chomp off the \r\n chars
+                    DialConformance::chomp( _stopurl );
+                    ATRACE("StopURL = %s********\n", _stopurl.c_str());
+                }
+            }
+        }
+
+        /**
+         * Get the status of this application
+         *
+         * @param[out] responseHeaders HTML response headers
+         * @param[out] responseBody HTML response body
+         */
+        void status(
+            string& responseHeaders,
+            string& responseBody )
+        { _pServer->getStatus( _name, responseHeaders, responseBody ); }
+
+        /**
+         * Stop the application
+         *
+         * @param[out] responseHeaders HTML response headers
+         */
+        void stop(
+            string& responseHeaders )
+        { stop(_stopurl, responseHeaders ); }
+
+        /**
+         * Stop the application using a custom stop URL.
+         *
+         * @param[out] responseHeaders HTML response headers
+         */
+        void stop( string& stopurl, string& responseHeaders)
+        {
+            if( !stopurl.empty() )
+            {
+                _pServer->stopApplication( stopurl, responseHeaders );
+            }
+#ifdef DEBUG
+            else ATRACE("%s: Not sending stop, stop URL is empty\n", __FUNCTION__);
+#endif
+        }
+
+    private:
+        bool _isErrorApp;
+        DialServer* _pServer;
+        State _state;
+        string _stopurl;
+        string _name;
+    };
+    // list of applications
+    vector<Application*> _apps;
+
+    // Internal helpers
+    void run_internal( DialServer* pServer );
+    bool execute_command( string& command, vector<string>& params );
+
+    // Get the Application pointer from an application string
+    Application* getApplication( string& command );
+
+    // extract the payload from a list of parameters
+    void getPayload(vector<string>& params, string& payload );
+
+    // Command execution functions
+    bool execute_launch( Application* pApp, vector<string>& params );
+    bool execute_status( Application* pApp, vector<string>& params );
+    bool execute_stop( Application* pApp, vector<string>& params );
+
+    // Helper function to extract a parameter.
+    void extractParamValue( string& param, string& value );
+
+    // Validation functions
+    bool validateParams( vector<string>& params, 
+            string& responseHeaders, string& responseBody );
+    bool validateHttpResponse( string& headers, string& params );
+    bool validateHttpHeaders( string& headers, string& params );
+    bool validateResponseBody( string& headers, string& params );
+
+public:
+    // Helper function to chomp off the carriage return line feed.
+    static void chomp(string& str)
+    {
+        string crlf("\r\n");
+        size_t pos = str.find_last_not_of( crlf );
+        if( pos != str.npos )
+        {
+            ATRACE("CHOMP\n");
+            str.erase(pos+1);
+        }
+    }
+};
+
+#endif  // DIALCONFORMANCE_H
diff --git a/src/client/DialDiscovery.cpp b/src/client/DialDiscovery.cpp
new file mode 100644
index 0000000..bfaa301
--- /dev/null
+++ b/src/client/DialDiscovery.cpp
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <string>
+#include <map>
+#include <curl/curl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <iostream>
+#include <vector>
+#include "DialDiscovery.h"
+#include <assert.h>
+
+#define SSDP_TIMEOUT 30
+#define SSDP_RESPONSE_TIMEOUT 3
+
+using namespace std;
+
+DialDiscovery *DialDiscovery::sDiscovery = 0;
+
+static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
+static int my_port = 0;
+static struct sockaddr_in saddr;
+typedef struct
+{
+    struct sockaddr_in saddr;
+    int sock;
+    socklen_t addrlen;
+}search_conn;
+
+static pthread_mutex_t list_locker = PTHREAD_MUTEX_INITIALIZER;
+class ScopeLocker
+{
+public:
+    ScopeLocker(pthread_mutex_t &m) : mLock(m) 
+        { pthread_mutex_lock(&mLock); }
+    virtual ~ScopeLocker() 
+        { pthread_mutex_unlock(&mLock); }
+private:
+    pthread_mutex_t mLock;
+};
+
+static const char ssdp_msearch[] = 
+    "M-SEARCH * HTTP/1.1\r\n"
+    "HOST: 239.255.255.250:1900\r\n"
+    "MAN: \"ssdp:discover\"\r\n"
+    "MX: 10\r\n"
+    "ST: urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
+
+static string getLocation(char *pResponse) 
+{
+    string loc(pResponse);
+    size_t prev = 0, index = loc.find("\r\n");
+    string locUrl, retUrl;
+    while( index != string::npos ) {
+        locUrl = loc.substr(prev, index-prev);
+        ATRACE("locUrl: ##%s## prev: %d index %d\n", locUrl.c_str(), (int)prev, (int)index );
+        if( locUrl.find("LOCATION: ") != string::npos ) {
+            index = locUrl.find("\r\n");
+            retUrl = locUrl.substr( 10 );
+            break;
+        }
+
+        prev = index+2; // move past the "\r\n" token
+        index = loc.find("\r\n", prev);
+    }
+    return retUrl;
+}
+
+static size_t header_cb(void* ptr, size_t size, size_t nmemb, void* userdata)
+{
+    if ((size * nmemb) != 0) {
+        string parse((char*)ptr);
+        if( parse.find("Application-URL: ") != string::npos ) {
+            size_t index_start = parse.find(":");
+            index_start += 2;
+            size_t index_end = parse.find("\r\n");
+            string *header = static_cast<string*>(userdata);
+            header->append(parse.substr(index_start, index_end-index_start));
+            ATRACE("Apps URL set to %s\n", header->c_str());
+#ifndef DEBUG
+        }
+#else
+        } else {
+            ATRACE("%s: Dropping %s\n", __FUNCTION__, (char*)ptr);
+        }
+#endif
+    }
+    return (size * nmemb);
+}
+
+static size_t receiveData(void *ptr, size_t size, size_t nmemb, void *userdata)
+{
+    if ((size * nmemb) != 0) {
+        string *url = static_cast<string*>(userdata);
+        url->append((char*)ptr);
+    }
+    return (size * nmemb);
+}
+
+static void getServerInfo( const string &server, string& appsUrl, string& ddxml ) 
+{
+    CURL *curl;
+    CURLcode res = CURLE_OK;
+
+    if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
+        fprintf(stderr, "curl_global_init() failed\n");
+        return; 
+    }
+
+    if ((curl = curl_easy_init()) == NULL) {
+        fprintf(stderr, "curl_easy_init() failed\n");
+        curl_global_cleanup();
+        return;
+    }
+
+    ATRACE("Sending ##%s##\n", server.c_str());
+    curl_easy_setopt(curl, CURLOPT_URL, server.c_str());
+    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
+    curl_easy_setopt(curl, CURLOPT_HEADERDATA, &appsUrl);
+    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ddxml);
+    res = curl_easy_perform(curl);
+
+    curl_easy_cleanup(curl);
+    //curl_global_cleanup();
+}
+
+void DialDiscovery::updateServerList(string& server)
+{
+    ServerMap::const_iterator it;
+    it = mServerMap.find(server);
+    if( it == mServerMap.end() ) {
+        // not found, add it
+        string appsUrl, ddxml;
+        getServerInfo(server, appsUrl, ddxml);
+        mServerMap[server] = new DialServer(server, appsUrl, ddxml);
+    } else {
+        // just mark that we found it
+        (*it).second->setFound(true);
+    }
+}
+
+void DialDiscovery::processServer(char *pResponse) 
+{
+    if (strstr(pResponse, "ST: urn:dial-multiscreen-org:service:dial:1")) {
+        string server;
+    
+        ScopeLocker s(list_locker);
+        // parse for LOCATION header
+        server = getLocation(pResponse);
+        ATRACE("FOUND server: %s\n", server.c_str());
+    
+        // save the appURL in the server
+        updateServerList(server);
+#ifndef DEBUG
+    }
+#else
+    } else {
+       ATRACE("Dropping Server\n");
+    }
+#endif
+}
+
+void *DialDiscovery::receiveResponses(void *p) 
+{
+    search_conn *pConn = (search_conn*)p;
+    int bytes;
+    char buf[4096] = {0,};
+    while (1) {
+        if (-1 == (bytes = recvfrom(pConn->sock, buf, sizeof(buf) - 1, 0,
+                                  (struct sockaddr *)&pConn->saddr, &pConn->addrlen))) {
+            perror("recvfrom");
+            break;
+        }
+        buf[bytes] = 0;
+        ATRACE("Received [%s:%d] %s\n", __FUNCTION__, __LINE__, buf);
+        DialDiscovery::instance()->processServer(buf);
+    }
+    return 0;
+}
+
+void DialDiscovery::cleanServerList(void)
+{
+    ScopeLocker s(list_locker);
+
+    ServerMap::const_iterator it;
+    vector<string> removal;
+    for( it = mServerMap.begin(); it != mServerMap.end(); it++ )
+    {
+        if( !(*it).second->isFound() ) {
+            removal.push_back((*it).second->getLocation());
+        }
+    }
+
+    // now remove and delete
+    vector<string>::iterator iter;
+    for( iter = removal.begin(); iter != removal.end(); iter ++)
+    {
+        ATRACE("Removing Server: %s\n", (*iter).c_str());
+        DialServer *p = mServerMap[(*iter)];
+        delete (p);
+        mServerMap.erase((*iter));
+    }
+}
+
+void DialDiscovery::resetDiscovery(void)
+{
+    ScopeLocker s(list_locker);
+    ServerMap::const_iterator it;
+    for( it = mServerMap.begin(); it != mServerMap.end(); it++ ) {
+        (*it).second->setFound(false);
+    }
+}
+
+void * DialDiscovery::send_mcast(void *p) 
+{
+    int one = 1, my_sock;
+    socklen_t addrlen;
+    //struct ip_mreq mreq;
+    char send_buf[strlen((char*)ssdp_msearch) + INET_ADDRSTRLEN + 256] = {0,};
+    int send_size;
+    pthread_attr_t attr;
+    search_conn connection;
+
+    send_size = snprintf(send_buf, sizeof(send_buf), ssdp_msearch, ip_addr, my_port);
+    ATRACE("[%s:%d] %s\n", __FUNCTION__, __LINE__, send_buf);
+
+    if (-1 == (my_sock = socket(AF_INET, SOCK_DGRAM, 0))) {
+        perror("socket");
+        exit(1);
+    }
+    if (-1 == setsockopt(my_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
+        perror("reuseaddr");
+        exit(1);
+    }
+    saddr.sin_family = AF_INET;
+    saddr.sin_addr.s_addr = inet_addr("239.255.255.250");
+    saddr.sin_port = htons(1900);
+
+    while (1) {
+        addrlen = sizeof(saddr);
+        ATRACE("Sending SSDP M-SEARCH to %s:%d\n",
+             inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
+        if (-1 == sendto(my_sock, send_buf, send_size, 0, (struct sockaddr *)&saddr, addrlen)) {
+          perror("sendto");
+            continue;
+        }
+
+        // set all servers to not found
+        DialDiscovery::instance()->resetDiscovery();
+
+        // spawn a response thread
+        connection.saddr = saddr;
+        connection.sock = my_sock;
+        connection.addrlen = addrlen;
+        pthread_attr_init(&attr);
+        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+        pthread_create(&DialDiscovery::instance()->_responseThread, &attr, DialDiscovery::receiveResponses, &connection);
+
+        // sleep SSDP_RESPONSE_TIMEOUT seconds to allow clients to response
+        sleep(SSDP_RESPONSE_TIMEOUT);
+        DialDiscovery::instance()->cleanServerList();
+
+        sleep(SSDP_TIMEOUT-SSDP_RESPONSE_TIMEOUT);
+        pthread_cancel(DialDiscovery::instance()->_responseThread);
+    }
+}
+
+void DialDiscovery::init() 
+{
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+    pthread_create(&_mcastThread, &attr, DialDiscovery::send_mcast, (void*)ssdp_msearch );
+}
+
+void DialDiscovery::getServerList( vector<DialServer*>& list )
+{
+    for( ServerMap::iterator it = mServerMap.begin(); it != mServerMap.end(); ++it ) {
+        list.push_back( it->second );
+    }
+}
+
+bool DialDiscovery::getServer( const string& friendlyName, DialServer &server )
+{
+    return true;
+}
+
+DialDiscovery::DialDiscovery() 
+{
+    assert( DialDiscovery::sDiscovery == 0 );
+    DialDiscovery::sDiscovery = this;
+}
+
+DialDiscovery::~DialDiscovery()
+{
+    assert( sDiscovery == this );
+    sDiscovery = 0;
+}
+
+DialDiscovery * DialDiscovery::create()
+{
+    assert( sDiscovery == 0 );
+    return new DialDiscovery();
+}
+
+DialDiscovery * DialDiscovery::instance()
+{
+    return DialDiscovery::sDiscovery;
+}
+
diff --git a/src/client/DialDiscovery.h b/src/client/DialDiscovery.h
new file mode 100644
index 0000000..81d67a5
--- /dev/null
+++ b/src/client/DialDiscovery.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIALDISCOVERY_H
+#define DIALDISCOVERY_H
+
+#include "DialServer.h"
+#include <map>
+
+using namespace std;
+
+class DialDiscovery
+{
+public:
+    /**
+     * Create a singleton
+     */
+    static DialDiscovery* create(void);
+
+    /**
+     * Get a pointer to the singleton
+     */
+    static DialDiscovery* instance(void);
+
+    ~DialDiscovery();
+
+    /**
+     * Initialize the discover object.  This will kick off a periodic
+     * worker thread that will poll for DIAL servers.
+     *
+     */
+    void init();
+
+    /**
+     * Get the list of servers that have been discovered.
+     *
+     * @param[out] list List of DIAL servers.  Returns an empty list if there
+     * are no servers.
+     */
+    void getServerList(vector<DialServer*>& list);
+
+    /**
+     * Get a DIAL server based on friendly name
+     *
+     * @param[in] friendlyName Friendly name of DIAL server
+     * @param[out] server Server object (if successful)
+     *
+     * @return true if successful, false otherwise
+     */
+    bool getServer( 
+        const string& friendlyName,
+        DialServer &server );
+
+private:
+    DialDiscovery();
+    void updateServerList(string& server);
+    static void *receiveResponses(void *p);
+    static void *send_mcast(void *p);
+    void processServer(char *pResponse);
+    void cleanServerList();
+    void resetDiscovery();
+
+    pthread_t _mcastThread;
+    pthread_t _responseThread;
+
+    typedef map<string, DialServer*> ServerMap;
+    ServerMap mServerMap;
+
+    static DialDiscovery* sDiscovery;
+};
+
+#endif // DIALDISCOVERY_H
diff --git a/src/client/DialServer.cpp b/src/client/DialServer.cpp
new file mode 100644
index 0000000..c0306b8
--- /dev/null
+++ b/src/client/DialServer.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DialServer.h"
+#include <curl/curl.h>
+
+using namespace std;
+
+enum DIAL_COMMAND{
+    COMMAND_LAUNCH,
+    COMMAND_STATUS,
+    COMMAND_KILL
+};
+
+static size_t header_cb(void* ptr, size_t size, size_t nmemb, void* userdata)
+{
+    if ((size * nmemb) != 0) {
+        string newHeader((char*)ptr);
+        string *header = static_cast<string*>(userdata);
+        header->append(newHeader);
+        ATRACE("%s: Adding header: %s", __FUNCTION__, newHeader.c_str());
+    }
+    return (size * nmemb);
+}
+
+static size_t receiveData(void *ptr, size_t size, size_t nmemb, void *userdata)
+{
+    if ((size * nmemb) != 0) {
+        string *body= static_cast<string*>(userdata);
+        body->append((char*)ptr);
+        ATRACE("%s: Adding to Body: %s", __FUNCTION__, (char*)ptr);
+    }
+    return (size * nmemb);
+}
+
+int DialServer::sendCommand( 
+        string &url, 
+        int command, 
+        string &payload, 
+        string &responseHeaders, 
+        string &responseBody ) 
+{
+    CURL *curl;
+    CURLcode res = CURLE_OK;
+
+    if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) 
+    {
+        fprintf(stderr, "curl_global_init() failed\n");
+        return 0;
+    }
+
+    if ((curl = curl_easy_init()) == NULL) 
+    {
+        fprintf(stderr, "curl_easy_init() failed\n");
+        curl_global_cleanup();
+        return 0;
+    }
+
+    if (command == COMMAND_LAUNCH) 
+    {
+        curl_easy_setopt(curl, CURLOPT_POST, true);
+        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, payload.size());
+        if( payload.size() )
+        {
+            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
+        }
+#ifdef DEBUG
+        else ATRACE("Sending empty POST\n");
+#endif
+    } 
+    else if (command == COMMAND_KILL)
+    {
+        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
+    }
+    ATRACE("Sending %s:%s\n", 
+        command == COMMAND_LAUNCH ? "LAUNCH" :
+            (command == COMMAND_KILL ? "KILL" : "STATUS"),
+        url.c_str());
+
+    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
+    curl_easy_setopt(curl, CURLOPT_HEADERDATA, &responseHeaders);
+    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBody);
+    res = curl_easy_perform(curl);
+
+    curl_easy_cleanup(curl);
+    curl_global_cleanup();
+    return (res == CURLE_OK);
+}
+
+string DialServer::getIpAddress()
+{
+    // m_appsUrl=http://192.168.1.103:36269/apps/
+    if( m_ipAddr.empty() )
+    {
+        size_t begin = m_appsUrl.find("//"); 
+        if( begin != m_appsUrl.npos )
+        {
+            begin += 2; // move to the start of the IP address
+            size_t end = m_appsUrl.find(":", begin);
+            if( end != m_appsUrl.npos )
+            {
+                m_ipAddr = m_appsUrl.substr( begin, end-begin );
+                ATRACE("IP ADDRESS: %s\n", m_ipAddr.c_str() );
+            }
+        }
+    }
+    return m_ipAddr;
+}
+
+bool DialServer::getFriendlyName( string& name )
+{
+    bool retval = false;
+    if( !m_ddxml.empty() )
+    {
+        string friendlyName = "<friendlyName>";
+        size_t pos;
+        if( ( pos = m_ddxml.find( friendlyName ) ) != m_ddxml.npos )
+        {
+            string friendlyNameEnd = "</friendlyName>";
+            size_t end = m_ddxml.find( friendlyNameEnd );
+            name = m_ddxml.substr( pos + friendlyName.size(), 
+                        (end - (pos + friendlyName.size())) );
+            ATRACE("***Friendly name=%s***", name.c_str());
+            retval = true;
+        }
+#ifdef DEBUG
+        else
+        {
+            ATRACE("Friendly name not found\n%s\n", m_ddxml.c_str());
+        }
+#endif
+    }
+
+    return retval;
+}
+
+bool DialServer::getUuid( string& uuid )
+{
+    bool retval = false;
+    if( !m_ddxml.empty() )
+    {
+        string udn = "<UDN>";
+        size_t pos;
+        if( ( pos = m_ddxml.find( udn ) ) != m_ddxml.npos )
+        {
+            string udnEnd = "</UDN>";
+            size_t end = m_ddxml.find( udnEnd );
+            uuid = m_ddxml.substr( pos + udn.size(), 
+                        (end - (pos + udn.size())) );
+            ATRACE("***UUID=%s***", uuid.c_str() );
+            retval = true;
+        }
+#ifdef DEBUG
+        else
+        {
+            ATRACE("Friendly name not found\n%s\n", m_ddxml.c_str());
+        }
+#endif
+    }
+
+    return retval;
+}
+
+int DialServer::launchApplication(
+    string &application,
+    string &payload, 
+    string &responseHeaders, 
+    string &responseBody )
+{
+    ATRACE("%s: Launch %s\n", __FUNCTION__, application.c_str());
+    string appUrl = m_appsUrl;
+    sendCommand( appUrl.append(application), COMMAND_LAUNCH, payload, responseHeaders, responseBody);
+    return 0;
+}
+
+int DialServer::getStatus(
+    string &application,
+    string &responseHeaders, 
+    string &responseBody )
+{
+    ATRACE("%s: GetStatus %s\n", __FUNCTION__, application.c_str());
+    string emptyPayload;
+    string appUrl = m_appsUrl;
+    sendCommand( appUrl.append(application), COMMAND_STATUS, emptyPayload, responseHeaders, responseBody );
+    return 0;
+}
+
+int DialServer::stopApplication(
+    string &application,
+    string &endPoint,
+    string &responseHeaders )
+{
+    ATRACE("%s: Quit %s\n", __FUNCTION__, application.c_str());
+    string emptyPayload, responseBody;  // dropping this
+    string appUrl = m_appsUrl;
+    sendCommand( 
+            (appUrl.append(application)).append(endPoint), 
+            COMMAND_KILL, emptyPayload, responseHeaders, responseBody );
+    return 0;
+}
+
+
+int DialServer::stopApplication(
+        string &stopurl,
+        string &responseHeaders )
+{
+    ATRACE("%s: Quit URL: %s\n", __FUNCTION__, stopurl.c_str());
+    string emptyPayload, responseBody;  // dropping this
+    sendCommand( stopurl, COMMAND_KILL, 
+            emptyPayload, responseHeaders, responseBody );
+    return 0;
+}
+
+int DialServer::getHttpResponseHeader( 
+    string &responseHeaders,
+    string &header,
+    string &value )
+{
+    return 0;
+}
diff --git a/src/client/DialServer.h b/src/client/DialServer.h
new file mode 100644
index 0000000..ce81e9f
--- /dev/null
+++ b/src/client/DialServer.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIALSERVER_H
+#define DIALSERVER_H
+
+//#define DEBUG
+#ifdef DEBUG
+#define ATRACE(...) printf(__VA_ARGS__)
+#else
+#define ATRACE(...)
+#endif
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+class DialServer
+{
+public:
+    /**
+     * Dial Server ctor
+     *
+     * @param[in] location dd.xml LOCATION header
+     * @param[in] appsUrl Parsed out Application URL
+     * @param[in] location dd.xml LOCATION header
+     * empty string to find any server
+     *
+     */
+    DialServer( string location, string appsUrl, string dd_xml ) : 
+    m_location(location),
+    m_appsUrl(appsUrl),
+    found(true),
+    m_ddxml(dd_xml) 
+    {}
+
+    ~DialServer() { ATRACE("%s\n", __FUNCTION__); }
+
+    /**
+     * Get the DIAL Server location
+     *
+     * @return Location of the server (http://<IP_ADDR>:<PORT>/dd.xml)
+     */
+    string getLocation() { return m_location; }
+
+    /**
+     * Get the DIAL Server IP address
+     *
+     * @return IP address of the server (X.X.X.X)
+     */
+    string getIpAddress();
+
+    /**
+     * Get the DIAL REST endpoint
+     *
+     * @return Location of the server (http://<IP_ADDR>:<PORT>/apps)
+     */
+    string getAppsUrl() { return m_appsUrl; }
+
+    /**
+     * Get the DIAL friendly name
+     *
+     * @return true if successful, false otherwise
+     */
+    bool getFriendlyName( string& name );
+
+    /**
+     * Get the DIAL UUID
+     *
+     * @return true if successful, false otherwise
+     */
+    bool getUuid( string& uuid );
+
+    /**
+     * Launch a DIAL application
+     *
+     * @param[in] application Name of the application to launch
+     * @param[in] payload launch POST data
+     * @param[out] responseHeaders Returns the HTTP response headers
+     * @param[out] responseBody Returns the HTTP response body
+     *
+     * @return 0 if successful, !0 otherwise
+     */
+    int launchApplication(
+        string &application,
+        string &payload, 
+        string &responseHeaders, 
+        string &responseBody );
+
+    /**
+     * Get the status of a DIAL application
+     *
+     * @param[in] application Name of the application to query
+     * @param[out] responseHeaders Returns the HTTP response headers
+     * @param[out] responseBody Returns the HTTP response body
+     *
+     * @return 0 if successful, !0 otherwise
+     */
+    int getStatus(
+        string &application,
+        string &responseHeaders, 
+        string &responseBody );
+
+    /**
+     * Stop an application.  Client *must* provide the endPoint
+     *
+     * @param[in] application Name of the application to stop
+     * @param[in] endPoint URL of the REST endpoint to stop
+     * @param[out] responseHeaders Returns the HTTP response headers
+     *
+     * @return 0 if successful, !0 otherwise
+     */
+    int stopApplication(
+        string &application,
+        string &endPoint,
+        string &responseHeaders );
+
+    /**
+     * Stop an application using a fully formed URL.
+     *
+     * @param[in] stopurl Full stop URL
+     * @param[out] responseHeaders Returns the HTTP response headers
+     *
+     * @return 0 if successful, !0 otherwise
+     */
+    int stopApplication(
+        string &stopurl,
+        string &responseHeaders );
+
+
+    /**  ********************* **/
+    /**  Convenience functions **/
+    /**  ********************* **/
+
+    /**
+     * Extract a header from the response
+     *
+     * @param[in] responseHeaders Response headers
+     * @param[in] header Header value to extract
+     * @param[out] value Value of the header provided
+     *
+     * @return 0 if successful, !0 otherwise
+     */
+    int getHttpResponseHeader( 
+        string &responseHeaders,
+        string &header,
+        string &value );
+
+    /**
+     * Returns true if the server has been recently found
+     *
+     * @return true if successful, false otherwise
+     */
+    bool isFound() { return found; }
+
+    /**
+     * Sets the "found status" of this server
+     */
+    void setFound(bool b) { found = b; }
+
+private:
+    int sendCommand( string &url, int command, string &payload, 
+        string &responseHeaders, string &responseBody );
+    string m_location;
+    string m_appsUrl;
+    string m_ipAddr;
+    bool found;
+    string m_ddxml; // information about the device
+};
+
+#endif // DIALSERVER_H
diff --git a/src/client/dialclient_input.txt b/src/client/dialclient_input.txt
new file mode 100644
index 0000000..01e783f
--- /dev/null
+++ b/src/client/dialclient_input.txt
@@ -0,0 +1,80 @@
+# Input file for DIAL client
+
+# List the applications defined.
+# Valid applications that should exist on your DIAL server implementation
+addApplication=Netflix
+addApplication=YouTube
+
+# Error applications that should not exist on your DIAL server implementation
+addErrorApplication=netflix
+addErrorApplication=Netflix1
+
+# Launch the Netflix application
+# HTTP response should be 201
+# HTTP response header should contain a Location header
+launch=Netflix httpresponse=201 httpresponse=Created httpresponseheader=Location 
+sleep=6000
+
+# Get the status of the application, it should be running
+status=Netflix httpresponse=200 resultbody=state resultbody=running
+
+# Stop the Netflix application, wait for 1 second while it shuts down.
+stop=Netflix
+sleep=1000
+
+# Call stop again and ensure the implementation returns the proper response
+stop=Netflix httpresponse=404
+
+# Get the status of the Netflix application and ensure it returns stopped
+status=Netflix httpresponse=200 resultbody=state resultbody=stopped
+sleep=2000
+
+# Launch the application with a parameter
+# NOTE: This test can not ensure that the parameter was taken properly
+launch=Netflix param="NETFLIX&WAS+HERE" httpresponse=201 httpresponse=Created httpresponseheader=Location 
+sleep=6000
+
+# Ensure the application is running
+status=Netflix httpresponse=200 resultbody=state resultbody=running
+
+# This will launch Netflix with the same parameter.  *Netflix should not relaunch*
+launch=Netflix param="NETFLIX&WAS*HERE" httpresponse=201 httpresponse=Created httpresponseheader=Location 
+sleep=6000
+
+# Ensure the application is running
+status=Netflix httpresponse=200 resultbody=state resultbody=running
+
+# This will launch Netflix with a different parameter.  *Netflix should not relaunch*
+launch=Netflix param="&&&%%%ThisShouldRelaunch%%%" httpresponse=201 httpresponse=Created httpresponseheader=Location 
+sleep=6000
+
+# Ensure the application is running
+status=Netflix httpresponse=200 resultbody=state resultbody=running
+
+# Stop Netflix
+stop=Netflix
+sleep=1500
+
+# Make sure netflix is reported as stopped
+status=Netflix httpresponse=200 resultbody=state resultbody=stopped
+
+# Test a parameter with over 4096 bytes and ensure 413 is returned.
+launch=Netflix param="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" httpresponse=413
+
+# Test for case sensitivity
+launch=netflix httpresponse=404
+sleep=200
+
+# Ensure the server is checking the full application name
+launch=Netflix1 httpresponse=404
+sleep=200
+
+# This executes correctly, but fails the XML
+status=Netflix1 httpresponse=404
+sleep=200
+
+# Currently can't run this test, the code won't execute a stop without a stop URL.
+#stop=Netflix1 httpresponse=404
+
+# Test ALL valid applications
+status=ALL httpresponse=200 resultbody=state resultbody=stopped
diff --git a/src/client/main.cpp b/src/client/main.cpp
new file mode 100644
index 0000000..ffce725
--- /dev/null
+++ b/src/client/main.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "DialDiscovery.h"
+#include "DialConformance.h"
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+using namespace std;
+
+static DialDiscovery* gpDiscovery;
+static bool gUseMenu = true;
+
+// TODO: Make it possible to pass applications from the command line
+static vector<string> gAppList;
+static string gOutputFile;
+static string gInputFile;
+
+// IP address of the DIAL server
+static string gIpAddress;
+
+static void printServerList( vector<DialServer*> list )
+{
+    int i;
+    vector<DialServer*>::iterator it;
+    for( i = 0, it = list.begin(); it < list.end(); it++, i++ )
+    {
+        string uuid, name;
+        (*it)->getFriendlyName( name );
+        (*it)->getUuid( uuid );
+        printf("%Zu: Server IP[%s] UUID[%s] FriendlyName[%s] \n", 
+            i+1, (*it)->getIpAddress().c_str(),
+            uuid.c_str(), name.c_str() );
+    }
+}
+
+static DialServer* getServerFromUser( vector<DialServer*> list )
+{
+    DialServer* pServer;
+    // show a list to the user
+    if( list.size() > 1 )
+    {
+        char buf[80] = {0,};
+        vector<DialServer*>::iterator it;
+
+        printf("Found Multiple servers\n");
+        printServerList(list);
+        printf("Enter server: ");
+        scanf("%s", buf);
+        unsigned int server = atoi(buf);
+        assert( server > 0 && server <= list.size() );
+        pServer = list[server-1];
+    }
+    else
+    {
+        pServer = list.front();
+    }
+    return pServer;
+}
+
+static void runConformance()
+{
+    vector<DialServer*> list;
+    gpDiscovery->getServerList(list);
+
+    if( list.size() )
+    {
+        DialServer *pServer = NULL;
+        if( !gIpAddress.empty() )
+        {
+            pServer = NULL;
+            vector<DialServer*>::iterator it;
+            for( it = list.begin(); it < list.end(); it ++ )
+            {
+                if( gIpAddress.compare((*it)->getIpAddress()) == 0 )
+                {
+                    ATRACE("Found server %s in the list of servers\n", 
+                                gIpAddress.c_str() );
+                    pServer = (*it);
+                    break;
+                }
+            }
+        }
+        else
+        {
+            pServer = getServerFromUser( list );
+        }
+
+        if( pServer )
+        {
+            string name;
+            bool serverExists = pServer->getFriendlyName(name);
+            assert( serverExists );
+            string uuid;
+            pServer->getUuid( uuid );
+            printf("\nRunning conformance against: IP[%s] UUID[%s] FriendlyName[%s] \n", 
+                    pServer->getIpAddress().c_str(),
+                    uuid.c_str(), name.c_str() );
+            DialConformance::instance()->run( 
+                pServer, 
+                gAppList, 
+                gInputFile, 
+                gOutputFile );
+        }
+        else 
+        {
+            printf("DIAL server not found\n");
+            printf("%Zu available server(s): \n", list.size());
+            printServerList(list);
+        }
+    }
+    else
+    {
+        printf("No servers available\n");
+    }
+}
+
+int handleUser(DialDiscovery *pDial) {
+    int processInput = 1;
+    char buf[80];
+    vector<DialServer*> list;
+
+    pDial->getServerList(list);
+    if( list.size() == 0 )
+    {
+        printf("No servers available\n");
+        return 1;
+    }
+    DialServer* pServer = getServerFromUser( list );
+
+    while(processInput)
+    {
+        string responseHeaders, responseBody, payload;
+        string endpoint = "/run";
+        string netflix = "Netflix";
+        string youtube = "YouTube";
+
+        memset(buf, 0, 80);
+        printf("0. List DIAL servers\n");
+        printf("1. Launch Netflix\n");
+        printf("2. Kill Netflix\n");
+        printf("3. Netflix status\n");
+        printf("4. Launch YouTube\n");
+        printf("5. Kill YouTube\n");
+        printf("6. YouTube status\n");
+        printf("7. Run conformance tests\n");
+        printf("8. QUIT\n");
+        printf("Command (0:1:2:3:4:5:6:7:8): ");
+        scanf("%s", buf);
+        switch( atoi(buf) )
+        {
+            case 0:
+            {
+                printf("\n\n******** %Zu servers found ********\n\n", list.size());
+                for( unsigned int i = 0; i < list.size(); i++ )
+                {
+                    string name;
+                    list[i]->getFriendlyName(name);
+                    printf("Server %Zu: %s\n", i+1, name.c_str());
+                }
+                printf("\n*********************************\n\n");
+            }break;
+            case 1:
+                printf("Launch Netflix\n");
+                pServer->launchApplication( netflix, payload, responseHeaders, responseBody );
+                break;
+            case 2:
+                printf("Kill Netflix\n");
+                pServer->stopApplication( netflix, endpoint, responseHeaders );
+                break;
+            case 3:
+                printf("Netflix Status: \n");
+                pServer->getStatus( netflix, responseHeaders, responseBody );
+                printf("RESPONSE: \n%s\n", responseBody.c_str());
+                break;
+            case 4:
+                printf("Launch YouTube\n");
+                pServer->launchApplication( youtube, payload, responseHeaders, responseBody );
+                break;
+            case 5:
+                printf("Kill YouTube\n");
+                pServer->stopApplication( youtube, endpoint, responseHeaders );
+                break;
+            case 6:
+                printf("YouTube Status: \n");
+                pServer->getStatus( youtube, responseHeaders, responseBody );
+                break;
+            case 7:
+                runConformance();
+                break;
+            case 8:
+                processInput = 0;
+                break;
+            default:
+                printf("Invalid, try again\n");
+        }
+    } 
+    return 0;
+}
+
+static const char usage[] = "\n"
+" If no option is specified, the program will run a conformance test.\n"
+"\n"
+"usage: dialclient <option>\n"
+" Option Parameter              Description\n"
+" -h     none                   Usage menu\n"
+" -s     filename (optional)    Run conformance test.  Use filename as\n"
+"                               the input, if provided\n"
+" -o     filename               Reporter output file (./report.html)\n"
+" -a     ip_address             IP address of DIAL server (used for conformance\n"
+"                               testing)\n"
+"\n";
+
+inline void notSupported( string s )
+{
+    printf( "%s not supported", s.c_str() );
+    printf( "%s\n", usage );
+    exit(0);
+}
+
+int parseArgs( int argc, char* argv[] )
+{
+    for( int i = 1; i < argc; i++ )
+    {
+        string input(argv[i]);
+        if( input[0] != '-' ) notSupported(input);
+        switch(input[1])
+        {
+        case 's':
+            gUseMenu = false;
+            if( argv[i+1] != NULL && argv[i+1][0] != '-' )
+            {
+                //filename provided
+                gInputFile = argv[++i];
+            }
+            break;
+        case 'o':
+            gOutputFile = argv[++i];
+            break;
+        case 'a':
+            gIpAddress = argv[++i];
+            break;
+        case 'h':
+            printf("%s", usage);
+            exit(0);
+            break;
+        default:
+            notSupported(input);
+        }
+    }
+    return 0;
+}
+
+int main(int argc, char* argv[]) {
+    parseArgs(argc, argv);
+
+    gpDiscovery = DialDiscovery::create();
+    gpDiscovery->init();
+    DialConformance::create();
+
+    // Sleep for 2 seconds to allow DIAL servers to response to MSEARCH.
+    sleep(2);
+
+    if ( gUseMenu )
+    {
+        return handleUser(gpDiscovery);
+    }
+
+    // not using the menu, just run the conformance test.
+    runConformance();
+    return 0;
+}
+
diff --git a/src/client/makefile b/src/client/makefile
new file mode 100644
index 0000000..68cf150
--- /dev/null
+++ b/src/client/makefile
@@ -0,0 +1,14 @@
+CC=$(TARGET)g++

+

+.PHONY: clean

+.DEFAULT_GOAL=dialclient

+

+includes = $(wildcard *.h)

+OBJS := main.cpp DialServer.cpp DialDiscovery.cpp DialConformance.cpp DialClientInput.cpp

+

+# You may not need all these libraries.  This example uses a build of curl that needs crypto, ssl, cares, and zlib

+dialclient: $(OBJS) ${includes}

+	$(CC) -Wall -Werror -g $(OBJS) $(INCLUDES) $(LDFLAGS) -ldl -lpthread -lcurl -lz -lcrypto -lssl -lcares -m32 -o dialclient

+

+clean:

+	rm -f *.o dialclient

diff --git a/src/makefile b/src/makefile
new file mode 100644
index 0000000..e934e46
--- /dev/null
+++ b/src/makefile
@@ -0,0 +1,8 @@
+DIRS = client

+DIRS += server

+

+all:

+	for dir in $(DIRS); do (make -C $$dir || exit 1) || exit 1; done

+clean:

+	for dir in $(DIRS); do (make clean -C $$dir || exit 1) || exit 1; done

+

diff --git a/src/server/dial_options.h b/src/server/dial_options.h
new file mode 100644
index 0000000..9d67392
--- /dev/null
+++ b/src/server/dial_options.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIAL_OPTIONS_H
+#define DIAL_OPTIONS_H
+
+#define DATA_PATH_OPTION "-D"
+#define DATA_PATH_OPTION_LONG "--data-path"
+#define DATA_PATH_DESCRIPTION "Path to netflix secure store"
+
+#define NETFLIX_PATH_OPTION "-N"
+#define NETFLIX_PATH_OPTION_LONG "--netflix-path"
+#define NETFLIX_PATH_DESCRIPTION "Path to Netflix application"
+
+#define FRIENDLY_NAME_OPTION "-F"
+#define FRIENDLY_NAME_OPTION_LONG "--friendly-name"
+#define FRIENDLY_NAME_DESCRIPTION "Device Friendly Name"
+
+#define MODELNAME_OPTION "-M"
+#define MODELNAME_OPTION_LONG "--model-name"
+#define MODELNAME_DESCRIPTION "Model name of the device"
+
+#define UUID_OPTION "-U"
+#define UUID_OPTION_LONG "--uuid-name"
+#define UUID_DESCRIPTION "UUID of the device"
+
+struct dial_options
+{
+    const char * pOption;
+    const char * pLongOption;
+    const char * pOptionDescription;
+}dial_options_t;
+
+struct dial_options gDialOptions[] = 
+{
+    {
+        DATA_PATH_OPTION,
+        DATA_PATH_OPTION_LONG,
+        DATA_PATH_DESCRIPTION
+    },
+    {
+        NETFLIX_PATH_OPTION,
+        NETFLIX_PATH_OPTION_LONG,
+        NETFLIX_PATH_DESCRIPTION
+    },
+    {
+        FRIENDLY_NAME_OPTION,
+        FRIENDLY_NAME_OPTION_LONG,
+        FRIENDLY_NAME_DESCRIPTION,
+    },
+    {
+        MODELNAME_OPTION,
+        MODELNAME_OPTION_LONG,
+        MODELNAME_DESCRIPTION
+    },
+    {
+        UUID_OPTION,
+        UUID_OPTION_LONG,
+        UUID_DESCRIPTION
+    }
+};
+
+#endif
+
diff --git a/src/server/dial_server.c b/src/server/dial_server.c
new file mode 100644
index 0000000..8929594
--- /dev/null
+++ b/src/server/dial_server.c
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dial_server.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include "mongoose.h"
+
+// TODO: Partners should define this port
+#define DIAL_PORT (56789)
+
+struct DIALApp_ {
+    struct DIALApp_ *next;
+    struct DIALAppCallbacks callbacks;
+    void *callback_data;
+    DIAL_run_t run_id;
+    DIALStatus state;
+    char *name;
+    char payload[DIAL_MAX_PAYLOAD];
+};
+
+typedef struct DIALApp_ DIALApp;
+
+struct DIALServer_ {
+    struct mg_context *ctx;
+    struct DIALApp_ *apps;
+    pthread_mutex_t mux;
+};
+
+static void ds_lock(DIALServer *ds) {
+    pthread_mutex_lock(&ds->mux);
+}
+
+static void ds_unlock(DIALServer *ds) {
+    pthread_mutex_unlock(&ds->mux);
+}
+
+// finds an app and returns a pointer to the previous element's next pointer
+// if not found, return a pointer to the last element's next pointer
+static DIALApp **find_app(DIALServer *ds, const char *app_name) {
+    DIALApp *app;
+    DIALApp **ret = &ds->apps;
+  
+    for (app = ds->apps; app != NULL; ret = &app->next, app = app->next) {
+        if (!strcmp(app_name, app->name)) {
+            break;
+        }
+    }
+    return ret;
+}
+
+/*
+ * A bad payload is defined to be an unprintable character or a
+ * non-ascii character.
+ */
+static int isBadPayload( const char* pPayload, int numBytes )
+{
+    int i = 0;
+    fprintf( stderr, "Checking %d bytes\n", numBytes );
+    for( ; i < numBytes; i++)
+    {
+        // High order bit should not be set
+        // 0x7F is DEL (non-printable)
+        // Anything under 32 is non-printable
+        if( ((pPayload[i] & 0x80) == 0x80)  || 
+            (pPayload[i] == 0x7F)           || 
+            (pPayload[i] <= 0x1F) )
+            return 1;
+    }
+    return 0;
+}
+
+static void handle_app_start(struct mg_connection *conn,
+                             const struct mg_request_info *request_info,
+                             const char *app_name) {
+    char body[DIAL_MAX_PAYLOAD+2] = {0,};
+    DIALApp *app;
+    DIALServer *ds = request_info->user_data;
+    int nread;
+  
+    ds_lock(ds);
+    app = *find_app(ds, app_name);
+    if (!app) {
+        mg_send_http_error(conn, 404, "Not Found", "Not Found");
+    } else {
+        nread = mg_read(conn, body, sizeof(body));
+        // NUL-terminate it just in case
+        if( nread > DIAL_MAX_PAYLOAD ) {
+            mg_send_http_error(conn, 413, "413 Request Entity Too Large",
+                             "413 Request Entity Too Large");
+        }
+        else if( isBadPayload( body, nread ) ){
+            mg_send_http_error(conn, 400, "400 Bad Request", "400 Bad Request");
+        }
+        else {
+            app->state = app->callbacks.start_cb(ds, app_name, body, nread,
+                                                 &app->run_id, app->callback_data);
+            if (app->state == kDIALStatusRunning) {
+                char laddr[INET6_ADDRSTRLEN];
+                const struct sockaddr_in *addr =
+                    (struct sockaddr_in *)&request_info->local_addr;
+                inet_ntop(addr->sin_family, &addr->sin_addr, laddr, sizeof(laddr));
+                mg_printf(conn, "HTTP/1.1 201 Created\r\n"
+                                "Content-Type: text/plain\r\n"
+                                "Location: http://%s:%d/apps/%s/run\r\n"
+                                "\r\n", laddr, DIAL_get_port(ds), app_name);
+                // copy the payload into the application struct
+                memset( app->payload, 0, DIAL_MAX_PAYLOAD );
+                memcpy( app->payload, body, nread );
+            } else {
+                mg_send_http_error(conn, 503, "Service Unavailable",
+                                 "Service Unavailable");
+            }
+        }
+    }
+    ds_unlock(ds);
+}
+
+static void handle_app_status(struct mg_connection *conn,
+                              const struct mg_request_info *request_info,
+                              const char *app_name) {
+    DIALApp *app;
+    int canStop = 0;
+    DIALServer *ds = request_info->user_data;
+  
+    ds_lock(ds);
+    app = *find_app(ds, app_name);
+    if (!app) {
+        mg_send_http_error(conn, 404, "Not Found", "Not Found");
+    } else {
+        app->state = app->callbacks.status_cb(ds, app_name, app->run_id,
+                                              &canStop, app->callback_data);
+        mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+                      "Content-Type: application/xml\r\n"
+                      "\r\n"
+                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
+                      "<service xmlns=\"urn:dial-multiscreen-org:schemas:dial\">\r\n"
+                      "  <name>%s</name>\r\n"
+                      "  <options allowStop=\"%s\"/>\r\n"
+                      "  <state>%s</state>\r\n"
+                      "%s"
+                      "</service>\r\n", app->name, canStop ? "true":"false",
+                      app->state ? "running" : "stopped",
+                      app->state ==  kDIALStatusStopped ?
+                      "" : "  <link rel=\"run\" href=\"run\"/>\r\n" );
+    }
+    ds_unlock(ds);
+}
+
+static void handle_app_stop(struct mg_connection *conn,
+                            const struct mg_request_info *request_info,
+                            const char *app_name) {
+    DIALApp *app;
+    DIALServer *ds = request_info->user_data;
+    int canStop = 0;
+  
+    ds_lock(ds);
+    app = *find_app(ds, app_name);
+
+    // update the application state
+    if( !app ) {
+        app->state = app->callbacks.status_cb(ds, app_name, app->run_id,
+                                              &canStop, app->callback_data);
+    }
+
+    if (!app || app->state != kDIALStatusRunning) {
+        mg_send_http_error(conn, 404, "Not Found", "Not Found");
+    } else {
+        app->callbacks.stop_cb(ds, app_name, app->run_id, app->callback_data);
+        app->state = kDIALStatusStopped;
+        mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+                        "Content-Type: text/plain\r\n"
+                        "\r\n");
+    }
+    ds_unlock(ds);
+}
+
+#define APPS_URI "/apps/"
+#define RUN_URI "/run"
+
+static void *request_handler(enum mg_event event,
+                             struct mg_connection *conn,
+                             const struct mg_request_info *request_info) {
+    if (event == MG_NEW_REQUEST) {
+        // URL ends with run
+        if (!strncmp(request_info->uri + strlen(request_info->uri)-4, RUN_URI, strlen(RUN_URI))) {
+            char app_name[256] = {0,}; // assuming the application name is not over 256 chars.
+            strncpy( app_name, request_info->uri + strlen(APPS_URI),
+              ((strlen(request_info->uri)-4) - (sizeof(APPS_URI)-1)) );
+      
+            // DELETE non-empty app name
+            if (app_name[0] != '\0' &&
+                !strcmp(request_info->request_method, "DELETE")) {
+                handle_app_stop(conn, request_info, app_name);
+            } else {
+                mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented");
+            }
+        }
+        // URI starts with "/apps/"
+        else if (!strncmp(request_info->uri, APPS_URI, sizeof(APPS_URI) - 1)) {
+            const char *app_name;
+            app_name = request_info->uri + sizeof(APPS_URI) - 1;
+            // start app
+            if (!strcmp(request_info->request_method, "POST")) {
+                handle_app_start(conn, request_info, app_name);
+            // get app status
+            } else if (!strcmp(request_info->request_method, "GET")) {
+                handle_app_status(conn, request_info, app_name);
+            } else {
+                mg_send_http_error(conn, 501, "Not Implemented", "Not Implemented");
+            }
+        } else {
+            mg_send_http_error(conn, 404, "Not Found", "Not Found");
+        }
+        return "done";
+    } else if (event == MG_EVENT_LOG) {
+        fprintf( stderr, "MG: %s\n", request_info->log_message);
+        return "done";
+    }
+    return NULL;
+}
+
+DIALServer *DIAL_start() {
+    DIALServer *ds = calloc(1, sizeof(DIALServer));
+    struct mg_context *ctx;
+  
+    pthread_mutex_init(&ds->mux, NULL);
+    ctx = mg_start(&request_handler, ds, DIAL_PORT);
+    if (!ctx) {
+        free(ds);
+        return NULL;
+    }
+    ds->ctx = ctx;
+    return ds;
+}
+
+void DIAL_stop(DIALServer *ds) {
+    mg_stop(ds->ctx);
+    pthread_mutex_destroy(&ds->mux);
+}
+
+in_port_t DIAL_get_port(DIALServer *ds) {
+    struct sockaddr sa;
+    socklen_t len = sizeof(sa);
+    if (!mg_get_listen_addr(ds->ctx, &sa, &len)) {
+        return 0;
+    }
+    return ntohs(((struct sockaddr_in *)&sa)->sin_port);
+}
+
+int DIAL_register_app(DIALServer *ds, const char *app_name,
+                      struct DIALAppCallbacks *callbacks,
+                      void *user_data) {
+    DIALApp **ptr, *app;
+    int ret;
+  
+    ds_lock(ds);
+    ptr = find_app(ds, app_name);
+    if (*ptr != NULL) { // app already registered
+        ds_unlock(ds);
+        ret = 0;
+    } else {
+        app = malloc(sizeof(DIALApp));
+        app->callbacks = *callbacks;
+        app->name = strdup(app_name);
+        app->next = *ptr;
+        app->state = kDIALStatusStopped;
+        app->callback_data = user_data;
+        *ptr = app;
+        ret = 1;
+    }
+  
+    ds_unlock(ds);
+    return ret;
+}
+
+int DIAL_unregister_app(DIALServer *ds, const char *app_name) {
+    DIALApp **ptr, *app;
+    int ret;
+  
+    ds_lock(ds);
+    ptr = find_app(ds, app_name);
+    if (*ptr == NULL) { // no such app
+        ret = 0;
+    } else {
+        app = *ptr;
+        *ptr = app->next;
+        free(app->name);
+        free(app);
+        ret = 1;
+    }
+  
+    ds_unlock(ds);
+    return ret;
+}
+
+const char * DIAL_get_payload(DIALServer *ds, const char *app_name)
+{
+    const char * pPayload = NULL;
+    DIALApp **ptr, *app;
+  
+    // NOTE: Don't grab the mutex as we are calling this function from 
+    // inside the application callback which already has the lock.
+    //ds_lock(ds);
+    ptr = find_app(ds, app_name);
+    if (*ptr != NULL)
+    {
+        app = *ptr;
+        pPayload = app->payload;
+    }
+    //ds_unlock(ds);
+    return pPayload;
+}
+
diff --git a/src/server/dial_server.h b/src/server/dial_server.h
new file mode 100644
index 0000000..fa309ad
--- /dev/null
+++ b/src/server/dial_server.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DIAL_SERVER_H_
+#define DIAL_SERVER_H_
+
+#include <netinet/in.h>
+
+/*
+ * Dial application states
+ */
+typedef enum {
+    kDIALStatusStopped,
+    kDIALStatusRunning
+} DIALStatus;
+
+/*
+ * The maximum DIAL payload accepted per the DIAL 1.6.1 specification.
+ */
+#define DIAL_MAX_PAYLOAD (4096)
+
+/*
+ * Opaque DIAL server handle
+ */
+struct DIALServer_;
+typedef struct DIALServer_ DIALServer;
+
+/*
+ * Opaque run id that can be system specific
+ */
+typedef void * DIAL_run_t;
+
+/*
+ * DIAL start callback
+ */
+typedef DIALStatus (*DIAL_app_start_cb)(DIALServer *ds, const char *app_name,
+                                        const char *args, size_t arglen,
+                                        DIAL_run_t *run_id, void *callback_data);
+/*
+ * DIAL stop callback
+ */
+typedef void (*DIAL_app_stop_cb)(DIALServer *ds, const char *app_name,
+                                 DIAL_run_t run_id, void *callback_data);
+/*
+ * DIAL status callback
+ */
+typedef DIALStatus (*DIAL_app_status_cb)(DIALServer *ds, const char *app_name,
+                                         DIAL_run_t run_id, int* pCanStop,
+                                         void *callback_data);
+
+/*
+ * DIAL callbacks
+ */
+struct DIALAppCallbacks {
+    DIAL_app_start_cb start_cb;
+    DIAL_app_stop_cb stop_cb;
+    DIAL_app_status_cb status_cb;
+};
+
+/*
+ * Start the DIAL server.  Returns a handle to the DIAL server.
+ */
+DIALServer *DIAL_start();
+
+/*
+ * Stop the DIAL server.  Returns a handle to the DIAL server.
+ *
+ * @param[in] ds DIAL server handle
+ */
+void DIAL_stop(DIALServer *ds);
+
+/*
+ * Register a DIAL application
+ *
+ * @param[in] ds DIAL server handle
+ * @param[in] app_name Name of the application.  This should match the DIAL 
+ * application end point.
+ * @param[in] callbacks Structure with application callbacks
+ * @param[in] callback_data Client user data
+ *
+ * @return 1 if successful, 0 otherwise
+ */
+int DIAL_register_app(DIALServer *ds, const char *app_name,
+                      struct DIALAppCallbacks *callbacks,
+                      void *callback_data);
+
+/*
+ * Unregsiter an application
+ *
+ * @param[in] ds DIAL server handle
+ * @param[in] app_name Name of the DIAL application
+ *
+ * @return 1 if successful, 0 otherwise
+ */
+int DIAL_unregister_app(DIALServer *ds, const char *app_name);
+
+/*
+ * Get the DIAL REST endpoint
+ *
+ * @return Port number of the DIAL rest endpoint.  Returns 0 on error.
+ */
+in_port_t DIAL_get_port(DIALServer *ds);
+
+/*
+ * Get the last payload delivered to an application.  This can be used
+ * by application clients to see if the payload changed between lauches.
+ *
+ * @param[in] ds DIAL server handle
+ * @param[in] app_name Name of the application
+ *
+ * @return Pointer to a NULL terminated string.
+ */
+const char * DIAL_get_payload(DIALServer *ds, const char *app_name);
+
+#endif  // DIAL_SERVER_H_
diff --git a/src/server/main.c b/src/server/main.c
new file mode 100644
index 0000000..aac852e
--- /dev/null
+++ b/src/server/main.c
@@ -0,0 +1,451 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <regex.h>
+
+#include "dial_server.h"
+#include "dial_options.h"
+
+#define BUFSIZE 256 
+
+static char *spAppNetflix = "netflix";      // name of the netflix executable
+static char *spSecret = "12345678910shhhNFLXwashereman";
+static char *spDefaultNetflix = "../../../src/platform/qt/netflix";
+static char *spDefaultData="../../../src/platform/qt/data";
+static char *spNfDataDir = "NF_DATA_DIR=";
+static char *defaultLaunchParam = "source_type=12";
+static char *spDefaultFriendlyName = "DIAL server sample";
+static char *spDefaultModelName = "NOT A VALID MODEL NAME";
+static char *spDefaultUuid = "deadbeef-dead-beef-dead-beefdeadbeef";
+static char spDataDir[BUFSIZE];
+static char spNetflix[BUFSIZE];
+static char spFriendlyName[BUFSIZE];
+static char spModelName[BUFSIZE];
+static char spUuid[BUFSIZE];
+static int gDialPort;
+
+static char *spAppYouTube = "chrome";
+static char *spAppYouTubeMatch = "chrome.*google-chrome-dial";
+static char *spAppYouTubeExecutable = "/opt/google/chrome/google-chrome";
+static char *spYouTubePS3UserAgent = "--user-agent="
+    "Mozilla/5.0 (PS3; Leanback Shell) AppleWebKit/535.22 (KHTML, like Gecko) "
+    "Chrome/19.0.1048.0 LeanbackShell/01.00.01.73 QA Safari/535.22 Sony PS3/ "
+    "(PS3, , no, CH)";
+
+// Adding 20 bytes for prepended source_type for Netflix
+static char sQueryParam[DIAL_MAX_PAYLOAD+20];
+
+static int doesMatch( char* pzExp, char* pzStr)
+{
+    regex_t exp;
+    int ret;
+    int match = 0;
+    if ((ret = regcomp( &exp, pzExp, REG_EXTENDED ))) {
+        char errbuf[1024] = {0,};
+        regerror(ret, &exp, errbuf, sizeof(errbuf));
+        fprintf( stderr, "regexp error: %s", errbuf );
+    } else {
+        regmatch_t matches[1];
+        if( regexec( &exp, pzStr, 1, matches, 0 ) == 0 ) {
+            match = 1;
+        }
+    }
+    regfree(&exp);
+    return match;
+}
+
+
+/* The URL encoding source code was obtained here: 
+ * http://www.geekhideout.com/urlcode.shtml 
+ */
+
+/* Converts a hex character to its integer value */
+char from_hex(char ch) {
+    return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
+}
+
+/* Converts an integer value to its hex character*/
+char to_hex(char code) {
+    static char hex[] = "0123456789abcdef";
+  return hex[code & 15];
+}
+
+/* Returns a url-encoded version of str */
+/* IMPORTANT: be sure to free() the returned string after use */
+char *url_encode(const char *str) {
+    const char *pstr;
+    char *buf, *pbuf;
+    pstr = str;
+    buf = malloc(strlen(str) * 3 + 1);
+    pbuf = buf;
+    if( buf )
+    {
+        while (*pstr) {
+            if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') 
+                *pbuf++ = *pstr;
+            else if (*pstr == ' ') 
+                *pbuf++ = '+';
+            else 
+                *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
+            pstr++;
+        }
+        *pbuf = '\0';
+    }
+    return buf;
+}
+
+/*
+ * End of URL ENCODE source
+ */
+
+/*
+ * This function will walk /proc and look for the application in
+ * /proc/<PID>/comm. and /proc/<PID>/cmdline to find it's command (executable
+ * name) and command line (if needed).
+ * Implementors can override this function with an equivalent.
+ */
+static int isAppRunning( char *pzName, char *pzCommandPattern ) {
+  DIR* proc_fd = opendir("/proc");
+  if( proc_fd != NULL ) {
+    struct dirent* procEntry;
+    while((procEntry=readdir(proc_fd)) != NULL) {
+      if( doesMatch( "^[0-9][0-9]*$", procEntry->d_name ) ) {
+        char exePath[64] = {0,};
+        char link[256] = {0,};
+        char cmdlinePath[64] = {0,};
+        char buffer[1024] = {0,};
+        int len;
+        sprintf( exePath, "/proc/%s/exe", procEntry->d_name);
+        sprintf( cmdlinePath, "/proc/%s/cmdline", procEntry->d_name);
+
+        if( (len = readlink( exePath, link, sizeof(link)-1)) != -1 ) {
+          char executable[256] = {0,};
+          strcat( executable, pzName );
+          strcat( executable, "$" );
+          // TODO: Make this search for EOL to prevent false positivies
+          if( !doesMatch( executable, link ) ) {
+            continue;
+          }
+          // else //fall through, we found it
+        }
+        else continue;
+
+        if (pzCommandPattern != NULL) {
+          FILE *cmdline = fopen(cmdlinePath, "r");
+          if (!cmdline) {
+            continue;
+          }
+          if (fgets(buffer, 1024, cmdline) == NULL) {
+            fclose(cmdline);
+            continue;
+          }
+          fclose(cmdline);
+
+          if (!doesMatch( pzCommandPattern, buffer )) {
+            continue;
+          }
+        }
+
+        closedir(proc_fd);
+        return atoi(procEntry->d_name);
+      }
+    }
+
+    closedir(proc_fd);
+  } else {
+    printf("/proc failed to open\n");
+  }
+  return 0;
+}
+
+static pid_t runApplication( const char * const args[], DIAL_run_t *run_id ) {
+  pid_t pid = fork();
+  if (pid != -1) {
+    if (!pid) { // child
+      putenv(spDataDir);
+      printf("Execute:\n");
+      for(int i = 0; args[i]; ++i) {
+        printf(" %d) %s\n", i, args[i]);
+      }
+      execv(*args, (char * const *) args);
+    } else {
+      *run_id = (void *)(long)pid; // parent PID
+    }
+    return kDIALStatusRunning;
+  } else {
+    return kDIALStatusStopped;
+  }
+}
+
+
+/* Compare the applications last launch parameters with the new parameters.
+ * If they match, return false
+ * If they don't match, return true
+ */
+static int shouldRelaunch( 
+    DIALServer *pServer, 
+    const char *pAppName, 
+    const char *args )
+{
+    return ( strncmp( DIAL_get_payload(pServer, pAppName), args, DIAL_MAX_PAYLOAD ) != 0 );
+}
+
+static DIALStatus youtube_start(DIALServer *ds, const char *appname,
+                                const char *args, size_t arglen,
+                                DIAL_run_t *run_id, void *callback_data) {
+    printf("\n\n ** LAUNCH YouTube **\n\n");
+
+    // TODO(steineldar): Verify that the args string is a valid URL query param.
+    for (int n = 0; args[n] != 0; ++n) {
+      char c = args[n];
+      if (!(( '0' <= c && c <= '9' ) ||
+            ( 'a' <= c && c <= 'z' ) ||
+            ( 'A' <= c && c <= 'Z' ) ||
+            ( c == '=' || c == '-' || c == '_' || c == '&' ))) {
+        printf("Invalid char [%d] = %c", (int) c, c);
+        args = "";
+        break;
+      }
+    }
+
+    char url[512] = {0,}, data[512] = {0,};
+    sprintf( url, "https://www.youtube.com/tv?%s", args);
+    sprintf( data, "--user-data-dir=%s/.config/google-chrome-dial", getenv("HOME") );
+
+    const char * const youtube_args[] = { spAppYouTubeExecutable,
+      spYouTubePS3UserAgent,
+      data, "--app", url, NULL
+    };
+    runApplication( youtube_args, run_id );
+
+    return kDIALStatusRunning;
+}
+
+static DIALStatus youtube_status(DIALServer *ds, const char *appname,
+                                 DIAL_run_t run_id, int *pCanStop, void *callback_data) {
+    // YouTube can stop
+    *pCanStop = 1;
+    return isAppRunning( spAppYouTube, spAppYouTubeMatch ) ? kDIALStatusRunning : kDIALStatusStopped;
+}
+
+static void youtube_stop(DIALServer *ds, const char *appname, DIAL_run_t run_id,
+                         void *callback_data) {
+    printf("\n\n ** KILL YouTube **\n\n");
+    pid_t pid;
+    if ((pid = isAppRunning( spAppYouTube, spAppYouTubeMatch ))) {
+        kill(pid, SIGTERM);
+    }
+}
+
+static DIALStatus netflix_start(DIALServer *ds, const char *appname,
+                                const char *args, size_t arglen,
+                                DIAL_run_t *run_id, void *callback_data) {
+    int shouldRelaunchApp = 0;
+    int payloadLen = 0;
+    int appPid = 0;
+  
+    // only launch Netflix if it isn't running
+    appPid = isAppRunning( spAppNetflix, NULL );
+    shouldRelaunchApp = shouldRelaunch( ds, appname, args );
+  
+    // construct the payload to determine if it has changed from the previous launch
+    payloadLen = strlen(args);
+    memset( sQueryParam, 0, DIAL_MAX_PAYLOAD );
+    strcat( sQueryParam, defaultLaunchParam );
+    if( payloadLen )
+    {
+        char * pUrlEncodedParams;
+        pUrlEncodedParams = url_encode( args );
+        if( pUrlEncodedParams )
+        {
+            strcat( sQueryParam, "&dial=");
+            strcat( sQueryParam, pUrlEncodedParams );
+            free( pUrlEncodedParams );
+        }
+    }
+  
+    printf("appPid = %s, shouldRelaunch = %s queryParams = %s\n",
+          appPid?"TRUE":"FALSE", 
+          shouldRelaunchApp?"TRUE":"FALSE",
+          sQueryParam );
+  
+    // if its not running, launch it.  The Netflix application should
+    // never be relaunched
+    if( !appPid )
+    {
+        const char * const netflix_args[] = {spNetflix, "-s", spSecret, "-Q", sQueryParam, 0};
+        return runApplication( netflix_args, run_id );
+    }
+    else return kDIALStatusRunning;
+}
+
+static DIALStatus netflix_status(DIALServer *ds, const char *appname,
+                                 DIAL_run_t run_id, int* pCanStop, void *callback_data) {
+    // Netflix application can stop
+    *pCanStop = 1;
+
+    waitpid((pid_t)run_id, NULL, WNOHANG); // reap child
+    return isAppRunning( spAppNetflix, NULL ) ? kDIALStatusRunning : kDIALStatusStopped;
+}
+
+static void netflix_stop(DIALServer *ds, const char *appname, DIAL_run_t run_id,
+                         void *callback_data) {
+    int pid;
+    pid = isAppRunning( spAppNetflix, NULL );
+    if( pid )
+    {
+        printf("Killing pid %d\n", pid);
+        kill((pid_t)pid, SIGKILL);
+        waitpid((pid_t)pid, NULL, 0); // reap child
+    }
+}
+
+void run_ssdp(int port, const char *pFriendlyName, const char * pModelName, const char *pUuid);
+
+static void printUsage()
+{
+    int i, numberOfOptions = sizeof(gDialOptions) / sizeof(dial_options_t);
+    printf("usage: dialserver <options>\n");
+    printf("options:\n");
+    for( i = 0; i < numberOfOptions; i++ )
+    {
+        printf("        %s|%s [value]: %s\n", 
+            gDialOptions[i].pOption,
+            gDialOptions[i].pLongOption,
+            gDialOptions[i].pOptionDescription );
+    }
+}
+
+static void setValue( char * pSource, char dest[] )
+{
+    // Destination is always one of our static buffers with size BUFSIZE
+    memset( dest, 0, BUFSIZE );
+    memcpy( dest, pSource, strlen(pSource) );
+}
+
+static void setDataDir(char *pData)
+{
+    setValue( spNfDataDir, spDataDir );
+    strcat(spDataDir, pData);
+}
+
+void runDial(void)
+{
+    DIALServer *ds;
+    ds = DIAL_start();
+    struct DIALAppCallbacks cb_nf = {netflix_start, netflix_stop, netflix_status};
+    struct DIALAppCallbacks cb_yt = {youtube_start, youtube_stop, youtube_status};
+
+    DIAL_register_app(ds, "Netflix", &cb_nf, NULL);
+    DIAL_register_app(ds, "YouTube", &cb_yt, NULL);
+    gDialPort = DIAL_get_port(ds);
+    printf("launcher listening on gDialPort %d\n", gDialPort);
+    run_ssdp(gDialPort, spFriendlyName, spModelName, spUuid);
+    
+    DIAL_stop(ds);
+}
+
+static void processOption( int index, char * pOption )
+{
+    switch(index)
+    {
+        case 0: // Data path
+            memset( spDataDir, 0, sizeof(spDataDir) );
+            setDataDir( pOption );
+            break;
+        case 1: // Netflix path
+            setValue( pOption, spNetflix );
+            break;
+        case 2: // Friendly name
+            setValue( pOption, spFriendlyName ); 
+            break;
+        case 3: // Model Name
+            setValue( pOption, spModelName );
+            break;
+        case 4: // UUID 
+            setValue( pOption, spUuid );
+            break;
+        default:
+            // Should not get here
+            fprintf( stderr, "Option %d not valid\n", index);
+            exit(1);
+    }
+}
+
+int main(int argc, char* argv[]) 
+{
+    int i;
+    i = isAppRunning(spAppNetflix, NULL );
+    printf("Netflix is %s\n", i ? "Running":"Not Running");
+    i = isAppRunning( spAppYouTube, spAppYouTubeMatch );
+    printf("YouTube is %s\n", i ? "Running":"Not Running");
+
+    // set all defaults
+    setValue(spDefaultFriendlyName, spFriendlyName );
+    setValue(spDefaultModelName, spModelName );
+    setValue(spDefaultUuid, spUuid );
+    setValue(spDefaultNetflix, spNetflix );
+    setDataDir(spDefaultData);
+
+    // Process command line options
+    // Loop through pairs of command line options.
+    for( i = 1; i < argc; i+=2 )
+    {
+        int numberOfOptions = sizeof(gDialOptions) / sizeof(dial_options_t);
+        while( --numberOfOptions >= 0 )
+        {
+            int shortLen, longLen;
+            shortLen = strlen(gDialOptions[numberOfOptions].pOption);
+            longLen = strlen(gDialOptions[numberOfOptions].pLongOption);
+            if( ( ( strncmp( argv[i], gDialOptions[numberOfOptions].pOption, shortLen ) == 0 ) || 
+                ( strncmp( argv[i], gDialOptions[numberOfOptions].pLongOption, longLen ) == 0 ) ) &&
+                ( (i+1) < argc ) )
+            {
+                processOption( numberOfOptions, argv[i+1] );
+                break;
+            }
+        }
+        // if we don't find an option in our list, bail out.
+        if( numberOfOptions < 0 )
+        {
+            printUsage();
+            exit(1);
+        }
+    }
+    runDial();
+
+    return 0;
+}
+
diff --git a/src/server/makefile b/src/server/makefile
new file mode 100644
index 0000000..c269204
--- /dev/null
+++ b/src/server/makefile
@@ -0,0 +1,18 @@
+CC=$(TARGET)gcc

+

+.PHONY: clean

+.DEFAULT_GOAL=dialserver

+

+OBJS := main.o dial_server.o mongoose.o quick_ssdp.o

+HEADERS := $(wildcard *.h)

+

+%.c: $(HEADERS)

+

+%.o: %.c $(HEADERS)

+	$(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o

+

+dialserver: $(OBJS)

+	$(CC) -Wall -Werror -g $(OBJS) -ldl -lpthread -o dialserver

+

+clean:

+	rm -f *.o dialserver

diff --git a/src/server/mongoose.c b/src/server/mongoose.c
new file mode 100644
index 0000000..14b4980
--- /dev/null
+++ b/src/server/mongoose.c
@@ -0,0 +1,952 @@
+// Copyright (c) 2004-2010 Sergey Lyubka

+//

+// Permission is hereby granted, free of charge, to any person obtaining a copy

+// of this software and associated documentation files (the "Software"), to deal

+// in the Software without restriction, including without limitation the rights

+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

+// copies of the Software, and to permit persons to whom the Software is

+// furnished to do so, subject to the following conditions:

+//

+// The above copyright notice and this permission notice shall be included in

+// all copies or substantial portions of the Software.

+//

+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

+// THE SOFTWARE.

+

+#include <sys/types.h>

+#include <sys/stat.h>

+#include <errno.h>

+#include <signal.h>

+#include <fcntl.h>

+#include <time.h>

+#include <stdlib.h>

+#include <stdarg.h>

+#include <assert.h>

+#include <string.h>

+#include <ctype.h>

+#include <limits.h>

+#include <stddef.h>

+#include <stdio.h>

+

+#ifndef BUFSIZ

+#define BUFSIZ  4096

+#endif

+

+#define MAX_REQUEST_SIZE 4096

+#define NUM_THREADS 4

+#include <sys/wait.h>

+#include <sys/socket.h>

+#include <sys/select.h>

+#include <netinet/in.h>

+#include <arpa/inet.h>

+#include <sys/time.h>

+#include <stdint.h>

+#include <inttypes.h>

+#include <netdb.h>

+#include <unistd.h>

+#include <pthread.h>

+

+

+#define ERRNO errno

+#define INVALID_SOCKET (-1)

+

+typedef int SOCKET;

+

+#include "mongoose.h"

+

+#define MONGOOSE_VERSION "3.0"

+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))

+

+#if defined(DEBUG)

+#define DEBUG_TRACE(x) do { \

+  flockfile(stdout); \

+  printf("*** %lu.%p.%s.%d: ", \

+         (unsigned long) time(NULL), (void *) pthread_self(), \

+         __func__, __LINE__); \

+  printf x; \

+  putchar('\n'); \

+  fflush(stdout); \

+  funlockfile(stdout); \

+} while (0)

+#else

+#define DEBUG_TRACE(x)

+#endif // DEBUG

+

+typedef void * (*mg_thread_func_t)(void *);

+

+

+// Describes a socket which was accept()-ed by the master thread and queued for

+// future handling by the worker thread.

+struct socket {

+  SOCKET sock;          // Listening socket

+  struct sockaddr_in local_addr;  // Local socket address

+  struct sockaddr_in remote_addr;  // Remote socket address

+};

+

+struct mg_context {

+  volatile int stop_flag;       // Should we stop event loop

+  mg_callback_t user_callback;  // User-defined callback function

+  void *user_data;              // User-defined data

+

+  SOCKET             local_socket;

+  struct sockaddr_in local_address;

+

+  volatile int num_threads;  // Number of threads

+  pthread_mutex_t mutex;     // Protects (max|num)_threads

+  pthread_cond_t  cond;      // Condvar for tracking workers terminations

+

+  struct socket queue[20];   // Accepted sockets

+  volatile int sq_head;      // Head of the socket queue

+  volatile int sq_tail;      // Tail of the socket queue

+  pthread_cond_t sq_full;    // Singaled when socket is produced

+  pthread_cond_t sq_empty;   // Signaled when socket is consumed

+};

+

+struct mg_connection {

+  struct mg_request_info request_info;

+  struct mg_context *ctx;

+  struct socket client;       // Connected client

+  time_t birth_time;          // Time connection was accepted

+  int64_t num_bytes_sent;     // Total bytes sent to client

+  int64_t content_len;        // Content-Length header value

+  int64_t consumed_content;   // How many bytes of content is already read

+  char *buf;                  // Buffer for received data

+  int buf_size;               // Buffer size

+  int request_len;            // Size of the request + headers in a buffer

+  int data_len;               // Total size of data in a buffer

+};

+

+static void *call_user(struct mg_connection *conn, enum mg_event event) {

+  conn->request_info.user_data = conn->ctx->user_data;

+  return conn->ctx->user_callback == NULL ? NULL :

+    conn->ctx->user_callback(event, conn, &conn->request_info);

+}

+

+// Print error message to the opened error log stream.

+static void cry(struct mg_connection *conn, const char *fmt, ...) {

+  char buf[BUFSIZ];

+  va_list ap;

+

+  va_start(ap, fmt);

+  (void) vsnprintf(buf, sizeof(buf), fmt, ap);

+  va_end(ap);

+

+  // Do not lock when getting the callback value, here and below.

+  // I suppose this is fine, since function cannot disappear in the

+  // same way string option can.

+  conn->request_info.log_message = buf;

+  if (call_user(conn, MG_EVENT_LOG) == NULL) {

+    DEBUG_TRACE((buf));

+  }

+  conn->request_info.log_message = NULL;

+}

+

+// Return fake connection structure. Used for logging, if connection

+// is not applicable at the moment of logging.

+static struct mg_connection *fc(struct mg_context *ctx) {

+  static struct mg_connection fake_connection;

+  fake_connection.ctx = ctx;

+  return &fake_connection;

+}

+

+const char *mg_version(void) {

+  return MONGOOSE_VERSION;

+}

+

+static int lowercase(const char *s) {

+  return tolower(* (const unsigned char *) s);

+}

+

+static int mg_strcasecmp(const char *s1, const char *s2) {

+  int diff;

+

+  do {

+    diff = lowercase(s1++) - lowercase(s2++);

+  } while (diff == 0 && s1[-1] != '\0');

+

+  return diff;

+}

+

+// Like snprintf(), but never returns negative value, or the value

+// that is larger than a supplied buffer.

+// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability

+// in his audit report.

+static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen,

+                        const char *fmt, va_list ap) {

+  int n;

+

+  if (buflen == 0)

+    return 0;

+

+  n = vsnprintf(buf, buflen, fmt, ap);

+

+  if (n < 0) {

+    cry(conn, "vsnprintf error");

+    n = 0;

+  } else if (n >= (int) buflen) {

+    cry(conn, "truncating vsnprintf buffer: [%.*s]",

+        n > 200 ? 200 : n, buf);

+    n = (int) buflen - 1;

+  }

+  buf[n] = '\0';

+

+  return n;

+}

+

+static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen,

+                       const char *fmt, ...) {

+  va_list ap;

+  int n;

+

+  va_start(ap, fmt);

+  n = mg_vsnprintf(conn, buf, buflen, fmt, ap);

+  va_end(ap);

+

+  return n;

+}

+

+// Skip the characters until one of the delimiters characters found.

+// 0-terminate resulting word. Skip the delimiter and following whitespaces if any.

+// Advance pointer to buffer to the next word. Return found 0-terminated word.

+// Delimiters can be quoted with quotechar.

+static char *skip_quoted(char **buf, const char *delimiters, const char *whitespace, char quotechar) {

+  char *p, *begin_word, *end_word, *end_whitespace;

+

+  begin_word = *buf;

+  end_word = begin_word + strcspn(begin_word, delimiters);

+

+  /* Check for quotechar */

+  if (end_word > begin_word) {

+    p = end_word - 1;

+    while (*p == quotechar) {

+      /* If there is anything beyond end_word, copy it */

+      if (*end_word == '\0') {

+        *p = '\0';

+        break;

+      } else {

+        size_t end_off = strcspn(end_word + 1, delimiters);

+        memmove (p, end_word, end_off + 1);

+        p += end_off; /* p must correspond to end_word - 1 */

+        end_word += end_off + 1;

+      }

+    }

+    for (p++; p < end_word; p++) {

+      *p = '\0';

+    }

+  }

+

+  if (*end_word == '\0') {

+    *buf = end_word;

+  } else {

+    end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace);

+

+    for (p = end_word; p < end_whitespace; p++) {

+      *p = '\0';

+    }

+

+    *buf = end_whitespace;

+  }

+

+  return begin_word;

+}

+

+// Simplified version of skip_quoted without quote char

+// and whitespace == delimiters

+static char *skip(char **buf, const char *delimiters) {

+  return skip_quoted(buf, delimiters, delimiters, 0);

+}

+

+

+// Return HTTP header value, or NULL if not found.

+static const char *get_header(const struct mg_request_info *ri,

+                              const char *name) {

+  int i;

+

+  for (i = 0; i < ri->num_headers; i++)

+    if (!mg_strcasecmp(name, ri->http_headers[i].name))

+      return ri->http_headers[i].value;

+

+  return NULL;

+}

+

+const char *mg_get_header(const struct mg_connection *conn, const char *name) {

+  return get_header(&conn->request_info, name);

+}

+

+static const char *suggest_connection_header(const struct mg_connection *conn) {

+  return "close";

+}

+

+void mg_send_http_error(struct mg_connection *conn, int status,

+                        const char *reason, const char *fmt, ...) {

+  char buf[BUFSIZ];

+  va_list ap;

+  int len;

+

+  conn->request_info.status_code = status;

+

+  buf[0] = '\0';

+  len = 0;

+

+  /* Errors 1xx, 204 and 304 MUST NOT send a body */

+  if (status > 199 && status != 204 && status != 304) {

+    len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason);

+    cry(conn, "%s", buf);

+    buf[len++] = '\n';

+

+    va_start(ap, fmt);

+    len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap);

+    va_end(ap);

+  }

+  DEBUG_TRACE(("[%s]", buf));

+

+  mg_printf(conn, "HTTP/1.1 %d %s\r\n"

+      "Content-Type: text/plain\r\n"

+      "Content-Length: %d\r\n"

+      "Connection: %s\r\n\r\n", status, reason, len,

+      suggest_connection_header(conn));

+  conn->num_bytes_sent += mg_printf(conn, "%s", buf);

+}

+

+static int start_thread(struct mg_context *ctx, mg_thread_func_t func,

+                        void *param) {

+  pthread_t thread_id;

+  pthread_attr_t attr;

+  int retval;

+

+  (void) pthread_attr_init(&attr);

+  (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

+  // TODO(lsm): figure out why mongoose dies on Linux if next line is enabled

+  // (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5);

+

+  if ((retval = pthread_create(&thread_id, &attr, func, param)) != 0) {

+    cry(fc(ctx), "%s: %s", __func__, strerror(retval));

+  }

+

+  return retval;

+}

+

+static int set_non_blocking_mode(SOCKET sock) {

+  int flags;

+

+  flags = fcntl(sock, F_GETFL, 0);

+  (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);

+

+  return 0;

+}

+

+// Write data to the IO channel - opened file descriptor, socket or SSL

+// descriptor. Return number of bytes written.

+static int64_t push(FILE *fp, SOCKET sock, const char *buf, int64_t len) {

+  int64_t sent;

+  int n, k;

+

+  sent = 0;

+  while (sent < len) {

+

+    /* How many bytes we send in this iteration */

+    k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);

+

+    if (fp != NULL) {

+      n = fwrite(buf + sent, 1, (size_t)k, fp);

+      if (ferror(fp))

+        n = -1;

+    } else {

+      n = send(sock, buf + sent, (size_t)k, 0);

+    }

+

+    if (n < 0)

+      break;

+

+    sent += n;

+  }

+

+  return sent;

+}

+

+// Read from IO channel - opened file descriptor, socket, or SSL descriptor.

+// Return number of bytes read.

+static int pull(SOCKET sock, char *buf, int len) {

+  int nread;

+

+  nread = recv(sock, buf, (size_t) len, 0);

+

+  return nread;

+}

+

+int mg_read(struct mg_connection *conn, void *buf, size_t len) {

+  int n, buffered_len, nread;

+  const char *buffered;

+

+  assert((conn->content_len == -1 && conn->consumed_content == 0) ||

+         conn->consumed_content <= conn->content_len);

+  DEBUG_TRACE(("%p %zu %lld %lld", buf, len,

+               conn->content_len, conn->consumed_content));

+  nread = 0;

+  if (conn->consumed_content < conn->content_len) {

+

+    // Adjust number of bytes to read.

+    int64_t to_read = conn->content_len - conn->consumed_content;

+    if (to_read < (int64_t) len) {

+      len = (int) to_read;

+    }

+

+    // How many bytes of data we have buffered in the request buffer?

+    buffered = conn->buf + conn->request_len + conn->consumed_content;

+    buffered_len = conn->data_len - conn->request_len;

+    assert(buffered_len >= 0);

+

+    // Return buffered data back if we haven't done that yet.

+    if (conn->consumed_content < (int64_t) buffered_len) {

+      buffered_len -= (int) conn->consumed_content;

+      if (len < (size_t) buffered_len) {

+        buffered_len = len;

+      }

+      memcpy(buf, buffered, (size_t)buffered_len);

+      len -= buffered_len;

+      buf = (char *) buf + buffered_len;

+      conn->consumed_content += buffered_len;

+      nread = buffered_len;

+    }

+

+    // We have returned all buffered data. Read new data from the remote socket.

+    while (len > 0) {

+      n = pull(conn->client.sock, (char *) buf, (int) len);

+      if (n <= 0) {

+        break;

+      }

+      buf = (char *) buf + n;

+      conn->consumed_content += n;

+      nread += n;

+      len -= n;

+    }

+  }

+  return nread;

+}

+

+int mg_write(struct mg_connection *conn, const void *buf, size_t len) {

+  return (int) push(NULL, conn->client.sock, (const char *) buf, (int64_t) len);

+}

+

+int mg_printf(struct mg_connection *conn, const char *fmt, ...) {

+  char buf[BUFSIZ];

+  int len;

+  va_list ap;

+

+  va_start(ap, fmt);

+  len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap);

+  va_end(ap);

+

+  return mg_write(conn, buf, (size_t)len);

+}

+

+// URL-decode input buffer into destination buffer.

+// 0-terminate the destination buffer. Return the length of decoded data.

+// form-url-encoded data differs from URI encoding in a way that it

+// uses '+' as character for space, see RFC 1866 section 8.2.1

+// http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt

+static size_t url_decode(const char *src, size_t src_len, char *dst,

+                         size_t dst_len, int is_form_url_encoded) {

+  size_t i, j;

+  int a, b;

+#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')

+

+  for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {

+    if (src[i] == '%' &&

+        isxdigit(* (const unsigned char *) (src + i + 1)) &&

+        isxdigit(* (const unsigned char *) (src + i + 2))) {

+      a = tolower(* (const unsigned char *) (src + i + 1));

+      b = tolower(* (const unsigned char *) (src + i + 2));

+      dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));

+      i += 2;

+    } else if (is_form_url_encoded && src[i] == '+') {

+      dst[j] = ' ';

+    } else {

+      dst[j] = src[i];

+    }

+  }

+

+  dst[j] = '\0'; /* Null-terminate the destination */

+

+  return j;

+}

+

+// Check whether full request is buffered. Return:

+//   -1  if request is malformed

+//    0  if request is not yet fully buffered

+//   >0  actual request length, including last \r\n\r\n

+static int get_request_len(const char *buf, int buflen) {

+  const char *s, *e;

+  int len = 0;

+

+  DEBUG_TRACE(("buf: %p, len: %d", buf, buflen));

+  for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)

+    // Control characters are not allowed but >=128 is.

+    if (!isprint(* (const unsigned char *) s) && *s != '\r' &&

+        *s != '\n' && * (const unsigned char *) s < 128) {

+      len = -1;

+    } else if (s[0] == '\n' && s[1] == '\n') {

+      len = (int) (s - buf) + 2;

+    } else if (s[0] == '\n' && &s[1] < e &&

+        s[1] == '\r' && s[2] == '\n') {

+      len = (int) (s - buf) + 3;

+    }

+

+  return len;

+}

+

+// Protect against directory disclosure attack by removing '..',

+// excessive '/' and '\' characters

+static void remove_double_dots_and_double_slashes(char *s) {

+  char *p = s;

+

+  while (*s != '\0') {

+    *p++ = *s++;

+    if (s[-1] == '/' || s[-1] == '\\') {

+      // Skip all following slashes and backslashes

+      while (*s == '/' || *s == '\\') {

+        s++;

+      }

+

+      // Skip all double-dots

+      while (*s == '.' && s[1] == '.') {

+        s += 2;

+      }

+    }

+  }

+  *p = '\0';

+}

+

+// Parse HTTP headers from the given buffer, advance buffer to the point

+// where parsing stopped.

+static void parse_http_headers(char **buf, struct mg_request_info *ri) {

+  int i;

+

+  for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {

+    ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0);

+    ri->http_headers[i].value = skip(buf, "\r\n");

+    if (ri->http_headers[i].name[0] == '\0')

+      break;

+    ri->num_headers = i + 1;

+  }

+}

+

+static int is_valid_http_method(const char *method) {

+  return !strcmp(method, "GET") || !strcmp(method, "POST") ||

+    !strcmp(method, "DELETE");

+}

+

+// Parse HTTP request, fill in mg_request_info structure.

+static int parse_http_request(char *buf, struct mg_request_info *ri) {

+  int status = 0;

+

+  // RFC says that all initial whitespaces should be ingored

+  while (*buf != '\0' && isspace(* (unsigned char *) buf)) {

+    buf++;

+  }

+

+  ri->request_method = skip(&buf, " ");

+  ri->uri = skip(&buf, " ");

+  ri->http_version = skip(&buf, "\r\n");

+

+  if (is_valid_http_method(ri->request_method) &&

+      strncmp(ri->http_version, "HTTP/", 5) == 0) {

+    ri->http_version += 5;   /* Skip "HTTP/" */

+    parse_http_headers(&buf, ri);

+    status = 1;

+  }

+

+  return status;

+}

+

+// Keep reading the input from socket sock

+// into buffer buf, until \r\n\r\n appears in the buffer (which marks the end

+// of HTTP request). Buffer buf may already have some data. The length of the

+// data is stored in nread.  Upon every read operation, increase nread by the

+// number of bytes read.

+static int read_request(SOCKET sock, char *buf, int bufsiz,

+                        int *nread) {

+  int n, request_len;

+

+  request_len = 0;

+  while (*nread < bufsiz && request_len == 0) {

+    n = pull(sock, buf + *nread, bufsiz - *nread);

+    if (n <= 0) {

+      break;

+    } else {

+      *nread += n;

+      request_len = get_request_len(buf, *nread);

+    }

+  }

+

+  return request_len;

+}

+

+// This is the heart of the Mongoose's logic.

+// This function is called when the request is read, parsed and validated,

+// and Mongoose must decide what action to take: serve a file, or

+// a directory, or call embedded function, etcetera.

+static void handle_request(struct mg_connection *conn) {

+  struct mg_request_info *ri = &conn->request_info;

+  int uri_len;

+

+  if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {

+    * conn->request_info.query_string++ = '\0';

+  }

+  uri_len = strlen(ri->uri);

+  (void) url_decode(ri->uri, (size_t)uri_len, ri->uri, (size_t)(uri_len + 1), 0);

+  remove_double_dots_and_double_slashes(ri->uri);

+

+  DEBUG_TRACE(("%s", ri->uri));

+  if (call_user(conn, MG_NEW_REQUEST) == NULL) {

+    mg_send_http_error(conn, 404, "Not Found", "%s", "File not found");

+  }

+}

+

+static void close_all_listening_sockets(struct mg_context *ctx) {

+  (void) close(ctx->local_socket);

+}

+

+// only reports address of the first listening socket

+int mg_get_listen_addr(struct mg_context *ctx,

+    struct sockaddr *addr, socklen_t *addrlen) {

+  size_t len = sizeof(ctx->local_address);

+  if (*addrlen < len) return 0;

+  *addrlen = len;

+  memcpy(addr, &ctx->local_address, len);

+  return 1;

+}

+

+static int set_ports_option(struct mg_context *ctx, int port) {

+  int reuseaddr = 1, success = 1;

+  socklen_t sock_len = sizeof(ctx->local_address);

+  // MacOS needs that. If we do not zero it, subsequent bind() will fail.

+  memset(&ctx->local_address, 0, sock_len);

+  ctx->local_address.sin_family = AF_INET;

+  ctx->local_address.sin_port = htons((uint16_t) port);

+  ctx->local_address.sin_addr.s_addr = htonl(INADDR_ANY);

+

+  struct timeval tv;

+  tv.tv_sec = 0;

+  tv.tv_usec = 500 * 1000;

+

+  if ((ctx->local_socket = socket(PF_INET, SOCK_STREAM, 6)) == INVALID_SOCKET ||

+      setsockopt(ctx->local_socket, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) != 0 ||

+      setsockopt(ctx->local_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0 ||

+      bind(ctx->local_socket, (const struct sockaddr *) &ctx->local_address, sock_len) != 0 ||

+      // TODO(steineldar): Replace 20 (max socket backlog len in connections).

+      listen(ctx->local_socket, 20) != 0) {

+    close(ctx->local_socket);

+    cry(fc(ctx), "%s: cannot bind to port %d: %s", __func__,

+        port, strerror(ERRNO));

+    success = 0;

+  } else if (getsockname(ctx->local_socket, (struct sockaddr *) &ctx->local_address, &sock_len)) {

+    close(ctx->local_socket);

+    cry(fc(ctx), "%s: %s", __func__, strerror(ERRNO));

+    success = 0;

+  }

+

+  if (!success) {

+    ctx->local_socket = INVALID_SOCKET;

+    close_all_listening_sockets(ctx);

+  }

+

+  return success;

+}

+

+

+static void reset_per_request_attributes(struct mg_connection *conn) {

+  struct mg_request_info *ri = &conn->request_info;

+

+  ri->request_method = ri->uri = ri->http_version = NULL;

+  ri->num_headers = 0;

+  ri->status_code = -1;

+

+  conn->num_bytes_sent = conn->consumed_content = 0;

+  conn->content_len = -1;

+  conn->request_len = conn->data_len = 0;

+}

+

+static void close_socket_gracefully(SOCKET sock) {

+  char buf[BUFSIZ];

+  int n;

+

+  // Send FIN to the client

+  (void) shutdown(sock, SHUT_WR);

+  set_non_blocking_mode(sock);

+

+  // Read and discard pending data. If we do not do that and close the

+  // socket, the data in the send buffer may be discarded. This

+  // behaviour is seen on Windows, when client keeps sending data

+  // when server decide to close the connection; then when client

+  // does recv() it gets no data back.

+  do {

+    n = pull(sock, buf, sizeof(buf));

+  } while (n > 0);

+

+  // Now we know that our FIN is ACK-ed, safe to close

+  (void) close(sock);

+}

+

+static void close_connection(struct mg_connection *conn) {

+  if (conn->client.sock != INVALID_SOCKET) {

+    close_socket_gracefully(conn->client.sock);

+  }

+}

+

+static void discard_current_request_from_buffer(struct mg_connection *conn) {

+  int buffered_len, body_len;

+

+  buffered_len = conn->data_len - conn->request_len;

+  assert(buffered_len >= 0);

+

+  if (conn->content_len == -1) {

+    body_len = 0;

+  } else if (conn->content_len < (int64_t) buffered_len) {

+    body_len = (int) conn->content_len;

+  } else {

+    body_len = buffered_len;

+  }

+

+  conn->data_len -= conn->request_len + body_len;

+  memmove(conn->buf, conn->buf + conn->request_len + body_len,

+          (size_t) conn->data_len);

+}

+

+static void process_new_connection(struct mg_connection *conn) {

+  struct mg_request_info *ri = &conn->request_info;

+  const char *cl;

+

+  reset_per_request_attributes(conn);

+

+  // If next request is not pipelined, read it in

+  if ((conn->request_len = get_request_len(conn->buf, conn->data_len)) == 0) {

+    conn->request_len = read_request(conn->client.sock,

+        conn->buf, conn->buf_size, &conn->data_len);

+  }

+  assert(conn->data_len >= conn->request_len);

+  if (conn->request_len == 0 && conn->data_len == conn->buf_size) {

+    mg_send_http_error(conn, 413, "Request Too Large", "");

+    return;

+  } if (conn->request_len <= 0) {

+    return;  // Remote end closed the connection

+  }

+

+  // Nul-terminate the request cause parse_http_request() uses sscanf

+  conn->buf[conn->request_len - 1] = '\0';

+  if (!parse_http_request(conn->buf, ri)) {

+    // Do not put garbage in the access log, just send it back to the client

+    mg_send_http_error(conn, 400, "Bad Request",

+        "Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf);

+  } else if (strcmp(ri->http_version, "1.0") && strcmp(ri->http_version, "1.1")) {

+    // Request seems valid, but HTTP version is strange

+    mg_send_http_error(conn, 505, "HTTP version not supported", "");

+  } else {

+    // Request is valid, handle it

+    cl = get_header(ri, "Content-Length");

+    conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10);

+    conn->birth_time = time(NULL);

+    handle_request(conn);

+    discard_current_request_from_buffer(conn);

+  }

+}

+

+// Worker threads take accepted socket from the queue

+static int consume_socket(struct mg_context *ctx, struct socket *sp) {

+  (void) pthread_mutex_lock(&ctx->mutex);

+  DEBUG_TRACE(("going idle"));

+

+  // If the queue is empty, wait. We're idle at this point.

+  while (ctx->sq_head == ctx->sq_tail && ctx->stop_flag == 0) {

+    pthread_cond_wait(&ctx->sq_full, &ctx->mutex);

+  }

+  // Master thread could wake us up without putting a socket.

+  // If this happens, it is time to exit.

+  if (ctx->stop_flag) {

+    (void) pthread_mutex_unlock(&ctx->mutex);

+    return 0;

+  }

+  assert(ctx->sq_head > ctx->sq_tail);

+

+  // Copy socket from the queue and increment tail

+  *sp = ctx->queue[ctx->sq_tail % ARRAY_SIZE(ctx->queue)];

+  ctx->sq_tail++;

+  DEBUG_TRACE(("grabbed socket %d, going busy", sp->sock));

+

+  // Wrap pointers if needed

+  while (ctx->sq_tail > (int) ARRAY_SIZE(ctx->queue)) {

+    ctx->sq_tail -= ARRAY_SIZE(ctx->queue);

+    ctx->sq_head -= ARRAY_SIZE(ctx->queue);

+  }

+

+  (void) pthread_cond_signal(&ctx->sq_empty);

+  (void) pthread_mutex_unlock(&ctx->mutex);

+

+  return 1;

+}

+

+

+static void worker_thread(struct mg_context *ctx) {

+  struct mg_connection *conn;

+  // This is the specified request size limit for DIAL requests.  Note that

+  // this will effectively make the request limit one byte *smaller* than the

+  // required in the DIAL specification.

+  int buf_size = MAX_REQUEST_SIZE;

+

+  conn = (struct mg_connection *) calloc(1, sizeof(*conn) + buf_size);

+  conn->buf_size = buf_size;

+  conn->buf = (char *) (conn + 1);

+  assert(conn != NULL);

+

+  while (ctx->stop_flag == 0 && consume_socket(ctx, &conn->client)) {

+    conn->birth_time = time(NULL);

+    conn->ctx = ctx;

+

+    // Fill in IP, port info early so even if SSL setup below fails,

+    // error handler would have the corresponding info.

+    // Thanks to Johannes Winkelmann for the patch.

+    memcpy(&conn->request_info.remote_addr,

+           &conn->client.remote_addr, sizeof(conn->client.remote_addr));

+

+    // Fill in local IP info

+    socklen_t addr_len = sizeof(conn->request_info.local_addr);

+    getsockname(conn->client.sock,

+        (struct sockaddr *) &conn->request_info.local_addr, &addr_len);

+

+    process_new_connection(conn);

+

+    close_connection(conn);

+  }

+  free(conn);

+

+  // Signal master that we're done with connection and exiting

+  (void) pthread_mutex_lock(&ctx->mutex);

+  ctx->num_threads--;

+  (void) pthread_cond_signal(&ctx->cond);

+  assert(ctx->num_threads >= 0);

+  (void) pthread_mutex_unlock(&ctx->mutex);

+

+  DEBUG_TRACE(("exiting"));

+}

+

+// Master thread adds accepted socket to a queue

+static void produce_socket(struct mg_context *ctx, const struct socket *sp) {

+  (void) pthread_mutex_lock(&ctx->mutex);

+

+  // If the queue is full, wait

+  while (ctx->sq_head - ctx->sq_tail >= (int) ARRAY_SIZE(ctx->queue)) {

+    (void) pthread_cond_wait(&ctx->sq_empty, &ctx->mutex);

+  }

+  assert(ctx->sq_head - ctx->sq_tail < (int) ARRAY_SIZE(ctx->queue));

+

+  // Copy socket to the queue and increment head

+  ctx->queue[ctx->sq_head % ARRAY_SIZE(ctx->queue)] = *sp;

+  ctx->sq_head++;

+  DEBUG_TRACE(("queued socket %d", sp->sock));

+

+  (void) pthread_cond_signal(&ctx->sq_full);

+  (void) pthread_mutex_unlock(&ctx->mutex);

+}

+

+

+static void master_thread(struct mg_context *ctx) {

+  struct socket accepted;

+

+  socklen_t sock_len = sizeof(accepted.local_addr);

+  memcpy(&accepted.local_addr, &ctx->local_address, sock_len);

+

+  while (ctx->stop_flag == 0) {

+    memset(&accepted.remote_addr, 0, sock_len);

+

+    accepted.sock = accept(ctx->local_socket,

+        (struct sockaddr *) &accepted.remote_addr, &sock_len);

+

+    if (accepted.sock != INVALID_SOCKET) {

+      // Put accepted socket structure into the queue.

+      DEBUG_TRACE(("accepted socket %d", accepted.sock));

+      produce_socket(ctx, &accepted);

+    }

+  }

+  DEBUG_TRACE(("stopping workers"));

+

+  // Stop signal received: somebody called mg_stop. Quit.

+  close_all_listening_sockets(ctx);

+

+  // Wakeup workers that are waiting for connections to handle.

+  pthread_cond_broadcast(&ctx->sq_full);

+

+  // Wait until all threads finish

+  (void) pthread_mutex_lock(&ctx->mutex);

+  while (ctx->num_threads > 0) {

+    (void) pthread_cond_wait(&ctx->cond, &ctx->mutex);

+  }

+  (void) pthread_mutex_unlock(&ctx->mutex);

+

+  // All threads exited, no sync is needed. Destroy mutex and condvars

+  (void) pthread_mutex_destroy(&ctx->mutex);

+  (void) pthread_cond_destroy(&ctx->cond);

+  (void) pthread_cond_destroy(&ctx->sq_empty);

+  (void) pthread_cond_destroy(&ctx->sq_full);

+

+  // Signal mg_stop() that we're done

+  ctx->stop_flag = 2;

+

+  DEBUG_TRACE(("exiting"));

+}

+

+static void free_context(struct mg_context *ctx) {

+  // Deallocate context itself

+  free(ctx);

+}

+

+void mg_stop(struct mg_context *ctx) {

+  ctx->stop_flag = 1;

+

+  // Wait until mg_fini() stops

+  while (ctx->stop_flag != 2) {

+    // TODO(steineldar): Avoid busy waiting.

+    (void) sleep(0);

+  }

+  free_context(ctx);

+}

+

+struct mg_context *mg_start(mg_callback_t user_callback, void *user_data, int port) {

+  struct mg_context *ctx;

+

+  // Allocate context and initialize reasonable general case defaults.

+  // TODO(lsm): do proper error handling here.

+  ctx = (struct mg_context *) calloc(1, sizeof(*ctx));

+  ctx->user_callback = user_callback;

+  ctx->user_data = user_data;

+

+  if (!set_ports_option(ctx, port)) {

+    free_context(ctx);

+    return NULL;

+  }

+  // Ignore SIGPIPE signal, so if browser cancels the request, it

+  // won't kill the whole process.

+  (void) signal(SIGPIPE, SIG_IGN);

+  (void) pthread_mutex_init(&ctx->mutex, NULL);

+  (void) pthread_cond_init(&ctx->cond, NULL);

+  (void) pthread_cond_init(&ctx->sq_empty, NULL);

+  (void) pthread_cond_init(&ctx->sq_full, NULL);

+

+  // Start master (listening) thread

+  start_thread(ctx, (mg_thread_func_t) master_thread, ctx);

+

+  // Start worker threads

+  for (int i = 0; i < NUM_THREADS; i++) {

+    if (start_thread(ctx, (mg_thread_func_t) worker_thread, ctx) != 0) {

+      cry(fc(ctx), "Cannot start worker thread: %d", ERRNO);

+    } else {

+      ctx->num_threads++;

+    }

+  }

+

+  return ctx;

+}

diff --git a/src/server/mongoose.h b/src/server/mongoose.h
new file mode 100644
index 0000000..7842aac
--- /dev/null
+++ b/src/server/mongoose.h
@@ -0,0 +1,160 @@
+// Copyright (c) 2004-2010 Sergey Lyubka

+//

+// Permission is hereby granted, free of charge, to any person obtaining a copy

+// of this software and associated documentation files (the "Software"), to deal

+// in the Software without restriction, including without limitation the rights

+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

+// copies of the Software, and to permit persons to whom the Software is

+// furnished to do so, subject to the following conditions:

+//

+// The above copyright notice and this permission notice shall be included in

+// all copies or substantial portions of the Software.

+//

+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

+// THE SOFTWARE.

+

+

+// NOTE: This is a SEVERELY stripped down version of mongoose, which only

+// supports GET, POST and DELETE HTTP commands, no CGI, no file or directory

+// access, no ACLs or authentication, and no proxying and no SSL.  HTTP Header

+// limit is 16 instead of 64, as it's not supposed to be called from standard

+// browsers. And most options are removed.

+

+#ifndef MONGOOSE_HEADER_INCLUDED

+#define  MONGOOSE_HEADER_INCLUDED

+

+#ifdef __cplusplus

+extern "C" {

+#endif // __cplusplus

+

+#include <sys/socket.h>

+#include <netinet/in.h>

+#include <netinet/ip.h>

+

+struct mg_context;     // Handle for the HTTP service itself

+struct mg_connection;  // Handle for the individual connection

+

+

+// This structure contains information about the HTTP request.

+struct mg_request_info {

+  void *user_data;       // User-defined pointer passed to mg_start()

+  char *request_method;  // "GET", "POST", etc

+  char *uri;             // URL-decoded URI

+  char *http_version;    // E.g. "1.0", "1.1"

+  char *query_string;    // \0 - terminated

+  char *request_body;    // \0 - terminated

+  char *log_message;     // Mongoose error log message

+  struct sockaddr_in local_addr; // Our server's address for this connection

+  struct sockaddr_in remote_addr; // The remote address for this connection

+  int status_code;       // HTTP reply status code

+  int num_headers;       // Number of headers

+  struct mg_header {

+    char *name;          // HTTP header name

+    char *value;         // HTTP header value

+  } http_headers[16];    // Maximum 16 headers

+};

+

+// Various events on which user-defined function is called by Mongoose.

+enum mg_event {

+  MG_NEW_REQUEST,   // New HTTP request has arrived from the client

+  MG_HTTP_ERROR,    // HTTP error must be returned to the client

+  MG_EVENT_LOG,     // Mongoose logs an event, request_info.log_message

+};

+

+// Prototype for the user-defined function. Mongoose calls this function

+// on every event mentioned above.

+//

+// Parameters:

+//   event: which event has been triggered.

+//   conn: opaque connection handler. Could be used to read, write data to the

+//         client, etc. See functions below that accept "mg_connection *".

+//   request_info: Information about HTTP request.

+//

+// Return:

+//   If handler returns non-NULL, that means that handler has processed the

+//   request by sending appropriate HTTP reply to the client. Mongoose treats

+//   the request as served.

+//   If callback returns NULL, that means that callback has not processed

+//   the request. Handler must not send any data to the client in this case.

+//   Mongoose proceeds with request handling as if nothing happened.

+typedef void * (*mg_callback_t)(enum mg_event event,

+                                struct mg_connection *conn,

+                                const struct mg_request_info *request_info);

+

+

+// Start web server.

+//

+// Parameters:

+//   callback: user defined event handling function or NULL.

+//

+// Example:

+//   struct mg_context *ctx = mg_start(&my_func, NULL);

+//

+// Please refer to http://code.google.com/p/mongoose/wiki/MongooseManual

+// for the list of valid option and their possible values.

+//

+// Return:

+//   web server context, or NULL on error.

+struct mg_context *mg_start(mg_callback_t callback, void *user_data, int port);

+

+

+// Stop the web server.

+//

+// Must be called last, when an application wants to stop the web server and

+// release all associated resources. This function blocks until all Mongoose

+// threads are stopped. Context pointer becomes invalid.

+void mg_stop(struct mg_context *);

+

+

+// Send data to the client.

+int mg_write(struct mg_connection *, const void *buf, size_t len);

+

+

+// Send data to the browser using printf() semantics.

+//

+// Works exactly like mg_write(), but allows to do message formatting.

+// Note that mg_printf() uses internal buffer of size IO_BUF_SIZE

+// (8 Kb by default) as temporary message storage for formatting. Do not

+// print data that is bigger than that, otherwise it will be truncated.

+int mg_printf(struct mg_connection *, const char *fmt, ...);

+

+

+// Read data from the remote end, return number of bytes read.

+int mg_read(struct mg_connection *, void *buf, size_t len);

+

+

+// Get the value of particular HTTP header.

+//

+// This is a helper function. It traverses request_info->http_headers array,

+// and if the header is present in the array, returns its value. If it is

+// not present, NULL is returned.

+const char *mg_get_header(const struct mg_connection *, const char *name);

+

+

+// Return Mongoose version.

+const char *mg_version(void);

+

+

+// MD5 hash given strings.

+// Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of

+// asciiz strings. When function returns, buf will contain human-readable

+// MD5 hash. Example:

+//   char buf[33];

+//   mg_md5(buf, "aa", "bb", NULL);

+void mg_md5(char *buf, ...);

+

+void mg_send_http_error(struct mg_connection *conn, int status,

+                        const char *reason, const char *fmt, ...);

+int mg_get_listen_addr(struct mg_context *ctx, struct sockaddr *addr,

+                       socklen_t *addrlen);

+

+#ifdef __cplusplus

+}

+#endif // __cplusplus

+

+#endif // MONGOOSE_HEADER_INCLUDED

diff --git a/src/server/quick_ssdp.c b/src/server/quick_ssdp.c
new file mode 100644
index 0000000..fa1d84a
--- /dev/null
+++ b/src/server/quick_ssdp.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2012 Netflix, Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without 
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+ * this list of conditions and the following disclaimer in the documentation 
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY NETFLIX, INC. AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+ * DISCLAIMED. IN NO EVENT SHALL NETFLIX OR CONTRIBUTORS BE LIABLE FOR ANY 
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "mongoose.h"
+
+// TODO: Partners should define this port
+#define SSDP_PORT (56790)
+
+
+static char gBuf[4096];
+
+// TODO: Partners should get the friendlyName from the system and insert here.
+// TODO: Partners should ensure the friendlyName is the same string returned
+//       in the ISsystem::getFriendlyName DPI function.
+// TODO: Partners should get the UUID from the system and insert here.
+// TODO: Partners should ensure the modelName is identifiably similar to the
+//       model name returned in the ISystem::getDeviceModel DPI function
+static const char ddxml[] = ""
+"<?xml version=\"1.0\"?>"
+"<root"
+"  xmlns=\"urn:schemas-upnp-org:device-1-0\""
+"  xmlns:r=\"urn:restful-tv-org:schemas:upnp-dd\">"
+"  <specVersion>"
+"    <major>1</major>"
+"    <minor>0</minor>"
+"  </specVersion>"
+"  <device>"
+"    <deviceType>urn:schemas-upnp-org:device:tvdevice:1</deviceType>"
+"    <friendlyName>%s</friendlyName>"
+"    <manufacturer> </manufacturer>"
+"    <modelName>%s</modelName>"
+"    <UDN>uuid:%s</UDN>"
+"  </device>"
+"</root>";
+
+// TODO: Partners should get the UUID from the system and insert here.
+static const char ssdp_reply[] = "HTTP/1.1 200 OK\r\n"
+                         "LOCATION: http://%s:%d/dd.xml\r\n"
+                         "CACHE-CONTROL: max-age=1800\r\n"
+                         "EXT:\r\n"
+                         "BOOTID.UPNP.ORG: 1\r\n"
+                         "SERVER: Linux/2.6 UPnP/1.0 quick_ssdp/1.0\r\n"
+                         "ST: urn:dial-multiscreen-org:service:dial:1\r\n"
+                         "USN: uuid:%s::"
+                           "urn:dial-multiscreen-org:service:dial:1\r\n\r\n";
+
+static char ip_addr[INET_ADDRSTRLEN] = "127.0.0.1";
+static int dial_port = 0;
+static int my_port = 0;
+static char friendly_name[256];
+static char uuid[256];
+static char model_name[256];
+static struct mg_context *ctx;
+
+static void *request_handler(enum mg_event event,
+                             struct mg_connection *conn,
+                             const struct mg_request_info *request_info) {
+  if (event == MG_NEW_REQUEST) {
+    if (!strcmp(request_info->uri, "/dd.xml") &&
+        !strcmp(request_info->request_method, "GET")) {
+      mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+                      "Content-Type: application/xml\r\n"
+                      "Application-URL: http://%s:%d/apps/\r\n"
+                      "\r\n", ip_addr, dial_port);
+      mg_printf(conn, ddxml, friendly_name, model_name, uuid);
+    } else {
+      mg_send_http_error(conn, 404, "Not Found", "Not Found");
+    }
+    return "done";
+  }
+  return NULL;
+}
+
+static void get_local_address() {
+  struct ifconf ifc;
+  char buf[4096];
+  int s, i;
+  if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) {
+    perror("socket");
+    exit(1);
+  }
+  ifc.ifc_len = sizeof(buf);
+  ifc.ifc_buf = buf;
+  if (0 > ioctl(s, SIOCGIFCONF, &ifc)) {
+    perror("SIOCGIFCONF");
+    exit(1);
+  }
+  if (ifc.ifc_len == sizeof(buf)) {
+    fprintf(stderr, "SIOCGIFCONF output too long");
+    exit(1);
+  }
+  close(s);
+  for (i = 0; i < ifc.ifc_len/sizeof(ifc.ifc_req[0]); i++) {
+    strcpy(ip_addr,
+      inet_ntoa(((struct sockaddr_in *)(&ifc.ifc_req[i].ifr_addr))->sin_addr));
+    // exit if we found a non-loopback address
+    if (strcmp("127.0.0.1", ip_addr)) {
+      break;
+    }
+  }
+}
+
+static void handle_mcast() {
+  int s, one = 1, bytes;
+  socklen_t addrlen;
+  struct sockaddr_in saddr;
+  struct ip_mreq mreq;
+  char send_buf[sizeof(ssdp_reply) + INET_ADDRSTRLEN + 256 + 256] = {0,};
+  int send_size;
+
+  send_size = snprintf(send_buf, sizeof(send_buf), ssdp_reply, ip_addr, my_port, uuid);
+
+  if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) {
+    perror("socket");
+    exit(1);
+  }
+  if (-1 == setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
+    perror("reuseaddr");
+    exit(1);
+  }
+  saddr.sin_family = AF_INET;
+  saddr.sin_addr.s_addr = inet_addr("239.255.255.250");
+  saddr.sin_port = htons(1900);
+  if (-1 == bind(s, (struct sockaddr *)&saddr, sizeof(saddr))) {
+    perror("bind");
+    exit(1);
+  }
+  mreq.imr_multiaddr.s_addr = inet_addr("239.255.255.250");
+  mreq.imr_interface.s_addr = inet_addr(ip_addr);
+  if (-1 == setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+                       &mreq, sizeof(mreq))) {
+    perror("add_membership");
+    exit(1);
+  }
+  //printf("Starting Multicast handling on 239.255.255.250\n");
+  while (1) {
+    addrlen = sizeof(saddr);
+    if (-1 == (bytes = recvfrom(s, gBuf, sizeof(gBuf) - 1, 0,
+                                (struct sockaddr *)&saddr, &addrlen))) {
+      perror("recvfrom");
+      continue;
+    }
+    gBuf[bytes] = 0;
+
+    // sophisticated SSDP parsing algorithm
+    if (!strstr(gBuf, "ST: urn:dial-multiscreen-org:service:dial:1"))
+    {
+#if 0  // use for debugging
+      printf("Dropping: \n");
+      {
+          int i;
+          for (i = 0; i < bytes; i++)
+          {
+              putchar(gBuf[i]);
+          }
+      }
+      printf("\n##### End of DROP #######\n");
+#endif
+      continue;
+    }
+    printf("Sending SSDP reply to %s:%d\n",
+           inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
+    if (-1 == sendto(s, send_buf, send_size, 0, (struct sockaddr *)&saddr, addrlen)) {
+      perror("sendto");
+      continue;
+    }
+  }
+}
+
+void run_ssdp(int port, const char *pFriendlyName, const char * pModelName, const char *pUuid) {
+  struct sockaddr sa;
+  socklen_t len = sizeof(sa);
+
+  if(pFriendlyName) {
+      strncpy(friendly_name, pFriendlyName, sizeof(friendly_name));
+      friendly_name[255] = '\0';
+  } else {
+      strcpy(friendly_name, "DIAL server sample");
+  }
+  if(pModelName) {
+      strncpy(model_name, pModelName, sizeof(model_name));
+      uuid[255] = '\0';
+  } else {
+      strcpy(model_name, "deadbeef-dead-beef-dead-beefdeadbeef");
+  }
+  if(pUuid) {
+      strncpy(uuid, pUuid, sizeof(uuid));
+      uuid[255] = '\0';
+  } else {
+      strcpy(uuid, "deadbeef-dead-beef-dead-beefdeadbeef");
+  }
+
+  dial_port = port;
+  get_local_address();
+  ctx = mg_start(&request_handler, NULL, SSDP_PORT);
+
+  if (mg_get_listen_addr(ctx, &sa, &len)) {
+    my_port = ntohs(((struct sockaddr_in *)&sa)->sin_port);
+  }
+
+  printf("SSDP listening on %s:%d\n", ip_addr, my_port);
+  handle_mcast();
+}