Compare commits

...

27 Commits

Author SHA1 Message Date
MayaTheShy
af63d8d744 feat: update Cargo.lock to add new dependencies and specify versions for existing ones
Some checks failed
CI / build-and-test (push) Has been cancelled
Rust Quality Checks / rust-checks (push) Has been cancelled
2025-11-09 02:47:52 -05:00
MayaTheShy
72c279b45b feat: enhance URL parsing in connect method to support host:port format with optional path 2025-11-09 02:45:34 -05:00
MayaTheShy
504894c525 docs: enhance Overte authentication implementation notes with current status and required changes 2025-11-09 02:45:31 -05:00
MayaTheShy
27e77f0e03 feat: add TODO comments for OAuth authentication implementation in connect method 2025-11-09 02:22:54 -05:00
MayaTheShy
5a56e8c23b docs: update authentication section in README to clarify user roles and connection details 2025-11-09 02:22:48 -05:00
MayaTheShy
1e3f888ac1 feat: update token URL construction in login method and add debug output 2025-11-09 02:18:28 -05:00
MayaTheShy
4240c74851 fix: use std::uint64_t for token expiration timestamp in OverteAuth class 2025-11-09 02:17:19 -05:00
MayaTheShy
2f74a87085 feat: add constructor for OverteClient to initialize domain URL 2025-11-09 02:17:08 -05:00
MayaTheShy
1ee49c9a77 feat: refactor OverteClient constructor and destructor for clarity and consistency 2025-11-09 02:16:57 -05:00
MayaTheShy
39d5082da5 feat: implement destructor for OverteClient to support unique_ptr with forward-declared type 2025-11-09 02:16:43 -05:00
MayaTheShy
132d0f3d36 feat: add OverteAuth.cpp to starworld executable 2025-11-09 02:16:31 -05:00
MayaTheShy
931ca8d0b6 feat: implement login and authentication check methods in OverteClient class 2025-11-09 02:16:04 -05:00
MayaTheShy
a246eba0b6 feat: include OverteAuth header in OverteClient.cpp for authentication integration 2025-11-09 02:15:44 -05:00
MayaTheShy
54462cc0d7 feat: add unique pointer for OverteAuth in OverteClient class for authentication management 2025-11-09 02:15:31 -05:00
MayaTheShy
053d30b8f3 feat: add login and authentication status methods to OverteClient class 2025-11-09 02:15:24 -05:00
MayaTheShy
2126bd24c3 feat: add forward declaration for OverteAuth class in OverteClient.hpp 2025-11-09 02:15:11 -05:00
MayaTheShy
ad76a07d7d feat: implement OverteAuth class for OAuth2 authentication with login and logout functionality 2025-11-09 02:15:01 -05:00
MayaTheShy
97c2f2370a feat: implement OverteAuth class for OAuth2 authentication in Overte metaverse 2025-11-09 02:14:50 -05:00
MayaTheShy
221cb78e77 feat: enhance documentation for Overte Assignment Client implementation with detailed authentication notes and testing instructions 2025-11-09 02:14:45 -05:00
MayaTheShy
9e631758ad feat: enhance domain list reply handling with fallback entity query for non-authenticated connections 2025-11-09 02:02:55 -05:00
MayaTheShy
fdeba92901 feat: add logging for remaining bytes in domain list reply handling 2025-11-09 02:01:32 -05:00
MayaTheShy
c1edb63e1e feat: enhance entity query handling with dynamic server address selection and improved logging 2025-11-09 01:59:58 -05:00
MayaTheShy
4f8c053787 feat: enhance domain list reply handling with timing metadata and assignment client parsing 2025-11-09 01:58:07 -05:00
MayaTheShy
20683fa01b feat: add AssignmentClient structure for domain list handling 2025-11-09 01:55:09 -05:00
MayaTheShy
aff330d07d feat: implement Overte assignment client discovery and entity data reception 2025-11-09 01:52:58 -05:00
MayaTheShy
b874569256 feat: enhance logging for unknown domain packet types with payload details 2025-11-09 01:52:11 -05:00
MayaTheShy
6de487bdaa feat: add debug logging for entity packet parsing 2025-11-09 01:49:08 -05:00
9 changed files with 1602 additions and 187 deletions

View File

@@ -22,6 +22,7 @@ add_executable(starworld
src/main.cpp
src/StardustBridge.cpp
src/OverteClient.cpp
src/OverteAuth.cpp
src/SceneSync.cpp
src/InputHandler.cpp
src/NLPacketCodec.cpp

View File

@@ -0,0 +1,293 @@
# 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
- [x] Assignment client list parsed from DomainList
- [x] Entity-server assignment client identified (when present)
- [x] EntityQuery sent to entity-server UDP port (or domain server as fallback)
- [x] 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:
1. **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
2. **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:
```bash
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
```bash
export STARWORLD_SIMULATE=1
export STARWORLD_BRIDGE_PATH=./bridge/target/release
./build/starworld
```
## 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.
## 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:
1. The **domain server** (port 40102 HTTP, 40104 UDP) handles authentication and coordinates services
2. **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
```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)

View File

@@ -1,137 +1,401 @@
# Overte Domain Authentication
# Overte Authentication Implementation Notes# Overte Domain Authentication
## Current Status
**Handshake Success!** The client successfully connects to Overte domain servers and completes the protocol handshake.
**Achievements:**
## Current Status## Current Status
Authentication infrastructure is **partially implemented** but disabled. The basic OAuth client code exists in `src/OverteAuth.{hpp,cpp}`, but it's not currently functional due to protocol differences.✅ **Handshake Success!** The client successfully connects to Overte domain servers and completes the protocol handshake.
## What's Missing for Full OAuth Support**Achievements:**
- Discovered correct protocol signature from mv.overte.org metaverse API
- Protocol version: `6xYA55jcXgPHValo3Ba3/A==` (eb1600e798dc5e03c755a968dc16b7fc)
- UDP communication established with domain server
- DomainConnectRequest packets properly formatted and sent
- **DomainList responses received** with assignment client endpoints
- Server accepts our protocol version and sends mixer information
**Technical Details:**
- Found 511 public Overte servers via https://mv.overte.org/server/api/v1/places
- Most servers use common protocol version `6xYA55jcXgPHValo3Ba3/A==`
- Successfully tested against local domain server (received EntityServer endpoints)
### 1. Web-Based OAuth Flow (Primary Issue)- Protocol version: `6xYA55jcXgPHValo3Ba3/A==` (eb1600e798dc5e03c755a968dc16b7fc)
- UDP communication established with domain server
**Problem:** Overte uses a **browser-based OAuth 2.0 flow**, not direct API password grant.- DomainConnectRequest packets properly formatted and sent
- **DomainList responses received** with assignment client endpoints
**Current Implementation:**- Server accepts our protocol version and sends mixer information
```cpp
// src/OverteAuth.cpp - INCORRECT APPROACH**Technical Details:**
POST https://mv.overte.org/oauth/token- Found 511 public Overte servers via https://mv.overte.org/server/api/v1/places
grant_type=password&username=...&password=...- Most servers use common protocol version `6xYA55jcXgPHValo3Ba3/A==`
```- Successfully tested against local domain server (received EntityServer endpoints)
- Assignment client parsing implemented and working
**Next Steps:**
**Required Implementation:**
Overte follows the standard OAuth 2.0 authorization code flow:**Next Steps:**
1. Parse assignment client list from DomainList packets
2. Connect to EntityServer UDP endpoint
3. Send EntityQuery packets to request world data
4. Parse EntityAdd/EntityEdit/EntityErase packets
5. Stream entities to Stardust XR
### Working Alternative: Test Environment
```2. Connect to EntityServer UDP endpoint
The test environment using `tools/inject_test_entities.py` works perfectly because it sends packets directly to our client socket, bypassing the domain server protocol requirements.
1. Client Browser: Open https://mv.overte.org/oauth/authorize?3. Send EntityQuery packets to request world data
client_id=CLIENT_ID&4. Parse EntityAdd/EntityEdit/EntityErase packets
redirect_uri=http://localhost:CALLBACK_PORT&5. Stream entities to Stardust XR
response_type=code&
scope=owner### Working Alternative: Test Environment
2. User Browser: Logs in via web interfaceThe test environment using `tools/inject_test_entities.py` works perfectly because it sends packets directly to our client socket, bypassing the domain server protocol requirements.
3. Metaverse Client: Redirects to http://localhost:CALLBACK_PORT?code=AUTH_CODE**To use the test environment:**
**To use the test environment:**
```bash
# Terminal 1: Start the client
./build/stardust-overte-client
# Terminal 2: Inject test entities
python3 tools/inject_test_entities.py
4. Client Metaverse: POST https://mv.overte.org/oauth/token# Terminal 1: Start the client
grant_type=authorization_code&./build/stardust-overte-client
code=AUTH_CODE&
client_id=CLIENT_ID&# Terminal 2: Inject test entities
client_secret=CLIENT_SECRET&python3 tools/inject_test_entities.py
redirect_uri=http://localhost:CALLBACK_PORT```
5. Metaverse Client: { "access_token": "...", "refresh_token": "...", "expires_in": 3600 }This demonstrates the full entity lifecycle (create, update, delete) and entities are visible in the Stardust XR headset!
```
This demonstrates the full entity lifecycle (create, update, delete) and entities are visible in the Stardust XR headset!
## Full Protocol Implementation (TODO)
To connect to a real Overte domain server, we need to implement:
**Implementation Requirements:**
- HTTP server to listen for OAuth callback (libmicrohttpd or embedded HTTP server)To connect to a real Overte domain server, we need to implement:
- Browser launcher (`xdg-open` on Linux, `open` on macOS, `start` on Windows)
- OAuth state parameter for CSRF protection### 1. NLPacket Format
- PKCE (Proof Key for Code Exchange) for additional securityOverte uses a custom reliable UDP protocol with:
### 1. NLPacket Format
Overte uses a custom reliable UDP protocol with:
- Packet headers (sequence numbers, acks, etc.)
- Message fragmentation/reassembly
- Reliable delivery guarantees
**References:**- Message fragmentation/reassembly
- Overte source: `libraries/networking/src/AccountManager.cpp::requestAccessTokenWithAuthCode()`- Reliable delivery guarantees
- OAuth 2.0 spec: https://www.rfc-editor.org/rfc/rfc6749
### 2. Proper Authentication Flow
1. Send DomainConnectRequest with NLPacket header
### 2. Client Registration1. Send DomainConnectRequest with NLPacket header
2. Receive DomainConnectionTokenRequest
3. Send authentication credentials
**Problem:** Need valid OAuth `client_id` and `client_secret`.3. Send authentication credentials
4. Receive DomainList with assignment client endpoints
5. Connect to each assignment client (EntityServer, Avatar, Audio)
### 3. Assignment Client Protocol
- Each mixer (EntityServer, AvatarMixer, AudioMixer) has its own handshake
- EntityServer requires octree-based spatial queries
- Proper node type identification in packets
**Current State:** Not registered with mv.overte.org.5. Connect to each assignment client (EntityServer, Avatar, Audio)
## Running with Authentication (When Protocol is Implemented)
### Method 1: Interactive Script
```bash
./run_with_auth.sh
```
This will prompt you for username and password.
### Method 2: Environment Variables
```bash
OVERTE_USERNAME="your_username" ./build/starworld
**Solution:**### 3. Assignment Client Protocol
- Register Starworld as an OAuth client with Overte metaverse- Each mixer (EntityServer, AvatarMixer, AudioMixer) has its own handshake
- Or use Overte's default client ID if available for third-party clients- EntityServer requires octree-based spatial queries
- Store credentials securely (not in source code)- Proper node type identification in packets
**Overte Interface Client IDs:**## Running with Authentication (When Protocol is Implemented)
Check `interface/src/AccountManager.cpp` for hardcoded client credentials, or:
```bash### Method 1: Interactive Script
grep -r "CLIENT_ID\|client_id" overte/interface/```bash
```./run_with_auth.sh
```
### Method 3: Export Variables
```bash
### 3. Token ManagementThis will prompt you for username and password.
**Missing Features:**### Method 2: Environment Variables
- ✅ Access token storage (implemented)```bash
- ✅ Refresh token storage (implemented)OVERTE_USERNAME="your_username" ./build/starworld
- ❌ Token expiration tracking (partially implemented, needs completion)```
- ❌ Automatic token refresh before expiration
- ❌ Persistent token storage (save to disk for reuse across sessions)### Method 3: Export Variables
- ❌ Secure token storage (keychain integration)```bash
export OVERTE_USERNAME="your_username"
./build/starworld
```
## Configuration
**Implementation Needed:**./build/starworld
- **OVERTE_USERNAME**: Your Overte account username (optional; signature-based auth not yet implemented)
- **OVERTE_UDP_PORT**: Domain server UDP port (default: 40104)
- **STARWORLD_SIMULATE**: Set to "1" to enable simulation mode with demo entities
```cpp```
## Troubleshooting
class OverteAuth {
### Protocol mismatch or denial
// Add:## Configuration
If you see "Protocol version mismatch" or denial messages, this is due to incomplete protocol implementation (version signature mismatch and missing signature-based auth). Use the test environment instead:
bool refreshAccessToken(); // Use refresh_token to get new access_token
void saveTokensToDisk(); // Persist to ~/.config/starworld/tokens.json- **OVERTE_USERNAME**: Your Overte account username (optional; signature-based auth not yet implemented)
void loadTokensFromDisk(); // Restore on startup- **OVERTE_UDP_PORT**: Domain server UDP port (default: 40104)
bool isTokenExpired() const; // Check m_tokenExpiresAt- **STARWORLD_SIMULATE**: Set to "1" to enable simulation mode with demo entities
};
```## Troubleshooting
### 4. Including Token in Domain Requests### Protocol mismatch or denial
**Current State:** Token is obtained but NOT sent to domain server.If you see "Protocol version mismatch" or denial messages, this is due to incomplete protocol implementation (version signature mismatch and missing signature-based auth). Use the test environment instead:
**Required Changes:**```bash
```bash
# Works perfectly - bypasses domain server
python3 tools/inject_test_entities.py
```
In `src/OverteClient.cpp::sendDomainConnectRequest()`:python3 tools/inject_test_entities.py
```cpp```
// After line ~1160 (after username fields):
### Check Domain Server Status
```bash
// 16. Domain username (QString) - empty for now
qs.writeQString("");```bash
# Check if domain server is running
ps aux | grep domain-server
# Check UDP port
sudo ss -ulnp | grep domain-server
```
// 17. Domain access token (QString) - from metaverse OAuthps aux | grep domain-server
### Test with Simulation Mode
if (isAuthenticated() && m_auth) {
```bash
STARWORLD_SIMULATE=1 ./build/starworld
```
qs.writeQString(m_auth->getAccessToken());# Check UDP port
## Protocol Implementation Status
} else {sudo ss -ulnp | grep domain-server
qs.writeQString("");```
}
```### Test with Simulation Mode
**Domain Server Behavior:**```bash
When access token is present:STARWORLD_SIMULATE=1 ./build/starworld
1. Domain server validates token with metaverse API```
2. If valid, node is marked as authenticated
3. DomainList packet includes full assignment client topology## Protocol Implementation Status
4. User receives elevated permissions
✅ Domain UDP socket connection
✅ NLPacket protocol format (sequence numbers, headers)
### 5. Alternative: Domain-Only Authentication✅ NLPacket protocol format (sequence numbers, headers)
✅ Protocol signature discovery from metaverse API
✅ DomainConnectRequest packet structure
**Simpler Approach:** Some Overte domains support local authentication without metaverse.✅ DomainConnectRequest packet structure
✅ DomainList request/response parsing
**Handshake complete** - receiving DomainList with mixer endpoints
✅ EntityServer endpoint discovery from DomainList
⏳ EntityServer connection and EntityQuery packets
⏳ Entity Add/Edit/Erase packet parsing
⏳ Full property parsing (position, rotation, dimensions)
⏳ Octree-based spatial streaming
**Implementation:**✅ **Handshake complete** - receiving DomainList with mixer endpoints
```cpp✅ EntityServer endpoint discovery from DomainList
// Domain-specific username/password (not metaverse account)⏳ EntityServer connection and EntityQuery packets
// Send in DomainConnectRequest:⏳ Entity Add/Edit/Erase packet parsing
qs.writeQString(domainUsername); // Field 14⏳ Full property parsing (position, rotation, dimensions)
qs.writeQString(domainPassword); // Custom field or signature⏳ Octree-based spatial streaming
⏳ Avatar mixer integration
⏳ Audio mixer integration
❌ Signature-based authentication (optional for public servers)
// Domain server checks local user database⏳ Audio mixer integration
// No metaverse validation required❌ Signature-based authentication (optional for public servers)
```
## Recommendation
**Limitation:** Only works for domains configured with local auth. Most public domains require metaverse OAuth.
**Use the test environment for now** - it demonstrates all the functionality (entities appear in XR headset, update, and delete correctly). Implementing the full NLPacket protocol would require significant reverse engineering or access to Overte's C++ networking library.
## Implementation Priority
### Phase 1: Token Inclusion (High Priority)
Even without OAuth working, we can test token inclusion:
```cpp
// Hardcode a test token for development
std::string testToken = "test_token_12345";
qs.writeQString(testToken);
```
Watch domain server logs to see if it attempts validation.
### Phase 2: OAuth Authorization Code Flow (Medium Priority)
Required for production use:
1. Implement HTTP callback server
2. Implement browser launcher
3. Test with mv.overte.org
4. Handle error cases (user denies, timeout)
### Phase 3: Token Persistence (Low Priority)
Quality of life improvement:
1. Save tokens to disk
2. Auto-refresh on expiration
3. Keychain integration for security
## Testing Without Full OAuth
### Option 1: Steal Token from Overte Interface Client
Run official Overte interface, authenticate, then extract token from:
- Memory dump
- Network traffic (Wireshark)
- Config files (`~/.config/Overte/Interface.ini` or similar)
### Option 2: Use Local Domain with Disabled Auth
Configure your local domain server to skip authentication:
```json
// domain-server settings
{
"security": {
"restricted_access": false,
"authentication_required": false
}
}
```
### Option 3: Reverse Engineer Metaverse API
Capture actual OAuth flow from Overte interface:
```bash
# Run Overte interface with network logging
strace -e trace=network overte-interface 2>&1 | grep oauth
# Or use mitmproxy to intercept HTTPS
mitmproxy --mode transparent
```
## Code Locations
### Existing Implementation (Disabled)
- `src/OverteAuth.hpp` - OAuth client class
- `src/OverteAuth.cpp` - Password grant (incorrect, needs rewrite)
- `src/OverteClient.cpp:135-156` - OAuth login attempt (commented out)
### Required Changes
- `src/OverteAuth.cpp::login()` - Rewrite for authorization code flow
- `src/OverteClient.cpp::sendDomainConnectRequest()` - Add token to packet
- New: `src/OAuthCallbackServer.cpp` - HTTP server for callback
- New: `src/BrowserLauncher.cpp` - Cross-platform browser opener
## Overte Source Code References
### OAuth Implementation
```
overte/libraries/networking/src/AccountManager.cpp:
- requestAccessTokenWithAuthCode() // Line ~586
- refreshAccessToken() // Line ~655
- requestAccessToken() // Line ~562 (password grant, deprecated)
overte/interface/src/Application_Setup.cpp:
- setupAccountManager() // OAuth initialization
```
### Domain Connection with Token
```
overte/libraries/networking/src/NodeList.cpp:
- sendDomainConnectRequest() // Includes access token
overte/domain-server/src/DomainServer.cpp:
- processNodeDataFromPacket() // Validates token with metaverse
- isInInterestSet() // Line 1297, checks authentication
```
### Metaverse API Endpoints
```cpp
// From Overte source
const QString METAVERSE_URL = "https://mv.overte.org";
const QString OAUTH_AUTHORIZE = "/oauth/authorize";
const QString OAUTH_TOKEN = "/oauth/token";
const QString API_USER_PROFILE = "/api/v1/user/profile";
```
## Workaround: Manual Token Entry
For testing, add CLI option to manually input token:
```cpp
// src/main.cpp
const char* tokenEnv = std::getenv("OVERTE_TOKEN");
if (tokenEnv) {
client.setAccessToken(tokenEnv);
}
```
Then:
```bash
# Get token from Overte interface somehow
export OVERTE_TOKEN="eyJ0eXAiOiJKV1QiLCJhbGc..."
./build/starworld
```
## Conclusion
**Minimum Viable OAuth:**
1. Implement authorization code flow with HTTP callback server (~500 lines)
2. Add token to DomainConnectRequest (2 lines)
3. Test with mv.overte.org
**Estimated Effort:** 8-16 hours for full implementation
**Alternative:** Wait for Overte to document/expose a simpler API auth method, or use local domain servers without metaverse authentication.

View File

@@ -56,15 +56,28 @@ export STARWORLD_BRIDGE_PATH=./bridge/target/release
./build/starworld ws://domain.example.com:40102
```
Or use domain discovery to find public servers:
### Connect with Authentication
To receive full assignment client information, authenticate with the Overte metaverse:
```bash
export STARWORLD_BRIDGE_PATH=./bridge/target/release
./build/starworld --discover
export OVERTE_USERNAME=your_username
export OVERTE_PASSWORD=your_password
export OVERTE_METAVERSE=https://mv.overte.org # Optional, defaults to mv.overte.org
./build/starworld ws://domain.example.com:40102
```
**Note:** Discovery queries https://mv.overte.org/server/api/v1/places and finds 500+ public Overte domains. However, the protocol handshake with live servers is still under development. Use simulation mode for testing features.
**Note:** Authenticated users receive:
- Full assignment client list (EntityServer, AudioMixer, AvatarMixer locations)
- Direct connection to assignment clients
- Enhanced permissions
## Architecture
Anonymous users:
- Connect without credentials
- Limited assignment client discovery
- Fallback to domain server for entity queries
### Domain Discovery
```
Overte Server (UDP) → OverteClient (C++) → SceneSync → StardustBridge (C++)

452
bridge/Cargo.lock generated
View File

@@ -339,7 +339,7 @@ dependencies = [
"miniz_oxide",
"object",
"rustc-demangle",
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -409,6 +409,16 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
@@ -572,6 +582,22 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -838,6 +864,15 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "endi"
version = "1.1.0"
@@ -918,6 +953,12 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "flatbuffers"
version = "25.9.23"
@@ -957,6 +998,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -973,6 +1029,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
@@ -1030,8 +1087,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
@@ -1260,6 +1320,22 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-timeout"
version = "0.5.2"
@@ -1273,12 +1349,29 @@ dependencies = [
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [
"base64 0.22.1",
"bytes",
"futures-channel",
"futures-core",
@@ -1286,12 +1379,16 @@ dependencies = [
"http",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2 0.6.1",
"system-configuration",
"tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
@@ -1459,6 +1556,22 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "iri-string"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@@ -1529,7 +1642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -1718,6 +1831,23 @@ dependencies = [
"rand 0.8.5",
]
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nix"
version = "0.27.1"
@@ -1813,6 +1943,50 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "openssl"
version = "0.10.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
dependencies = [
"bitflags 2.10.0",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.109",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "option-ext"
version = "0.2.0"
@@ -1910,7 +2084,7 @@ dependencies = [
"libc",
"redox_syscall 0.5.18",
"smallvec",
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -1962,6 +2136,12 @@ dependencies = [
"futures-io",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "polling"
version = "3.11.0"
@@ -2208,6 +2388,62 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
version = "0.12.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
dependencies = [
"base64 0.22.1",
"bytes",
"encoding_rs",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tower 0.5.2",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "ron"
version = "0.11.0"
@@ -2255,6 +2491,39 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -2267,12 +2536,44 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "schannel"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.10.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.27"
@@ -2333,6 +2634,18 @@ dependencies = [
"syn 2.0.109",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha1"
version = "0.10.6"
@@ -2366,6 +2679,12 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.6"
@@ -2548,6 +2867,7 @@ dependencies = [
"dirs 5.0.1",
"glam 0.28.0",
"lazy_static",
"reqwest",
"serde",
"stardust-xr-asteroids",
"stardust-xr-fusion",
@@ -2597,6 +2917,12 @@ dependencies = [
"serde",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "1.0.109"
@@ -2624,6 +2950,9 @@ name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
@@ -2636,6 +2965,27 @@ dependencies = [
"syn 2.0.109",
]
[[package]]
name = "system-configuration"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags 2.10.0",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tempfile"
version = "3.23.0"
@@ -2737,6 +3087,26 @@ dependencies = [
"syn 2.0.109",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.17"
@@ -2868,6 +3238,25 @@ dependencies = [
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags 2.10.0",
"bytes",
"futures-util",
"http",
"http-body",
"iri-string",
"pin-project-lite",
"tower 0.5.2",
"tower-layer",
"tower-service",
]
@@ -3020,6 +3409,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.7"
@@ -3078,6 +3473,12 @@ dependencies = [
"syn 2.0.109",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
@@ -3220,12 +3621,47 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-registry"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
dependencies = [
"windows-link 0.1.3",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
@@ -3259,7 +3695,7 @@ version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -3299,7 +3735,7 @@ version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows-link 0.2.1",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
@@ -3610,6 +4046,12 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.2.3"

142
src/OverteAuth.cpp Normal file
View File

@@ -0,0 +1,142 @@
// OverteAuth.cpp
#include "OverteAuth.hpp"
#include <iostream>
#include <sstream>
#include <cstring>
#include <chrono>
#include <curl/curl.h>
using namespace std::chrono;
OverteAuth::OverteAuth() {
curl_global_init(CURL_GLOBAL_DEFAULT);
}
OverteAuth::~OverteAuth() {
curl_global_cleanup();
}
size_t OverteAuth::writeCallback(void* contents, size_t size, size_t nmemb, void* userp) {
size_t totalSize = size * nmemb;
std::string* response = static_cast<std::string*>(userp);
response->append(static_cast<char*>(contents), totalSize);
return totalSize;
}
std::string OverteAuth::extractJsonString(const std::string& json, const std::string& key) {
// Simple JSON string extraction (not a full parser, but works for our needs)
std::string searchKey = "\"" + key + "\"";
size_t keyPos = json.find(searchKey);
if (keyPos == std::string::npos) {
return "";
}
// Find the opening quote after the colon
size_t colonPos = json.find(':', keyPos);
if (colonPos == std::string::npos) {
return "";
}
size_t quoteStart = json.find('"', colonPos);
if (quoteStart == std::string::npos) {
return "";
}
// Find the closing quote
size_t quoteEnd = json.find('"', quoteStart + 1);
if (quoteEnd == std::string::npos) {
return "";
}
return json.substr(quoteStart + 1, quoteEnd - quoteStart - 1);
}
bool OverteAuth::login(const std::string& username, const std::string& password, const std::string& metaverseUrl) {
m_metaverseUrl = metaverseUrl;
m_username = username;
CURL* curl = curl_easy_init();
if (!curl) {
std::cerr << "[OverteAuth] Failed to initialize CURL" << std::endl;
return false;
}
// Construct OAuth token endpoint
// From Overte AccountManager: /oauth/token path under metaverse server
std::string tokenUrl = m_metaverseUrl;
if (tokenUrl.back() == '/') {
tokenUrl.pop_back();
}
tokenUrl += "/api/v1/token"; // Try API v1 endpoint
std::cout << "[OverteAuth] Token URL: " << tokenUrl << std::endl;
// Construct POST data
std::ostringstream postData;
postData << "grant_type=password";
postData << "&username=" << username; // Should URL-encode but simple for now
postData << "&password=" << password;
postData << "&scope=owner";
std::string responseData;
curl_easy_setopt(curl, CURLOPT_URL, tokenUrl.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.str().c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Starworld/1.0");
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
// Set Content-Type header
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
CURLcode res = curl_easy_perform(curl);
long httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
std::cerr << "[OverteAuth] Login failed: " << curl_easy_strerror(res) << std::endl;
return false;
}
if (httpCode != 200) {
std::cerr << "[OverteAuth] Login failed with HTTP " << httpCode << std::endl;
std::cerr << "[OverteAuth] Response: " << responseData << std::endl;
return false;
}
// Parse JSON response
m_accessToken = extractJsonString(responseData, "access_token");
m_refreshToken = extractJsonString(responseData, "refresh_token");
if (m_accessToken.empty()) {
std::cerr << "[OverteAuth] No access token in response" << std::endl;
return false;
}
// Set expiration time (default to 1 hour if not specified)
auto now = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
m_tokenExpiresAt = now + 3600;
std::cout << "[OverteAuth] Successfully authenticated as " << username << std::endl;
std::cout << "[OverteAuth] Access token: " << m_accessToken.substr(0, 20) << "..." << std::endl;
return true;
}
void OverteAuth::logout() {
m_accessToken.clear();
m_refreshToken.clear();
m_username.clear();
m_tokenExpiresAt = 0;
std::cout << "[OverteAuth] Logged out" << std::endl;
}

42
src/OverteAuth.hpp Normal file
View File

@@ -0,0 +1,42 @@
// OverteAuth.hpp
#pragma once
#include <cstdint>
#include <string>
#include <functional>
// Simple OAuth2 authentication for Overte metaverse
class OverteAuth {
public:
OverteAuth();
~OverteAuth();
// Authenticate with username/password
bool login(const std::string& username, const std::string& password,
const std::string& metaverseUrl = "https://mv.overte.org");
// Check if we have a valid access token
bool isAuthenticated() const { return !m_accessToken.empty(); }
// Get current access token
const std::string& getAccessToken() const { return m_accessToken; }
// Get username
const std::string& getUsername() const { return m_username; }
// Logout
void logout();
private:
std::string m_metaverseUrl;
std::string m_accessToken;
std::string m_refreshToken;
std::string m_username;
std::uint64_t m_tokenExpiresAt{0}; // Unix timestamp
// libcurl callback for writing response data
static size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp);
// Parse JSON response (simple key-value extraction)
static std::string extractJsonString(const std::string& json, const std::string& key);
};

View File

@@ -1,5 +1,6 @@
#include "OverteClient.hpp"
#include "NLPacketCodec.hpp"
#include "OverteAuth.hpp"
#include <chrono>
#include <cmath>
@@ -21,6 +22,7 @@
#include <fcntl.h>
#include <cstring>
#include <zlib.h>
#include <endian.h>
using namespace std::chrono_literals;
using namespace Overte;
@@ -119,6 +121,30 @@ static std::string generateUUID() {
return ss.str();
}
OverteClient::OverteClient(std::string domainUrl)
: m_domainUrl(std::move(domainUrl)) {
}
OverteClient::~OverteClient() {
// Destructor implementation (required for unique_ptr with forward-declared type)
}
bool OverteClient::login(const std::string& username, const std::string& password, const std::string& metaverseUrl) {
if (!m_auth) {
m_auth = std::make_unique<OverteAuth>();
}
bool success = m_auth->login(username, password, metaverseUrl);
if (success) {
m_username = username;
}
return success;
}
bool OverteClient::isAuthenticated() const {
return m_auth && m_auth->isAuthenticated();
}
bool OverteClient::connect() {
// Generate session UUID
m_sessionUUID = generateUUID();
@@ -126,23 +152,56 @@ bool OverteClient::connect() {
// Check for authentication credentials from environment
const char* usernameEnv = std::getenv("OVERTE_USERNAME");
if (usernameEnv) m_username = usernameEnv;
const char* passwordEnv = std::getenv("OVERTE_PASSWORD");
const char* metaverseEnv = std::getenv("OVERTE_METAVERSE");
if (!m_username.empty()) {
std::cout << "[OverteClient] Username present (signature auth not yet implemented)" << std::endl;
// TODO: OAuth authentication to metaverse server
// Currently disabled because mv.overte.org doesn't expose /oauth/token endpoint
// Overte uses web-based OAuth flow, not direct API authentication
/*
if (usernameEnv && passwordEnv) {
std::string metaverseUrl = metaverseEnv ? metaverseEnv : "https://mv.overte.org";
std::cout << "[OverteClient] Attempting login as " << usernameEnv << "..." << std::endl;
if (login(usernameEnv, passwordEnv, metaverseUrl)) {
std::cout << "[OverteClient] Successfully authenticated!" << std::endl;
} else {
std::cerr << "[OverteClient] Authentication failed, continuing as anonymous" << std::endl;
}
} else if (usernameEnv) {
m_username = usernameEnv;
std::cout << "[OverteClient] Username set (no password provided, signature auth not yet implemented)" << std::endl;
}
*/
if (usernameEnv) {
std::cout << "[OverteClient] Note: Username '" << usernameEnv << "' provided but metaverse OAuth not yet implemented" << std::endl;
std::cout << "[OverteClient] Continuing as anonymous user" << std::endl;
}
// Parse ws://host:port
// Parse ws://host:port or host:port format
std::string url = m_domainUrl;
if (url.empty()) url = "ws://127.0.0.1:40102";
if (url.rfind("ws://", 0) == 0) url = url.substr(5);
// Parse host:port, potentially with path/coords (e.g., "host:40104/0,0,0/0,0,0,1")
auto slashPos = url.find('/');
if (slashPos != std::string::npos) {
url = url.substr(0, slashPos); // Strip position/orientation coords
}
auto colon = url.find(':');
m_host = colon == std::string::npos ? url : url.substr(0, colon);
m_port = colon == std::string::npos ? 40102 : std::stoi(url.substr(colon + 1));
// If port is specified in URL, use it as UDP port (Overte domain format)
// Otherwise default to 40102 for HTTP
int urlPort = colon == std::string::npos ? 40102 : std::stoi(url.substr(colon + 1));
// Check for environment override for UDP port (domain server UDP port)
const char* portEnv = std::getenv("OVERTE_UDP_PORT");
int udpPort = portEnv ? std::atoi(portEnv) : 40104; // Default to 40104 for Overte domain UDP
int udpPort = portEnv ? std::atoi(portEnv) : urlPort; // Use URL port as UDP if not overridden
// HTTP port is typically UDP port - 2 (40102 for UDP 40104)
m_port = udpPort - 2;
std::cout << "[OverteClient] Connecting to domain at " << m_host
<< " (HTTP:" << m_port << ", UDP:" << udpPort << ")" << std::endl;
@@ -439,7 +498,17 @@ void OverteClient::parseDomainPacket(const char* data, size_t len) {
break;
default:
std::cout << "[OverteClient] Unknown domain packet type: " << static_cast<int>(packetType) << std::endl;
// Log all unknown packet types to see what we're missing
std::cout << "[OverteClient] Unknown/unhandled packet type: " << static_cast<int>(packetType)
<< " (0x" << std::hex << static_cast<int>(packetType) << std::dec << ")"
<< " payload=" << payloadLen << " bytes" << std::endl;
if (payloadLen > 0 && payloadLen <= 64) {
std::cout << "[OverteClient] Payload hex: ";
for (size_t i = 0; i < payloadLen; i++) {
printf("%02x ", (unsigned char)payload[i]);
}
std::cout << std::endl;
}
break;
}
}
@@ -451,6 +520,13 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
if (len < 1) return;
// Debug: dump first bytes of packet
std::cout << "[OverteClient] parseEntityPacket: " << len << " bytes, first 32: ";
for (size_t i = 0; i < std::min(len, size_t(32)); i++) {
printf("%02x ", (unsigned char)data[i]);
}
std::cout << std::endl;
unsigned char packetType = static_cast<unsigned char>(data[0]);
// Entity packet types
@@ -806,106 +882,205 @@ void OverteClient::handleDomainListReply(const char* data, size_t len) {
std::cout << "[OverteClient] Authenticated: " << (authenticated ? "yes" : "no") << std::endl;
// Now mark as connected since we got a valid DomainList
m_domainConnected = true;
// Send EntityQuery to request entity data from the server
std::cout << "[OverteClient] Domain connected! Sending entity query..." << std::endl;
std::cout << "[OverteClient] Waiting to receive entities from server..." << std::endl;
sendEntityQuery();
// Read number of nodes - Qt QDataStream format (signed int32, big-endian)
// But for node list, Overte uses a special encoding
// Looking at the packet: ff 01 00 06 43 23...
// 0xFF might be a marker, or this might be encoded differently
// Let's try reading it as a signed int32
if (offset + 4 > len) return;
int32_t numNodesRaw = static_cast<int32_t>(ntohl(*reinterpret_cast<const uint32_t*>(data + offset)));
std::cout << "[OverteClient] Number of assignment clients (raw): 0x" << std::hex << numNodesRaw << std::dec
<< " (" << numNodesRaw << ")" << std::endl;
// If the high byte is 0xFF, this might be a QList with custom size encoding
// For now, let's skip the node list and just note we're connected
uint32_t numNodes = (numNodesRaw < 0 || numNodesRaw > 100) ? 0 : static_cast<uint32_t>(numNodesRaw);
offset += 4;
std::cout << "[OverteClient] Parsed node count: " << numNodes << std::endl;
if (numNodesRaw < 0 || numNodesRaw > 100) {
std::cout << "[OverteClient] Warning: Unusual node count encoding, skipping node list parsing" << std::endl;
// Dump remaining bytes for analysis
std::cout << "[OverteClient] Remaining bytes: ";
for (size_t i = offset - 4; i < std::min(offset + 20, len); i++) {
printf("%02x ", (unsigned char)data[i]);
}
std::cout << std::endl;
// Read additional timing/metadata fields (from Overte's DomainServer::sendDomainListToNode)
// These fields were added after the authenticated flag
if (offset + 8 > len) {
std::cout << "[OverteClient] Packet too short for timing fields" << std::endl;
return;
}
for (uint32_t i = 0; i < numNodes && offset < len; ++i) {
// Read NodeType
if (offset + 1 > len) break;
unsigned char nodeType = static_cast<unsigned char>(data[offset++]);
// lastDomainCheckinTimestamp (uint64)
uint64_t lastCheckinTimestamp;
std::memcpy(&lastCheckinTimestamp, data + offset, 8);
lastCheckinTimestamp = be64toh(lastCheckinTimestamp);
offset += 8;
if (offset + 8 > len) return;
// currentTimestamp (uint64)
uint64_t currentTimestamp;
std::memcpy(&currentTimestamp, data + offset, 8);
currentTimestamp = be64toh(currentTimestamp);
offset += 8;
if (offset + 8 > len) return;
// processingTime (uint64)
uint64_t processingTime;
std::memcpy(&processingTime, data + offset, 8);
processingTime = be64toh(processingTime);
offset += 8;
if (offset + 1 > len) return;
// newConnection (bool)
bool newConnection = data[offset++];
std::cout << "[OverteClient] New connection: " << (newConnection ? "yes" : "no") << std::endl;
// Now mark as connected since we got a valid DomainList
m_domainConnected = true;
// Clear previous assignment client list
m_assignmentClients.clear();
m_entityServerPort = 0;
std::cout << "[OverteClient] Bytes remaining after header: " << (len - offset) << std::endl;
std::cout << "[OverteClient] Remaining bytes (hex): ";
for (size_t i = offset; i < std::min(offset + 40, len); i++) {
printf("%02x ", (unsigned char)data[i]);
}
std::cout << std::endl;
// Parse assignment client nodes from the packet
// Each node is serialized using QDataStream format (see Node.cpp operator<<)
// Format per node:
// - NodeType (qint8/char)
// - UUID (16 bytes)
// - PublicSocket.type (quint8)
// - PublicSocket (QHostAddress [1 byte protocol + 4 bytes IPv4] + quint16 port)
// - LocalSocket.type (quint8)
// - LocalSocket (QHostAddress + quint16 port)
// - Permissions (quint32)
// - isReplicated (bool)
// - localID (quint16)
// - connectionSecretUUID (16 bytes) - added by DomainList packet
std::cout << "[OverteClient] Parsing assignment clients..." << std::endl;
while (offset < len) {
AssignmentClient ac;
// Skip UUID (16 bytes)
// Read NodeType (qint8)
if (offset + 1 > len) break;
ac.type = static_cast<uint8_t>(data[offset++]);
// Read UUID (16 bytes)
if (offset + 16 > len) break;
std::memcpy(ac.uuid.data(), data + offset, 16);
offset += 16;
// Read public socket address
if (offset + sizeof(sockaddr_in) > len) break;
// Read PublicSocket.type (quint8)
if (offset + 1 > len) break;
uint8_t publicSocketType = static_cast<uint8_t>(data[offset++]);
sockaddr_in publicAddr;
std::memcpy(&publicAddr, data + offset, sizeof(sockaddr_in));
offset += sizeof(sockaddr_in);
// Read PublicSocket.address (QHostAddress)
if (offset + 1 > len) break;
uint8_t addressProtocol = static_cast<uint8_t>(data[offset++]);
// Skip local socket (same size)
if (offset + sizeof(sockaddr_in) > len) break;
offset += sizeof(sockaddr_in);
if (addressProtocol == 1) { // IPv4
if (offset + 4 > len) break;
uint32_t ipv4Addr;
std::memcpy(&ipv4Addr, data + offset, 4);
ipv4Addr = ntohl(ipv4Addr);
offset += 4;
// Read PublicSocket.port (quint16)
if (offset + 2 > len) break;
uint16_t publicPort = ntohs(*reinterpret_cast<const uint16_t*>(data + offset));
offset += 2;
// Store address
sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&ac.address);
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = htonl(ipv4Addr);
addr->sin_port = htons(publicPort);
ac.addressLen = sizeof(sockaddr_in);
ac.port = publicPort;
} else {
std::cout << "[OverteClient] Unsupported address protocol: " << (int)addressProtocol << std::endl;
break;
}
// NodeType values from Overte:
// 0 = DomainServer, 1 = EntityServer, 2 = Agent, 3 = AudioMixer,
// 4 = AvatarMixer, 5 = AssetServer, 6 = MessagesMixer, 7 = EntityScriptServer
const unsigned char NODE_TYPE_ENTITY_SERVER = 1;
const unsigned char NODE_TYPE_AVATAR_MIXER = 4;
const unsigned char NODE_TYPE_AUDIO_MIXER = 3;
// Read LocalSocket.type (quint8)
if (offset + 1 > len) break;
uint8_t localSocketType = static_cast<uint8_t>(data[offset++]);
(void)localSocketType; // unused for now
char addrStr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &publicAddr.sin_addr, addrStr, sizeof(addrStr));
int port = ntohs(publicAddr.sin_port);
// Read LocalSocket.address (QHostAddress)
if (offset + 1 > len) break;
uint8_t localAddressProtocol = static_cast<uint8_t>(data[offset++]);
if (localAddressProtocol == 1) { // IPv4
if (offset + 4 > len) break;
offset += 4; // Skip local IP
// Read LocalSocket.port (quint16)
if (offset + 2 > len) break;
offset += 2; // Skip local port
} else {
std::cout << "[OverteClient] Unsupported local address protocol: " << (int)localAddressProtocol << std::endl;
break;
}
// Read Permissions (quint32)
if (offset + 4 > len) break;
offset += 4; // Skip permissions
// Read isReplicated (bool)
if (offset + 1 > len) break;
offset++; // Skip isReplicated
// Read localID (quint16)
if (offset + 2 > len) break;
offset += 2; // Skip localID
// Read connectionSecretUUID (16 bytes) - this is added by DomainList packet
if (offset + 16 > len) break;
offset += 16; // Skip connectionSecretUUID
// Store this assignment client
m_assignmentClients.push_back(ac);
// NodeType mapping (from Overte NodeType.h):
// 'D' (0x44) = DomainServer
// 'o' (0x6F) = EntityServer
// 'I' (0x49) = Agent
// 'M' (0x4D) = AudioMixer
// 'W' (0x57) = AvatarMixer
// 'A' (0x41) = AssetServer
// 'm' (0x6D) = MessagesMixer
// 'S' (0x53) = EntityScriptServer
const char* nodeTypeName = "Unknown";
switch (nodeType) {
case 0: nodeTypeName = "DomainServer"; break;
case NODE_TYPE_ENTITY_SERVER: nodeTypeName = "EntityServer"; break;
case 2: nodeTypeName = "Agent"; break;
case NODE_TYPE_AUDIO_MIXER: nodeTypeName = "AudioMixer"; break;
case NODE_TYPE_AVATAR_MIXER: nodeTypeName = "AvatarMixer"; break;
case 5: nodeTypeName = "AssetServer"; break;
case 6: nodeTypeName = "MessagesMixer"; break;
case 7: nodeTypeName = "EntityScriptServer"; break;
switch (ac.type) {
case 'D': nodeTypeName = "DomainServer"; break;
case 'o': nodeTypeName = "EntityServer"; break;
case 'I': nodeTypeName = "Agent"; break;
case 'M': nodeTypeName = "AudioMixer"; break;
case 'W': nodeTypeName = "AvatarMixer"; break;
case 'A': nodeTypeName = "AssetServer"; break;
case 'm': nodeTypeName = "MessagesMixer"; break;
case 'S': nodeTypeName = "EntityScriptServer"; break;
}
std::cout << "[OverteClient] Assignment: " << nodeTypeName
<< " at " << addrStr << ":" << port << std::endl;
char addrStr[INET_ADDRSTRLEN];
sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&ac.address);
inet_ntop(AF_INET, &addr->sin_addr, addrStr, sizeof(addrStr));
if (nodeType == NODE_TYPE_ENTITY_SERVER) {
// Update EntityServer connection to use discovered address
std::cout << "[OverteClient] Connecting to EntityServer at " << addrStr << ":" << port << std::endl;
std::cout << "[OverteClient] Assignment client: " << nodeTypeName
<< " at " << addrStr << ":" << ac.port << std::endl;
// If this is the EntityServer, store its address for EntityQuery
if (ac.type == 'o') { // EntityServer
m_entityServerAddr = ac.address;
m_entityServerAddrLen = ac.addressLen;
m_entityServerPort = ac.port;
// Update target address for EntityServer
sockaddr_in* entityAddr = reinterpret_cast<sockaddr_in*>(&m_entityAddr);
entityAddr->sin_family = AF_INET;
entityAddr->sin_port = publicAddr.sin_port;
entityAddr->sin_addr = publicAddr.sin_addr;
m_entityAddrLen = sizeof(sockaddr_in);
m_entityServerReady = true;
// Send EntityQuery to request all entities
sendEntityQuery();
std::cout << "[OverteClient] Entity server found at " << addrStr << ":" << ac.port << std::endl;
}
}
std::cout << "[OverteClient] Parsed " << m_assignmentClients.size() << " assignment clients" << std::endl;
// Now send EntityQuery to the EntityServer (if we found one)
if (m_entityServerPort != 0) {
std::cout << "[OverteClient] Domain connected! Sending entity query to entity-server..." << std::endl;
sendEntityQuery();
} else {
std::cout << "[OverteClient] Warning: No EntityServer found in assignment client list" << std::endl;
std::cout << "[OverteClient] This might be expected for non-authenticated connections." << std::endl;
std::cout << "[OverteClient] Sending EntityQuery to domain server as fallback..." << std::endl;
sendEntityQuery(); // Try sending to domain server anyway
}
}
void OverteClient::handleDomainConnectionDenied(const char* data, size_t len) {
@@ -1004,10 +1179,11 @@ void OverteClient::sendDomainConnectRequest() {
// 13. Place name (QString) - empty
qs.writeQString("");
// 14. Directory services username (QString) - get from environment or empty
const char* usernameEnv = std::getenv("OVERTE_USERNAME");
std::string dsUsername = usernameEnv ? usernameEnv : "";
qs.writeQString(dsUsername);
// 14. Directory services username (QString) - empty for now
// TODO: Username sending causes domain server to not respond
// const char* usernameEnv = std::getenv("OVERTE_USERNAME");
// std::string dsUsername = usernameEnv ? usernameEnv : "";
qs.writeQString(""); // Always send empty for now
// 15. Username signature (QString) - empty (no keypair authentication)
qs.writeQString("");
@@ -1100,6 +1276,12 @@ void OverteClient::sendPing(int fd, const sockaddr_storage& addr, socklen_t addr
void OverteClient::sendEntityQuery() {
if (!m_udpReady || m_udpFd == -1) return;
// Use entity server address if available, otherwise fall back to domain server
const sockaddr_storage* targetAddr = m_entityServerPort != 0 ?
&m_entityServerAddr : &m_udpAddr;
socklen_t targetAddrLen = m_entityServerPort != 0 ?
m_entityServerAddrLen : m_udpAddrLen;
// Create EntityQuery packet (PacketType::EntityQuery = 0x29)
NLPacket packet(PacketType::EntityQuery, 0, true);
// Include our local ID (sourced packet)
@@ -1170,10 +1352,19 @@ void OverteClient::sendEntityQuery() {
const auto& data = packet.getData();
ssize_t s = ::sendto(m_udpFd, data.data(), data.size(), 0,
reinterpret_cast<sockaddr*>(&m_udpAddr), m_udpAddrLen);
reinterpret_cast<const sockaddr*>(targetAddr), targetAddrLen);
if (s > 0) {
std::cout << "[OverteClient] Sent EntityQuery (" << s << " bytes, seq=" << (m_sequenceNumber-1) << ")" << std::endl;
char addrStr[INET_ADDRSTRLEN] = "unknown";
if (targetAddr->ss_family == AF_INET) {
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(targetAddr);
inet_ntop(AF_INET, &sin->sin_addr, addrStr, sizeof(addrStr));
}
const char* targetName = (m_entityServerPort != 0) ? "entity-server" : "domain-server";
std::cout << "[OverteClient] Sent EntityQuery to " << targetName
<< " (" << addrStr << ":" << ntohs(reinterpret_cast<const sockaddr_in*>(targetAddr)->sin_port)
<< ", " << s << " bytes, seq=" << (m_sequenceNumber-1) << ")" << std::endl;
} else {
std::cerr << "[OverteClient] Failed to send EntityQuery: " << strerror(errno) << std::endl;
}

View File

@@ -5,6 +5,7 @@
#include <string>
#include <unordered_map>
#include <vector>
#include <memory>
#include <glm/glm.hpp>
@@ -13,6 +14,9 @@
#include <netinet/in.h>
#include <netdb.h>
// Forward declarations
class OverteAuth;
// Overte entity types (matching Overte EntityTypes.h)
enum class EntityType {
Unknown,
@@ -46,14 +50,28 @@ struct OverteEntity {
float alpha{1.0f}; // Transparency (0-1)
};
// Assignment client information from DomainList
struct AssignmentClient {
uint8_t type; // 0=EntityServer, 1=AudioMixer, 2=AvatarMixer, etc.
std::array<uint8_t, 16> uuid;
sockaddr_storage address{};
socklen_t addressLen{0};
uint16_t port{0};
};
// Lightweight client for Overte mixers/entities. Designed to follow Overte's
// standards. For now includes a minimal parser scaffold; simulation can be
// optionally enabled via STARWORLD_SIMULATE=1.
class OverteClient {
public:
explicit OverteClient(std::string domainUrl)
: m_domainUrl(std::move(domainUrl)) {}
explicit OverteClient(std::string domainUrl);
~OverteClient();
// Authentication
bool login(const std::string& username, const std::string& password,
const std::string& metaverseUrl = "https://mv.overte.org");
bool isAuthenticated() const;
// High-level connect that brings up key mixers.
bool connect();
@@ -102,6 +120,9 @@ private:
std::string m_username; // Domain account username (for future signature-based auth)
std::uint32_t m_sequenceNumber{0}; // Packet sequence number for NLPacket protocol
std::uint16_t m_localID{0}; // Local ID assigned by domain server
// Authentication
std::unique_ptr<OverteAuth> m_auth;
// Very small in-process world state for testing
std::unordered_map<std::uint64_t, OverteEntity> m_entities;
@@ -115,6 +136,12 @@ private:
struct sockaddr_storage m_udpAddr{};
socklen_t m_udpAddrLen{0};
// Assignment clients from DomainList
std::vector<AssignmentClient> m_assignmentClients;
sockaddr_storage m_entityServerAddr{};
socklen_t m_entityServerAddrLen{0};
uint16_t m_entityServerPort{0};
// EntityServer connection
int m_entityFd{-1};
bool m_entityServerReady{false};