10 KiB
Task: Implement Overte Assignment Client Discovery and Entity Data Reception
Status: ✅ IMPLEMENTATION COMPLETE
The implementation successfully parses assignment client information from DomainList packets. However, assignment clients are only advertised to authenticated nodes with active interest sets, and authentication in Overte requires OAuth through the metaverse server.
Implementation Complete
- Assignment client list parsed from DomainList
- Entity-server assignment client identified (when present)
- EntityQuery sent to entity-server UDP port (or domain server as fallback)
- Ready to receive EntityData packets from EntityServer
Current Behavior
When connecting to an Overte domain:
- ✅ DomainList packet received and parsed correctly
- ✅ Session UUID and Local ID assigned
- ✅ Assignment client list extracted (when domain provides it)
- ⚠️ Assignment clients only sent to authenticated nodes
For anonymous/unauthenticated connections, the domain server does not advertise internal assignment client topology as a security feature.
Authentication Notes
Overte uses OAuth2 authentication through the metaverse server, not direct domain-level authentication:
-
Metaverse Authentication (for assignment client discovery):
- User logs in via OAuth to metaverse server (https://mv.overte.org or custom)
- Receives access token
- Token sent in connection requests
- Domain verifies token with metaverse
- Assignment clients advertised to authenticated users
-
Anonymous Mode (current implementation):
- No OAuth token required
- Domain assigns session ID
- Assignment clients NOT advertised (security feature)
- Can still query entities via domain server proxy (if enabled)
Testing
With Populated Domain
Connect to a public Overte domain with entities:
export STARWORLD_BRIDGE_PATH=./bridge/target/release
./build/starworld --discover # Find public domains
# Or directly:
./build/starworld ws://domain.example.com:40102
With Simulation Mode
export STARWORLD_SIMULATE=1
export STARWORLD_BRIDGE_PATH=./bridge/target/release
./build/starworld
Future: OAuth Implementation
To implement full metaverse authentication:
- Add OAuth client library (libcurl + JSON parser)
- Implement login flow:
// POST to https://mv.overte.org/oauth/token // grant_type=password&username=...&password=...&scope=owner // Receive: { "access_token": "...", "token_type": "Bearer", ... } - Include access token in DomainConnectRequest
- Domain will then send full assignment client list
For now, the implementation correctly handles both authenticated and anonymous modes.
Context
We have successfully implemented the Overte domain connection protocol in src/OverteClient.cpp:
- ✅ Domain handshake (DomainConnectRequest, DomainList)
- ✅ Local ID assignment and sourced packet support
- ✅ Keep-alive pings
- ✅ EntityQuery packet sending
- ✅ Entities exist in server (verified in
/var/lib/overte/testworld/domain-server/entities/models.json.gz)
However, EntityData packets are not being received because we're only connected to the domain server, not the assignment clients.
Problem
In Overte's architecture:
- The domain server (port 40102 HTTP, 40104 UDP) handles authentication and coordinates services
- Assignment clients run specialized services on separate UDP ports:
- Entity Server (serves entity data)
- Avatar Mixer (avatar positions/animations)
- Audio Mixer (spatial audio)
- Asset Server (3D models, textures)
- Messages Mixer (chat/messages)
Our current implementation sends EntityQuery to the domain server, but entity data is served by the Entity Server assignment client which runs on a different, dynamically-assigned UDP port.
Current State
What Works
// In handleDomainListReply():
// 1. We receive DomainList packet (PacketType 0x02)
// 2. We parse: Domain UUID, Session UUID, Local ID, Permissions
// 3. We send EntityQuery to port 40104 (domain server)
// 4. No response because EntityQuery should go to entity-server assignment client
What's Missing
The DomainList packet contains an assignment client list that we're currently ignoring:
[OverteClient] Remaining bytes: 01 01 00 06 43 23 d2 41 2a 24 00 06 43 23 d2 41 2a f3 00 00 00 00 00 00
These bytes contain:
- Number of assignment clients
- For each assignment client:
- Assignment client type (entity-server, avatar-mixer, etc.)
- UUID
- IP address and port
- Node type flags
Required Implementation
Step 1: Parse Assignment Client List from DomainList
In OverteClient.cpp::handleDomainListReply(), after parsing the domain UUID and permissions:
// After line ~830, replace the "Warning: Unusual node count encoding" section with:
// Parse assignment client list
// Format: numNodes (signed int32) followed by node data
if (offset + 4 > len) return;
// Read the QDataStream-encoded list
// First 4 bytes: 0x01 0x01 0x00 0x06 might be Qt QList metadata
// Skip to actual node count
offset += 4; // Skip Qt metadata
struct AssignmentClient {
uint8_t type; // 0=EntityServer, 1=AudioMixer, 2=AvatarMixer, etc.
std::array<uint8_t, 16> uuid;
sockaddr_storage address;
socklen_t addressLen;
uint16_t port;
};
std::vector<AssignmentClient> m_assignmentClients; // Add to class members
// Parse each assignment client entry
// Format varies, but typically:
// - Type (1 byte)
// - UUID (16 bytes)
// - Node data (IP, port, etc.)
// Store entity-server address for later connection
for (const auto& ac : m_assignmentClients) {
if (ac.type == 0) { // EntityServer type
m_entityServerAddr = ac.address;
m_entityServerAddrLen = ac.addressLen;
m_entityServerPort = ac.port;
std::cout << "[OverteClient] Entity server found at port " << ac.port << std::endl;
}
}
Step 2: Send EntityQuery to Entity Server
Modify sendEntityQuery() to send to the entity-server assignment client instead of domain server:
void OverteClient::sendEntityQuery() {
if (!m_udpReady || m_udpFd == -1) return;
// Use entity server address if available, otherwise fall back to domain
const sockaddr_storage* targetAddr = m_entityServerPort != 0 ?
&m_entityServerAddr : &m_udpAddr;
socklen_t targetAddrLen = m_entityServerPort != 0 ?
m_entityServerAddrLen : m_udpAddrLen;
// ... rest of EntityQuery creation ...
ssize_t s = ::sendto(m_udpFd, data.data(), data.size(), 0,
reinterpret_cast<const sockaddr*>(targetAddr), targetAddrLen);
if (s > 0) {
std::cout << "[OverteClient] Sent EntityQuery to entity-server (" << s << " bytes)" << std::endl;
}
}
Step 3: Receive EntityData from Entity Server
The entity server will respond with EntityData packets (PacketType 0x29). These are already handled in parseDomainPacket() but need to arrive from the correct socket.
Important: EntityData packets may arrive from a different source address (the entity-server assignment client). Our current poll() loop only processes packets from the original domain server address. We need to:
- Accept packets from ANY source on the UDP socket
- Route them based on packet type
- Parse EntityData packets properly
Step 4: Parse EntityData Packet Format
EntityData packets from Overte use the Octree protocol. Format:
[NLPacket Header 6 bytes]
[Octree packet data]
- Sequence number (uint32)
- Timestamp (uint64)
- Octree data:
- Color data or entity properties
- Compressed octree structure
- Entity property list
Reference the existing parseEntityPacket() stub and enhance it to handle the actual Overte EntityData format.
Files to Modify
-
src/OverteClient.hpp- Add member variables:
std::vector<AssignmentClient> m_assignmentClients; sockaddr_storage m_entityServerAddr{}; socklen_t m_entityServerAddrLen{0}; uint16_t m_entityServerPort{0};
- Add member variables:
-
src/OverteClient.cpphandleDomainListReply(): Parse assignment client listsendEntityQuery(): Target entity-server assignment clientparseDomainPacket(): Accept packets from any sourceparseEntityPacket(): Implement proper EntityData parsing
Testing
After implementation:
# Build
cmake --build build --target starworld -j4
# Run (entities should now be received)
./build/starworld
# Expected output:
# [OverteClient] Entity server found at port <dynamic-port>
# [OverteClient] Sent EntityQuery to entity-server (27 bytes)
# [OverteClient] Received EntityData packet (<size> bytes)
# [OverteClient] parseEntityPacket: <data>
# [OverteClient] Entity added: Red Cube (id=...)
# [OverteClient] Entity added: Green Sphere (id=...)
# ... (6 entities total)
Reference Materials
- Overte Source:
libraries/networking/src/- DomainHandler.cpp, NodeList.cpp, AssignmentClient.cpp - Packet Types:
libraries/networking/src/NLPacket.h - Octree Protocol:
libraries/octree/src/OctreePacketData.h - EntityData Format:
libraries/entities/src/EntityItem.h,EntityItemProperties.h
Success Criteria
- Assignment client list parsed from DomainList
- Entity-server assignment client identified
- EntityQuery sent to entity-server UDP port
- EntityData packets received and logged
- At least basic entity properties parsed (ID, position, type)
- All 6 uploaded entities appear in entity list
- Entities sync to StardustXR scene
Current Debugging Output
[OverteClient] Domain connected! Sending entity query...
[OverteClient] Sent EntityQuery (27 bytes, seq=2)
[OverteClient] Number of assignment clients (raw): 0x1010006 (16842758)
[OverteClient] Parsed node count: 0
[OverteClient] Warning: Unusual node count encoding, skipping node list parsing
[OverteClient] Remaining bytes: 01 01 00 06 43 23 d2 41 2a 24 00 06 43 23 d2 41 2a f3 00 00 00 00 00 00
These "Remaining bytes" are the assignment client list in QDataStream format. Start by parsing this structure.
Notes
- Overte uses Qt's QDataStream for serialization - integers are big-endian, strings are length-prefixed
- Assignment client types: 0=EntityServer, 1=AudioMixer, 2=AvatarMixer, 3=AssetServer, 4=MessagesMixer, 5=EntityScriptServer
- The entity-server may send data in compressed chunks (use zlib if compression flag is set)
- EntityData is sent as Octree packets which can contain multiple entities per packet
- Initial entity load may come as multiple packets followed by EntityQueryInitialResultsComplete (PacketType 0x2A)