summaryrefslogtreecommitdiff
path: root/indra/viewer_components
diff options
context:
space:
mode:
Diffstat (limited to 'indra/viewer_components')
-rw-r--r--indra/viewer_components/CMakeLists.txt5
-rw-r--r--indra/viewer_components/login/CMakeLists.txt56
-rw-r--r--indra/viewer_components/login/lllogin.cpp374
-rw-r--r--indra/viewer_components/login/lllogin.h133
-rw-r--r--indra/viewer_components/login/tests/lllogin_test.cpp417
5 files changed, 985 insertions, 0 deletions
diff --git a/indra/viewer_components/CMakeLists.txt b/indra/viewer_components/CMakeLists.txt
new file mode 100644
index 0000000000..c95c854b7c
--- /dev/null
+++ b/indra/viewer_components/CMakeLists.txt
@@ -0,0 +1,5 @@
+# -*- cmake -*-
+
+add_subdirectory(login)
+add_subdirectory(eventhost)
+
diff --git a/indra/viewer_components/login/CMakeLists.txt b/indra/viewer_components/login/CMakeLists.txt
new file mode 100644
index 0000000000..fb65779eb7
--- /dev/null
+++ b/indra/viewer_components/login/CMakeLists.txt
@@ -0,0 +1,56 @@
+# -*- cmake -*-
+
+project(login)
+
+include(00-Common)
+include(LLAddBuildTest)
+include(LLCommon)
+include(LLMath)
+include(LLXML)
+include(Pth)
+
+include_directories(
+ ${LLCOMMON_INCLUDE_DIRS}
+ ${LLMATH_INCLUDE_DIRS}
+ ${LLXML_INCLUDE_DIRS}
+ ${PTH_INCLUDE_DIRS}
+ )
+
+set(login_SOURCE_FILES
+ lllogin.cpp
+ )
+
+set(login_HEADER_FILES
+ lllogin.h
+ )
+
+set_source_files_properties(${login_HEADER_FILES}
+ PROPERTIES HEADER_FILE_ONLY TRUE)
+
+list(APPEND
+ login_SOURCE_FILES
+ ${login_HEADER_FILES}
+ )
+
+add_library(lllogin
+ ${login_SOURCE_FILES}
+ )
+
+target_link_libraries(lllogin
+ ${LLCOMMON_LIBRARIES}
+ ${LLMATH_LIBRARIES}
+ ${LLXML_LIBRARIES}
+ ${PTH_LIBRARIES}
+ )
+
+SET(lllogin_TEST_SOURCE_FILES
+ lllogin.cpp
+ )
+
+set_source_files_properties(
+ lllogin.cpp
+ PROPERTIES
+ LL_TEST_ADDITIONAL_LIBRARIES "${PTH_LIBRARIES}"
+ )
+
+LL_ADD_PROJECT_UNIT_TESTS(lllogin "${lllogin_TEST_SOURCE_FILES}")
diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp
new file mode 100644
index 0000000000..7a30315b9a
--- /dev/null
+++ b/indra/viewer_components/login/lllogin.cpp
@@ -0,0 +1,374 @@
+/**
+ * @file lllogin.cpp
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include <boost/coroutine/coroutine.hpp>
+#include "linden_common.h"
+#include "llsd.h"
+#include "llsdutil.h"
+
+/*==========================================================================*|
+#ifdef LL_WINDOWS
+ // non-virtual destructor warning, boost::statechart does this intentionally.
+ #pragma warning (disable : 4265)
+#endif
+|*==========================================================================*/
+
+#include "lllogin.h"
+
+#include <boost/bind.hpp>
+
+#include "llcoros.h"
+#include "llevents.h"
+#include "lleventfilter.h"
+#include "lleventcoro.h"
+
+//*********************
+// LLLogin
+// *NOTE:Mani - Is this Impl needed now that the state machine runs the show?
+class LLLogin::Impl
+{
+public:
+ Impl():
+ mPump("login", true) // Create the module's event pump with a tweaked (unique) name.
+ {
+ mValidAuthResponse["status"] = LLSD();
+ mValidAuthResponse["errorcode"] = LLSD();
+ mValidAuthResponse["error"] = LLSD();
+ mValidAuthResponse["transfer_rate"] = LLSD();
+ }
+
+ void connect(const std::string& uri, const LLSD& credentials);
+ void disconnect();
+ LLEventPump& getEventPump() { return mPump; }
+
+private:
+ void sendProgressEvent(const std::string& state, const std::string& change,
+ const LLSD& data = LLSD())
+ {
+ LLSD status_data;
+ status_data["state"] = state;
+ status_data["change"] = change;
+ status_data["progress"] = 0.0f;
+
+ if(mAuthResponse.has("transfer_rate"))
+ {
+ status_data["transfer_rate"] = mAuthResponse["transfer_rate"];
+ }
+
+ if(data.isDefined())
+ {
+ status_data["data"] = data;
+ }
+
+ mPump.post(status_data);
+ }
+
+ LLSD validateResponse(const std::string& pumpName, const LLSD& response)
+ {
+ // Validate the response. If we don't recognize it, things
+ // could get ugly.
+ std::string mismatch(llsd_matches(mValidAuthResponse, response));
+ if (! mismatch.empty())
+ {
+ LL_ERRS("LLLogin") << "Received unrecognized event (" << mismatch << ") on "
+ << pumpName << "pump: " << response
+ << LL_ENDL;
+ return LLSD();
+ }
+
+ return response;
+ }
+
+ // In a coroutine's top-level function args, do NOT NOT NOT accept
+ // references (const or otherwise) to anything but the self argument! Pass
+ // by value only!
+ void login_(LLCoros::self& self, std::string uri, LLSD credentials);
+
+ LLEventStream mPump;
+ LLSD mAuthResponse, mValidAuthResponse;
+};
+
+void LLLogin::Impl::connect(const std::string& uri, const LLSD& credentials)
+{
+ // Launch a coroutine with our login_() method. Run the coroutine until
+ // its first wait; at that point, return here.
+ std::string coroname =
+ LLCoros::instance().launch("LLLogin::Impl::login_",
+ boost::bind(&Impl::login_, this, _1, uri, credentials));
+}
+
+void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD credentials)
+{
+ LL_INFOS("LLLogin") << "Entering coroutine " << LLCoros::instance().getName(self)
+ << " with uri '" << uri << "', credentials " << credentials << LL_ENDL;
+ // Arriving in SRVRequest state
+ LLEventStream replyPump("reply", true);
+ // Should be an array of one or more uri strings.
+ LLSD rewrittenURIs;
+ {
+ LLEventTimeout filter(replyPump);
+ sendProgressEvent("offline", "srvrequest");
+
+ // Request SRV record.
+ LL_INFOS("LLLogin") << "Requesting SRV record from " << uri << LL_ENDL;
+
+ // *NOTE:Mani - Completely arbitrary timeout value for SRV request.
+ filter.errorAfter(5, "SRV Request timed out!");
+
+ // Make request
+ LLSD request;
+ request["op"] = "rewriteURI";
+ request["uri"] = uri;
+ request["reply"] = replyPump.getName();
+ rewrittenURIs = postAndWait(self, request, "LLAres", filter);
+ } // we no longer need the filter
+
+ LLEventPump& xmlrpcPump(LLEventPumps::instance().obtain("LLXMLRPCTransaction"));
+
+ // Loop through the rewrittenURIs, counting attempts along the way.
+ // Because of possible redirect responses, we may make more than one
+ // attempt per rewrittenURIs entry.
+ LLSD::Integer attempts = 0;
+ for (LLSD::array_const_iterator urit(rewrittenURIs.beginArray()),
+ urend(rewrittenURIs.endArray());
+ urit != urend; ++urit)
+ {
+ LLSD request(credentials);
+ request["reply"] = replyPump.getName();
+ request["uri"] = *urit;
+ std::string status;
+
+ // Loop back to here if login attempt redirects to a different
+ // request["uri"]
+ for (;;)
+ {
+ ++attempts;
+ LLSD progress_data;
+ progress_data["attempt"] = attempts;
+ progress_data["request"] = request;
+ sendProgressEvent("offline", "authenticating", progress_data);
+
+ // We expect zero or more "Downloading" status events, followed by
+ // exactly one event with some other status. Use postAndWait() the
+ // first time, because -- at least in unit-test land -- it's
+ // possible for the reply to arrive before the post() call
+ // returns. Subsequent responses, of course, must be awaited
+ // without posting again.
+ for (mAuthResponse = validateResponse(replyPump.getName(),
+ postAndWait(self, request, xmlrpcPump, replyPump, "reply"));
+ mAuthResponse["status"].asString() == "Downloading";
+ mAuthResponse = validateResponse(replyPump.getName(),
+ waitForEventOn(self, replyPump)))
+ {
+ // Still Downloading -- send progress update.
+ sendProgressEvent("offline", "downloading");
+ }
+ status = mAuthResponse["status"].asString();
+
+ // Okay, we've received our final status event for this
+ // request. Unless we got a redirect response, break the retry
+ // loop for the current rewrittenURIs entry.
+ if (! (status == "Complete" &&
+ mAuthResponse["responses"]["login"].asString() == "indeterminate"))
+ {
+ break;
+ }
+
+ // Here the login service at the current URI is redirecting us
+ // to some other URI ("indeterminate" -- why not "redirect"?).
+ // The response should contain another uri to try, with its
+ // own auth method.
+ request["uri"] = mAuthResponse["next_url"];
+ request["method"] = mAuthResponse["next_method"];
+ } // loop back to try the redirected URI
+
+ // Here we're done with redirects for the current rewrittenURIs
+ // entry.
+ if (status == "Complete")
+ {
+ // StatusComplete does not imply auth success. Check the
+ // actual outcome of the request. We've already handled the
+ // "indeterminate" case in the loop above.
+ if (mAuthResponse["responses"]["login"].asString() == "true")
+ {
+ sendProgressEvent("online", "connect", mAuthResponse["responses"]);
+ }
+ else
+ {
+ sendProgressEvent("offline", "fail.login", mAuthResponse["responses"]);
+ }
+ return; // Done!
+ }
+ // If we don't recognize status at all, trouble
+ if (! (status == "CURLError"
+ || status == "XMLRPCError"
+ || status == "OtherError"))
+ {
+ LL_ERRS("LLLogin") << "Unexpected status from " << xmlrpcPump.getName() << " pump: "
+ << mAuthResponse << LL_ENDL;
+ return;
+ }
+
+ // Here status IS one of the errors tested above.
+ } // Retry if there are any more rewrittenURIs.
+
+ // Here we got through all the rewrittenURIs without succeeding. Tell
+ // caller this didn't work out so well. Of course, the only failure data
+ // we can reasonably show are from the last of the rewrittenURIs.
+ sendProgressEvent("offline", "fail.login", mAuthResponse["responses"]);
+}
+
+void LLLogin::Impl::disconnect()
+{
+ sendProgressEvent("offline", "disconnect");
+}
+
+//*********************
+// LLLogin
+LLLogin::LLLogin() :
+ mImpl(new LLLogin::Impl())
+{
+}
+
+LLLogin::~LLLogin()
+{
+}
+
+void LLLogin::connect(const std::string& uri, const LLSD& credentials)
+{
+ mImpl->connect(uri, credentials);
+}
+
+
+void LLLogin::disconnect()
+{
+ mImpl->disconnect();
+}
+
+LLEventPump& LLLogin::getEventPump()
+{
+ return mImpl->getEventPump();
+}
+
+// The following is the list of important functions that happen in the
+// current login process that we want to move to this login module.
+
+// The list associates to event with the original idle_startup() 'STATE'.
+
+// Rewrite URIs
+ // State_LOGIN_AUTH_INIT
+// Given a vector of login uris (usually just one), perform a dns lookup for the
+// SRV record from each URI. I think this is used to distribute login requests to
+// a single URI to multiple hosts.
+// This is currently a synchronous action. (See LLSRV::rewriteURI() implementation)
+// On dns lookup error the output uris == the input uris.
+//
+// Input: A vector of login uris
+// Output: A vector of login uris
+//
+// Code:
+// std::vector<std::string> uris;
+// LLViewerLogin::getInstance()->getLoginURIs(uris);
+// std::vector<std::string>::const_iterator iter, end;
+// for (iter = uris.begin(), end = uris.end(); iter != end; ++iter)
+// {
+// std::vector<std::string> rewritten;
+// rewritten = LLSRV::rewriteURI(*iter);
+// sAuthUris.insert(sAuthUris.end(),
+// rewritten.begin(), rewritten.end());
+// }
+// sAuthUriNum = 0;
+
+// Authenticate
+// STATE_LOGIN_AUTHENTICATE
+// Connect to the login server, presumably login.cgi, requesting the login
+// and a slew of related initial connection information.
+// This is an asynch action. The final response, whether success or error
+// is handled by STATE_LOGIN_PROCESS_REPONSE.
+// There is no immediate error or output from this call.
+//
+// Input:
+// URI
+// Credentials (first, last, password)
+// Start location
+// Bool Flags:
+// skip optional update
+// accept terms of service
+// accept critical message
+// Last exec event. (crash state of previous session)
+// requested optional data (inventory skel, initial outfit, etc.)
+// local mac address
+// viewer serial no. (md5 checksum?)
+
+//sAuthUriNum = llclamp(sAuthUriNum, 0, (S32)sAuthUris.size()-1);
+//LLUserAuth::getInstance()->authenticate(
+// sAuthUris[sAuthUriNum],
+// auth_method,
+// firstname,
+// lastname,
+// password, // web_login_key,
+// start.str(),
+// gSkipOptionalUpdate,
+// gAcceptTOS,
+// gAcceptCriticalMessage,
+// gLastExecEvent,
+// requested_options,
+// hashed_mac_string,
+// LLAppViewer::instance()->getSerialNumber());
+
+//
+// Download the Response
+// STATE_LOGIN_NO_REPONSE_YET and STATE_LOGIN_DOWNLOADING
+// I had assumed that this was default behavior of the message system. However...
+// During login, the message system is checked only by these two states in idle_startup().
+// I guess this avoids the overhead of checking network messages for those login states
+// that don't need to do so, but geez!
+// There are two states to do this one function just to update the login
+// status text from 'Logging In...' to 'Downloading...'
+//
+
+//
+// Handle Login Response
+// STATE_LOGIN_PROCESS_RESPONSE
+//
+// This state handle the result of the request to login. There is a metric ton of
+// code in this case. This state will transition to:
+// STATE_WORLD_INIT, on success.
+// STATE_AUTHENTICATE, on failure.
+// STATE_UPDATE_CHECK, to handle user during login interaction like TOS display.
+//
+// Much of the code in this case belongs on the viewer side of the fence and not in login.
+// Login should probably return with a couple of events, success and failure.
+// Failure conditions can be specified in the events data pacet to allow the viewer
+// to re-engauge login as is appropriate. (Or should there be multiple failure messages?)
+// Success is returned with the data requested from the login. According to OGP specs
+// there may be intermediate steps before reaching this result in future login
+// implementations.
diff --git a/indra/viewer_components/login/lllogin.h b/indra/viewer_components/login/lllogin.h
new file mode 100644
index 0000000000..0598b4e457
--- /dev/null
+++ b/indra/viewer_components/login/lllogin.h
@@ -0,0 +1,133 @@
+/**
+ * @file lllogin.h
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLLOGIN_H
+#define LL_LLLOGIN_H
+
+#include <boost/scoped_ptr.hpp>
+
+class LLSD;
+class LLEventPump;
+
+/**
+ * @class LLLogin
+ * @brief Class to encapsulate the action and state of grid login.
+ */
+class LLLogin
+{
+public:
+ LLLogin();
+ ~LLLogin();
+
+ /**
+ * Make a connection to a grid.
+ * @param uri The 'well known and published' authentication URL.
+ * @param credentials LLSD data that contians the credentials.
+ * *NOTE:Mani The credential data can vary depending upon the authentication
+ * method used. The current interface matches the values passed to
+ * the XMLRPC login request.
+ {
+ method : string,
+ first : string,
+ last : string,
+ passwd : string,
+ start : string,
+ skipoptional : bool,
+ agree_to_tos : bool,
+ read_critical : bool,
+ last_exec_event : int,
+ version : string,
+ channel : string,
+ mac : string,
+ id0 : string,
+ options : [ strings ]
+ }
+
+ */
+ void connect(const std::string& uri, const LLSD& credentials);
+
+ /**
+ * Disconnect from a the current connection.
+ */
+ void disconnect();
+
+ /**
+ * Retrieve the event pump from this login class.
+ */
+ LLEventPump& getEventPump();
+
+ /*
+ Event API
+
+ LLLogin will issue multiple events to it pump to indicate the
+ progression of states through login. The most important
+ states are "offline" and "online" which indicate auth failure
+ and auth success respectively.
+
+ pump: login (tweaked)
+ These are the events posted to the 'login'
+ event pump from the login module.
+ {
+ state : string, // See below for the list of states.
+ progress : real // for progress bar.
+ data : LLSD // Dependent upon state.
+ }
+
+ States for method 'login_to_simulator'
+ offline - set initially state and upon failure. data is the server response.
+ srvrequest - upon uri rewrite request. no data.
+ authenticating - upon auth request. data, 'attempt' number and 'request' llsd.
+ downloading - upon ack from auth server, before completion. no data
+ online - upon auth success. data is server response.
+
+
+ Dependencies:
+ pump: LLAres
+ LLLogin makes a request for a SRV record from the uri provided by the connect method.
+ The following event pump should exist to service that request.
+ pump name: LLAres
+ request = {
+ op : "rewriteURI"
+ uri : string
+ reply : string
+
+ pump: LLXMLRPCListener
+ The request merely passes the credentials LLSD along, with one additional
+ member, 'reply', which is the string name of the event pump to reply on.
+
+ */
+
+private:
+ class Impl;
+ boost::scoped_ptr<Impl> mImpl;
+};
+
+#endif // LL_LLLOGIN_H
diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp
new file mode 100644
index 0000000000..a8ae2883d5
--- /dev/null
+++ b/indra/viewer_components/login/tests/lllogin_test.cpp
@@ -0,0 +1,417 @@
+/**
+ * @file lllogin_test.cpp
+ * @author Mark Palange
+ * @date 2009-02-26
+ * @brief Tests of lllogin.cpp.
+ *
+ * $LicenseInfo:firstyear=2009&license=internal$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if LL_WINDOWS
+#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
+#endif
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "../lllogin.h"
+// STL headers
+// std headers
+#include <iostream>
+// external library headers
+// other Linden headers
+#include "llsd.h"
+#include "../../../test/lltut.h"
+//#define DEBUG_ON
+#include "../../../test/debug.h"
+#include "llevents.h"
+#include "stringize.h"
+
+/*****************************************************************************
+* Helper classes
+*****************************************************************************/
+// This is a listener to receive results from lllogin.
+class LoginListener: public LLEventTrackable
+{
+ std::string mName;
+ LLSD mLastEvent;
+ Debug mDebug;
+public:
+ LoginListener(const std::string& name) :
+ mName(name),
+ mDebug(stringize(*this))
+ {}
+
+ bool call(const LLSD& event)
+ {
+ mDebug(STRINGIZE("LoginListener called!: " << event));
+ mLastEvent = event;
+ return false;
+ }
+
+ LLBoundListener listenTo(LLEventPump& pump)
+ {
+ return pump.listen(mName, boost::bind(&LoginListener::call, this, _1));
+ }
+
+ LLSD lastEvent() const { return mLastEvent; }
+
+ friend std::ostream& operator<<(std::ostream& out, const LoginListener& listener)
+ {
+ return out << "LoginListener(" << listener.mName << ')';
+ }
+};
+
+class LLAresListener: public LLEventTrackable
+{
+ std::string mName;
+ LLSD mEvent;
+ bool mImmediateResponse;
+ bool mMultipleURIResponse;
+ Debug mDebug;
+
+public:
+ LLAresListener(const std::string& name,
+ bool i = false,
+ bool m = false
+ ) :
+ mName(name),
+ mImmediateResponse(i),
+ mMultipleURIResponse(m),
+ mDebug(stringize(*this))
+ {}
+
+ bool handle_event(const LLSD& event)
+ {
+ mDebug(STRINGIZE("LLAresListener called!: " << event));
+ mEvent = event;
+ if(mImmediateResponse)
+ {
+ sendReply();
+ }
+ return false;
+ }
+
+ void sendReply()
+ {
+ if(mEvent["op"].asString() == "rewriteURI")
+ {
+ LLSD result;
+ if(mMultipleURIResponse)
+ {
+ result.append(LLSD("login.foo.com"));
+ }
+ result.append(mEvent["uri"]);
+ LLEventPumps::instance().obtain(mEvent["reply"]).post(result);
+ }
+ }
+
+ LLBoundListener listenTo(LLEventPump& pump)
+ {
+ return pump.listen(mName, boost::bind(&LLAresListener::handle_event, this, _1));
+ }
+
+ friend std::ostream& operator<<(std::ostream& out, const LLAresListener& listener)
+ {
+ return out << "LLAresListener(" << listener.mName << ')';
+ }
+};
+
+class LLXMLRPCListener: public LLEventTrackable
+{
+ std::string mName;
+ LLSD mEvent;
+ bool mImmediateResponse;
+ LLSD mResponse;
+ Debug mDebug;
+
+public:
+ LLXMLRPCListener(const std::string& name,
+ bool i = false,
+ const LLSD& response = LLSD()
+ ) :
+ mName(name),
+ mImmediateResponse(i),
+ mResponse(response),
+ mDebug(stringize(*this))
+ {
+ if(mResponse.isUndefined())
+ {
+ mResponse["status"] = "Complete"; // StatusComplete
+ mResponse["errorcode"] = 0;
+ mResponse["error"] = "dummy response";
+ mResponse["transfer_rate"] = 0;
+ mResponse["responses"]["login"] = true;
+ }
+ }
+
+ void setResponse(const LLSD& r)
+ {
+ mResponse = r;
+ }
+
+ bool handle_event(const LLSD& event)
+ {
+ mDebug(STRINGIZE("LLXMLRPCListener called!: " << event));
+ mEvent = event;
+ if(mImmediateResponse)
+ {
+ sendReply();
+ }
+ return false;
+ }
+
+ void sendReply()
+ {
+ LLEventPumps::instance().obtain(mEvent["reply"]).post(mResponse);
+ }
+
+ LLBoundListener listenTo(LLEventPump& pump)
+ {
+ return pump.listen(mName, boost::bind(&LLXMLRPCListener::handle_event, this, _1));
+ }
+
+ friend std::ostream& operator<<(std::ostream& out, const LLXMLRPCListener& listener)
+ {
+ return out << "LLXMLRPCListener(" << listener.mName << ')';
+ }
+};
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llviewerlogin_data
+ {
+ llviewerlogin_data() :
+ pumps(LLEventPumps::instance())
+ {}
+ LLEventPumps& pumps;
+ };
+
+ typedef test_group<llviewerlogin_data> llviewerlogin_group;
+ typedef llviewerlogin_group::object llviewerlogin_object;
+ llviewerlogin_group llviewerlogingrp("llviewerlogin");
+
+ template<> template<>
+ void llviewerlogin_object::test<1>()
+ {
+ DEBUG;
+ // Testing login with immediate repsonses from Ares and XMLPRC
+ // The response from both requests will come before the post request exits.
+ // This tests an edge case of the login state handling.
+ LLEventStream llaresPump("LLAres"); // Dummy LLAres pump.
+ LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump
+
+ bool respond_immediately = true;
+ // Have 'dummy ares' repsond immediately.
+ LLAresListener dummyLLAres("dummy_llares", respond_immediately);
+ dummyLLAres.listenTo(llaresPump);
+
+ // Have dummy XMLRPC respond immediately.
+ LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc", respond_immediately);
+ dummyXMLRPC.listenTo(xmlrpcPump);
+
+ LLLogin login;
+
+ LoginListener listener("test_ear");
+ listener.listenTo(login.getEventPump());
+
+ LLSD credentials;
+ credentials["first"] = "foo";
+ credentials["last"] = "bar";
+ credentials["passwd"] = "secret";
+
+ login.connect("login.bar.com", credentials);
+
+ ensure_equals("Online state", listener.lastEvent()["state"].asString(), "online");
+ }
+
+ template<> template<>
+ void llviewerlogin_object::test<2>()
+ {
+ DEBUG;
+ // Tests a successful login in with delayed responses.
+ // Also includes 'failure' that cause the login module
+ // To re-attempt connection, once from a basic failure
+ // and once from the 'indeterminate' response.
+
+ set_test_name("LLLogin multiple srv uris w/ success");
+
+ // Testing normal login procedure.
+ LLEventStream llaresPump("LLAres"); // Dummy LLAres pump.
+ LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump
+
+ bool respond_immediately = false;
+ bool multiple_addresses = true;
+ LLAresListener dummyLLAres("dummy_llares", respond_immediately, multiple_addresses);
+ dummyLLAres.listenTo(llaresPump);
+
+ LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc");
+ dummyXMLRPC.listenTo(xmlrpcPump);
+
+ LLLogin login;
+
+ LoginListener listener("test_ear");
+ listener.listenTo(login.getEventPump());
+
+ LLSD credentials;
+ credentials["first"] = "foo";
+ credentials["last"] = "bar";
+ credentials["passwd"] = "secret";
+
+ login.connect("login.bar.com", credentials);
+
+ ensure_equals("SRV state", listener.lastEvent()["change"].asString(), "srvrequest");
+
+ dummyLLAres.sendReply();
+
+ // Test Authenticating State prior to first response.
+ ensure_equals("Auth state 1", listener.lastEvent()["change"].asString(), "authenticating");
+ ensure_equals("Attempt 1", listener.lastEvent()["data"]["attempt"].asInteger(), 1);
+ ensure_equals("URI 1", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.foo.com");
+
+ // First send emulated LLXMLRPCListener failure,
+ // this should return login to the authenticating step and increase the attempt
+ // count.
+ LLSD data;
+ data["status"] = "OtherError";
+ data["errorcode"] = 0;
+ data["error"] = "dummy response";
+ data["transfer_rate"] = 0;
+ dummyXMLRPC.setResponse(data);
+ dummyXMLRPC.sendReply();
+
+ ensure_equals("Fail back to authenticate 1", listener.lastEvent()["change"].asString(), "authenticating");
+ ensure_equals("Attempt 2", listener.lastEvent()["data"]["attempt"].asInteger(), 2);
+ ensure_equals("URI 2", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.bar.com");
+
+ // Now send the 'indeterminate' response.
+ data.clear();
+ data["status"] = "Complete"; // StatusComplete
+ data["errorcode"] = 0;
+ data["error"] = "dummy response";
+ data["transfer_rate"] = 0;
+ data["responses"]["login"] = "indeterminate";
+ data["next_url"] = "login.indeterminate.com";
+ data["next_method"] = "test_login_method";
+ dummyXMLRPC.setResponse(data);
+ dummyXMLRPC.sendReply();
+
+ ensure_equals("Fail back to authenticate 2", listener.lastEvent()["change"].asString(), "authenticating");
+ ensure_equals("Attempt 3", listener.lastEvent()["data"]["attempt"].asInteger(), 3);
+ ensure_equals("URI 3", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.indeterminate.com");
+
+ // Finally let the auth succeed.
+ data.clear();
+ data["status"] = "Complete"; // StatusComplete
+ data["errorcode"] = 0;
+ data["error"] = "dummy response";
+ data["transfer_rate"] = 0;
+ data["responses"]["login"] = "true";
+ dummyXMLRPC.setResponse(data);
+ dummyXMLRPC.sendReply();
+
+ ensure_equals("Success state", listener.lastEvent()["state"].asString(), "online");
+
+ login.disconnect();
+
+ ensure_equals("Disconnected state", listener.lastEvent()["state"].asString(), "offline");
+ }
+
+ template<> template<>
+ void llviewerlogin_object::test<3>()
+ {
+ DEBUG;
+ // Test completed response, that fails to login.
+ set_test_name("LLLogin valid response, failure (eg. bad credentials)");
+
+ // Testing normal login procedure.
+ LLEventStream llaresPump("LLAres"); // Dummy LLAres pump.
+ LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump
+
+ LLAresListener dummyLLAres("dummy_llares");
+ dummyLLAres.listenTo(llaresPump);
+
+ LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc");
+ dummyXMLRPC.listenTo(xmlrpcPump);
+
+ LLLogin login;
+ LoginListener listener("test_ear");
+ listener.listenTo(login.getEventPump());
+
+ LLSD credentials;
+ credentials["first"] = "who";
+ credentials["last"] = "what";
+ credentials["passwd"] = "badpasswd";
+
+ login.connect("login.bar.com", credentials);
+
+ ensure_equals("SRV state", listener.lastEvent()["change"].asString(), "srvrequest");
+
+ dummyLLAres.sendReply();
+
+ ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating");
+
+ // Send the failed auth request reponse
+ LLSD data;
+ data["status"] = "Complete";
+ data["errorcode"] = 0;
+ data["error"] = "dummy response";
+ data["transfer_rate"] = 0;
+ data["responses"]["login"] = "false";
+ dummyXMLRPC.setResponse(data);
+ dummyXMLRPC.sendReply();
+
+ ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline");
+ }
+
+ template<> template<>
+ void llviewerlogin_object::test<4>()
+ {
+ DEBUG;
+ // Test incomplete response, that end the attempt.
+ set_test_name("LLLogin valid response, failure (eg. bad credentials)");
+
+ // Testing normal login procedure.
+ LLEventStream llaresPump("LLAres"); // Dummy LLAres pump.
+ LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump
+
+ LLAresListener dummyLLAres("dummy_llares");
+ dummyLLAres.listenTo(llaresPump);
+
+ LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc");
+ dummyXMLRPC.listenTo(xmlrpcPump);
+
+ LLLogin login;
+ LoginListener listener("test_ear");
+ listener.listenTo(login.getEventPump());
+
+ LLSD credentials;
+ credentials["first"] = "these";
+ credentials["last"] = "don't";
+ credentials["passwd"] = "matter";
+
+ login.connect("login.bar.com", credentials);
+
+ ensure_equals("SRV state", listener.lastEvent()["change"].asString(), "srvrequest");
+
+ dummyLLAres.sendReply();
+
+ ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating");
+
+ // Send the failed auth request reponse
+ LLSD data;
+ data["status"] = "OtherError";
+ data["errorcode"] = 0;
+ data["error"] = "dummy response";
+ data["transfer_rate"] = 0;
+ dummyXMLRPC.setResponse(data);
+ dummyXMLRPC.sendReply();
+
+ ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline");
+ }
+}