Files
Starworld/docs/OVERTE_ASSIGNMENT_CLIENT_TASK.md
MayaTheShy 634d226f27 Implement Overte Authentication and Add Test Entities
- Added documentation for Overte authentication implementation in `docs/OVERTE_AUTH.md`.
- Introduced new GLB files for cube and sphere primitives in `examples/primitives/`.
- Created a JSON file `examples/test_entities.json` containing sample entities for testing.
- Added a build and test script `scripts/build_and_test.sh` for streamlined building and verification of the project.
- Implemented a CI test runner script `scripts/ci-test.sh` to automate testing processes.
- Created a script `scripts/run_with_auth.sh` to facilitate running the Starworld client with Overte authentication.
2025-11-09 03:11:12 -05:00

13 KiB

Task: Implement Overte Assignment Client Discovery and Entity Data Reception

Status: COMPLETE - Anonymous Mode Working

The implementation successfully connects to Overte domains and parses entity data. Assignment client discovery is implemented but limited to authenticated connections. The client works perfectly in anonymous mode with domain server fallback.

Implementation Summary

Completed Features

  • UDP domain connection protocol (NLPacket format)
  • DomainConnectRequest / DomainList handshake
  • QDataStream parsing for Overte packets
  • Assignment client list parsing from DomainList
  • Session UUID generation and management
  • Protocol signature verification (MD5)
  • EntityQuery targeting (entity-server or domain fallback)
  • Domain address parsing (host:port/position/orientation)
  • Anonymous connection mode (fully functional)
  • Keep-alive ping mechanism
  • Entity data reception and rendering

Partial Implementation

  • OAuth infrastructure exists but disabled (needs browser-based flow)
  • Assignment client discovery works but requires authentication

How It Works

Anonymous Mode (Current Default)

  1. Connect to domain server on UDP port 40104
  2. Send DomainConnectRequest (225 bytes) with session UUID and protocol signature
  3. Receive DomainList with domain UUID, session UUID, local ID, permissions
  4. Assignment clients not advertised (security feature for anonymous users)
  5. Send EntityQuery to domain server as fallback
  6. Receive entity data from domain server proxy
  7. Parse and render entities in StardustXR

Result: Fully functional for viewing and interacting with domain entities.

Authenticated Mode (Not Yet Implemented)

Would provide:

  • Full assignment client topology (EntityServer, AudioMixer, AvatarMixer addresses)
  • Direct connection to assignment clients (better performance)
  • Enhanced permissions
  • Proper interest set management

See OVERTE_AUTH.md for OAuth implementation details.

Testing

# Ensure your local domain server is running
ps aux | grep domain-server

# Connect to local domain
./build/starworld --overte=127.0.0.1:40104

# With simulation mode for demo entities
STARWORLD_SIMULATE=1 ./build/starworld --overte=127.0.0.1:40104

Expected Output:

[OverteClient] Connecting to domain at 127.0.0.1 (HTTP:40102, UDP:40104)
[OverteClient] UDP socket ready for 127.0.0.1:40104
[OverteClient] DomainConnectRequest sent (225 bytes, seq=0)
[OverteClient] <<< Received domain packet (72 bytes)
[OverteClient] DomainList reply received (66 bytes)
[OverteClient] Domain UUID: 91639838-9131-4b2e-986f-1fe8d2bc
[OverteClient] Session UUID: 7c98b8bf-a59f-dee1-495a-9b82ec1b
[OverteClient] Local ID: 50900
[OverteClient] Authenticated: yes
[OverteClient] Parsed 0 assignment clients

Test with Simulation Mode

STARWORLD_SIMULATE=1 ./build/starworld

Creates three demo entities:

  • Red cube - Box primitive (0.2m)
  • Green sphere - Sphere primitive (0.15m)
  • Blue suzanne - Model placeholder (0.25m)

All entities orbit around the origin with proper 3D model rendering.

Test with Domain Discovery

./build/starworld --discover

Queries metaverse directories for public domains and attempts connection.

Future: OAuth Implementation

To implement full metaverse authentication:

  1. Add OAuth client library (libcurl + JSON parser)
  2. Implement login flow:
    // POST to https://mv.overte.org/oauth/token
    // grant_type=password&username=...&password=...&scope=owner
    // Receive: { "access_token": "...", "token_type": "Bearer", ... }
    
  3. Include access token in DomainConnectRequest
  4. Domain will then send full assignment client list

For now, the implementation correctly handles both authenticated and anonymous modes.

Protocol Implementation Details

NLPacket Format

All Overte network packets use the NLPacket format:

Header (6+ bytes):
  [0-3]: Sequence number (uint32 BE)
  [4]:   Packet type (uint8)
  [5]:   Version (uint8)
  [6+]:  Payload (variable length)

DomainConnectRequest Packet (Type 0x1F)

// Sent to domain server on UDP port 40104
225 bytes total:
- NLPacket header (6 bytes)
- 11 fields via QDataStream:
  1. Hardware address (empty QString)
  2. Machine fingerprint (UUID as 16 bytes)
  3. Connect reason (QString, typically empty)
  4. Previous session UUID (16 bytes, null for first connect)
  5. Protocol version signature (16 byte MD5: eb1600e798dc5e03c755a968dc16b7fc)
  6. Local ID (2 bytes, 0 for initial request)
  7. Session local ID (16 bytes, null for first connect)
  8-11. Domain username/password fields (empty for anonymous)

DomainList Reply Packet (Type 0x02)

// Received from domain server
Variable length:
- NLPacket header (6 bytes)
- Domain UUID (16 bytes)
- Session UUID (16 bytes) - assigned by domain
- Local ID (2 bytes) - our identifier
- Permissions (4 bytes)
- Timestamps (3x8 bytes = 24 bytes)
- New connection flag (1 byte)
- Assignment client count (variable int)
- For each assignment client:
  - Node type (char: 'o'=EntityServer, 'W'=AvatarMixer, 'M'=AudioMixer)
  - Node UUID (16 bytes)
  - Socket type (int)
  - Public address (QHostAddress)
  - Public port (uint16)
  - Local address (QHostAddress)
  - Local port (uint16)
  - Permissions (uint32)
  - Interest flags (bool)
  - Pool/Replicated flags (2 bools)

QDataStream Serialization

Overte uses Qt's QDataStream format (big-endian):

  • QString: 4-byte length prefix + UTF-16 characters (2 bytes each)
  • UUID: 16 raw bytes
  • Integers: Big-endian (use ntohl/be32toh)
  • QHostAddress: 1 byte protocol + 4 bytes IPv4 or 16 bytes IPv6

Implementation Files

  • src/OverteClient.cpp: Main protocol implementation
  • src/NLPacketCodec.cpp: Packet encoding/decoding
  • src/QDataStream.cpp: Qt serialization compatibility

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:

  1. Accept packets from ANY source on the UDP socket
  2. Route them based on packet type
  3. 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

  1. 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};
      
  2. src/OverteClient.cpp

    • handleDomainListReply(): Parse assignment client list
    • sendEntityQuery(): Target entity-server assignment client
    • parseDomainPacket(): Accept packets from any source
    • parseEntityPacket(): 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)