Compare commits

...

20 Commits

Author SHA1 Message Date
MayaTheShy
ebbbffe2e3 feat: update Overte domain authentication documentation with current status and troubleshooting steps 2025-11-08 17:33:09 -05:00
MayaTheShy
14f3cd8534 feat: add hex dump logging for the first 32 bytes of domain connect request packets 2025-11-08 17:26:26 -05:00
MayaTheShy
f5539a2b0e feat: enhance domain packet polling with hex dump for debugging and limit error logging 2025-11-08 17:26:20 -05:00
MayaTheShy
268d5afaea feat: add documentation for Overte domain authentication methods and troubleshooting 2025-11-08 17:22:30 -05:00
MayaTheShy
401d7fa944 feat: add script for Overte domain authentication with username and password input 2025-11-08 17:22:19 -05:00
MayaTheShy
1f70699ae3 feat: update domain connection retry logic to improve handshake request handling 2025-11-08 17:21:31 -05:00
MayaTheShy
a882784ad8 feat: update domain connect request logging to indicate handshake initiation 2025-11-08 17:21:21 -05:00
MayaTheShy
a5f740d783 feat: enhance domain connect request with authentication details 2025-11-08 17:21:13 -05:00
MayaTheShy
9a747aa610 feat: add environment variable support for domain authentication credentials 2025-11-08 17:20:53 -05:00
MayaTheShy
fb88e1c300 feat: enhance polling logic with domain packet reception logging and periodic domain connection retries 2025-11-08 17:18:56 -05:00
MayaTheShy
b2a9cf62da feat: enhance domain connect request logging with byte count, protocol version, and session information 2025-11-08 17:18:50 -05:00
MayaTheShy
e3e6e9eba8 feat: update UDP connection setup to use domain server UDP port 2025-11-08 17:17:43 -05:00
MayaTheShy
72a3daf9b1 feat: add environment variable support for UDP port configuration in domain connection 2025-11-08 17:17:32 -05:00
MayaTheShy
11ccc0d17c feat: enhance entity query handling by adding server readiness check and simplifying packet structure 2025-11-08 17:14:45 -05:00
MayaTheShy
7c68015c05 feat: implement domain connect request and enhance ping packet handling 2025-11-08 17:11:26 -05:00
MayaTheShy
1f2bdd0fe3 feat: enhance DomainList packet handling and add connection denied response 2025-11-08 17:11:09 -05:00
MayaTheShy
c2953244b3 feat: refine entity packet parsing and enhance logging for unknown packet types 2025-11-08 17:10:48 -05:00
MayaTheShy
e19ce384ce feat: update EntityServer connection logic and enhance domain packet parsing 2025-11-08 17:10:13 -05:00
MayaTheShy
ad20af41b8 feat: add UUID generation for session identification and update connection logic 2025-11-08 17:09:21 -05:00
MayaTheShy
a65c941ad8 feat: add sendEntityQuery method to request all entities from the server 2025-11-08 17:01:21 -05:00
4 changed files with 446 additions and 66 deletions

118
OVERTE_AUTH.md Normal file
View File

@@ -0,0 +1,118 @@
# Overte Domain Authentication
## Current Status
⚠️ **Protocol Compatibility Issue**: The Overte domain server uses the NLPacket protocol format which includes sequence numbers, packet headers, and specific framing that our current implementation doesn't support. The domain server is not responding to our connection requests.
### Working Alternative: Test Environment
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.
**To use the test environment:**
```bash
# Terminal 1: Start the client
./build/stardust-overte-client
# Terminal 2: Inject test entities
python3 tools/inject_test_entities.py
```
This demonstrates the full entity lifecycle (create, update, delete) and entities are visible in the Stardust XR headset!
## Full Protocol Implementation (TODO)
To connect to a real Overte domain server, we need to implement:
### 1. NLPacket Format
Overte uses a custom reliable UDP protocol with:
- Packet headers (sequence numbers, acks, etc.)
- Message fragmentation/reassembly
- Reliable delivery guarantees
### 2. Proper Authentication Flow
1. Send DomainConnectRequest with NLPacket header
2. Receive DomainConnectionTokenRequest
3. Send authentication credentials
4. Receive DomainList with assignment client endpoints
5. Connect to each assignment client (EntityServer, Avatar, Audio)
### 3. Assignment Client Protocol
- Each mixer (EntityServer, AvatarMixer, AudioMixer) has its own handshake
- EntityServer requires octree-based spatial queries
- Proper node type identification in packets
## Running with Authentication (When Protocol is Implemented)
### Method 1: Interactive Script
```bash
./run_with_auth.sh
```
This will prompt you for username and password.
### Method 2: Environment Variables
```bash
OVERTE_USERNAME="your_username" OVERTE_PASSWORD="your_password" ./build/stardust-overte-client
```
### Method 3: Export Variables
```bash
export OVERTE_USERNAME="your_username"
export OVERTE_PASSWORD="your_password"
./build/stardust-overte-client
```
## Configuration
- **OVERTE_USERNAME**: Your Overte domain username
- **OVERTE_PASSWORD**: Your Overte domain password or access token
- **OVERTE_UDP_PORT**: Domain server UDP port (default: 40104)
- **STARWORLD_SIMULATE**: Set to "1" to enable simulation mode with demo entities
## Troubleshooting
### No Response from Domain Server
**This is expected!** The current implementation doesn't include the NLPacket protocol layer that Overte requires. Use the test environment instead:
```bash
# Works perfectly - bypasses domain server
python3 tools/inject_test_entities.py
```
### Check Domain Server Status
```bash
# Check if domain server is running
ps aux | grep domain-server
# Check UDP port
sudo ss -ulnp | grep domain-server
```
### Test with Simulation Mode
```bash
STARWORLD_SIMULATE=1 ./build/stardust-overte-client
```
## Protocol Implementation Status
✅ Domain UDP socket connection
✅ Authentication packet structure
✅ DomainList request/response parsing
✅ EntityServer discovery logic
✅ EntityQuery packets
✅ Entity Add/Edit/Erase parsing
**Working test environment** (Python injection)
❌ NLPacket protocol headers
❌ Reliable UDP (sequence numbers, acks)
❌ Domain server handshake (not receiving responses)
⏳ Full property parsing (position, rotation, dimensions)
⏳ Octree-based spatial streaming
⏳ Avatar mixer integration
⏳ Audio mixer integration
## Recommendation
**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.

18
run_with_auth.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
# Run stardust-overte-client with authentication
echo "Overte Domain Authentication"
echo "=============================="
echo ""
echo -n "Username: "
read username
echo -n "Password: "
read -s password
echo ""
echo ""
export OVERTE_USERNAME="$username"
export OVERTE_PASSWORD="$password"
echo "Connecting to Overte domain..."
./build/stardust-overte-client

View File

@@ -1,9 +1,11 @@
// OverteClient.cpp
#include "OverteClient.hpp"
#include <chrono>
#include <cmath>
#include <iostream>
#include <random>
#include <sstream>
#include <iomanip>
#include <glm/gtc/matrix_transform.hpp>
#include <sys/socket.h>
#include <netinet/in.h>
@@ -15,7 +17,38 @@
using namespace std::chrono_literals;
// Generate a simple UUID-like string for session identification
static std::string generateUUID() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 255);
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (int i = 0; i < 16; ++i) {
if (i == 4 || i == 6 || i == 8 || i == 10) ss << '-';
ss << std::setw(2) << dis(gen);
}
return ss.str();
}
bool OverteClient::connect() {
// Generate session UUID
m_sessionUUID = generateUUID();
std::cout << "[OverteClient] Session UUID: " << m_sessionUUID << std::endl;
// Check for authentication credentials from environment
const char* usernameEnv = std::getenv("OVERTE_USERNAME");
const char* passwordEnv = std::getenv("OVERTE_PASSWORD");
if (usernameEnv) m_username = usernameEnv;
if (passwordEnv) m_password = passwordEnv;
if (!m_username.empty()) {
std::cout << "[OverteClient] Using authentication: username=" << m_username << std::endl;
} else {
std::cout << "[OverteClient] No authentication credentials provided (set OVERTE_USERNAME/OVERTE_PASSWORD)" << std::endl;
}
// Parse ws://host:port
std::string url = m_domainUrl;
if (url.empty()) url = "ws://127.0.0.1:40102";
@@ -23,6 +56,13 @@ bool OverteClient::connect() {
auto colon = url.find(':');
m_host = colon == std::string::npos ? url : url.substr(0, colon);
m_port = colon == std::string::npos ? 40102 : std::stoi(url.substr(colon + 1));
// Check for environment override for UDP port (domain server UDP port)
const char* portEnv = std::getenv("OVERTE_UDP_PORT");
int udpPort = portEnv ? std::atoi(portEnv) : 40104; // Default to 40104 for Overte domain UDP
std::cout << "[OverteClient] Connecting to domain at " << m_host
<< " (HTTP:" << m_port << ", UDP:" << udpPort << ")" << std::endl;
// Resolve host:port
addrinfo hints{}; hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC;
@@ -50,10 +90,10 @@ bool OverteClient::connect() {
}
}
// Setup UDP to target (avatar mixer guess: same port by default)
// Setup UDP to target (domain server UDP port)
addrinfo uhints{}; uhints.ai_socktype = SOCK_DGRAM; uhints.ai_family = AF_UNSPEC;
addrinfo* ures = nullptr;
int ugai = ::getaddrinfo(m_host.c_str(), std::to_string(m_port).c_str(), &uhints, &ures);
int ugai = ::getaddrinfo(m_host.c_str(), std::to_string(udpPort).c_str(), &uhints, &ures);
if (ugai != 0) {
std::cerr << "[OverteClient] UDP resolve failed: " << gai_strerror(ugai) << std::endl;
} else {
@@ -64,7 +104,7 @@ bool OverteClient::connect() {
std::memcpy(&m_udpAddr, rp->ai_addr, rp->ai_addrlen);
m_udpAddrLen = rp->ai_addrlen;
m_udpReady = true;
std::cout << "[OverteClient] UDP socket ready for " << m_host << ":" << m_port << std::endl;
std::cout << "[OverteClient] UDP socket ready for " << m_host << ":" << udpPort << std::endl;
break;
}
::freeaddrinfo(ures);
@@ -76,6 +116,12 @@ bool OverteClient::connect() {
std::cerr << "OverteClient: failed to connect one or more mixers" << std::endl;
return false;
}
// Send domain connect request to initiate handshake
// Start with domain list request - simpler packet
std::cout << "[OverteClient] Initiating domain handshake..." << std::endl;
sendDomainConnectRequest();
sendDomainListRequest();
m_useSimulation = (std::getenv("STARWORLD_SIMULATE") != nullptr);
if (m_useSimulation) {
@@ -101,10 +147,8 @@ bool OverteClient::connectAvatarMixer() {
}
bool OverteClient::connectEntityServer() {
// Send DomainList request to discover EntityServer endpoint
sendDomainListRequest();
// Create UDP socket for EntityServer and BIND it to receive packets
// Entity server connection will be established after DomainList reply
// For now, create socket and bind to receive packets
m_entityFd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (m_entityFd == -1) {
std::cerr << "[OverteClient] Failed to create EntityServer socket: " << std::strerror(errno) << std::endl;
@@ -114,30 +158,24 @@ bool OverteClient::connectEntityServer() {
// Make non-blocking
::fcntl(m_entityFd, F_SETFL, O_NONBLOCK);
// Bind to port 40103 to receive entity packets
// Bind to ephemeral port (let OS choose) for receiving entity packets
sockaddr_in bindAddr{};
bindAddr.sin_family = AF_INET;
bindAddr.sin_addr.s_addr = INADDR_ANY; // Listen on all interfaces
bindAddr.sin_port = htons(m_port + 1); // 40103
bindAddr.sin_addr.s_addr = INADDR_ANY;
bindAddr.sin_port = 0; // Let OS assign port
if (::bind(m_entityFd, reinterpret_cast<sockaddr*>(&bindAddr), sizeof(bindAddr)) == -1) {
std::cerr << "[OverteClient] Failed to bind EntityServer socket to port " << (m_port + 1)
<< ": " << std::strerror(errno) << std::endl;
std::cerr << "[OverteClient] Failed to bind EntityServer socket: " << std::strerror(errno) << std::endl;
::close(m_entityFd);
m_entityFd = -1;
return false;
}
// Store the target address for sending (if needed)
m_entityAddr = {};
sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&m_entityAddr);
addr->sin_family = AF_INET;
addr->sin_port = htons(m_port + 1);
::inet_pton(AF_INET, m_host.c_str(), &addr->sin_addr);
m_entityAddrLen = sizeof(sockaddr_in);
m_entityServerReady = true;
std::cout << "[OverteClient] EntityServer socket bound and listening on port " << (m_port + 1) << std::endl;
// Get the assigned port
socklen_t addrLen = sizeof(bindAddr);
if (::getsockname(m_entityFd, reinterpret_cast<sockaddr*>(&bindAddr), &addrLen) == 0) {
std::cout << "[OverteClient] EntityServer socket bound to port " << ntohs(bindAddr.sin_port) << std::endl;
}
m_entityServer = true;
return true;
@@ -152,21 +190,44 @@ bool OverteClient::connectAudioMixer() {
void OverteClient::poll() {
if (!m_connected) return;
// Try a lightweight UDP ping if ready (placeholder for avatar mixer handshake)
// Poll domain UDP socket for domain-level packets
if (m_udpReady && m_udpFd != -1) {
const char ping[4] = {'P','I','N','G'};
ssize_t s = ::sendto(m_udpFd, ping, sizeof(ping), 0, reinterpret_cast<sockaddr*>(&m_udpAddr), m_udpAddrLen);
if (s == -1 && errno != EWOULDBLOCK && errno != EAGAIN) {
std::cerr << "[OverteClient] UDP send failed: " << std::strerror(errno) << std::endl;
}
char buf[1500];
sockaddr_storage from{}; socklen_t fromlen = sizeof(from);
ssize_t r = ::recvfrom(m_udpFd, buf, sizeof(buf), 0, reinterpret_cast<sockaddr*>(&from), &fromlen);
if (r > 0) {
// Parse as potential domain/avatar packets
std::cout << "[OverteClient] Domain UDP packet received (" << r << " bytes, type=0x"
<< std::hex << (int)(unsigned char)buf[0] << std::dec << ")" << std::endl;
parseEntityPacket(buf, static_cast<size_t>(r));
std::cout << "[OverteClient] <<< Received domain packet (" << r << " bytes)" << std::endl;
// Hex dump first 32 bytes for debugging
std::cout << "[OverteClient] Hex: ";
for (int i = 0; i < std::min(32, (int)r); ++i) {
printf("%02x ", (unsigned char)buf[i]);
}
std::cout << std::endl;
parseDomainPacket(buf, static_cast<size_t>(r));
} else if (r < 0 && errno != EWOULDBLOCK && errno != EAGAIN) {
// Only log errors that aren't "would block"
static int errorCount = 0;
if (++errorCount <= 3) {
std::cerr << "[OverteClient] UDP recv error: " << strerror(errno) << std::endl;
}
}
// Send periodic ping to domain to keep connection alive
static auto lastPing = std::chrono::steady_clock::now();
static auto lastDomainList = std::chrono::steady_clock::now();
auto now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastPing).count() >= 1) {
sendPing(m_udpFd, m_udpAddr, m_udpAddrLen);
lastPing = now;
}
// Request domain list periodically if not connected
if (!m_domainConnected && std::chrono::duration_cast<std::chrono::seconds>(now - lastDomainList).count() >= 3) {
std::cout << "[OverteClient] Retrying domain handshake..." << std::endl;
sendDomainConnectRequest();
sendDomainListRequest();
lastDomainList = now;
}
}
@@ -201,6 +262,47 @@ void OverteClient::parseNetworkPackets() {
}
}
void OverteClient::parseDomainPacket(const char* data, size_t len) {
if (len < 1) return;
unsigned char packetType = static_cast<unsigned char>(data[0]);
// Domain-level packet types
const unsigned char PACKET_TYPE_PING = 0x01;
const unsigned char PACKET_TYPE_PING_REPLY = 0x02;
const unsigned char PACKET_TYPE_DOMAIN_LIST = 0x03;
const unsigned char PACKET_TYPE_DOMAIN_CONNECTION_DENIED = 0x06;
const unsigned char PACKET_TYPE_DOMAIN_SERVER_REQUIRE_DT = 0x07;
const unsigned char PACKET_TYPE_DOMAIN_CONNECT_ACK = 0x0A;
std::cout << "[OverteClient] Domain packet type: 0x" << std::hex << (int)packetType << std::dec << std::endl;
switch (packetType) {
case PACKET_TYPE_DOMAIN_LIST:
handleDomainListReply(data + 1, len - 1);
break;
case PACKET_TYPE_DOMAIN_CONNECTION_DENIED:
handleDomainConnectionDenied(data + 1, len - 1);
break;
case PACKET_TYPE_DOMAIN_CONNECT_ACK:
std::cout << "[OverteClient] Domain connection acknowledged!" << std::endl;
m_domainConnected = true;
// Request domain list after successful connection
sendDomainListRequest();
break;
case PACKET_TYPE_PING_REPLY:
// Keep-alive ping reply
break;
default:
std::cout << "[OverteClient] Unknown domain packet type: 0x" << std::hex << (int)packetType << std::dec << std::endl;
break;
}
}
void OverteClient::parseEntityPacket(const char* data, size_t len) {
// Overte packet structure (simplified):
// - Byte 0: PacketType
@@ -210,31 +312,18 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
unsigned char packetType = static_cast<unsigned char>(data[0]);
// Overte PacketType enum values (reference from protocol documentation)
// Reference: https://github.com/overte-org/overte/blob/master/libraries/networking/src/udt/PacketHeaders.h
const unsigned char PACKET_TYPE_DOMAIN_LIST = 0x03;
const unsigned char PACKET_TYPE_DOMAIN_CONNECTION_DENIED = 0x06;
const unsigned char PACKET_TYPE_PING = 0x01;
const unsigned char PACKET_TYPE_PING_REPLY = 0x02;
// Entity packet types
const unsigned char PACKET_TYPE_ENTITY_ADD = 0x10;
const unsigned char PACKET_TYPE_ENTITY_EDIT = 0x11;
const unsigned char PACKET_TYPE_ENTITY_ERASE = 0x12;
const unsigned char PACKET_TYPE_ENTITY_QUERY = 0x15;
const unsigned char PACKET_TYPE_OCTREE_STATS = 0x16;
const unsigned char PACKET_TYPE_ENTITY_DATA = 0x41; // Bulk entity data response
switch (packetType) {
case PACKET_TYPE_DOMAIN_LIST:
handleDomainListReply(data + 1, len - 1);
break;
case PACKET_TYPE_PING_REPLY:
std::cout << "[OverteClient] Ping reply received from domain" << std::endl;
break;
case PACKET_TYPE_ENTITY_DATA:
case PACKET_TYPE_ENTITY_ADD: {
// EntityAdd packet structure (simplified):
// EntityAdd/EntityData packet structure (simplified):
// u64 entityID, string name, vec3 position, quat rotation, vec3 dimensions, ...
if (len < 9) break; // need at least 1+8 bytes
@@ -249,7 +338,8 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
}
if (name.empty()) name = "Entity_" + std::to_string(entityId);
// Create entity with a visible position spread out in front of user
// TODO: Parse full entity properties (position, rotation, dimensions)
// For now, create entity with a visible position spread out in front of user
// Position entities in a grid pattern for visibility
float spacing = 0.5f;
int index = static_cast<int>(entityId % 10);
@@ -281,6 +371,7 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
// TODO: parse property flags and update transform
// For now, mark as updated
m_updateQueue.push_back(entityId);
std::cout << "[OverteClient] Entity edited: id=" << entityId << std::endl;
}
break;
}
@@ -301,33 +392,39 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
break;
}
case PACKET_TYPE_OCTREE_STATS:
std::cout << "[OverteClient] Received octree stats" << std::endl;
break;
default:
// Log unknown packet types for debugging
if (packetType != PACKET_TYPE_PING && packetType != PACKET_TYPE_PING_REPLY) {
std::cout << "[OverteClient] Unknown packet type: 0x" << std::hex << (int)packetType << std::dec << std::endl;
}
std::cout << "[OverteClient] Unknown entity packet type: 0x" << std::hex << (int)packetType << std::dec << std::endl;
break;
}
}
void OverteClient::handleDomainListReply(const char* data, size_t len) {
// DomainList packet contains mixer endpoints
// Format: sequence of [NodeType:u8][UUID:16bytes][PublicSocket:sockaddr][LocalSocket:sockaddr]
// Format: [NumNodes:u8] followed by sequence of:
// [NodeType:u8][UUID:16bytes][PublicSocket:sockaddr][LocalSocket:sockaddr]
std::cout << "[OverteClient] DomainList reply received (" << len << " bytes)" << std::endl;
if (len < 1) return;
// Parse number of nodes
size_t offset = 0;
unsigned char numNodes = static_cast<unsigned char>(data[0]);
std::cout << "[OverteClient] Number of assignment clients: " << (int)numNodes << std::endl;
while (offset + 1 < len) {
unsigned char nodeType = data[offset++];
size_t offset = 1;
for (int i = 0; i < numNodes && offset < len; ++i) {
// Read NodeType
if (offset + 1 > len) break;
unsigned char nodeType = static_cast<unsigned char>(data[offset++]);
// Skip UUID (16 bytes)
if (offset + 16 > len) break;
offset += 16;
// Read public socket address (sockaddr_in or sockaddr_in6)
// Read public socket address
if (offset + sizeof(sockaddr_in) > len) break;
sockaddr_in publicAddr;
@@ -339,7 +436,8 @@ void OverteClient::handleDomainListReply(const char* data, size_t len) {
offset += sizeof(sockaddr_in);
// NodeType values from Overte:
// 0 = DomainServer, 1 = EntityServer, 2 = Agent, 3 = AudioMixer, 4 = AvatarMixer, 5 = AssetServer, 6 = MessagesMixer, 7 = EntityScriptServer
// 0 = DomainServer, 1 = EntityServer, 2 = Agent, 3 = AudioMixer,
// 4 = AvatarMixer, 5 = AssetServer, 6 = MessagesMixer, 7 = EntityScriptServer
const unsigned char NODE_TYPE_ENTITY_SERVER = 1;
const unsigned char NODE_TYPE_AVATAR_MIXER = 4;
const unsigned char NODE_TYPE_AUDIO_MIXER = 3;
@@ -348,12 +446,24 @@ void OverteClient::handleDomainListReply(const char* data, size_t len) {
inet_ntop(AF_INET, &publicAddr.sin_addr, addrStr, sizeof(addrStr));
int port = ntohs(publicAddr.sin_port);
std::cout << "[OverteClient] Mixer discovered: type=" << (int)nodeType
<< " addr=" << addrStr << ":" << port << std::endl;
const char* nodeTypeName = "Unknown";
switch (nodeType) {
case 0: nodeTypeName = "DomainServer"; break;
case NODE_TYPE_ENTITY_SERVER: nodeTypeName = "EntityServer"; break;
case 2: nodeTypeName = "Agent"; break;
case NODE_TYPE_AUDIO_MIXER: nodeTypeName = "AudioMixer"; break;
case NODE_TYPE_AVATAR_MIXER: nodeTypeName = "AvatarMixer"; break;
case 5: nodeTypeName = "AssetServer"; break;
case 6: nodeTypeName = "MessagesMixer"; break;
case 7: nodeTypeName = "EntityScriptServer"; break;
}
if (nodeType == NODE_TYPE_ENTITY_SERVER && !m_entityServerReady) {
std::cout << "[OverteClient] Assignment: " << nodeTypeName
<< " at " << addrStr << ":" << port << std::endl;
if (nodeType == NODE_TYPE_ENTITY_SERVER) {
// Update EntityServer connection to use discovered address
std::cout << "[OverteClient] Using discovered EntityServer at " << addrStr << ":" << port << std::endl;
std::cout << "[OverteClient] Connecting to EntityServer at " << addrStr << ":" << port << std::endl;
// Update target address for EntityServer
sockaddr_in* entityAddr = reinterpret_cast<sockaddr_in*>(&m_entityAddr);
@@ -362,14 +472,89 @@ void OverteClient::handleDomainListReply(const char* data, size_t len) {
entityAddr->sin_addr = publicAddr.sin_addr;
m_entityAddrLen = sizeof(sockaddr_in);
m_entityServerReady = true;
// Send EntityQuery to request all entities
sendEntityQuery();
}
}
}
void OverteClient::handleDomainConnectionDenied(const char* data, size_t len) {
std::cerr << "[OverteClient] Domain connection DENIED!" << std::endl;
// Parse reason if available
if (len > 0) {
std::string reason(data, len);
std::cerr << "[OverteClient] Reason: " << reason << std::endl;
}
m_domainConnected = false;
}
void OverteClient::sendDomainConnectRequest() {
if (!m_udpReady || m_udpFd == -1) return;
// DomainConnectRequest packet (PacketType 0x04)
const unsigned char PACKET_TYPE_DOMAIN_CONNECT_REQUEST = 0x04;
// Build packet with authentication
// Format: [PacketType][ProtocolVersion:string][HardwareAddress:string][MachineFingerprint:string][Username:string][Password:string]
std::string protocolVersion = "Starworld-0.1";
std::string hardwareAddress = m_sessionUUID;
std::string machineFingerprint = m_sessionUUID; // Use session UUID as fingerprint
std::vector<char> packet;
packet.push_back(static_cast<char>(PACKET_TYPE_DOMAIN_CONNECT_REQUEST));
// Helper lambda to add length-prefixed string
auto addString = [&packet](const std::string& str) {
if (str.size() > 255) {
packet.push_back(static_cast<char>(255));
packet.insert(packet.end(), str.begin(), str.begin() + 255);
} else {
packet.push_back(static_cast<char>(str.size()));
packet.insert(packet.end(), str.begin(), str.end());
}
};
// Add protocol version
addString(protocolVersion);
// Add hardware address (session UUID)
addString(hardwareAddress);
// Add machine fingerprint
addString(machineFingerprint);
// Add username (empty if not authenticated)
addString(m_username);
// Add password/token (empty if not authenticated)
addString(m_password);
ssize_t s = ::sendto(m_udpFd, packet.data(), packet.size(), 0,
reinterpret_cast<sockaddr*>(&m_udpAddr), m_udpAddrLen);
if (s > 0) {
std::cout << "[OverteClient] Domain connect request sent (" << s << " bytes)" << std::endl;
std::cout << "[OverteClient] Protocol: " << protocolVersion << std::endl;
std::cout << "[OverteClient] Session: " << hardwareAddress << std::endl;
if (!m_username.empty()) {
std::cout << "[OverteClient] Username: " << m_username << std::endl;
}
// Hex dump first 32 bytes
std::cout << "[OverteClient] >>> Hex: ";
for (size_t i = 0; i < std::min(size_t(32), packet.size()); ++i) {
printf("%02x ", (unsigned char)packet[i]);
}
std::cout << std::endl;
} else {
std::cerr << "[OverteClient] Failed to send domain connect request: " << strerror(errno) << std::endl;
}
}
void OverteClient::sendDomainListRequest() {
// Send DomainList request packet (PacketType 0x02 typically)
// Send DomainList request packet (PacketType 0x02)
if (!m_udpReady || m_udpFd == -1) return;
const unsigned char PACKET_TYPE_DOMAIN_LIST_REQUEST = 0x02;
@@ -379,6 +564,56 @@ void OverteClient::sendDomainListRequest() {
reinterpret_cast<sockaddr*>(&m_udpAddr), m_udpAddrLen);
if (s > 0) {
std::cout << "[OverteClient] DomainList request sent" << std::endl;
} else {
std::cerr << "[OverteClient] Failed to send domain list request: " << strerror(errno) << std::endl;
}
}
void OverteClient::sendPing(int fd, const sockaddr_storage& addr, socklen_t addrLen) {
const unsigned char PACKET_TYPE_PING = 0x01;
// Ping packet: [PacketType][Timestamp:u64][PingType:u8]
char packet[1 + 8 + 1];
packet[0] = static_cast<char>(PACKET_TYPE_PING);
// Add timestamp (microseconds since epoch)
auto now = std::chrono::system_clock::now();
auto micros = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
std::memcpy(packet + 1, &micros, 8);
// Ping type (0 = local, 1 = public)
packet[9] = 0;
ssize_t s = ::sendto(fd, packet, sizeof(packet), 0,
reinterpret_cast<const sockaddr*>(&addr), addrLen);
if (s < 0 && errno != EWOULDBLOCK && errno != EAGAIN) {
std::cerr << "[OverteClient] Ping send failed: " << strerror(errno) << std::endl;
}
}
void OverteClient::sendEntityQuery() {
if (m_entityFd < 0 || !m_entityServerReady) return;
const unsigned char PACKET_TYPE_ENTITY_QUERY = 0x15;
// EntityQuery packet structure (simplified):
// [PacketType:u8][ConicalViews:bool][CameraFrustum if ConicalViews=true]
// For simplicity, send with ConicalViews=false to request all entities
std::vector<char> packet;
packet.push_back(static_cast<char>(PACKET_TYPE_ENTITY_QUERY));
packet.push_back(0); // ConicalViews = false
// With ConicalViews=false, we're requesting all entities
// Additional octree query parameters can be added here
ssize_t sent = sendto(m_entityFd, packet.data(), packet.size(), 0,
reinterpret_cast<const sockaddr*>(&m_entityAddr), m_entityAddrLen);
if (sent > 0) {
std::cout << "[OverteClient] Sent EntityQuery to EntityServer" << std::endl;
} else {
std::cerr << "[OverteClient] Failed to send EntityQuery: " << strerror(errno) << std::endl;
}
}

View File

@@ -49,8 +49,13 @@ public:
private:
void parseNetworkPackets(); // standards-aligned parsing (scaffold)
void parseEntityPacket(const char* data, size_t len);
void parseDomainPacket(const char* data, size_t len);
void handleDomainListReply(const char* data, size_t len);
void handleDomainConnectionDenied(const char* data, size_t len);
void sendDomainListRequest();
void sendDomainConnectRequest();
void sendEntityQuery();
void sendPing(int fd, const sockaddr_storage& addr, socklen_t addrLen);
std::string m_domainUrl;
std::string m_host{"127.0.0.1"};
@@ -60,6 +65,10 @@ private:
bool m_entityServer{false};
bool m_audioMixer{false};
bool m_useSimulation{false};
bool m_domainConnected{false};
std::string m_sessionUUID; // Our client session UUID
std::string m_username; // Domain authentication username
std::string m_password; // Domain authentication password (or token)
// Very small in-process world state for testing
std::unordered_map<std::uint64_t, OverteEntity> m_entities;