diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 018c23963cb..5ae4e1afb23 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -24,6 +24,72 @@ * $/LicenseInfo$ */ +// +// LOGIN AND CONNECTION SEQUENCE OVERVIEW +// ====================================== +// The Viewer connects to the SL service in two phases: HTTP authentication +// followed by UDP "Circuit" establishment to the first Simulator. +// +// PHASE 1: HTTP LOGIN (see lllogin.cpp, process_login_success_response()) +// ----------------------------------------------------------------------- +// Viewer sends an XMLRPC HTTP POST to LoginServer containing: +// - Credentials (first name, last name, password) +// - Client version, channel, MAC address, machine ID +// - Start location preferences +// +// Login-server responds with critical connection data: +// - agent_id, session_id, secure_session_id (authentication tokens) +// - Circuit_code (used to establish UDP Circuit with Simulator) +// - sim_ip, sim_port (Simulator address to connect to) +// - seed_capability (base URL for HTTP capability requests) +// - region_x, region_y (region grid coordinates) +// +// PHASE 2: UDP CIRCUIT ESTABLISHMENT (see idle_startup() state machine below) +// --------------------------------------------------------------------------- +// After HTTP login succeeds, Viewer establishes a UDP Circuit with the +// simulator. This also happens whenever the Viewer connects to new Simulators +// in the same session. The following UDP messages are exchanged: +// +// 1. UseCircuitCode (Viewer -> Simulator) +// - Sent in STATE_WORLD_INIT +// - Contains: Circuit_code, session_id, agent_id +// - Establishes the UDP Circuit with the Simulator +// +// 2. RegionHandshake (Simulator -> Viewer) +// - Handled by process_region_handshake() in llworld.cpp +// - Contains: region name, terrain textures, water height, region flags +// - Viewer responds with RegionHandshakeReply +// +// 3. CompleteAgentMovement (Viewer -> Simulator) +// - Sent in STATE_AGENT_SEND via send_complete_agent_movement() +// - Signals the Viewer is ready to enter the world +// +// 4. AgentMovementComplete (Simulator -> Viewer) +// - Handled by process_agent_movement_complete() in llviewermessage.cpp +// - Contains: final agent position, look_at direction, region handle +// - Sets gAgentMovementCompleted = true +// - Agent is now fully connected to the region +// +// STARTUP STATE MACHINE +// --------------------- +// The connection sequence is managed by idle_startup() which progresses +// through these key states: +// +// STATE_LOGIN_WAIT - Waiting for HTTP login response +// STATE_LOGIN_PROCESS_RESPONSE - Processing login response data +// STATE_WORLD_INIT - Send UseCircuitCode, enable UDP Circuit +// STATE_WORLD_WAIT - Wait for Circuit acknowledgment +// STATE_AGENT_SEND - Send CompleteAgentMovement +// STATE_AGENT_WAIT - Wait for AgentMovementComplete +// STATE_INVENTORY_SEND - Agent connected, begin loading inventory +// +// HTTP CAPABILITIES +// ----------------- +// After UDP connection, Viewer fetches "capability" URLs from the +// seed_capability endpoint. These provide HTTP endpoints for various +// services (inventory, textures, etc.) that supplement the UDP protocol. +// + #include "llviewerprecompiledheaders.h" #include "llappviewer.h" @@ -271,7 +337,6 @@ void show_first_run_dialog(); bool first_run_dialog_callback(const LLSD& notification, const LLSD& response); void set_startup_status(const F32 frac, const std::string& string, const std::string& msg); bool login_alert_status(const LLSD& notification, const LLSD& response); -void use_circuit_callback(void**, S32 result); void register_viewer_callbacks(LLMessageSystem* msg); void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32); bool callback_choose_gender(const LLSD& notification, const LLSD& response); @@ -1707,13 +1772,48 @@ bool idle_startup() gUseCircuitCallbackCalled = false; msg->enableCircuit(gFirstSim, true); - // now, use the circuit info to tell simulator about us! + + // UDP CONNECTION STEP 1: Send UseCircuitCode + // This is the first UDP message sent to Simulator after HTTP login. + // It establishes the UDP circuit using the circuit_code received from + // LoginServer. Simulator will respond with an ACK, then send + // RegionHandshake message with region details. LL_INFOS("AppInit") << "viewer: UserLoginLocationReply() Enabling " << gFirstSim << " with code " << msg->mOurCircuitCode << LL_ENDL; msg->newMessageFast(_PREHASH_UseCircuitCode); msg->nextBlockFast(_PREHASH_CircuitCode); msg->addU32Fast(_PREHASH_Code, msg->mOurCircuitCode); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->addUUIDFast(_PREHASH_ID, gAgent.getID()); + + // build a lambda to be used as callback on ACK or timeout + void (*use_circuit_callback)(void**, S32) = [](void**, S32 result) + { + // bail if we're quitting. + if(LLApp::isExiting()) return; + if( !gUseCircuitCallbackCalled ) + { + gUseCircuitCallbackCalled = true; + if (result != LL_ERR_NOERR) + { + // Make sure user knows something bad happened. JC + LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL; + if (gRememberPassword) + { + LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); + } + else + { + LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); + } + reset_login(); + } + else + { + gGotUseCircuitCodeAck = true; + } + } + }; + msg->sendReliable( gFirstSim, gSavedSettings.getS32("UseCircuitCodeMaxRetries"), @@ -1731,6 +1831,9 @@ bool idle_startup() //--------------------------------------------------------------------- // World Wait //--------------------------------------------------------------------- + // UDP CONNECTION STEP 2: Wait for UseCircuitCode acknowledgment + // While waiting, Simulator also sends RegionHandshake (handled by + // process_region_handshake() in llworld.cpp) containing region info. if(STATE_WORLD_WAIT == LLStartUp::getStartupState()) { LL_DEBUGS("AppInit") << "Waiting for simulator ack...." << LL_ENDL; @@ -1746,13 +1849,16 @@ bool idle_startup() //--------------------------------------------------------------------- // Agent Send //--------------------------------------------------------------------- + // UDP CONNECTION STEP 3: Send CompleteAgentMovement + // After the circuit is established and RegionHandshake received, we signal + // to Simulator the Viewer is ready to enter the world. if (STATE_AGENT_SEND == LLStartUp::getStartupState()) { LL_DEBUGS("AppInit") << "Connecting to region..." << LL_ENDL; set_startup_status(0.60f, LLTrans::getString("LoginConnectingToRegion"), gAgent.mMOTD); do_startup_frame(); - // register with the message system so it knows we're - // expecting this message + // Register handler process_agent_movement_complete for AgentMovementComplete - + // the final UDP message confirming the agent is connected. LLMessageSystem* msg = gMessageSystem; msg->setHandlerFuncFast( _PREHASH_AgentMovementComplete, @@ -1795,12 +1901,17 @@ bool idle_startup() //--------------------------------------------------------------------- // Agent Wait //--------------------------------------------------------------------- + // UDP CONNECTION STEP 4: Wait for AgentMovementComplete + // Simulator responds with the agent's confirmed position and look_at + // direction. Once received, gAgentMovementCompleted is set true and the + // agent is fully connected to the region. if (STATE_AGENT_WAIT == LLStartUp::getStartupState()) { do_startup_frame(); if (gAgentMovementCompleted) { + // Connection complete - agent is now in-world LLStartUp::setStartupState( STATE_INVENTORY_SEND ); } do_startup_frame(); @@ -2728,34 +2839,6 @@ bool login_alert_status(const LLSD& notification, const LLSD& response) } -void use_circuit_callback(void**, S32 result) -{ - // bail if we're quitting. - if(LLApp::isExiting()) return; - if( !gUseCircuitCallbackCalled ) - { - gUseCircuitCallbackCalled = true; - if (result) - { - // Make sure user knows something bad happened. JC - LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL; - if (gRememberPassword) - { - LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); - } - else - { - LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); - } - reset_login(); - } - else - { - gGotUseCircuitCodeAck = true; - } - } -} - void register_viewer_callbacks(LLMessageSystem* msg) { msg->setHandlerFuncFast(_PREHASH_LayerData, process_layer_data ); @@ -3648,6 +3731,14 @@ bool init_benefits(LLSD& response) return succ; } +// HTTP LOGIN RESPONSE PROCESSING +// Called after successful HTTP XMLRPC authentication. Extracts critical data +// from LoginServer response needed to establish the UDP connection: +// - agent_id, session_id, secure_session_id (authentication tokens) +// - circuit_code (used in UseCircuitCode UDP message) +// - sim_ip, sim_port (simulator address for UDP circuit) +// - seed_capability (URL for fetching HTTP capability endpoints) +// bool process_login_success_response() { LLSD response = LLLoginInstance::getInstance()->getResponse(); @@ -3770,6 +3861,8 @@ bool process_login_success_response() gAgentStartLocation.assign(text); } + // Extract UDP circuit parameters from login response. + // These are used in STATE_WORLD_INIT to establish the UDP circuit. text = response["circuit_code"].asString(); if(!text.empty()) { diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 5d8bd452186..7a9651ed53e 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -418,7 +418,26 @@ void send_complete_agent_movement(const LLHost& sim_host) msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->addU32Fast(_PREHASH_CircuitCode, msg->mOurCircuitCode); - msg->sendReliable(sim_host); + + // build a lambda to be used as callback on ACK or timeout + void (*complete_agent_movement_callback)(void**, S32) = [](void**, S32 result) + { + if(LLApp::isExiting()) return; + if (result != LL_ERR_NOERR) + { + LL_WARNS("Messaging") << "CompleteAgentMovement failed with err=" << result << LL_ENDL; + } + }; + + // We use same retry strategy as UseCircuitCode because this is a crucial message + // that MUST arrive else we'll suffer a failed login/teleport/region-cross + msg->sendReliable( + sim_host, + gSavedSettings.getS32("UseCircuitCodeMaxRetries"), + false, + (F32Seconds)gSavedSettings.getF32("UseCircuitCodeTimeout"), + complete_agent_movement_callback, + NULL); } void process_logout_reply(LLMessageSystem* msg, void**) diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 42a587f3764..8c6a5d80961 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3202,7 +3202,26 @@ void LLViewerRegion::unpackRegionHandshake() flags |= 0x00000002; //set the bit 1 to be 1 to tell sim the cache file is empty, no need to send cache probes. } msg->addU32("Flags", flags ); - msg->sendReliable(host); + + // build a lambda to be used as callback on ACK or timeout + void (*region_handshake_reply_callback)(void**, S32) = [](void**, S32 result) + { + if(LLApp::isExiting()) return; + if (result != LL_ERR_NOERR) + { + LL_WARNS("Messaging") << "RegionHandshakeReply failed with err=" << result << LL_ENDL; + } + }; + + // This is a crucial message for establishing a connection to a region + // (either the main region or a visible neighbor). + msg->sendReliable( + host, + gSavedSettings.getS32("UseCircuitCodeMaxRetries"), + false, + (F32Seconds)gSavedSettings.getF32("UseCircuitCodeTimeout"), + region_handshake_reply_callback, + NULL); mRegionTimer.reset(); //reset region timer. }