backend-infra-engineer: Release v0.3.3 snapshot

This commit is contained in:
scawful
2025-11-21 21:35:50 -05:00
parent 3d71417f62
commit 476dd1cd1c
818 changed files with 65706 additions and 35514 deletions

View File

@@ -14,60 +14,57 @@ CollaborationService::CollaborationService(Rom* rom)
version_mgr_(nullptr),
approval_mgr_(nullptr),
client_(std::make_unique<WebSocketClient>()),
sync_in_progress_(false) {
}
sync_in_progress_(false) {}
CollaborationService::~CollaborationService() {
Disconnect();
}
absl::Status CollaborationService::Initialize(
const Config& config,
RomVersionManager* version_mgr,
const Config& config, RomVersionManager* version_mgr,
ProposalApprovalManager* approval_mgr) {
config_ = config;
version_mgr_ = version_mgr;
approval_mgr_ = approval_mgr;
if (!version_mgr_) {
return absl::InvalidArgumentError("version_mgr cannot be null");
}
if (!approval_mgr_) {
return absl::InvalidArgumentError("approval_mgr cannot be null");
}
// Set up network event callbacks
client_->OnMessage("rom_sync", [this](const nlohmann::json& payload) {
OnRomSyncReceived(payload);
});
client_->OnMessage("proposal_shared", [this](const nlohmann::json& payload) {
OnProposalReceived(payload);
});
client_->OnMessage("proposal_vote_received", [this](const nlohmann::json& payload) {
OnProposalUpdated(payload);
});
client_->OnMessage(
"proposal_vote_received",
[this](const nlohmann::json& payload) { OnProposalUpdated(payload); });
client_->OnMessage("proposal_updated", [this](const nlohmann::json& payload) {
OnProposalUpdated(payload);
});
client_->OnMessage("participant_joined", [this](const nlohmann::json& payload) {
OnParticipantJoined(payload);
});
client_->OnMessage(
"participant_joined",
[this](const nlohmann::json& payload) { OnParticipantJoined(payload); });
client_->OnMessage("participant_left", [this](const nlohmann::json& payload) {
OnParticipantLeft(payload);
});
// Store initial ROM hash
if (rom_ && rom_->is_loaded()) {
last_sync_hash_ = version_mgr_->GetCurrentHash();
}
return absl::OkStatus();
}
@@ -81,82 +78,69 @@ void CollaborationService::Disconnect() {
}
}
absl::Status CollaborationService::HostSession(
const std::string& session_name,
const std::string& username,
bool ai_enabled) {
absl::Status CollaborationService::HostSession(const std::string& session_name,
const std::string& username,
bool ai_enabled) {
if (!client_->IsConnected()) {
return absl::FailedPreconditionError("Not connected to server");
}
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// Get current ROM hash
std::string rom_hash = version_mgr_->GetCurrentHash();
// Create initial safe point
auto snapshot_result = version_mgr_->CreateSnapshot(
"Session start",
username,
true // is_checkpoint
auto snapshot_result = version_mgr_->CreateSnapshot("Session start", username,
true // is_checkpoint
);
if (snapshot_result.ok()) {
version_mgr_->MarkAsSafePoint(*snapshot_result);
}
// Host session on server
auto session_result = client_->HostSession(
session_name,
username,
rom_hash,
ai_enabled
);
auto session_result =
client_->HostSession(session_name, username, rom_hash, ai_enabled);
if (!session_result.ok()) {
return session_result.status();
}
last_sync_hash_ = rom_hash;
return absl::OkStatus();
}
absl::Status CollaborationService::JoinSession(
const std::string& session_code,
const std::string& username) {
absl::Status CollaborationService::JoinSession(const std::string& session_code,
const std::string& username) {
if (!client_->IsConnected()) {
return absl::FailedPreconditionError("Not connected to server");
}
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// Create backup before joining
auto snapshot_result = version_mgr_->CreateSnapshot(
"Before joining session",
username,
true
);
auto snapshot_result =
version_mgr_->CreateSnapshot("Before joining session", username, true);
if (snapshot_result.ok()) {
version_mgr_->MarkAsSafePoint(*snapshot_result);
}
// Join session
auto session_result = client_->JoinSession(session_code, username);
if (!session_result.ok()) {
return session_result.status();
}
last_sync_hash_ = version_mgr_->GetCurrentHash();
return absl::OkStatus();
}
@@ -164,82 +148,73 @@ absl::Status CollaborationService::LeaveSession() {
if (!client_->InSession()) {
return absl::FailedPreconditionError("Not in a session");
}
return client_->LeaveSession();
}
absl::Status CollaborationService::SubmitChangesAsProposal(
const std::string& description,
const std::string& username) {
const std::string& description, const std::string& username) {
if (!client_->InSession()) {
return absl::FailedPreconditionError("Not in a session");
}
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// Generate diff from last sync
std::string current_hash = version_mgr_->GetCurrentHash();
if (current_hash == last_sync_hash_) {
return absl::OkStatus(); // No changes to submit
}
std::string diff = GenerateDiff(last_sync_hash_, current_hash);
// Create proposal data
nlohmann::json proposal_data = {
{"description", description},
{"type", "rom_modification"},
{"diff_data", diff},
{"from_hash", last_sync_hash_},
{"to_hash", current_hash}
};
nlohmann::json proposal_data = {{"description", description},
{"type", "rom_modification"},
{"diff_data", diff},
{"from_hash", last_sync_hash_},
{"to_hash", current_hash}};
// Submit to server
auto status = client_->ShareProposal(proposal_data, username);
if (status.ok() && config_.require_approval_for_sync) {
// Proposal submitted, waiting for approval
// The actual application will happen when approved
}
return status;
}
absl::Status CollaborationService::ApplyRomSync(
const std::string& diff_data,
const std::string& rom_hash,
const std::string& sender) {
absl::Status CollaborationService::ApplyRomSync(const std::string& diff_data,
const std::string& rom_hash,
const std::string& sender) {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
if (sync_in_progress_) {
return absl::UnavailableError("Sync already in progress");
}
sync_in_progress_ = true;
// Create snapshot before applying
if (config_.create_snapshot_before_sync) {
auto snapshot_result = version_mgr_->CreateSnapshot(
absl::StrFormat("Before sync from %s", sender),
"system",
false
);
absl::StrFormat("Before sync from %s", sender), "system", false);
if (!snapshot_result.ok()) {
sync_in_progress_ = false;
return absl::InternalError("Failed to create backup snapshot");
}
}
// Apply the diff
auto status = ApplyDiff(diff_data);
if (status.ok()) {
last_sync_hash_ = rom_hash;
} else {
@@ -251,74 +226,63 @@ absl::Status CollaborationService::ApplyRomSync(
}
}
}
sync_in_progress_ = false;
return status;
}
absl::Status CollaborationService::HandleIncomingProposal(
const std::string& proposal_id,
const nlohmann::json& proposal_data,
const std::string& proposal_id, const nlohmann::json& proposal_data,
const std::string& sender) {
if (!approval_mgr_) {
return absl::FailedPreconditionError("Approval manager not initialized");
}
// Submit to approval manager
return approval_mgr_->SubmitProposal(
proposal_id,
sender,
proposal_data["description"],
proposal_data
);
proposal_id, sender, proposal_data["description"], proposal_data);
}
absl::Status CollaborationService::VoteOnProposal(
const std::string& proposal_id,
bool approved,
const std::string& proposal_id, bool approved,
const std::string& username) {
if (!client_->InSession()) {
return absl::FailedPreconditionError("Not in a session");
}
// Vote locally
auto status = approval_mgr_->VoteOnProposal(proposal_id, username, approved);
if (!status.ok()) {
return status;
}
// Send vote to server
return client_->VoteOnProposal(proposal_id, approved, username);
}
absl::Status CollaborationService::ApplyApprovedProposal(
const std::string& proposal_id) {
if (!approval_mgr_->IsProposalApproved(proposal_id)) {
return absl::FailedPreconditionError("Proposal not approved");
}
auto proposal_result = approval_mgr_->GetProposalStatus(proposal_id);
if (!proposal_result.ok()) {
return proposal_result.status();
}
// Apply the proposal (implementation depends on proposal type)
// For now, just update status
auto status = client_->UpdateProposalStatus(proposal_id, "applied");
if (status.ok()) {
// Create snapshot after applying
version_mgr_->CreateSnapshot(
absl::StrFormat("Applied proposal %s", proposal_id.substr(0, 8)),
"system",
false
);
"system", false);
}
return status;
}
@@ -340,9 +304,9 @@ void CollaborationService::OnRomSyncReceived(const nlohmann::json& payload) {
std::string diff_data = payload["diff_data"];
std::string rom_hash = payload["rom_hash"];
std::string sender = payload["sender"];
auto status = ApplyRomSync(diff_data, rom_hash, sender);
if (!status.ok()) {
// Log error or notify user
}
@@ -352,22 +316,22 @@ void CollaborationService::OnProposalReceived(const nlohmann::json& payload) {
std::string proposal_id = payload["proposal_id"];
nlohmann::json proposal_data = payload["proposal_data"];
std::string sender = payload["sender"];
HandleIncomingProposal(proposal_id, proposal_data, sender);
}
void CollaborationService::OnProposalUpdated(const nlohmann::json& payload) {
std::string proposal_id = payload["proposal_id"];
if (payload.contains("status")) {
std::string status = payload["status"];
if (status == "approved" && approval_mgr_) {
// Proposal was approved, consider applying it
// This would be triggered by the host or based on voting results
}
}
if (payload.contains("votes")) {
// Vote update received
nlohmann::json votes = payload["votes"];
@@ -387,21 +351,19 @@ void CollaborationService::OnParticipantLeft(const nlohmann::json& payload) {
// Helper functions
std::string CollaborationService::GenerateDiff(
const std::string& from_hash,
const std::string& to_hash) {
std::string CollaborationService::GenerateDiff(const std::string& from_hash,
const std::string& to_hash) {
// Simplified diff generation
// In production, this would generate a binary diff
// For now, just return placeholder
if (!rom_ || !rom_->is_loaded()) {
return "";
}
// TODO: Implement proper binary diff generation
// This could use algorithms like bsdiff or a custom format
return "diff_placeholder";
}
@@ -409,10 +371,10 @@ absl::Status CollaborationService::ApplyDiff(const std::string& diff_data) {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// TODO: Implement proper diff application
// For now, just return success
return absl::OkStatus();
}
@@ -420,18 +382,18 @@ bool CollaborationService::ShouldAutoSync() {
if (!config_.auto_sync_enabled) {
return false;
}
if (!client_->IsConnected() || !client_->InSession()) {
return false;
}
if (sync_in_progress_) {
return false;
}
// Check if enough time has passed since last sync
// (Implementation would track last sync time)
return true;
}

View File

@@ -17,12 +17,12 @@ namespace net {
/**
* @class CollaborationService
* @brief High-level service integrating version management with networking
*
*
* Bridges the gap between:
* - Local ROM version management
* - Remote collaboration via WebSocket
* - Proposal approval workflow
*
*
* Features:
* - Automatic ROM sync on changes
* - Network-aware proposal approval
@@ -37,124 +37,115 @@ class CollaborationService {
bool require_approval_for_sync = true;
bool create_snapshot_before_sync = true;
};
explicit CollaborationService(Rom* rom);
~CollaborationService();
/**
* Initialize the service
*/
absl::Status Initialize(
const Config& config,
RomVersionManager* version_mgr,
ProposalApprovalManager* approval_mgr);
absl::Status Initialize(const Config& config, RomVersionManager* version_mgr,
ProposalApprovalManager* approval_mgr);
/**
* Connect to collaboration server
*/
absl::Status Connect(const std::string& host, int port = 8765);
/**
* Disconnect from server
*/
void Disconnect();
/**
* Host a new session
*/
absl::Status HostSession(
const std::string& session_name,
const std::string& username,
bool ai_enabled = true);
absl::Status HostSession(const std::string& session_name,
const std::string& username, bool ai_enabled = true);
/**
* Join existing session
*/
absl::Status JoinSession(
const std::string& session_code,
const std::string& username);
absl::Status JoinSession(const std::string& session_code,
const std::string& username);
/**
* Leave current session
*/
absl::Status LeaveSession();
/**
* Submit local changes as proposal
*/
absl::Status SubmitChangesAsProposal(
const std::string& description,
const std::string& username);
absl::Status SubmitChangesAsProposal(const std::string& description,
const std::string& username);
/**
* Apply received ROM sync
*/
absl::Status ApplyRomSync(
const std::string& diff_data,
const std::string& rom_hash,
const std::string& sender);
absl::Status ApplyRomSync(const std::string& diff_data,
const std::string& rom_hash,
const std::string& sender);
/**
* Handle incoming proposal
*/
absl::Status HandleIncomingProposal(
const std::string& proposal_id,
const nlohmann::json& proposal_data,
const std::string& sender);
absl::Status HandleIncomingProposal(const std::string& proposal_id,
const nlohmann::json& proposal_data,
const std::string& sender);
/**
* Vote on proposal
*/
absl::Status VoteOnProposal(
const std::string& proposal_id,
bool approved,
const std::string& username);
absl::Status VoteOnProposal(const std::string& proposal_id, bool approved,
const std::string& username);
/**
* Apply approved proposal
*/
absl::Status ApplyApprovedProposal(const std::string& proposal_id);
/**
* Get connection status
*/
bool IsConnected() const;
/**
* Get session info
*/
absl::StatusOr<SessionInfo> GetSessionInfo() const;
/**
* Get WebSocket client (for advanced usage)
*/
WebSocketClient* GetClient() { return client_.get(); }
/**
* Enable/disable auto-sync
*/
void SetAutoSync(bool enabled);
private:
Rom* rom_;
RomVersionManager* version_mgr_;
ProposalApprovalManager* approval_mgr_;
std::unique_ptr<WebSocketClient> client_;
Config config_;
// Sync state
std::string last_sync_hash_;
bool sync_in_progress_;
// Callbacks for network events
void OnRomSyncReceived(const nlohmann::json& payload);
void OnProposalReceived(const nlohmann::json& payload);
void OnProposalUpdated(const nlohmann::json& payload);
void OnParticipantJoined(const nlohmann::json& payload);
void OnParticipantLeft(const nlohmann::json& payload);
// Helper functions
std::string GenerateDiff(const std::string& from_hash, const std::string& to_hash);
std::string GenerateDiff(const std::string& from_hash,
const std::string& to_hash);
absl::Status ApplyDiff(const std::string& diff_data);
bool ShouldAutoSync();
};

View File

@@ -29,9 +29,10 @@ target_precompile_headers(yaze_net PRIVATE
target_include_directories(yaze_net PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/lib
${CMAKE_SOURCE_DIR}/src/lib/imgui
${SDL2_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/ext
${CMAKE_SOURCE_DIR}/ext/imgui
${CMAKE_SOURCE_DIR}/ext/json/include
${CMAKE_SOURCE_DIR}/ext/httplib
${PROJECT_BINARY_DIR}
)
@@ -39,13 +40,14 @@ target_link_libraries(yaze_net PUBLIC
yaze_util
yaze_common
${ABSL_TARGETS}
${YAZE_SDL2_TARGETS}
)
# Add JSON and httplib support if enabled
if(YAZE_WITH_JSON)
target_include_directories(yaze_net PUBLIC
${CMAKE_SOURCE_DIR}/third_party/json/include
${CMAKE_SOURCE_DIR}/third_party/httplib)
# Link nlohmann_json which provides the include directories automatically
target_link_libraries(yaze_net PUBLIC nlohmann_json::nlohmann_json)
target_include_directories(yaze_net PUBLIC ${CMAKE_SOURCE_DIR}/ext/httplib)
target_compile_definitions(yaze_net PUBLIC YAZE_WITH_JSON)
# Add threading support (cross-platform)
@@ -55,18 +57,30 @@ if(YAZE_WITH_JSON)
# Only link OpenSSL if gRPC is NOT enabled (to avoid duplicate symbol errors)
# When gRPC is enabled, it brings its own OpenSSL which we'll use instead
if(NOT YAZE_WITH_GRPC)
find_package(OpenSSL QUIET)
if(OpenSSL_FOUND)
target_link_libraries(yaze_net PUBLIC OpenSSL::SSL OpenSSL::Crypto)
target_compile_definitions(yaze_net PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
message(STATUS " - WebSocket with SSL/TLS support enabled")
# CRITICAL FIX: Disable OpenSSL on Windows to avoid missing header errors
# Windows CI doesn't have OpenSSL headers properly configured
# WebSocket will work with plain HTTP (no SSL/TLS) on Windows
if(NOT WIN32)
find_package(OpenSSL QUIET)
if(OpenSSL_FOUND)
target_link_libraries(yaze_net PUBLIC OpenSSL::SSL OpenSSL::Crypto)
target_compile_definitions(yaze_net PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
message(STATUS " - WebSocket with SSL/TLS support enabled")
else()
message(STATUS " - WebSocket without SSL/TLS (OpenSSL not found)")
endif()
else()
message(STATUS " - WebSocket without SSL/TLS (OpenSSL not found)")
message(STATUS " - Windows: WebSocket using plain HTTP (no SSL) - OpenSSL headers not available in CI")
endif()
else()
# When gRPC is enabled, still enable OpenSSL features but use gRPC's OpenSSL
target_compile_definitions(yaze_net PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
message(STATUS " - WebSocket with SSL/TLS support enabled via gRPC's OpenSSL")
# CRITICAL: Skip on Windows - gRPC's OpenSSL headers aren't accessible in Windows CI
if(NOT WIN32)
target_compile_definitions(yaze_net PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
message(STATUS " - WebSocket with SSL/TLS support enabled via gRPC's OpenSSL")
else()
message(STATUS " - Windows + gRPC: WebSocket using plain HTTP (no SSL) - OpenSSL headers not available")
endif()
endif()
# Windows-specific socket library
@@ -78,21 +92,7 @@ endif()
# Add gRPC support for ROM service
if(YAZE_WITH_GRPC)
target_add_protobuf(yaze_net ${PROJECT_SOURCE_DIR}/src/protos/rom_service.proto)
target_link_libraries(yaze_net PUBLIC
grpc++
grpc++_reflection
)
if(YAZE_PROTOBUF_TARGETS)
target_link_libraries(yaze_net PUBLIC ${YAZE_PROTOBUF_TARGETS})
if(MSVC AND YAZE_PROTOBUF_WHOLEARCHIVE_TARGETS)
foreach(_yaze_proto_target IN LISTS YAZE_PROTOBUF_WHOLEARCHIVE_TARGETS)
target_link_options(yaze_net PUBLIC /WHOLEARCHIVE:$<TARGET_FILE:${_yaze_proto_target}>)
endforeach()
endif()
endif()
target_link_libraries(yaze_net PUBLIC yaze_grpc_support)
message(STATUS " - gRPC ROM service enabled")
endif()

View File

@@ -3,8 +3,8 @@
#ifdef YAZE_WITH_GRPC
#include "absl/strings/str_format.h"
#include "app/rom.h"
#include "app/net/rom_version_manager.h"
#include "app/rom.h"
// Proto namespace alias for convenience
namespace rom_svc = ::yaze::proto;
@@ -13,77 +13,70 @@ namespace yaze {
namespace net {
RomServiceImpl::RomServiceImpl(
Rom* rom,
RomVersionManager* version_manager,
ProposalApprovalManager* approval_manager)
RomServiceImpl::RomServiceImpl(Rom* rom, RomVersionManager* version_manager,
ProposalApprovalManager* approval_manager)
: rom_(rom),
version_mgr_(version_manager),
approval_mgr_(approval_manager) {
}
approval_mgr_(approval_manager) {}
void RomServiceImpl::SetConfig(const Config& config) {
config_ = config;
}
grpc::Status RomServiceImpl::ReadBytes(
grpc::ServerContext* context,
const rom_svc::ReadBytesRequest* request,
rom_svc::ReadBytesResponse* response) {
grpc::Status RomServiceImpl::ReadBytes(grpc::ServerContext* context,
const rom_svc::ReadBytesRequest* request,
rom_svc::ReadBytesResponse* response) {
if (!rom_ || !rom_->is_loaded()) {
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded");
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
"ROM not loaded");
}
uint32_t address = request->address();
uint32_t length = request->length();
// Validate range
if (address + length > rom_->size()) {
return grpc::Status(
grpc::StatusCode::OUT_OF_RANGE,
absl::StrFormat("Read beyond ROM: 0x%X+%d > %d",
address, length, rom_->size()));
return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
absl::StrFormat("Read beyond ROM: 0x%X+%d > %d",
address, length, rom_->size()));
}
// Read data
const auto* data = rom_->data() + address;
response->set_data(data, length);
response->set_success(true);
return grpc::Status::OK;
}
grpc::Status RomServiceImpl::WriteBytes(
grpc::ServerContext* context,
const rom_svc::WriteBytesRequest* request,
grpc::ServerContext* context, const rom_svc::WriteBytesRequest* request,
rom_svc::WriteBytesResponse* response) {
if (!rom_ || !rom_->is_loaded()) {
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded");
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
"ROM not loaded");
}
uint32_t address = request->address();
const std::string& data = request->data();
// Validate range
if (address + data.size() > rom_->size()) {
return grpc::Status(
grpc::StatusCode::OUT_OF_RANGE,
absl::StrFormat("Write beyond ROM: 0x%X+%zu > %d",
address, data.size(), rom_->size()));
return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
absl::StrFormat("Write beyond ROM: 0x%X+%zu > %d",
address, data.size(), rom_->size()));
}
// Check if approval required
if (config_.require_approval_for_writes && approval_mgr_) {
// Create a proposal for this write
std::string proposal_id = absl::StrFormat(
"write_0x%X_%zu_bytes", address, data.size());
std::string proposal_id =
absl::StrFormat("write_0x%X_%zu_bytes", address, data.size());
if (request->has_proposal_id()) {
proposal_id = request->proposal_id();
}
// Check if proposal is approved
auto status = approval_mgr_->GetProposalStatus(proposal_id);
if (status != ProposalApprovalManager::ApprovalStatus::kApproved) {
@@ -93,7 +86,7 @@ grpc::Status RomServiceImpl::WriteBytes(
return grpc::Status::OK; // Not an error, just needs approval
}
}
// Create snapshot before write
if (version_mgr_) {
std::string snapshot_desc = absl::StrFormat(
@@ -103,104 +96,87 @@ grpc::Status RomServiceImpl::WriteBytes(
response->set_snapshot_id(std::to_string(snapshot_result.value()));
}
}
// Perform write
std::memcpy(rom_->mutable_data() + address, data.data(), data.size());
response->set_success(true);
response->set_message("Write successful");
return grpc::Status::OK;
}
grpc::Status RomServiceImpl::GetRomInfo(
grpc::ServerContext* context,
const rom_svc::GetRomInfoRequest* request,
grpc::ServerContext* context, const rom_svc::GetRomInfoRequest* request,
rom_svc::GetRomInfoResponse* response) {
if (!rom_ || !rom_->is_loaded()) {
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded");
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
"ROM not loaded");
}
auto* info = response->mutable_info();
info->set_title(rom_->title());
info->set_size(rom_->size());
info->set_is_loaded(rom_->is_loaded());
info->set_filename(rom_->filename());
return grpc::Status::OK;
}
grpc::Status RomServiceImpl::GetTileData(
grpc::ServerContext* context,
const rom_svc::GetTileDataRequest* request,
grpc::ServerContext* context, const rom_svc::GetTileDataRequest* request,
rom_svc::GetTileDataResponse* response) {
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
"GetTileData not yet implemented");
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
"GetTileData not yet implemented");
}
grpc::Status RomServiceImpl::SetTileData(
grpc::ServerContext* context,
const rom_svc::SetTileDataRequest* request,
grpc::ServerContext* context, const rom_svc::SetTileDataRequest* request,
rom_svc::SetTileDataResponse* response) {
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
"SetTileData not yet implemented");
"SetTileData not yet implemented");
}
grpc::Status RomServiceImpl::GetMapData(
grpc::ServerContext* context,
const rom_svc::GetMapDataRequest* request,
grpc::ServerContext* context, const rom_svc::GetMapDataRequest* request,
rom_svc::GetMapDataResponse* response) {
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
"GetMapData not yet implemented");
"GetMapData not yet implemented");
}
grpc::Status RomServiceImpl::SetMapData(
grpc::ServerContext* context,
const rom_svc::SetMapDataRequest* request,
grpc::ServerContext* context, const rom_svc::SetMapDataRequest* request,
rom_svc::SetMapDataResponse* response) {
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
"SetMapData not yet implemented");
"SetMapData not yet implemented");
}
grpc::Status RomServiceImpl::GetSpriteData(
grpc::ServerContext* context,
const rom_svc::GetSpriteDataRequest* request,
grpc::ServerContext* context, const rom_svc::GetSpriteDataRequest* request,
rom_svc::GetSpriteDataResponse* response) {
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
"GetSpriteData not yet implemented");
"GetSpriteData not yet implemented");
}
grpc::Status RomServiceImpl::SetSpriteData(
grpc::ServerContext* context,
const rom_svc::SetSpriteDataRequest* request,
grpc::ServerContext* context, const rom_svc::SetSpriteDataRequest* request,
rom_svc::SetSpriteDataResponse* response) {
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
"SetSpriteData not yet implemented");
"SetSpriteData not yet implemented");
}
grpc::Status RomServiceImpl::GetDialogue(
grpc::ServerContext* context,
const rom_svc::GetDialogueRequest* request,
grpc::ServerContext* context, const rom_svc::GetDialogueRequest* request,
rom_svc::GetDialogueResponse* response) {
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
"GetDialogue not yet implemented");
"GetDialogue not yet implemented");
}
grpc::Status RomServiceImpl::SetDialogue(
grpc::ServerContext* context,
const rom_svc::SetDialogueRequest* request,
grpc::ServerContext* context, const rom_svc::SetDialogueRequest* request,
rom_svc::SetDialogueResponse* response) {
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
"SetDialogue not yet implemented");
"SetDialogue not yet implemented");
}
} // namespace net

View File

@@ -15,6 +15,7 @@
#undef ERROR
#endif // _WIN32
#include <grpcpp/grpcpp.h>
#include "protos/rom_service.grpc.pb.h"
#ifdef _WIN32
#pragma pop_macro("DWORD")
@@ -23,8 +24,8 @@
// Note: Proto files will be generated to build directory
#endif
#include "app/rom.h"
#include "app/net/rom_version_manager.h"
#include "app/rom.h"
namespace yaze {
@@ -34,13 +35,13 @@ namespace net {
/**
* @brief gRPC service implementation for remote ROM manipulation
*
*
* Enables remote clients (like z3ed CLI) to:
* - Read/write ROM data
* - Submit proposals for collaborative editing
* - Manage ROM versions and snapshots
* - Query ROM structures (overworld, dungeons, sprites)
*
*
* Thread-safe and designed for concurrent access.
*/
class RomServiceImpl final : public proto::RomService::Service {
@@ -54,120 +55,113 @@ class RomServiceImpl final : public proto::RomService::Service {
int max_read_size_bytes = 1024 * 1024; // 1MB max per read
bool allow_raw_rom_access = true; // Allow direct byte access
};
/**
* @brief Construct ROM service
* @param rom Pointer to ROM instance (not owned)
* @param version_mgr Pointer to version manager (not owned, optional)
* @param approval_mgr Pointer to approval manager (not owned, optional)
*/
RomServiceImpl(Rom* rom,
RomVersionManager* version_mgr = nullptr,
RomServiceImpl(Rom* rom, RomVersionManager* version_mgr = nullptr,
ProposalApprovalManager* approval_mgr = nullptr);
~RomServiceImpl() override = default;
// Initialize with configuration
void SetConfig(const Config& config);
// =========================================================================
// Basic ROM Operations
// =========================================================================
grpc::Status ReadBytes(
grpc::ServerContext* context,
const proto::ReadBytesRequest* request,
proto::ReadBytesResponse* response) override;
grpc::Status WriteBytes(
grpc::ServerContext* context,
const proto::WriteBytesRequest* request,
proto::WriteBytesResponse* response) override;
grpc::Status GetRomInfo(
grpc::ServerContext* context,
const proto::GetRomInfoRequest* request,
proto::GetRomInfoResponse* response) override;
grpc::Status ReadBytes(grpc::ServerContext* context,
const proto::ReadBytesRequest* request,
proto::ReadBytesResponse* response) override;
grpc::Status WriteBytes(grpc::ServerContext* context,
const proto::WriteBytesRequest* request,
proto::WriteBytesResponse* response) override;
grpc::Status GetRomInfo(grpc::ServerContext* context,
const proto::GetRomInfoRequest* request,
proto::GetRomInfoResponse* response) override;
// =========================================================================
// Overworld Operations
// =========================================================================
grpc::Status ReadOverworldMap(
grpc::ServerContext* context,
const proto::ReadOverworldMapRequest* request,
proto::ReadOverworldMapResponse* response) override;
grpc::Status WriteOverworldTile(
grpc::ServerContext* context,
const proto::WriteOverworldTileRequest* request,
proto::WriteOverworldTileResponse* response) override;
// =========================================================================
// Dungeon Operations
// =========================================================================
grpc::Status ReadDungeonRoom(
grpc::ServerContext* context,
const proto::ReadDungeonRoomRequest* request,
proto::ReadDungeonRoomResponse* response) override;
grpc::Status WriteDungeonTile(
grpc::ServerContext* context,
const proto::WriteDungeonTileRequest* request,
proto::WriteDungeonTileResponse* response) override;
// =========================================================================
// Sprite Operations
// =========================================================================
grpc::Status ReadSprite(
grpc::ServerContext* context,
const proto::ReadSpriteRequest* request,
proto::ReadSpriteResponse* response) override;
grpc::Status ReadSprite(grpc::ServerContext* context,
const proto::ReadSpriteRequest* request,
proto::ReadSpriteResponse* response) override;
// =========================================================================
// Proposal System
// =========================================================================
grpc::Status SubmitRomProposal(
grpc::ServerContext* context,
const proto::SubmitRomProposalRequest* request,
proto::SubmitRomProposalResponse* response) override;
grpc::Status GetProposalStatus(
grpc::ServerContext* context,
const proto::GetProposalStatusRequest* request,
proto::GetProposalStatusResponse* response) override;
// =========================================================================
// Version Management
// =========================================================================
grpc::Status CreateSnapshot(
grpc::ServerContext* context,
const proto::CreateSnapshotRequest* request,
proto::CreateSnapshotResponse* response) override;
grpc::Status CreateSnapshot(grpc::ServerContext* context,
const proto::CreateSnapshotRequest* request,
proto::CreateSnapshotResponse* response) override;
grpc::Status RestoreSnapshot(
grpc::ServerContext* context,
const proto::RestoreSnapshotRequest* request,
proto::RestoreSnapshotResponse* response) override;
grpc::Status ListSnapshots(
grpc::ServerContext* context,
const proto::ListSnapshotsRequest* request,
proto::ListSnapshotsResponse* response) override;
grpc::Status ListSnapshots(grpc::ServerContext* context,
const proto::ListSnapshotsRequest* request,
proto::ListSnapshotsResponse* response) override;
private:
Config config_;
Rom* rom_; // Not owned
RomVersionManager* version_mgr_; // Not owned, may be null
Rom* rom_; // Not owned
RomVersionManager* version_mgr_; // Not owned, may be null
ProposalApprovalManager* approval_mgr_; // Not owned, may be null
// Helper to check if ROM is loaded
grpc::Status ValidateRomLoaded();
// Helper to create snapshot before write operations
absl::Status MaybeCreateSnapshot(const std::string& description);
};

View File

@@ -4,8 +4,8 @@
#include <chrono>
#include <cstring>
#include "absl/strings/str_format.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
// For compression (placeholder - would use zlib or similar)
#include <vector>
@@ -33,13 +33,15 @@ std::string ComputeHash(const std::vector<uint8_t>& data) {
std::string GenerateId() {
auto now = std::chrono::system_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()).count();
now.time_since_epoch())
.count();
return absl::StrFormat("snap_%lld", ms);
}
int64_t GetCurrentTimestamp() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
std::chrono::system_clock::now().time_since_epoch())
.count();
}
} // namespace
@@ -49,9 +51,7 @@ int64_t GetCurrentTimestamp() {
// ============================================================================
RomVersionManager::RomVersionManager(Rom* rom)
: rom_(rom),
last_backup_time_(0) {
}
: rom_(rom), last_backup_time_(0) {}
RomVersionManager::~RomVersionManager() {
// Cleanup if needed
@@ -59,34 +59,29 @@ RomVersionManager::~RomVersionManager() {
absl::Status RomVersionManager::Initialize(const Config& config) {
config_ = config;
// Create initial snapshot
auto initial_result = CreateSnapshot(
"Initial state",
"system",
true);
auto initial_result = CreateSnapshot("Initial state", "system", true);
if (!initial_result.ok()) {
return initial_result.status();
}
// Mark as safe point
return MarkAsSafePoint(*initial_result);
}
absl::StatusOr<std::string> RomVersionManager::CreateSnapshot(
const std::string& description,
const std::string& creator,
const std::string& description, const std::string& creator,
bool is_checkpoint) {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// Get ROM data
std::vector<uint8_t> rom_data(rom_->size());
std::memcpy(rom_data.data(), rom_->data(), rom_->size());
// Create snapshot
RomSnapshot snapshot;
snapshot.snapshot_id = GenerateId();
@@ -96,7 +91,7 @@ absl::StatusOr<std::string> RomVersionManager::CreateSnapshot(
snapshot.creator = creator;
snapshot.is_checkpoint = is_checkpoint;
snapshot.is_safe_point = false;
// Compress if enabled
if (config_.compress_snapshots) {
snapshot.rom_data = CompressData(rom_data);
@@ -105,37 +100,39 @@ absl::StatusOr<std::string> RomVersionManager::CreateSnapshot(
snapshot.rom_data = std::move(rom_data);
snapshot.compressed_size = snapshot.rom_data.size();
}
#ifdef YAZE_WITH_JSON
snapshot.metadata = nlohmann::json::object();
snapshot.metadata["size"] = rom_->size();
snapshot.metadata["auto_backup"] = !is_checkpoint;
#endif
// Store snapshot
snapshots_[snapshot.snapshot_id] = std::move(snapshot);
last_known_hash_ = snapshots_[snapshot.snapshot_id].rom_hash;
// Cleanup if needed
if (snapshots_.size() > config_.max_snapshots) {
CleanupOldSnapshots();
}
return snapshots_[snapshot.snapshot_id].snapshot_id;
}
absl::Status RomVersionManager::RestoreSnapshot(const std::string& snapshot_id) {
absl::Status RomVersionManager::RestoreSnapshot(
const std::string& snapshot_id) {
auto it = snapshots_.find(snapshot_id);
if (it == snapshots_.end()) {
return absl::NotFoundError(absl::StrCat("Snapshot not found: ", snapshot_id));
return absl::NotFoundError(
absl::StrCat("Snapshot not found: ", snapshot_id));
}
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
const RomSnapshot& snapshot = it->second;
// Decompress if needed
std::vector<uint8_t> rom_data;
if (config_.compress_snapshots) {
@@ -143,55 +140,54 @@ absl::Status RomVersionManager::RestoreSnapshot(const std::string& snapshot_id)
} else {
rom_data = snapshot.rom_data;
}
// Verify size matches
if (rom_data.size() != rom_->size()) {
return absl::DataLossError("Snapshot size mismatch");
}
// Create backup before restore
auto backup_result = CreateSnapshot(
"Pre-restore backup",
"system",
false);
auto backup_result = CreateSnapshot("Pre-restore backup", "system", false);
if (!backup_result.ok()) {
return absl::InternalError("Failed to create pre-restore backup");
}
// Restore ROM data
std::memcpy(rom_->mutable_data(), rom_data.data(), rom_data.size());
last_known_hash_ = snapshot.rom_hash;
return absl::OkStatus();
}
absl::Status RomVersionManager::MarkAsSafePoint(const std::string& snapshot_id) {
absl::Status RomVersionManager::MarkAsSafePoint(
const std::string& snapshot_id) {
auto it = snapshots_.find(snapshot_id);
if (it == snapshots_.end()) {
return absl::NotFoundError("Snapshot not found");
}
it->second.is_safe_point = true;
return absl::OkStatus();
}
std::vector<RomSnapshot> RomVersionManager::GetSnapshots(bool safe_points_only) const {
std::vector<RomSnapshot> RomVersionManager::GetSnapshots(
bool safe_points_only) const {
std::vector<RomSnapshot> result;
for (const auto& [id, snapshot] : snapshots_) {
if (!safe_points_only || snapshot.is_safe_point) {
result.push_back(snapshot);
}
}
// Sort by timestamp (newest first)
std::sort(result.begin(), result.end(),
[](const RomSnapshot& a, const RomSnapshot& b) {
return a.timestamp > b.timestamp;
});
return result;
}
@@ -209,12 +205,12 @@ absl::Status RomVersionManager::DeleteSnapshot(const std::string& snapshot_id) {
if (it == snapshots_.end()) {
return absl::NotFoundError("Snapshot not found");
}
// Don't allow deleting safe points
if (it->second.is_safe_point) {
return absl::FailedPreconditionError("Cannot delete safe point");
}
snapshots_.erase(it);
return absl::OkStatus();
}
@@ -223,29 +219,29 @@ absl::StatusOr<bool> RomVersionManager::DetectCorruption() {
if (!config_.enable_corruption_detection) {
return false;
}
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// Compute current hash
std::vector<uint8_t> current_data(rom_->size());
std::memcpy(current_data.data(), rom_->data(), rom_->size());
std::string current_hash = ComputeHash(current_data);
// Basic integrity checks
auto integrity_status = ValidateRomIntegrity();
if (!integrity_status.ok()) {
return true; // Corruption detected
}
// Check against last known good hash (if modified unexpectedly)
if (!last_known_hash_.empty() && current_hash != last_known_hash_) {
// ROM changed without going through version manager
// This might be intentional, so just flag it
return false;
}
return false;
}
@@ -255,7 +251,7 @@ absl::Status RomVersionManager::AutoRecover() {
if (snapshots.empty()) {
return absl::NotFoundError("No safe points available for recovery");
}
// Restore from most recent safe point
return RestoreSnapshot(snapshots[0].snapshot_id);
}
@@ -264,7 +260,7 @@ std::string RomVersionManager::GetCurrentHash() const {
if (!rom_ || !rom_->is_loaded()) {
return "";
}
std::vector<uint8_t> data(rom_->size());
std::memcpy(data.data(), rom_->data(), rom_->size());
return ComputeHash(data);
@@ -273,53 +269,56 @@ std::string RomVersionManager::GetCurrentHash() const {
absl::Status RomVersionManager::CleanupOldSnapshots() {
// Keep safe points and checkpoints
// Remove oldest auto-backups first
std::vector<std::pair<int64_t, std::string>> auto_backups;
for (const auto& [id, snapshot] : snapshots_) {
if (!snapshot.is_safe_point && !snapshot.is_checkpoint) {
auto_backups.push_back({snapshot.timestamp, id});
}
}
// Sort by timestamp (oldest first)
std::sort(auto_backups.begin(), auto_backups.end());
// Delete oldest until within limits
while (snapshots_.size() > config_.max_snapshots && !auto_backups.empty()) {
snapshots_.erase(auto_backups.front().second);
auto_backups.erase(auto_backups.begin());
}
// Check storage limit
while (GetTotalStorageUsed() > config_.max_storage_mb * 1024 * 1024 &&
!auto_backups.empty()) {
snapshots_.erase(auto_backups.front().second);
auto_backups.erase(auto_backups.begin());
}
return absl::OkStatus();
}
RomVersionManager::Stats RomVersionManager::GetStats() const {
Stats stats = {};
stats.total_snapshots = snapshots_.size();
for (const auto& [id, snapshot] : snapshots_) {
if (snapshot.is_safe_point) stats.safe_points++;
if (snapshot.is_checkpoint) stats.manual_checkpoints++;
if (!snapshot.is_checkpoint) stats.auto_backups++;
if (snapshot.is_safe_point)
stats.safe_points++;
if (snapshot.is_checkpoint)
stats.manual_checkpoints++;
if (!snapshot.is_checkpoint)
stats.auto_backups++;
stats.total_storage_bytes += snapshot.compressed_size;
if (stats.oldest_snapshot_timestamp == 0 ||
snapshot.timestamp < stats.oldest_snapshot_timestamp) {
stats.oldest_snapshot_timestamp = snapshot.timestamp;
}
if (snapshot.timestamp > stats.newest_snapshot_timestamp) {
stats.newest_snapshot_timestamp = snapshot.timestamp;
}
}
return stats;
}
@@ -329,7 +328,7 @@ std::string RomVersionManager::ComputeRomHash() const {
if (!rom_ || !rom_->is_loaded()) {
return "";
}
std::vector<uint8_t> data(rom_->size());
std::memcpy(data.data(), rom_->data(), rom_->size());
return ComputeHash(data);
@@ -352,18 +351,18 @@ absl::Status RomVersionManager::ValidateRomIntegrity() const {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// Basic checks
if (rom_->size() == 0) {
return absl::DataLossError("ROM size is zero");
}
// Check for valid SNES header
// (This is a simplified check - real validation would be more thorough)
if (rom_->size() < 0x8000) {
return absl::DataLossError("ROM too small to be valid");
}
return absl::OkStatus();
}
@@ -380,9 +379,7 @@ size_t RomVersionManager::GetTotalStorageUsed() const {
// ============================================================================
ProposalApprovalManager::ProposalApprovalManager(RomVersionManager* version_mgr)
: version_mgr_(version_mgr),
mode_(ApprovalMode::kHostOnly) {
}
: version_mgr_(version_mgr), mode_(ApprovalMode::kHostOnly) {}
void ProposalApprovalManager::SetApprovalMode(ApprovalMode mode) {
mode_ = mode;
@@ -393,53 +390,46 @@ void ProposalApprovalManager::SetHost(const std::string& host_username) {
}
absl::Status ProposalApprovalManager::SubmitProposal(
const std::string& proposal_id,
const std::string& sender,
const std::string& description,
const nlohmann::json& proposal_data) {
const std::string& proposal_id, const std::string& sender,
const std::string& description, const nlohmann::json& proposal_data) {
ApprovalStatus status;
status.proposal_id = proposal_id;
status.status = "pending";
status.created_at = GetCurrentTimestamp();
status.decided_at = 0;
// Create snapshot before potential application
auto snapshot_result = version_mgr_->CreateSnapshot(
absl::StrCat("Before proposal: ", description),
sender,
false);
absl::StrCat("Before proposal: ", description), sender, false);
if (!snapshot_result.ok()) {
return snapshot_result.status();
}
status.snapshot_before = *snapshot_result;
proposals_[proposal_id] = status;
return absl::OkStatus();
}
absl::Status ProposalApprovalManager::VoteOnProposal(
const std::string& proposal_id,
const std::string& username,
const std::string& proposal_id, const std::string& username,
bool approved) {
auto it = proposals_.find(proposal_id);
if (it == proposals_.end()) {
return absl::NotFoundError("Proposal not found");
}
ApprovalStatus& status = it->second;
if (status.status != "pending") {
return absl::FailedPreconditionError("Proposal already decided");
}
// Record vote
status.votes[username] = approved;
// Check if decision can be made
if (CheckApprovalThreshold(status)) {
status.status = "approved";
@@ -448,23 +438,23 @@ absl::Status ProposalApprovalManager::VoteOnProposal(
// Check if rejection threshold reached
size_t rejection_count = 0;
for (const auto& [user, vote] : status.votes) {
if (!vote) rejection_count++;
if (!vote)
rejection_count++;
}
// If host rejected (in host-only mode), reject immediately
if (mode_ == ApprovalMode::kHostOnly &&
username == host_username_ && !approved) {
if (mode_ == ApprovalMode::kHostOnly && username == host_username_ &&
!approved) {
status.status = "rejected";
status.decided_at = GetCurrentTimestamp();
}
}
return absl::OkStatus();
}
bool ProposalApprovalManager::CheckApprovalThreshold(
const ApprovalStatus& status) const {
switch (mode_) {
case ApprovalMode::kHostOnly:
// Only host vote matters
@@ -472,29 +462,31 @@ bool ProposalApprovalManager::CheckApprovalThreshold(
return status.votes.at(host_username_);
}
return false;
case ApprovalMode::kMajorityVote: {
size_t approval_count = 0;
for (const auto& [user, approved] : status.votes) {
if (approved) approval_count++;
if (approved)
approval_count++;
}
return approval_count > participants_.size() / 2;
}
case ApprovalMode::kUnanimous: {
if (status.votes.size() < participants_.size()) {
return false; // Not everyone voted yet
}
for (const auto& [user, approved] : status.votes) {
if (!approved) return false;
if (!approved)
return false;
}
return true;
}
case ApprovalMode::kAutoApprove:
return true;
}
return false;
}
@@ -507,7 +499,7 @@ bool ProposalApprovalManager::IsProposalApproved(
return it->second.status == "approved";
}
std::vector<ProposalApprovalManager::ApprovalStatus>
std::vector<ProposalApprovalManager::ApprovalStatus>
ProposalApprovalManager::GetPendingProposals() const {
std::vector<ApprovalStatus> pending;
for (const auto& [id, status] : proposals_) {
@@ -518,7 +510,7 @@ ProposalApprovalManager::GetPendingProposals() const {
return pending;
}
absl::StatusOr<ProposalApprovalManager::ApprovalStatus>
absl::StatusOr<ProposalApprovalManager::ApprovalStatus>
ProposalApprovalManager::GetProposalStatus(
const std::string& proposal_id) const {
auto it = proposals_.find(proposal_id);

View File

@@ -29,12 +29,12 @@ struct RomSnapshot {
std::string rom_hash;
std::vector<uint8_t> rom_data;
size_t compressed_size;
// Metadata
std::string creator;
bool is_checkpoint; // Manual checkpoint vs auto-backup
bool is_safe_point; // Marked as "known good" by host
#ifdef YAZE_WITH_JSON
nlohmann::json metadata; // Custom metadata (proposals applied, etc.)
#endif
@@ -55,7 +55,7 @@ struct VersionDiff {
/**
* @class RomVersionManager
* @brief Manages ROM versioning, snapshots, and rollback capabilities
*
*
* Provides:
* - Automatic periodic snapshots
* - Manual checkpoints
@@ -73,87 +73,84 @@ class RomVersionManager {
bool compress_snapshots = true;
bool enable_corruption_detection = true;
};
explicit RomVersionManager(Rom* rom);
~RomVersionManager();
/**
* Initialize version management
*/
absl::Status Initialize(const Config& config);
/**
* Create a snapshot of current ROM state
*/
absl::StatusOr<std::string> CreateSnapshot(
const std::string& description,
const std::string& creator,
bool is_checkpoint = false);
absl::StatusOr<std::string> CreateSnapshot(const std::string& description,
const std::string& creator,
bool is_checkpoint = false);
/**
* Restore ROM to a previous snapshot
*/
absl::Status RestoreSnapshot(const std::string& snapshot_id);
/**
* Mark a snapshot as a safe point (host-verified)
*/
absl::Status MarkAsSafePoint(const std::string& snapshot_id);
/**
* Get all snapshots, sorted by timestamp
*/
std::vector<RomSnapshot> GetSnapshots(bool safe_points_only = false) const;
/**
* Get a specific snapshot
*/
absl::StatusOr<RomSnapshot> GetSnapshot(const std::string& snapshot_id) const;
/**
* Delete a snapshot
*/
absl::Status DeleteSnapshot(const std::string& snapshot_id);
/**
* Generate diff between two snapshots
*/
absl::StatusOr<VersionDiff> GenerateDiff(
const std::string& from_id,
const std::string& to_id) const;
absl::StatusOr<VersionDiff> GenerateDiff(const std::string& from_id,
const std::string& to_id) const;
/**
* Check for ROM corruption
*/
absl::StatusOr<bool> DetectCorruption();
/**
* Auto-recover from corruption using nearest safe point
*/
absl::Status AutoRecover();
/**
* Export snapshot to file
*/
absl::Status ExportSnapshot(
const std::string& snapshot_id,
const std::string& filepath);
absl::Status ExportSnapshot(const std::string& snapshot_id,
const std::string& filepath);
/**
* Import snapshot from file
*/
absl::Status ImportSnapshot(const std::string& filepath);
/**
* Get current ROM hash
*/
std::string GetCurrentHash() const;
/**
* Cleanup old snapshots based on policy
*/
absl::Status CleanupOldSnapshots();
/**
* Get statistics
*/
@@ -167,18 +164,19 @@ class RomVersionManager {
int64_t newest_snapshot_timestamp;
};
Stats GetStats() const;
private:
Rom* rom_;
Config config_;
std::map<std::string, RomSnapshot> snapshots_;
std::string last_known_hash_;
int64_t last_backup_time_;
// Helper functions
std::string ComputeRomHash() const;
std::vector<uint8_t> CompressData(const std::vector<uint8_t>& data) const;
std::vector<uint8_t> DecompressData(const std::vector<uint8_t>& compressed) const;
std::vector<uint8_t> DecompressData(
const std::vector<uint8_t>& compressed) const;
absl::Status ValidateRomIntegrity() const;
size_t GetTotalStorageUsed() const;
void PruneOldSnapshots();
@@ -187,7 +185,7 @@ class RomVersionManager {
/**
* @class ProposalApprovalManager
* @brief Manages proposal approval workflow for collaborative sessions
*
*
* Features:
* - Host approval required for all changes
* - Participant voting system
@@ -197,12 +195,12 @@ class RomVersionManager {
class ProposalApprovalManager {
public:
enum class ApprovalMode {
kHostOnly, // Only host can approve
kMajorityVote, // Majority of participants must approve
kUnanimous, // All participants must approve
kAutoApprove // No approval needed (dangerous!)
kHostOnly, // Only host can approve
kMajorityVote, // Majority of participants must approve
kUnanimous, // All participants must approve
kAutoApprove // No approval needed (dangerous!)
};
struct ApprovalStatus {
std::string proposal_id;
std::string status; // "pending", "approved", "rejected", "applied"
@@ -212,76 +210,71 @@ class ProposalApprovalManager {
std::string snapshot_before; // Snapshot ID before applying
std::string snapshot_after; // Snapshot ID after applying
};
explicit ProposalApprovalManager(RomVersionManager* version_mgr);
/**
* Set approval mode for the session
*/
void SetApprovalMode(ApprovalMode mode);
/**
* Set host username
*/
void SetHost(const std::string& host_username);
/**
* Submit a proposal for approval
*/
absl::Status SubmitProposal(
const std::string& proposal_id,
const std::string& sender,
const std::string& description,
const nlohmann::json& proposal_data);
absl::Status SubmitProposal(const std::string& proposal_id,
const std::string& sender,
const std::string& description,
const nlohmann::json& proposal_data);
/**
* Vote on a proposal
*/
absl::Status VoteOnProposal(
const std::string& proposal_id,
const std::string& username,
bool approved);
absl::Status VoteOnProposal(const std::string& proposal_id,
const std::string& username, bool approved);
/**
* Apply an approved proposal
*/
absl::Status ApplyProposal(
const std::string& proposal_id,
Rom* rom);
absl::Status ApplyProposal(const std::string& proposal_id, Rom* rom);
/**
* Reject and rollback a proposal
*/
absl::Status RejectProposal(const std::string& proposal_id);
/**
* Get proposal status
*/
absl::StatusOr<ApprovalStatus> GetProposalStatus(
const std::string& proposal_id) const;
/**
* Get all pending proposals
*/
std::vector<ApprovalStatus> GetPendingProposals() const;
/**
* Check if proposal is approved
*/
bool IsProposalApproved(const std::string& proposal_id) const;
/**
* Get audit log
*/
std::vector<ApprovalStatus> GetAuditLog() const;
private:
RomVersionManager* version_mgr_;
ApprovalMode mode_;
std::string host_username_;
std::map<std::string, ApprovalStatus> proposals_;
std::vector<std::string> participants_;
bool CheckApprovalThreshold(const ApprovalStatus& status) const;
};

View File

@@ -9,7 +9,9 @@
// Cross-platform WebSocket support using httplib
#ifdef YAZE_WITH_JSON
#ifndef _WIN32
#define CPPHTTPLIB_OPENSSL_SUPPORT
#endif
#include "httplib.h"
#endif
@@ -23,112 +25,111 @@ namespace net {
class WebSocketClient::Impl {
public:
Impl() : connected_(false), should_stop_(false) {}
~Impl() {
Disconnect();
}
~Impl() { Disconnect(); }
absl::Status Connect(const std::string& host, int port) {
std::lock_guard<std::mutex> lock(mutex_);
if (connected_) {
return absl::AlreadyExistsError("Already connected");
}
host_ = host;
port_ = port;
try {
// httplib WebSocket connection (cross-platform)
std::string url = absl::StrFormat("ws://%s:%d", host, port);
// Create WebSocket connection
client_ = std::make_unique<httplib::Client>(host, port);
client_->set_connection_timeout(5, 0); // 5 seconds
client_->set_read_timeout(30, 0); // 30 seconds
client_->set_read_timeout(30, 0); // 30 seconds
connected_ = true;
should_stop_ = false;
// Start receive thread
receive_thread_ = std::thread([this]() { ReceiveLoop(); });
return absl::OkStatus();
} catch (const std::exception& e) {
return absl::UnavailableError(
absl::StrCat("Failed to connect: ", e.what()));
}
}
void Disconnect() {
std::lock_guard<std::mutex> lock(mutex_);
if (!connected_) return;
if (!connected_)
return;
should_stop_ = true;
connected_ = false;
if (receive_thread_.joinable()) {
receive_thread_.join();
}
client_.reset();
}
absl::Status Send(const std::string& message) {
std::lock_guard<std::mutex> lock(mutex_);
if (!connected_) {
return absl::FailedPreconditionError("Not connected");
}
try {
// In a real implementation, this would use WebSocket send
// For now, we'll use HTTP POST as fallback
auto res = client_->Post("/message", message, "application/json");
if (!res) {
return absl::UnavailableError("Failed to send message");
}
if (res->status != 200) {
return absl::InternalError(
absl::StrFormat("Server error: %d", res->status));
}
return absl::OkStatus();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrCat("Send failed: ", e.what()));
}
}
void SetMessageCallback(std::function<void(const std::string&)> callback) {
std::lock_guard<std::mutex> lock(mutex_);
message_callback_ = callback;
}
void SetErrorCallback(std::function<void(const std::string&)> callback) {
std::lock_guard<std::mutex> lock(mutex_);
error_callback_ = callback;
}
bool IsConnected() const {
std::lock_guard<std::mutex> lock(mutex_);
return connected_;
}
private:
void ReceiveLoop() {
while (!should_stop_) {
try {
// Poll for messages (platform-independent)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// In a real WebSocket implementation, this would receive messages
// For now, this is a placeholder for the receive loop
} catch (const std::exception& e) {
if (error_callback_) {
error_callback_(e.what());
@@ -136,16 +137,16 @@ class WebSocketClient::Impl {
}
}
}
mutable std::mutex mutex_;
std::unique_ptr<httplib::Client> client_;
std::thread receive_thread_;
std::string host_;
int port_;
bool connected_;
bool should_stop_;
std::function<void(const std::string&)> message_callback_;
std::function<void(const std::string&)> error_callback_;
};
@@ -174,9 +175,7 @@ class WebSocketClient::Impl {
// ============================================================================
WebSocketClient::WebSocketClient()
: impl_(std::make_unique<Impl>()),
state_(ConnectionState::kDisconnected) {
}
: impl_(std::make_unique<Impl>()), state_(ConnectionState::kDisconnected) {}
WebSocketClient::~WebSocketClient() {
Disconnect();
@@ -184,13 +183,13 @@ WebSocketClient::~WebSocketClient() {
absl::Status WebSocketClient::Connect(const std::string& host, int port) {
auto status = impl_->Connect(host, port);
if (status.ok()) {
SetState(ConnectionState::kConnected);
} else {
SetState(ConnectionState::kError);
}
return status;
}
@@ -201,31 +200,25 @@ void WebSocketClient::Disconnect() {
}
absl::StatusOr<SessionInfo> WebSocketClient::HostSession(
const std::string& session_name,
const std::string& username,
const std::string& rom_hash,
bool ai_enabled) {
const std::string& session_name, const std::string& username,
const std::string& rom_hash, bool ai_enabled) {
#ifdef YAZE_WITH_JSON
if (!IsConnected()) {
return absl::FailedPreconditionError("Not connected to server");
}
nlohmann::json message = {
{"type", "host_session"},
{"payload", {
{"session_name", session_name},
{"username", username},
{"rom_hash", rom_hash},
{"ai_enabled", ai_enabled}
}}
};
nlohmann::json message = {{"type", "host_session"},
{"payload",
{{"session_name", session_name},
{"username", username},
{"rom_hash", rom_hash},
{"ai_enabled", ai_enabled}}}};
auto status = SendRaw(message);
if (!status.ok()) {
return status;
}
// In a real implementation, we'd wait for the server response
// For now, return a placeholder
SessionInfo session;
@@ -233,7 +226,7 @@ absl::StatusOr<SessionInfo> WebSocketClient::HostSession(
session.host = username;
session.rom_hash = rom_hash;
session.ai_enabled = ai_enabled;
current_session_ = session;
return session;
#else
@@ -242,31 +235,25 @@ absl::StatusOr<SessionInfo> WebSocketClient::HostSession(
}
absl::StatusOr<SessionInfo> WebSocketClient::JoinSession(
const std::string& session_code,
const std::string& username) {
const std::string& session_code, const std::string& username) {
#ifdef YAZE_WITH_JSON
if (!IsConnected()) {
return absl::FailedPreconditionError("Not connected to server");
}
nlohmann::json message = {
{"type", "join_session"},
{"payload", {
{"session_code", session_code},
{"username", username}
}}
};
{"type", "join_session"},
{"payload", {{"session_code", session_code}, {"username", username}}}};
auto status = SendRaw(message);
if (!status.ok()) {
return status;
}
// Placeholder - would wait for server response
SessionInfo session;
session.session_code = session_code;
current_session_ = session;
return session;
#else
@@ -279,12 +266,9 @@ absl::Status WebSocketClient::LeaveSession() {
if (!InSession()) {
return absl::FailedPreconditionError("Not in a session");
}
nlohmann::json message = {
{"type", "leave_session"},
{"payload", {}}
};
nlohmann::json message = {{"type", "leave_session"}, {"payload", {}}};
auto status = SendRaw(message);
current_session_ = SessionInfo{};
return status;
@@ -293,80 +277,57 @@ absl::Status WebSocketClient::LeaveSession() {
#endif
}
absl::Status WebSocketClient::SendChatMessage(
const std::string& message,
const std::string& sender) {
absl::Status WebSocketClient::SendChatMessage(const std::string& message,
const std::string& sender) {
#ifdef YAZE_WITH_JSON
nlohmann::json msg = {
{"type", "chat_message"},
{"payload", {
{"message", message},
{"sender", sender}
}}
};
{"type", "chat_message"},
{"payload", {{"message", message}, {"sender", sender}}}};
return SendRaw(msg);
#else
return absl::UnimplementedError("JSON support required");
#endif
}
absl::Status WebSocketClient::SendRomSync(
const std::string& diff_data,
const std::string& rom_hash,
const std::string& sender) {
absl::Status WebSocketClient::SendRomSync(const std::string& diff_data,
const std::string& rom_hash,
const std::string& sender) {
#ifdef YAZE_WITH_JSON
nlohmann::json message = {
{"type", "rom_sync"},
{"payload", {
{"diff_data", diff_data},
{"rom_hash", rom_hash},
{"sender", sender}
}}
};
{"type", "rom_sync"},
{"payload",
{{"diff_data", diff_data}, {"rom_hash", rom_hash}, {"sender", sender}}}};
return SendRaw(message);
#else
return absl::UnimplementedError("JSON support required");
#endif
}
absl::Status WebSocketClient::ShareProposal(
const nlohmann::json& proposal_data,
const std::string& sender) {
absl::Status WebSocketClient::ShareProposal(const nlohmann::json& proposal_data,
const std::string& sender) {
#ifdef YAZE_WITH_JSON
nlohmann::json message = {
{"type", "proposal_share"},
{"payload", {
{"sender", sender},
{"proposal_data", proposal_data}
}}
};
{"type", "proposal_share"},
{"payload", {{"sender", sender}, {"proposal_data", proposal_data}}}};
return SendRaw(message);
#else
return absl::UnimplementedError("JSON support required");
#endif
}
absl::Status WebSocketClient::VoteOnProposal(
const std::string& proposal_id,
bool approved,
const std::string& username) {
absl::Status WebSocketClient::VoteOnProposal(const std::string& proposal_id,
bool approved,
const std::string& username) {
#ifdef YAZE_WITH_JSON
nlohmann::json message = {
{"type", "proposal_vote"},
{"payload", {
{"proposal_id", proposal_id},
{"approved", approved},
{"username", username}
}}
};
nlohmann::json message = {{"type", "proposal_vote"},
{"payload",
{{"proposal_id", proposal_id},
{"approved", approved},
{"username", username}}}};
return SendRaw(message);
#else
return absl::UnimplementedError("JSON support required");
@@ -374,25 +335,20 @@ absl::Status WebSocketClient::VoteOnProposal(
}
absl::Status WebSocketClient::UpdateProposalStatus(
const std::string& proposal_id,
const std::string& status) {
const std::string& proposal_id, const std::string& status) {
#ifdef YAZE_WITH_JSON
nlohmann::json message = {
{"type", "proposal_update"},
{"payload", {
{"proposal_id", proposal_id},
{"status", status}
}}
};
{"type", "proposal_update"},
{"payload", {{"proposal_id", proposal_id}, {"status", status}}}};
return SendRaw(message);
#else
return absl::UnimplementedError("JSON support required");
#endif
}
void WebSocketClient::OnMessage(const std::string& type, MessageCallback callback) {
void WebSocketClient::OnMessage(const std::string& type,
MessageCallback callback) {
message_callbacks_[type].push_back(callback);
}
@@ -418,7 +374,7 @@ void WebSocketClient::HandleMessage(const std::string& message) {
try {
auto json = nlohmann::json::parse(message);
std::string type = json["type"];
auto it = message_callbacks_.find(type);
if (it != message_callbacks_.end()) {
for (auto& callback : it->second) {

View File

@@ -48,7 +48,7 @@ struct SessionInfo {
/**
* @class WebSocketClient
* @brief WebSocket client for connecting to yaze-server
*
*
* Provides:
* - Connection management with auto-reconnect
* - Session hosting and joining
@@ -61,148 +61,138 @@ class WebSocketClient {
using MessageCallback = std::function<void(const nlohmann::json&)>;
using ErrorCallback = std::function<void(const std::string&)>;
using StateCallback = std::function<void(ConnectionState)>;
WebSocketClient();
~WebSocketClient();
/**
* Connect to yaze-server
* @param host Server hostname/IP
* @param port Server port (default: 8765)
*/
absl::Status Connect(const std::string& host, int port = 8765);
/**
* Disconnect from server
*/
void Disconnect();
/**
* Host a new collaboration session
*/
absl::StatusOr<SessionInfo> HostSession(
const std::string& session_name,
const std::string& username,
const std::string& rom_hash,
bool ai_enabled = true);
absl::StatusOr<SessionInfo> HostSession(const std::string& session_name,
const std::string& username,
const std::string& rom_hash,
bool ai_enabled = true);
/**
* Join an existing session
*/
absl::StatusOr<SessionInfo> JoinSession(
const std::string& session_code,
const std::string& username);
absl::StatusOr<SessionInfo> JoinSession(const std::string& session_code,
const std::string& username);
/**
* Leave current session
*/
absl::Status LeaveSession();
/**
* Send chat message
*/
absl::Status SendChatMessage(
const std::string& message,
const std::string& sender);
absl::Status SendChatMessage(const std::string& message,
const std::string& sender);
/**
* Send ROM sync
*/
absl::Status SendRomSync(
const std::string& diff_data,
const std::string& rom_hash,
const std::string& sender);
absl::Status SendRomSync(const std::string& diff_data,
const std::string& rom_hash,
const std::string& sender);
/**
* Share snapshot
*/
absl::Status ShareSnapshot(
const std::string& snapshot_data,
const std::string& snapshot_type,
const std::string& sender);
absl::Status ShareSnapshot(const std::string& snapshot_data,
const std::string& snapshot_type,
const std::string& sender);
/**
* Share proposal for approval
*/
absl::Status ShareProposal(
const nlohmann::json& proposal_data,
const std::string& sender);
absl::Status ShareProposal(const nlohmann::json& proposal_data,
const std::string& sender);
/**
* Vote on proposal (approve/reject)
*/
absl::Status VoteOnProposal(
const std::string& proposal_id,
bool approved,
const std::string& username);
absl::Status VoteOnProposal(const std::string& proposal_id, bool approved,
const std::string& username);
/**
* Update proposal status
*/
absl::Status UpdateProposalStatus(
const std::string& proposal_id,
const std::string& status);
absl::Status UpdateProposalStatus(const std::string& proposal_id,
const std::string& status);
/**
* Send AI query
*/
absl::Status SendAIQuery(
const std::string& query,
const std::string& username);
absl::Status SendAIQuery(const std::string& query,
const std::string& username);
/**
* Register callback for specific message type
*/
void OnMessage(const std::string& type, MessageCallback callback);
/**
* Register callback for errors
*/
void OnError(ErrorCallback callback);
/**
* Register callback for connection state changes
*/
void OnStateChange(StateCallback callback);
/**
* Get current connection state
*/
ConnectionState GetState() const { return state_; }
/**
* Get current session info (if in a session)
*/
absl::StatusOr<SessionInfo> GetSessionInfo() const;
/**
* Check if connected
*/
bool IsConnected() const { return state_ == ConnectionState::kConnected; }
/**
* Check if in a session
*/
bool InSession() const { return !current_session_.session_id.empty(); }
private:
// Implementation details (using native WebSocket or library)
class Impl;
std::unique_ptr<Impl> impl_;
ConnectionState state_;
SessionInfo current_session_;
// Callbacks
std::map<std::string, std::vector<MessageCallback>> message_callbacks_;
std::vector<ErrorCallback> error_callbacks_;
std::vector<StateCallback> state_callbacks_;
// Internal message handling
void HandleMessage(const std::string& message);
void HandleError(const std::string& error);
void SetState(ConnectionState state);
// Send raw message
absl::Status SendRaw(const nlohmann::json& message);
};