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/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
|
||||||
|
|||||||
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
|
- 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.
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -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
452
bridge/Cargo.lock
generated
@@ -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
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 "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(¤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;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
Reference in New Issue
Block a user