<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>http://osimdev.org/wiki/index.php?action=history&amp;feed=atom&amp;title=OpenSimulator_Internals%2FCode_Map%2FROBUST%2FUserAgentService</id>
	<title>OpenSimulator Internals/Code Map/ROBUST/UserAgentService - Revision history</title>
	<link rel="self" type="application/atom+xml" href="http://osimdev.org/wiki/index.php?action=history&amp;feed=atom&amp;title=OpenSimulator_Internals%2FCode_Map%2FROBUST%2FUserAgentService"/>
	<link rel="alternate" type="text/html" href="http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/ROBUST/UserAgentService&amp;action=history"/>
	<updated>2026-08-04T15:31:29Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/ROBUST/UserAgentService&amp;diff=67&amp;oldid=prev</id>
		<title>Jwbshaw: first</title>
		<link rel="alternate" type="text/html" href="http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/ROBUST/UserAgentService&amp;diff=67&amp;oldid=prev"/>
		<updated>2026-07-07T12:07:39Z</updated>

		<summary type="html">&lt;p&gt;first&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= OpenSimulator Internals/Code Map/ROBUST/UserAgentService =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
UserAgentService is the home grid&amp;#039;s agent tracking service for HyperGrid. It maintains a traveling agent database, handles outbound HG teleports, authenticates agents at foreign grids, and provides friend status and user info to foreign grids on behalf of local users.&lt;br /&gt;
&lt;br /&gt;
The class comment notes this service exists because HG1.5 clients don&amp;#039;t carry private state themselves -- the home service carries it for them. The comment suggests this shouldn&amp;#039;t be needed once clients improve.&lt;br /&gt;
&lt;br /&gt;
Source files:&lt;br /&gt;
&lt;br /&gt;
 OpenSim/Services/HypergridService/UserAgentService.cs&lt;br /&gt;
 OpenSim/Services/HypergridService/UserAgentServiceBase.cs&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Constructor and Config Loading ===&lt;br /&gt;
&lt;br /&gt;
 UserAgentServiceBase(IConfigSource config)&lt;br /&gt;
&lt;br /&gt;
Config is read in two layers; [UserAgentService] overrides [DatabaseService]:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Section !! Key !! Default !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| [DatabaseService] || StorageProvider || (none) || DLL name -- fallback&lt;br /&gt;
|-&lt;br /&gt;
| [DatabaseService] || ConnectionString || (none) || DB connection string -- fallback&lt;br /&gt;
|-&lt;br /&gt;
| [UserAgentService] || StorageProvider || (inherited) || Overrides [DatabaseService]&lt;br /&gt;
|-&lt;br /&gt;
| [UserAgentService] || ConnectionString || (inherited) || Overrides [DatabaseService]&lt;br /&gt;
|-&lt;br /&gt;
| [UserAgentService] || Realm || hg_traveling_data || Table name&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Throws if StorageProvider is empty or plugin cannot be loaded. Loaded plugin stored as m_Database (IHGTravelingData).&lt;br /&gt;
&lt;br /&gt;
 UserAgentService(IConfigSource config, IFriendsSimConnector friendsConnector)&lt;br /&gt;
&lt;br /&gt;
[UserAgentService] section is required -- throws if absent. All static fields initialized once (m_Initialized flag).&lt;br /&gt;
&lt;br /&gt;
friendsConnector argument is always assigned to m_FriendsLocalSimConnector if non-null, even on subsequent instantiations (bypasses the m_Initialized guard).&lt;br /&gt;
&lt;br /&gt;
Services loaded from [UserAgentService]:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Key !! Interface !! Required&lt;br /&gt;
|-&lt;br /&gt;
| GridService || IGridService || Yes -- throws if empty&lt;br /&gt;
|-&lt;br /&gt;
| GridUserService || IGridUserService || Yes -- throws if empty&lt;br /&gt;
|-&lt;br /&gt;
| GatekeeperService || IGatekeeperService || Yes -- throws if empty&lt;br /&gt;
|-&lt;br /&gt;
| FriendsService || IFriendsService || No&lt;br /&gt;
|-&lt;br /&gt;
| PresenceService || IPresenceService || No&lt;br /&gt;
|-&lt;br /&gt;
| UserAccountService || IUserAccountService || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Additional config:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Section !! Key !! Default !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| [UserAgentService] || BypassClientVerification || false || If true, VerifyClient() always returns true&lt;br /&gt;
|-&lt;br /&gt;
| [UserAgentService] || LevelOutsideContacts || 0 || Minimum UserLevel to be visible to foreign grids via GetUUID()&lt;br /&gt;
|-&lt;br /&gt;
| [UserAgentService] || ShowUserDetailsInHGProfile || true || If false, user_flags/user_created/user_title return zeros in GetUserInfo()&lt;br /&gt;
|-&lt;br /&gt;
| [UserAgentService] || ForeignTripsAllowed_Level_N || (none) || Per-level bool: whether users at that level may visit foreign grids&lt;br /&gt;
|-&lt;br /&gt;
| [UserAgentService] || AllowExcept_Level_N || (none) || Per-level comma-separated grid URLs exempt from ForeignTripsAllowed=true&lt;br /&gt;
|-&lt;br /&gt;
| [UserAgentService] || DisallowExcept_Level_N || (none) || Per-level comma-separated grid URLs exempt from ForeignTripsAllowed=false&lt;br /&gt;
|-&lt;br /&gt;
| [Startup]/[Hypergrid]/[UserAgentService] || GatekeeperURI || (none) || This grid&amp;#039;s external URL; also tries ExternalName in [UserAgentService] then [GatekeeperService]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GatekeeperURI is resolved via DNS at startup. m_MyExternalIP stores the resolved IP for NAT detection in VerifyClient(). Throws if URI cannot be parsed or hostname cannot be resolved.&lt;br /&gt;
&lt;br /&gt;
m_Database.DeleteOld() is called at the end of constructor to purge stale travel records.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Traveling Agent Database ===&lt;br /&gt;
&lt;br /&gt;
IHGTravelingData (table: hg_traveling_data) stores one row per active HG session:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Field !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| SessionID || Agent session UUID&lt;br /&gt;
|-&lt;br /&gt;
| UserID || Agent UUID&lt;br /&gt;
|-&lt;br /&gt;
| GridExternalName || Grid URL where the agent currently is&lt;br /&gt;
|-&lt;br /&gt;
| ServiceToken || The ServiceSessionID generated for this hop (used by VerifyAgent)&lt;br /&gt;
|-&lt;br /&gt;
| ClientIPAddress || Client IP as seen at login time&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
TravelingAgentInfo is an in-memory wrapper over HGTravelingData. Not persisted directly -- StoreTravelInfo() converts it back to HGTravelingData for storage.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== GetHomeRegion() ===&lt;br /&gt;
&lt;br /&gt;
 public GridRegion GetHomeRegion(UUID userID, out Vector3 position, out Vector3 lookAt)&lt;br /&gt;
&lt;br /&gt;
Looks up GridUserInfo for userID. If HomeRegionID is set, fetches the region from GridService. If that fails or HomeRegionID is zero, falls back to GridService.GetDefaultRegions()[0]. Returns null if nothing found. Default position is (128, 128, 0).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== LoginAgentToGrid() ===&lt;br /&gt;
&lt;br /&gt;
 public bool LoginAgentToGrid(GridRegion source, AgentCircuitData agentCircuit, GridRegion gatekeeper,&lt;br /&gt;
     GridRegion finalDestination, bool fromLogin, out string reason)&lt;br /&gt;
&lt;br /&gt;
Handles outbound HG teleport from this grid to a foreign (or local) grid.&lt;br /&gt;
&lt;br /&gt;
# Account check: agent must be a local user (GetUserAccount) -- refuses foreign users with &amp;quot;Forbidden to launch your agents from here&amp;quot;&lt;br /&gt;
# Foreign trip policy (if destination grid != this grid):&lt;br /&gt;
#* Checks m_ForeignTripsAllowed[account.UserLevel] -- if key absent, no restriction&lt;br /&gt;
#* Applies AllowExcept/DisallowExcept per-level exceptions (exact URL match, trailing slash normalized)&lt;br /&gt;
#* Returns false if not allowed&lt;br /&gt;
# Builds composite GridRegion: gatekeeper&amp;#039;s ServerURI + finalDestination&amp;#039;s host/port/name/ID/coordinates&lt;br /&gt;
# Generates new ServiceSessionID: region.ServerURI + &amp;quot;;&amp;quot; + random UUID&lt;br /&gt;
# CreateTravelInfo(): stores new HGTravelingData, returns existing record if any&lt;br /&gt;
#* If not fromLogin and existing record has a stored IP: overwrites agentCircuit.IPAddress with the stored IP (preserves original client IP across hops)&lt;br /&gt;
# Launch:&lt;br /&gt;
#* If destination is this grid: calls GatekeeperService.LoginAgent() directly (local path)&lt;br /&gt;
#* Otherwise: calls GatekeeperConnector.CreateAgent() on the foreign gatekeeper (remote path)&lt;br /&gt;
#* TODO comment in source notes QueryAccess is not called on the remote path&lt;br /&gt;
# On failure: restores old TravelingAgentInfo if it existed, else deletes the session record&lt;br /&gt;
# On success: stores the new TravelingAgentInfo&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== LogoutAgent() ===&lt;br /&gt;
&lt;br /&gt;
 public void LogoutAgent(UUID userID, UUID sessionID)&lt;br /&gt;
&lt;br /&gt;
Deletes the travel record for sessionID. Calls GridUserService.LoggedOut() using the last known position from GridUserInfo.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== IsAgentComingHome() ===&lt;br /&gt;
&lt;br /&gt;
 public bool IsAgentComingHome(UUID sessionID, string thisGridExternalName)&lt;br /&gt;
&lt;br /&gt;
Called by GatekeeperService to distinguish a returning local user from a foreign agent with a colliding UUID.&lt;br /&gt;
&lt;br /&gt;
Looks up the travel record for sessionID. Returns true if GridExternalName matches thisGridExternalName (case-insensitive). Returns false if no record exists.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== VerifyClient() ===&lt;br /&gt;
&lt;br /&gt;
 public bool VerifyClient(UUID sessionID, string reportedIP)&lt;br /&gt;
&lt;br /&gt;
Called by simulators to verify a connecting client is who they claim to be.&lt;br /&gt;
&lt;br /&gt;
If BypassClientVerification = true: always returns true.&lt;br /&gt;
&lt;br /&gt;
Otherwise: fetches travel record, compares reportedIP against stored ClientIPAddress. Also accepts m_MyExternalIP as a match (NAT: client and server share the same external IP).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== VerifyAgent() ===&lt;br /&gt;
&lt;br /&gt;
 public bool VerifyAgent(UUID sessionID, string token)&lt;br /&gt;
&lt;br /&gt;
Called by GatekeeperService.Authenticate() on the home grid side. Fetches travel record, compares token against stored ServiceToken. Returns false if no record.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== GetOnlineFriends() ===&lt;br /&gt;
&lt;br /&gt;
 public List&amp;lt;UUID&amp;gt; GetOnlineFriends(UUID foreignUserID, List&amp;lt;string&amp;gt; friends)&lt;br /&gt;
&lt;br /&gt;
Called by a foreign grid to find which local friends of a visiting user are online.&lt;br /&gt;
&lt;br /&gt;
For each UUI in friends:&lt;br /&gt;
* Parses the UUI to extract localUserID and secret&lt;br /&gt;
* Fetches FriendInfo for localUserID&lt;br /&gt;
* Checks that the foreign user is in the friend list (matching by UUID prefix and secret) AND has CanSeeOnline rights&lt;br /&gt;
&lt;br /&gt;
Then calls PresenceService.GetAgents() on the confirmed list. Returns UUIDs of those with active presence records.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== GetUserInfo() ===&lt;br /&gt;
&lt;br /&gt;
 public Dictionary&amp;lt;string, object&amp;gt; GetUserInfo(UUID userID)&lt;br /&gt;
&lt;br /&gt;
Returns basic user info for display on foreign grids. Always returns user_firstname and user_lastname. If ShowUserDetailsInHGProfile = true: returns actual user_flags, user_created, user_title. If false: returns zeros and empty string.&lt;br /&gt;
&lt;br /&gt;
Returns a dict with result = &amp;quot;fail&amp;quot; if UserAccountService is not configured.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== GetServerURLs() ===&lt;br /&gt;
&lt;br /&gt;
 public Dictionary&amp;lt;string, object&amp;gt; GetServerURLs(UUID userID)&lt;br /&gt;
&lt;br /&gt;
Returns the ServiceURLs dictionary from the UserAccount record. Returns empty dict if UserAccountService not configured or user not found.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== LocateUser() ===&lt;br /&gt;
&lt;br /&gt;
 public string LocateUser(UUID userID)&lt;br /&gt;
&lt;br /&gt;
Searches all travel records for userID. Returns the GridExternalName of the first session where the agent is on a foreign grid (not this grid). Returns empty string if not found or only on home grid.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== GetUUI() ===&lt;br /&gt;
&lt;br /&gt;
 public string GetUUI(UUID userID, UUID targetUserID)&lt;br /&gt;
&lt;br /&gt;
Returns the Universal User Identifier for targetUserID as seen by userID.&lt;br /&gt;
&lt;br /&gt;
First checks local UserAccounts -- returns agentID + &amp;quot;;&amp;quot; + m_GridName + &amp;quot;;&amp;quot; + name.&lt;br /&gt;
&lt;br /&gt;
If not local: searches userID&amp;#039;s friend list for a record starting with targetUserID. Strips the secret from the UUI (replaces it with &amp;quot;0&amp;quot;) before returning.&lt;br /&gt;
&lt;br /&gt;
Returns empty string if not found.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== GetUUID() ===&lt;br /&gt;
&lt;br /&gt;
 public UUID GetUUID(string first, string last)&lt;br /&gt;
&lt;br /&gt;
Looks up a local user by name. Returns UUID.Zero if not found or if account.UserLevel &amp;lt; LevelOutsideContacts.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== StatusNotification() ===&lt;br /&gt;
&lt;br /&gt;
 [Obsolete]&lt;br /&gt;
 public List&amp;lt;UUID&amp;gt; StatusNotification(List&amp;lt;string&amp;gt; friends, UUID foreignUserID, bool online)&lt;br /&gt;
&lt;br /&gt;
Marked Obsolete. Notifies local friends of a foreign user&amp;#039;s online/offline status change. The cross-grid notification path (forwarding to users visiting other grids) is commented out with a note that HG status notifications are &amp;quot;still not implemented.&amp;quot; Only the first online local friend is notified; the rest of the loop was truncated.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== Notes ===&lt;br /&gt;
&lt;br /&gt;
* ForeignTripsAllowed is keyed by UserLevel integer -- if a level has no entry, no restriction applies for that level.&lt;br /&gt;
* The remote path in LoginAgentToGrid() skips QueryAccess; a TODO comment in the source acknowledges this.&lt;br /&gt;
* StatusNotification() is marked Obsolete and the cross-grid notification path inside it is commented out.&lt;br /&gt;
* The class comment explicitly calls this service a stopgap for HG1.5 client limitations.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[OpenSimulator Internals/Code Map/ROBUST]]&lt;br /&gt;
* [[OpenSimulator Internals/Code Map/ROBUST/GatekeeperService]]&lt;br /&gt;
* [[OpenSimulator Internals/Code Map/ROBUST/PresenceService]]&lt;br /&gt;
* [[OpenSimulator Internals/Connector Architecture]]&lt;/div&gt;</summary>
		<author><name>Jwbshaw</name></author>
	</entry>
</feed>