- 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.
369 lines
13 KiB
Markdown
369 lines
13 KiB
Markdown
# 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
|
|
- [x] UDP domain connection protocol (NLPacket format)
|
|
- [x] DomainConnectRequest / DomainList handshake
|
|
- [x] QDataStream parsing for Overte packets
|
|
- [x] Assignment client list parsing from DomainList
|
|
- [x] Session UUID generation and management
|
|
- [x] Protocol signature verification (MD5)
|
|
- [x] EntityQuery targeting (entity-server or domain fallback)
|
|
- [x] Domain address parsing (host:port/position/orientation)
|
|
- [x] Anonymous connection mode (fully functional)
|
|
- [x] Keep-alive ping mechanism
|
|
- [x] 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](OVERTE_AUTH.md) for OAuth implementation details.**
|
|
|
|
## Testing
|
|
|
|
### Test with Local Domain (Recommended)
|
|
```bash
|
|
# 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
|
|
```bash
|
|
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
|
|
```bash
|
|
./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:
|
|
```cpp
|
|
// 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)
|
|
```cpp
|
|
// 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)
|
|
```cpp
|
|
// 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
|
|
```cpp
|
|
// 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:
|
|
|
|
```cpp
|
|
// 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:
|
|
|
|
```cpp
|
|
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:
|
|
```cpp
|
|
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:
|
|
```bash
|
|
# 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)
|