Compare commits
27 Commits
fb952f1d7a
...
af63d8d744
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af63d8d744 | ||
|
|
72c279b45b | ||
|
|
504894c525 | ||
|
|
27e77f0e03 | ||
|
|
5a56e8c23b | ||
|
|
1e3f888ac1 | ||
|
|
4240c74851 | ||
|
|
2f74a87085 | ||
|
|
1ee49c9a77 | ||
|
|
39d5082da5 | ||
|
|
132d0f3d36 | ||
|
|
931ca8d0b6 | ||
|
|
a246eba0b6 | ||
|
|
54462cc0d7 | ||
|
|
053d30b8f3 | ||
|
|
2126bd24c3 | ||
|
|
ad76a07d7d | ||
|
|
97c2f2370a | ||
|
|
221cb78e77 | ||
|
|
9e631758ad | ||
|
|
fdeba92901 | ||
|
|
c1edb63e1e | ||
|
|
4f8c053787 | ||
|
|
20683fa01b | ||
|
|
aff330d07d | ||
|
|
b874569256 | ||
|
|
6de487bdaa |
@@ -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
|
||||
|
||||
293
OVERTE_ASSIGNMENT_CLIENT_TASK.md
Normal file
293
OVERTE_ASSIGNMENT_CLIENT_TASK.md
Normal 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)
|
||||
428
OVERTE_AUTH.md
428
OVERTE_AUTH.md
@@ -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.
|
||||
|
||||
21
README.md
21
README.md
@@ -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
452
bridge/Cargo.lock
generated
@@ -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
142
src/OverteAuth.cpp
Normal 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
42
src/OverteAuth.hpp
Normal 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);
|
||||
};
|
||||
@@ -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(¤tTimestamp, 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;
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user