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/main.cpp
src/StardustBridge.cpp src/StardustBridge.cpp
src/OverteClient.cpp src/OverteClient.cpp
src/OverteAuth.cpp
src/SceneSync.cpp src/SceneSync.cpp
src/InputHandler.cpp src/InputHandler.cpp
src/NLPacketCodec.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 - 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:** ### 1. Web-Based OAuth Flow (Primary Issue)- Protocol version: `6xYA55jcXgPHValo3Ba3/A==` (eb1600e798dc5e03c755a968dc16b7fc)
- Found 511 public Overte servers via https://mv.overte.org/server/api/v1/places
- Most servers use common protocol version `6xYA55jcXgPHValo3Ba3/A==` - UDP communication established with domain server
- Successfully tested against local domain server (received EntityServer endpoints)
**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 - 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 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 ```bash
# Terminal 1: Start the client
./build/stardust-overte-client
# Terminal 2: Inject test entities 4. Client Metaverse: POST https://mv.overte.org/oauth/token# Terminal 1: Start the client
python3 tools/inject_test_entities.py
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) ## 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.) - 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 ### 2. Proper Authentication Flow
1. Send DomainConnectRequest with NLPacket header
### 2. Client Registration1. Send DomainConnectRequest with NLPacket header
2. Receive DomainConnectionTokenRequest 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 4. Receive DomainList with assignment client endpoints
5. Connect to each assignment client (EntityServer, Avatar, Audio)
### 3. Assignment Client Protocol **Current State:** Not registered with mv.overte.org.5. Connect to each assignment client (EntityServer, Avatar, Audio)
- Each mixer (EntityServer, AvatarMixer, AudioMixer) has its own handshake
- EntityServer requires octree-based spatial queries
- Proper node type identification in packets
## 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 **Solution:**### 3. Assignment Client Protocol
```bash
OVERTE_USERNAME="your_username" ./build/starworld - 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 ### 3. Token ManagementThis will prompt you for username and password.
```bash
**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" 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) ```cpp```
- **OVERTE_UDP_PORT**: Domain server UDP port (default: 40104)
- **STARWORLD_SIMULATE**: Set to "1" to enable simulation mode with demo entities
## 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 # 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 ### Check Domain Server Status
```bash // 16. Domain username (QString) - empty for now
qs.writeQString("");```bash
# Check if domain server is running # Check if domain server is running
ps aux | grep domain-server
# Check UDP port // 17. Domain access token (QString) - from metaverse OAuthps aux | grep domain-server
sudo ss -ulnp | grep domain-server
```
### Test with Simulation Mode if (isAuthenticated() && m_auth) {
```bash qs.writeQString(m_auth->getAccessToken());# Check UDP port
STARWORLD_SIMULATE=1 ./build/starworld
```
## 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 ✅ 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 ✅ 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 ✅ DomainList request/response parsing
**Handshake complete** - receiving DomainList with mixer endpoints
✅ EntityServer endpoint discovery from DomainList **Implementation:**✅ **Handshake complete** - receiving DomainList with mixer endpoints
⏳ EntityServer connection and EntityQuery packets
⏳ Entity Add/Edit/Erase packet parsing ```cpp✅ EntityServer endpoint discovery from DomainList
⏳ Full property parsing (position, rotation, dimensions)
⏳ Octree-based spatial streaming // 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 ⏳ 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 ## 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. **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 ./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 ```bash
export STARWORLD_BRIDGE_PATH=./bridge/target/release 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++) Overte Server (UDP) → OverteClient (C++) → SceneSync → StardustBridge (C++)

452
bridge/Cargo.lock generated
View File

@@ -339,7 +339,7 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
"object", "object",
"rustc-demangle", "rustc-demangle",
"windows-link", "windows-link 0.2.1",
] ]
[[package]] [[package]]
@@ -409,6 +409,16 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 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]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
@@ -572,6 +582,22 @@ dependencies = [
"unicode-segmentation", "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]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.17" version = "0.2.17"
@@ -838,6 +864,15 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 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]] [[package]]
name = "endi" name = "endi"
version = "1.1.0" version = "1.1.0"
@@ -918,6 +953,12 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]] [[package]]
name = "flatbuffers" name = "flatbuffers"
version = "25.9.23" version = "25.9.23"
@@ -957,6 +998,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 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]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.2" version = "1.2.2"
@@ -973,6 +1029,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@@ -1030,8 +1087,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-io",
"futures-macro", "futures-macro",
"futures-sink",
"futures-task", "futures-task",
"memchr",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"slab", "slab",
@@ -1260,6 +1320,22 @@ dependencies = [
"want", "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]] [[package]]
name = "hyper-timeout" name = "hyper-timeout"
version = "0.5.2" version = "0.5.2"
@@ -1273,12 +1349,29 @@ dependencies = [
"tower-service", "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]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.17" version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [ dependencies = [
"base64 0.22.1",
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@@ -1286,12 +1379,16 @@ dependencies = [
"http", "http",
"http-body", "http-body",
"hyper", "hyper",
"ipnet",
"libc", "libc",
"percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2 0.6.1", "socket2 0.6.1",
"system-configuration",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
"windows-registry",
] ]
[[package]] [[package]]
@@ -1459,6 +1556,22 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.2" version = "1.70.2"
@@ -1529,7 +1642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-link", "windows-link 0.2.1",
] ]
[[package]] [[package]]
@@ -1718,6 +1831,23 @@ dependencies = [
"rand 0.8.5", "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]] [[package]]
name = "nix" name = "nix"
version = "0.27.1" version = "0.27.1"
@@ -1813,6 +1943,50 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 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]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
@@ -1910,7 +2084,7 @@ dependencies = [
"libc", "libc",
"redox_syscall 0.5.18", "redox_syscall 0.5.18",
"smallvec", "smallvec",
"windows-link", "windows-link 0.2.1",
] ]
[[package]] [[package]]
@@ -1962,6 +2136,12 @@ dependencies = [
"futures-io", "futures-io",
] ]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "polling" name = "polling"
version = "3.11.0" version = "3.11.0"
@@ -2208,6 +2388,62 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 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]] [[package]]
name = "ron" name = "ron"
version = "0.11.0" version = "0.11.0"
@@ -2255,6 +2491,39 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
@@ -2267,12 +2536,44 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 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]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 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]] [[package]]
name = "semver" name = "semver"
version = "1.0.27" version = "1.0.27"
@@ -2333,6 +2634,18 @@ dependencies = [
"syn 2.0.109", "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]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@@ -2366,6 +2679,12 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.6" version = "1.4.6"
@@ -2548,6 +2867,7 @@ dependencies = [
"dirs 5.0.1", "dirs 5.0.1",
"glam 0.28.0", "glam 0.28.0",
"lazy_static", "lazy_static",
"reqwest",
"serde", "serde",
"stardust-xr-asteroids", "stardust-xr-asteroids",
"stardust-xr-fusion", "stardust-xr-fusion",
@@ -2597,6 +2917,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@@ -2624,6 +2950,9 @@ name = "sync_wrapper"
version = "1.0.2" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
[[package]] [[package]]
name = "synstructure" name = "synstructure"
@@ -2636,6 +2965,27 @@ dependencies = [
"syn 2.0.109", "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]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.23.0" version = "3.23.0"
@@ -2737,6 +3087,26 @@ dependencies = [
"syn 2.0.109", "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]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.17" version = "0.1.17"
@@ -2868,6 +3238,25 @@ dependencies = [
"futures-util", "futures-util",
"pin-project-lite", "pin-project-lite",
"sync_wrapper", "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-layer",
"tower-service", "tower-service",
] ]
@@ -3020,6 +3409,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.7" version = "2.5.7"
@@ -3078,6 +3473,12 @@ dependencies = [
"syn 2.0.109", "syn 2.0.109",
] ]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
@@ -3220,12 +3621,47 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
@@ -3259,7 +3695,7 @@ version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [ dependencies = [
"windows-link", "windows-link 0.2.1",
] ]
[[package]] [[package]]
@@ -3299,7 +3735,7 @@ version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [ dependencies = [
"windows-link", "windows-link 0.2.1",
"windows_aarch64_gnullvm 0.53.1", "windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1", "windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1", "windows_i686_gnu 0.53.1",
@@ -3610,6 +4046,12 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]] [[package]]
name = "zerotrie" name = "zerotrie"
version = "0.2.3" 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 "OverteClient.hpp"
#include "NLPacketCodec.hpp" #include "NLPacketCodec.hpp"
#include "OverteAuth.hpp"
#include <chrono> #include <chrono>
#include <cmath> #include <cmath>
@@ -21,6 +22,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <cstring> #include <cstring>
#include <zlib.h> #include <zlib.h>
#include <endian.h>
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace Overte; using namespace Overte;
@@ -119,6 +121,30 @@ static std::string generateUUID() {
return ss.str(); 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() { bool OverteClient::connect() {
// Generate session UUID // Generate session UUID
m_sessionUUID = generateUUID(); m_sessionUUID = generateUUID();
@@ -126,23 +152,56 @@ bool OverteClient::connect() {
// Check for authentication credentials from environment // Check for authentication credentials from environment
const char* usernameEnv = std::getenv("OVERTE_USERNAME"); 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()) { // TODO: OAuth authentication to metaverse server
std::cout << "[OverteClient] Username present (signature auth not yet implemented)" << std::endl; // 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; std::string url = m_domainUrl;
if (url.empty()) url = "ws://127.0.0.1:40102"; if (url.empty()) url = "ws://127.0.0.1:40102";
if (url.rfind("ws://", 0) == 0) url = url.substr(5); 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(':'); auto colon = url.find(':');
m_host = colon == std::string::npos ? url : url.substr(0, colon); 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) // Check for environment override for UDP port (domain server UDP port)
const char* portEnv = std::getenv("OVERTE_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 std::cout << "[OverteClient] Connecting to domain at " << m_host
<< " (HTTP:" << m_port << ", UDP:" << udpPort << ")" << std::endl; << " (HTTP:" << m_port << ", UDP:" << udpPort << ")" << std::endl;
@@ -439,7 +498,17 @@ void OverteClient::parseDomainPacket(const char* data, size_t len) {
break; break;
default: 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; break;
} }
} }
@@ -451,6 +520,13 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
if (len < 1) return; 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]); unsigned char packetType = static_cast<unsigned char>(data[0]);
// Entity packet types // 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; std::cout << "[OverteClient] Authenticated: " << (authenticated ? "yes" : "no") << std::endl;
// Now mark as connected since we got a valid DomainList // Read additional timing/metadata fields (from Overte's DomainServer::sendDomainListToNode)
m_domainConnected = true; // These fields were added after the authenticated flag
if (offset + 8 > len) {
// Send EntityQuery to request entity data from the server std::cout << "[OverteClient] Packet too short for timing fields" << std::endl;
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;
return; return;
} }
for (uint32_t i = 0; i < numNodes && offset < len; ++i) { // lastDomainCheckinTimestamp (uint64)
// Read NodeType uint64_t lastCheckinTimestamp;
if (offset + 1 > len) break; std::memcpy(&lastCheckinTimestamp, data + offset, 8);
unsigned char nodeType = static_cast<unsigned char>(data[offset++]); 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; if (offset + 16 > len) break;
std::memcpy(ac.uuid.data(), data + offset, 16);
offset += 16; offset += 16;
// Read public socket address // Read PublicSocket.type (quint8)
if (offset + sizeof(sockaddr_in) > len) break; if (offset + 1 > len) break;
uint8_t publicSocketType = static_cast<uint8_t>(data[offset++]);
sockaddr_in publicAddr; // Read PublicSocket.address (QHostAddress)
std::memcpy(&publicAddr, data + offset, sizeof(sockaddr_in)); if (offset + 1 > len) break;
offset += sizeof(sockaddr_in); uint8_t addressProtocol = static_cast<uint8_t>(data[offset++]);
// Skip local socket (same size) if (addressProtocol == 1) { // IPv4
if (offset + sizeof(sockaddr_in) > len) break; if (offset + 4 > len) break;
offset += sizeof(sockaddr_in); 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: // Read LocalSocket.type (quint8)
// 0 = DomainServer, 1 = EntityServer, 2 = Agent, 3 = AudioMixer, if (offset + 1 > len) break;
// 4 = AvatarMixer, 5 = AssetServer, 6 = MessagesMixer, 7 = EntityScriptServer uint8_t localSocketType = static_cast<uint8_t>(data[offset++]);
const unsigned char NODE_TYPE_ENTITY_SERVER = 1; (void)localSocketType; // unused for now
const unsigned char NODE_TYPE_AVATAR_MIXER = 4;
const unsigned char NODE_TYPE_AUDIO_MIXER = 3;
char addrStr[INET_ADDRSTRLEN]; // Read LocalSocket.address (QHostAddress)
inet_ntop(AF_INET, &publicAddr.sin_addr, addrStr, sizeof(addrStr)); if (offset + 1 > len) break;
int port = ntohs(publicAddr.sin_port); 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"; const char* nodeTypeName = "Unknown";
switch (nodeType) { switch (ac.type) {
case 0: nodeTypeName = "DomainServer"; break; case 'D': nodeTypeName = "DomainServer"; break;
case NODE_TYPE_ENTITY_SERVER: nodeTypeName = "EntityServer"; break; case 'o': nodeTypeName = "EntityServer"; break;
case 2: nodeTypeName = "Agent"; break; case 'I': nodeTypeName = "Agent"; break;
case NODE_TYPE_AUDIO_MIXER: nodeTypeName = "AudioMixer"; break; case 'M': nodeTypeName = "AudioMixer"; break;
case NODE_TYPE_AVATAR_MIXER: nodeTypeName = "AvatarMixer"; break; case 'W': nodeTypeName = "AvatarMixer"; break;
case 5: nodeTypeName = "AssetServer"; break; case 'A': nodeTypeName = "AssetServer"; break;
case 6: nodeTypeName = "MessagesMixer"; break; case 'm': nodeTypeName = "MessagesMixer"; break;
case 7: nodeTypeName = "EntityScriptServer"; break; case 'S': nodeTypeName = "EntityScriptServer"; break;
} }
std::cout << "[OverteClient] Assignment: " << nodeTypeName char addrStr[INET_ADDRSTRLEN];
<< " at " << addrStr << ":" << port << std::endl; 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) { std::cout << "[OverteClient] Assignment client: " << nodeTypeName
// Update EntityServer connection to use discovered address << " at " << addrStr << ":" << ac.port << std::endl;
std::cout << "[OverteClient] Connecting to EntityServer at " << addrStr << ":" << 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 std::cout << "[OverteClient] Entity server found at " << addrStr << ":" << ac.port << std::endl;
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] 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) { void OverteClient::handleDomainConnectionDenied(const char* data, size_t len) {
@@ -1004,10 +1179,11 @@ void OverteClient::sendDomainConnectRequest() {
// 13. Place name (QString) - empty // 13. Place name (QString) - empty
qs.writeQString(""); qs.writeQString("");
// 14. Directory services username (QString) - get from environment or empty // 14. Directory services username (QString) - empty for now
const char* usernameEnv = std::getenv("OVERTE_USERNAME"); // TODO: Username sending causes domain server to not respond
std::string dsUsername = usernameEnv ? usernameEnv : ""; // const char* usernameEnv = std::getenv("OVERTE_USERNAME");
qs.writeQString(dsUsername); // std::string dsUsername = usernameEnv ? usernameEnv : "";
qs.writeQString(""); // Always send empty for now
// 15. Username signature (QString) - empty (no keypair authentication) // 15. Username signature (QString) - empty (no keypair authentication)
qs.writeQString(""); qs.writeQString("");
@@ -1100,6 +1276,12 @@ void OverteClient::sendPing(int fd, const sockaddr_storage& addr, socklen_t addr
void OverteClient::sendEntityQuery() { void OverteClient::sendEntityQuery() {
if (!m_udpReady || m_udpFd == -1) return; 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) // Create EntityQuery packet (PacketType::EntityQuery = 0x29)
NLPacket packet(PacketType::EntityQuery, 0, true); NLPacket packet(PacketType::EntityQuery, 0, true);
// Include our local ID (sourced packet) // Include our local ID (sourced packet)
@@ -1170,10 +1352,19 @@ void OverteClient::sendEntityQuery() {
const auto& data = packet.getData(); const auto& data = packet.getData();
ssize_t s = ::sendto(m_udpFd, data.data(), data.size(), 0, 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) { 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 { } else {
std::cerr << "[OverteClient] Failed to send EntityQuery: " << strerror(errno) << std::endl; std::cerr << "[OverteClient] Failed to send EntityQuery: " << strerror(errno) << std::endl;
} }

View File

@@ -5,6 +5,7 @@
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <memory>
#include <glm/glm.hpp> #include <glm/glm.hpp>
@@ -13,6 +14,9 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <netdb.h> #include <netdb.h>
// Forward declarations
class OverteAuth;
// Overte entity types (matching Overte EntityTypes.h) // Overte entity types (matching Overte EntityTypes.h)
enum class EntityType { enum class EntityType {
Unknown, Unknown,
@@ -46,14 +50,28 @@ struct OverteEntity {
float alpha{1.0f}; // Transparency (0-1) 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 // Lightweight client for Overte mixers/entities. Designed to follow Overte's
// standards. For now includes a minimal parser scaffold; simulation can be // standards. For now includes a minimal parser scaffold; simulation can be
// optionally enabled via STARWORLD_SIMULATE=1. // optionally enabled via STARWORLD_SIMULATE=1.
class OverteClient { class OverteClient {
public: public:
explicit OverteClient(std::string domainUrl) explicit OverteClient(std::string domainUrl);
: m_domainUrl(std::move(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. // High-level connect that brings up key mixers.
bool connect(); bool connect();
@@ -102,6 +120,9 @@ private:
std::string m_username; // Domain account username (for future signature-based auth) 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::uint32_t m_sequenceNumber{0}; // Packet sequence number for NLPacket protocol
std::uint16_t m_localID{0}; // Local ID assigned by domain server 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 // Very small in-process world state for testing
std::unordered_map<std::uint64_t, OverteEntity> m_entities; std::unordered_map<std::uint64_t, OverteEntity> m_entities;
@@ -115,6 +136,12 @@ private:
struct sockaddr_storage m_udpAddr{}; struct sockaddr_storage m_udpAddr{};
socklen_t m_udpAddrLen{0}; 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 // EntityServer connection
int m_entityFd{-1}; int m_entityFd{-1};
bool m_entityServerReady{false}; bool m_entityServerReady{false};