added claude sonnet 3.7 comments
This commit is contained in:
parent
ac5254329d
commit
fb7615ed22
@ -45,6 +45,10 @@ cl /std:c++17 client.cpp -Fe:client.exe -Ic:\Users\cupcake\github\vcpkg\installe
|
|||||||
|
|
||||||
cl /std:c++17 server.cpp -Fe:server.exe -Ic:\Users\cupcake\github\vcpkg\installed\x64-windows\include\ /link c:\Users\cupcake\github\vcpkg\installed\x64-windows\lib\libzmq-mt-4_3_5.lib
|
cl /std:c++17 server.cpp -Fe:server.exe -Ic:\Users\cupcake\github\vcpkg\installed\x64-windows\include\ /link c:\Users\cupcake\github\vcpkg\installed\x64-windows\lib\libzmq-mt-4_3_5.lib
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
clang++ -o bin/Darwin/client obj/Darwin/client.o -mmacosx-version-min=11.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -framework Cocoa -framework IOKit -framework CoreVideo -framework CoreFoundation -framework Accelerate -fvisibility=hidden -O5 -rpath @executable_path -weak_library ./lib/libvulkan.dylib -L./lib -L../libzmq/build/lib -lbella_engine_sdk -lm -lzmq -ldl
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
355
client.cpp
355
client.cpp
@ -1,23 +1,42 @@
|
|||||||
#include <iostream>
|
/**
|
||||||
#include <fstream>
|
* ZeroMQ Client Implementation
|
||||||
#include <filesystem>
|
*
|
||||||
#include <random>
|
* This program creates a client that:
|
||||||
#include <thread>
|
* - Connects to a ZeroMQ server for network communication
|
||||||
#include <zmq.hpp>
|
* - Sends commands and processes responses
|
||||||
|
* - Uses secure communication with curve cryptography
|
||||||
|
* - Supports file transfer operations (send/get)
|
||||||
|
* - Maintains connection health through heartbeats
|
||||||
|
*/
|
||||||
|
|
||||||
#include <string>
|
#include <iostream> // For input/output operations (cout, cerr)
|
||||||
#include <vector>
|
#include <fstream> // For file operations (ifstream, ofstream)
|
||||||
#include <chrono>
|
#include <filesystem> // For filesystem operations
|
||||||
#include <vector>
|
#include <random> // For random number generation
|
||||||
|
#include <thread> // For creating and managing threads
|
||||||
|
#include <zmq.hpp> // ZeroMQ C++ binding for network communication
|
||||||
|
|
||||||
#include <atomic>
|
#include <string> // For string handling
|
||||||
#include <condition_variable>
|
#include <vector> // For dynamic arrays (vectors)
|
||||||
#include <mutex>
|
#include <chrono> // For time-related functions
|
||||||
|
#include <vector> // Redundant include
|
||||||
|
|
||||||
std::atomic<bool> heartbeat_state (true);
|
#include <atomic> // For thread-safe variables
|
||||||
std::atomic<bool> connection_state (false);
|
#include <condition_variable> // For thread synchronization
|
||||||
std::atomic<bool> abort_state (false);
|
#include <mutex> // For thread synchronization
|
||||||
|
|
||||||
|
// Atomic variables for thread-safe state tracking across multiple threads
|
||||||
|
std::atomic<bool> heartbeat_state (true); // Tracks if heartbeat communication is active
|
||||||
|
std::atomic<bool> connection_state (false); // Tracks if connection to server is established
|
||||||
|
std::atomic<bool> abort_state (false); // Flag to signal threads to terminate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to check if a string ends with a specific suffix
|
||||||
|
*
|
||||||
|
* @param str The string to check
|
||||||
|
* @param suffix The suffix to look for
|
||||||
|
* @return true if the string ends with the suffix, false otherwise
|
||||||
|
*/
|
||||||
bool ends_with_suffix(const std::string& str, const std::string& suffix) {
|
bool ends_with_suffix(const std::string& str, const std::string& suffix) {
|
||||||
if (str.length() >= 4) {
|
if (str.length() >= 4) {
|
||||||
return str.substr(str.length() - 4) == suffix;
|
return str.substr(str.length() - 4) == suffix;
|
||||||
@ -25,231 +44,283 @@ bool ends_with_suffix(const std::string& str, const std::string& suffix) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles command processing and communication with the server
|
||||||
|
*
|
||||||
|
* @param server_pkey The server's public key for secure communication
|
||||||
|
* @param client_pkey The client's public key
|
||||||
|
* @param client_skey The client's secret key
|
||||||
|
*/
|
||||||
void command_thread(std::string server_pkey, std::string client_pkey, std::string client_skey) {
|
void command_thread(std::string server_pkey, std::string client_pkey, std::string client_skey) {
|
||||||
const size_t chunk_size = 65536;
|
const size_t chunk_size = 65536; // 64KB chunks for file transfers
|
||||||
zmq::context_t ctx;
|
|
||||||
zmq::socket_t command_sock (ctx, zmq::socket_type::req);
|
// ZeroMQ setup
|
||||||
//command_sock.set(zmq::sockopt::sndtimeo, 10000);
|
zmq::context_t ctx; // Create a ZeroMQ context (required for all sockets)
|
||||||
|
zmq::socket_t command_sock (ctx, zmq::socket_type::req); // Create a REQ (request) socket
|
||||||
|
//command_sock.set(zmq::sockopt::sndtimeo, 10000); // Commented out timeout settings
|
||||||
//command_sock.set(zmq::sockopt::rcvtimeo, 10000);
|
//command_sock.set(zmq::sockopt::rcvtimeo, 10000);
|
||||||
command_sock.set(zmq::sockopt::curve_serverkey, server_pkey);
|
|
||||||
command_sock.set(zmq::sockopt::curve_publickey, client_pkey);
|
// Set up security for the socket
|
||||||
command_sock.set(zmq::sockopt::curve_secretkey, client_skey);
|
command_sock.set(zmq::sockopt::curve_serverkey, server_pkey); // Server's public key
|
||||||
command_sock.set(zmq::sockopt::linger, 1); // Close immediately on disconnect
|
command_sock.set(zmq::sockopt::curve_publickey, client_pkey); // Our public key
|
||||||
command_sock.connect("tcp://localhost:5556");
|
command_sock.set(zmq::sockopt::curve_secretkey, client_skey); // Our secret key
|
||||||
|
command_sock.set(zmq::sockopt::linger, 1); // Close immediately on disconnect
|
||||||
|
command_sock.connect("tcp://localhost:5556"); // Connect to the server's command port
|
||||||
|
|
||||||
std::string input;
|
std::string input;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// Check if we should terminate this thread
|
||||||
if(abort_state.load()==true) {
|
if(abort_state.load()==true) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get command from user input
|
||||||
std::getline(std::cin, input);
|
std::getline(std::cin, input);
|
||||||
std::stringstream ss(input);
|
std::stringstream ss(input); // Use stringstream to split input into words
|
||||||
std::string arg;
|
std::string arg;
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args; // Store each word as a separate argument
|
||||||
while (ss >> arg) {
|
while (ss >> arg) {
|
||||||
args.push_back(arg);
|
args.push_back(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity checks on input before sending to server
|
// Validate user input before sending to server
|
||||||
int num_args = args.size();
|
int num_args = args.size();
|
||||||
std::string command;
|
std::string command;
|
||||||
if (num_args > 0) {
|
if (num_args > 0) {
|
||||||
command = args[0];
|
command = args[0]; // First argument is the command
|
||||||
|
|
||||||
|
// Handle SEND command
|
||||||
if ( command == "send") {
|
if ( command == "send") {
|
||||||
if(num_args == 1) {
|
if(num_args == 1) { // Check if a filename was provided
|
||||||
std::cout << "Please provide a .bsz file" << std::endl;
|
std::cout << "Please provide a .bsz file" << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(!ends_with_suffix(args[1],"bsz")) {
|
if(!ends_with_suffix(args[1],"bsz")) { // Check file extension
|
||||||
std::cout << "Only .bsz files can be sent" << std::endl;
|
std::cout << "Only .bsz files can be sent" << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
std::cout << "Sending:" << args[1] << std::endl;
|
std::cout << "Sending:" << args[1] << std::endl;
|
||||||
} else if (command == "get") {
|
}
|
||||||
if(num_args == 1) {
|
// Handle GET command
|
||||||
|
else if (command == "get") {
|
||||||
|
if(num_args == 1) { // Check if a filename was provided
|
||||||
std::cout << "Please provide image filename" << std::endl;
|
std::cout << "Please provide image filename" << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if (command == "exit") {
|
}
|
||||||
|
// Handle EXIT command
|
||||||
|
else if (command == "exit") {
|
||||||
std::cout << "now" << std::endl;
|
std::cout << "now" << std::endl;
|
||||||
break;
|
break; // Exit the command loop
|
||||||
} else if (command == "render") {
|
}
|
||||||
|
// Handle RENDER command
|
||||||
|
else if (command == "render") {
|
||||||
std::string compoundArg;
|
std::string compoundArg;
|
||||||
if(num_args > 1) {
|
if(num_args > 1) {
|
||||||
|
// Combine remaining args into a single string
|
||||||
for (size_t i = 1; i < args.size(); ++i) {
|
for (size_t i = 1; i < args.size(); ++i) {
|
||||||
compoundArg += args[i];
|
compoundArg += args[i];
|
||||||
if (i < args.size() - 1) {
|
if (i < args.size() - 1) {
|
||||||
compoundArg += " "; // Add spaces between arguments
|
compoundArg += " "; // Add spaces between arguments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::cout << compoundArg << std::endl;
|
std::cout << compoundArg << std::endl;
|
||||||
}
|
}
|
||||||
} else if (command == "hello") {
|
}
|
||||||
;
|
// Handle HELLO command
|
||||||
} else {
|
else if (command == "hello") {
|
||||||
|
; // No special validation needed
|
||||||
|
}
|
||||||
|
// Handle unknown commands
|
||||||
|
else {
|
||||||
std::cout << "unknown" << std::endl;
|
std::cout << "unknown" << std::endl;
|
||||||
continue;
|
continue; // Skip sending to server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check input complete
|
// Send validated command to server over encrypted socket
|
||||||
// Push to server over encrypted socket
|
|
||||||
zmq::message_t server_response;
|
zmq::message_t server_response;
|
||||||
zmq::message_t msg_command(command);
|
zmq::message_t msg_command(command);
|
||||||
//>>>ZOUT
|
//>>>ZOUT (This comment indicates sending data to the network)
|
||||||
command_sock.send(msg_command, zmq::send_flags::none); //SEND
|
command_sock.send(msg_command, zmq::send_flags::none); // Send command to server
|
||||||
std::cout << "Sent: " << input.data() << std::endl;
|
std::cout << "Sent: " << input.data() << std::endl;
|
||||||
|
|
||||||
//ZIN<<<
|
//ZIN<<< (This comment indicates receiving data from the network)
|
||||||
command_sock.recv(server_response, zmq::recv_flags::none); //RECV
|
command_sock.recv(server_response, zmq::recv_flags::none); // Wait for server response
|
||||||
std::string response_str(static_cast<char*>(server_response.data()), server_response.size()-1);
|
std::string response_str(static_cast<char*>(server_response.data()), server_response.size()-1);
|
||||||
|
|
||||||
if(response_str=="RDY") { // Server acknowledges readiness for multi message commands
|
// Process server response
|
||||||
|
if(response_str=="RDY") { // Server indicates it's ready for further communication
|
||||||
std::cout << "Server Readiness: " << response_str << std::endl;
|
std::cout << "Server Readiness: " << response_str << std::endl;
|
||||||
|
|
||||||
|
// Handle different command types
|
||||||
if(input == "exit") {
|
if(input == "exit") {
|
||||||
break;
|
break; // Exit command - terminate the thread
|
||||||
// RENDER
|
// RENDER command
|
||||||
} else if(command == "render") {
|
} else if(command == "render") {
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("render"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("render"), zmq::send_flags::none); // Send render command again
|
||||||
//ZIN<<<
|
//ZIN<<<
|
||||||
command_sock.recv(server_response, zmq::recv_flags::none);
|
command_sock.recv(server_response, zmq::recv_flags::none); // Get final acknowledgment
|
||||||
|
// STAT command - get status from server
|
||||||
} else if(command == "stat") {
|
} else if(command == "stat") {
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("stat"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("stat"), zmq::send_flags::none); // Request status
|
||||||
//ZIN<<<
|
//ZIN<<<
|
||||||
command_sock.recv(server_response, zmq::recv_flags::none);
|
command_sock.recv(server_response, zmq::recv_flags::none); // Receive status data
|
||||||
|
|
||||||
// GET
|
// GET command - download a file from server
|
||||||
} else if(command == "get") {
|
} else if(command == "get") {
|
||||||
std::ofstream output_file("orange-juice.png", std::ios::binary); // Open file in binary mode
|
std::ofstream output_file("orange-juice.png", std::ios::binary); // Open output file in binary mode
|
||||||
std::cout << "getting\n";
|
std::cout << "getting\n";
|
||||||
if (!output_file.is_open()) {
|
if (!output_file.is_open()) {
|
||||||
std::cerr << "Error opening file for writing" << std::endl;
|
std::cerr << "Error opening file for writing" << std::endl;
|
||||||
std::cout << "ERR" << std::endl;
|
std::cout << "ERR" << std::endl;
|
||||||
continue; // Don't bother server
|
continue; // Skip sending to server if we can't write to local file
|
||||||
} else {
|
} else {
|
||||||
|
// File transfer loop - receive file in chunks
|
||||||
while (true) {
|
while (true) {
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("GO"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("GO"), zmq::send_flags::none); // Request next chunk
|
||||||
zmq::message_t recv_data;
|
zmq::message_t recv_data;
|
||||||
//ZIN<<<
|
//ZIN<<<
|
||||||
command_sock.recv(recv_data, zmq::recv_flags::none); // data transfer
|
command_sock.recv(recv_data, zmq::recv_flags::none); // Receive chunk or status
|
||||||
|
|
||||||
// inline messaging with data, breaks to exit loop
|
// Check if we received a status message instead of data
|
||||||
if (recv_data.size() < 8) {
|
if (recv_data.size() < 8) { // Small messages are likely status signals
|
||||||
std::string recv_string(static_cast<const char*>(recv_data.data()), recv_data.size()-1);
|
std::string recv_string(static_cast<const char*>(recv_data.data()), recv_data.size()-1);
|
||||||
//std::string recv_string = recv_data.to_string();
|
if (recv_string == "EOF") { // End of file signal
|
||||||
if (recv_string == "EOF") {
|
|
||||||
std::cout << "EOF" << std::endl;
|
std::cout << "EOF" << std::endl;
|
||||||
break; // End of file
|
break; // Done receiving file
|
||||||
} else if(recv_string == "ERR") { //LIKELY ERR\0 from client, can't find file
|
} else if(recv_string == "ERR") { // Error signal (file not found, etc.)
|
||||||
std::cout << "ERR client read ACK" << std::endl;
|
std::cout << "ERR client read ACK" << std::endl;
|
||||||
break; // Err
|
break; // Stop due to error
|
||||||
} else {
|
} else {
|
||||||
std::cout << "HUH" << recv_string << std::endl;
|
std::cout << "HUH" << recv_string << std::endl; // Unexpected response
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// by reaching this point we assume binary data ( even 8 bytes will reach here )
|
|
||||||
std::cout << recv_data.size() << std::endl;
|
// Process binary data chunk (larger messages are assumed to be file data)
|
||||||
output_file.write(static_cast<char*>(recv_data.data()), recv_data.size());
|
std::cout << recv_data.size() << std::endl; // Print chunk size
|
||||||
|
output_file.write(static_cast<char*>(recv_data.data()), recv_data.size()); // Write to file
|
||||||
}
|
}
|
||||||
output_file.close();
|
output_file.close(); // Close file when done
|
||||||
}
|
}
|
||||||
// SEND
|
// SEND command - upload a file to server
|
||||||
} else if(command == "send") {
|
} else if(command == "send") {
|
||||||
std::string read_file = "./orange-juice.bsz";
|
std::string read_file = "./orange-juice.bsz"; // Hardcoded filename to send
|
||||||
std::cout << "sending\n";
|
std::cout << "sending\n";
|
||||||
std::ifstream binaryInputFile;
|
std::ifstream binaryInputFile;
|
||||||
binaryInputFile.open(read_file, std::ios::binary);// for reading
|
binaryInputFile.open(read_file, std::ios::binary); // Open file in binary mode
|
||||||
if (!binaryInputFile.is_open()) {
|
if (!binaryInputFile.is_open()) {
|
||||||
std::cerr << "Error opening file for read" << std::endl;
|
std::cerr << "Error opening file for read" << std::endl;
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("ERR"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("ERR"), zmq::send_flags::none); // Signal error to server
|
||||||
///ZIN<<<
|
///ZIN<<<
|
||||||
command_sock.recv(server_response, zmq::recv_flags::none);
|
command_sock.recv(server_response, zmq::recv_flags::none); // Get acknowledgment
|
||||||
} else {
|
} else {
|
||||||
std::vector<char> send_buffer(chunk_size);
|
// File sending loop - send file in chunks
|
||||||
|
std::vector<char> send_buffer(chunk_size); // Buffer for file chunks
|
||||||
std::streamsize bytes_read_in_chunk;
|
std::streamsize bytes_read_in_chunk;
|
||||||
while (true) {
|
while (true) {
|
||||||
binaryInputFile.read(send_buffer.data(), chunk_size); // read the file into the buffer
|
binaryInputFile.read(send_buffer.data(), chunk_size); // Read chunk from file
|
||||||
bytes_read_in_chunk = binaryInputFile.gcount(); // Actual bytes read
|
bytes_read_in_chunk = binaryInputFile.gcount(); // Get actual bytes read
|
||||||
if(bytes_read_in_chunk > 0){
|
if(bytes_read_in_chunk > 0){ // If we read data
|
||||||
zmq::message_t message(send_buffer.data(), bytes_read_in_chunk);
|
zmq::message_t message(send_buffer.data(), bytes_read_in_chunk); // Create message with data
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(message, zmq::send_flags::none);
|
command_sock.send(message, zmq::send_flags::none); // Send chunk to server
|
||||||
//ZIN<<<
|
//ZIN<<<
|
||||||
command_sock.recv(server_response, zmq::recv_flags::none);
|
command_sock.recv(server_response, zmq::recv_flags::none); // Get acknowledgment
|
||||||
} else {
|
} else {
|
||||||
break;
|
break; // Exit when file is fully read
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//<<<ZOUT
|
//<<<ZOUT
|
||||||
command_sock.send(zmq::message_t("EOF"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("EOF"), zmq::send_flags::none); // Signal end of file
|
||||||
//ZIN>>>
|
//ZIN>>>
|
||||||
command_sock.recv(server_response, zmq::recv_flags::none);
|
command_sock.recv(server_response, zmq::recv_flags::none); // Get final acknowledgment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// For simple commands, just print the server's response
|
||||||
std::cout << "Server response: " << response_str << std::endl;
|
std::cout << "Server response: " << response_str << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
command_sock.close();
|
command_sock.close(); // Clean up socket
|
||||||
ctx.close();
|
ctx.close(); // Clean up context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintains a heartbeat connection with the server to detect disconnections
|
||||||
|
*
|
||||||
|
* @param server_pkey The server's public key for secure communication
|
||||||
|
* @param client_pkey The client's public key
|
||||||
|
* @param client_skey The client's secret key
|
||||||
|
*/
|
||||||
void heartbeat_thread(std::string server_pkey, std::string client_pkey, std::string client_skey) {
|
void heartbeat_thread(std::string server_pkey, std::string client_pkey, std::string client_skey) {
|
||||||
|
// ZeroMQ setup
|
||||||
zmq::context_t ctx;
|
zmq::context_t ctx;
|
||||||
zmq::socket_t heartbeat_sock (ctx, zmq::socket_type::req);
|
zmq::socket_t heartbeat_sock (ctx, zmq::socket_type::req); // Create REQ socket for heartbeats
|
||||||
heartbeat_sock.set(zmq::sockopt::curve_serverkey, server_pkey);
|
|
||||||
heartbeat_sock.set(zmq::sockopt::curve_publickey, client_pkey);
|
// Set up security for the socket
|
||||||
heartbeat_sock.set(zmq::sockopt::curve_secretkey, client_skey);
|
heartbeat_sock.set(zmq::sockopt::curve_serverkey, server_pkey); // Server's public key
|
||||||
heartbeat_sock.set(zmq::sockopt::linger, 1); // Close immediately on disconnect
|
heartbeat_sock.set(zmq::sockopt::curve_publickey, client_pkey); // Our public key
|
||||||
heartbeat_sock.connect("tcp://localhost:5555");
|
heartbeat_sock.set(zmq::sockopt::curve_secretkey, client_skey); // Our secret key
|
||||||
|
heartbeat_sock.set(zmq::sockopt::linger, 1); // Close immediately on disconnect
|
||||||
|
heartbeat_sock.connect("tcp://localhost:5555"); // Connect to server's heartbeat port
|
||||||
|
|
||||||
int heartbeat_count = 0;
|
int heartbeat_count = 0;
|
||||||
std::vector<zmq::pollitem_t> items = {};
|
std::vector<zmq::pollitem_t> items = {};
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// Check if we should terminate this thread
|
||||||
if(abort_state.load()==true) {
|
if(abort_state.load()==true) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Short delay between heartbeats
|
||||||
|
|
||||||
|
// Only send heartbeats if connection is established
|
||||||
if(connection_state == true) {
|
if(connection_state == true) {
|
||||||
|
// Send heartbeat to server
|
||||||
heartbeat_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
heartbeat_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
||||||
// Wait for response (poll for ZMQ_POLLIN)
|
|
||||||
|
// Wait for response with timeout
|
||||||
zmq::pollitem_t response_item = { heartbeat_sock, 0, ZMQ_POLLIN, 0 };
|
zmq::pollitem_t response_item = { heartbeat_sock, 0, ZMQ_POLLIN, 0 };
|
||||||
zmq::poll(&response_item, 1, 100); // Wait for response with timeout
|
zmq::poll(&response_item, 1, 100); // Poll with 100ms timeout
|
||||||
if (response_item.revents & ZMQ_POLLIN) {
|
|
||||||
|
if (response_item.revents & ZMQ_POLLIN) { // If we got a response
|
||||||
zmq::message_t msg_response;
|
zmq::message_t msg_response;
|
||||||
heartbeat_sock.recv(msg_response, zmq::recv_flags::none);
|
heartbeat_sock.recv(msg_response, zmq::recv_flags::none); // Receive it
|
||||||
//std::cout << "Heartbeat Response: " << std::endl;
|
//std::cout << "Heartbeat Response: " << std::endl; // Commented out debug print
|
||||||
} else {
|
} else { // No response within timeout
|
||||||
std::cout << "Bella Server is unavailable" << std::endl;
|
std::cout << "Bella Server is unavailable" << std::endl;
|
||||||
heartbeat_state = false;
|
heartbeat_state = false; // Mark heartbeat as failed
|
||||||
connection_state = false;
|
connection_state = false; // Mark connection as down
|
||||||
break;
|
break; // Exit heartbeat loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
heartbeat_sock.close();
|
heartbeat_sock.close(); // Clean up socket
|
||||||
ctx.close();
|
ctx.close(); // Clean up context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the server's public key to establish a secure connection
|
||||||
|
*
|
||||||
|
* @return The server's public key as a string
|
||||||
|
*/
|
||||||
std::string get_pubkey_from_srv() {
|
std::string get_pubkey_from_srv() {
|
||||||
// No authentication is used, server will give out pubkey to anybody
|
// Note: This initial connection is not encrypted, but subsequent connections will be
|
||||||
// Could use a unique message but since socket is unencrypted this provides
|
// ZeroMQ will establish perfect forward secrecy after initial handshake
|
||||||
// no protection. In main loop we establish an encrypted connection with the server
|
|
||||||
// now that we have the pubkey and in combo with the client_secret_key we can
|
|
||||||
// be secure. 0MQ uses PFS perfect forward security, because this initial
|
|
||||||
// back and forth is extended with behind the scenes new keypairs taken care of by
|
|
||||||
// 0MQ after we establish our intitial encrypted socket
|
|
||||||
zmq::context_t ctx;
|
zmq::context_t ctx;
|
||||||
zmq::socket_t pubkey_sock(ctx, zmq::socket_type::req);
|
zmq::socket_t pubkey_sock(ctx, zmq::socket_type::req); // Create REQ socket
|
||||||
pubkey_sock.connect("tcp://127.0.0.1:9555");
|
pubkey_sock.connect("tcp://127.0.0.1:9555"); // Connect to server's key exchange port
|
||||||
|
|
||||||
|
// Prepare authentication message with passphrase
|
||||||
zmq::message_t z_out(std::string("Bellarender123"));
|
zmq::message_t z_out(std::string("Bellarender123"));
|
||||||
|
|
||||||
|
// Send the passphrase to request the public key
|
||||||
try {
|
try {
|
||||||
zmq::send_result_t send_result = pubkey_sock.send(z_out, zmq::send_flags::none);
|
zmq::send_result_t send_result = pubkey_sock.send(z_out, zmq::send_flags::none);
|
||||||
} catch (const zmq::error_t& e) {
|
} catch (const zmq::error_t& e) {
|
||||||
@ -257,59 +328,71 @@ std::string get_pubkey_from_srv() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "bellazmq connecting to server..." << std::endl;
|
std::cout << "bellazmq connecting to server..." << std::endl;
|
||||||
|
|
||||||
|
// Receive the server's public key
|
||||||
zmq::message_t z_in;
|
zmq::message_t z_in;
|
||||||
pubkey_sock.recv(z_in);
|
pubkey_sock.recv(z_in);
|
||||||
std::string pub_key = z_in.to_string();
|
std::string pub_key = z_in.to_string();
|
||||||
|
|
||||||
|
// Clean up resources
|
||||||
pubkey_sock.close();
|
pubkey_sock.close();
|
||||||
ctx.close();
|
ctx.close();
|
||||||
|
|
||||||
std::cout << "connection successful" << std::endl;
|
std::cout << "connection successful" << std::endl;
|
||||||
connection_state = true;
|
connection_state = true; // Mark connection as established
|
||||||
return pub_key;
|
return pub_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function - program entry point
|
||||||
|
* Sets up security, creates threads, and manages the overall connection
|
||||||
|
*/
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
const size_t chunk_size = 32768;
|
const size_t chunk_size = 32768; // 32KB chunk size (not used in main)
|
||||||
// Dynamically create keypair, every run is bespoke
|
|
||||||
// [TODO] send pubkey to server, mkdir, render to that dir
|
// Generate a unique cryptographic keypair for this client
|
||||||
char client_skey[41] = { 0 };
|
char client_skey[41] = { 0 }; // Secret key buffer (private key)
|
||||||
char client_pkey[41] = { 0 };
|
char client_pkey[41] = { 0 }; // Public key buffer
|
||||||
if ( zmq_curve_keypair(&client_pkey[0], &client_skey[0])) {
|
if ( zmq_curve_keypair(&client_pkey[0], &client_skey[0])) { // Generate the keypair
|
||||||
// 1 is fail
|
// 1 is failure
|
||||||
std::cout << "\ncurve keypair gen failed.";
|
std::cout << "\ncurve keypair gen failed.";
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE); // Exit program if key generation fails
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get server pubkey, set client keypair
|
// Get server's public key to establish secure connection
|
||||||
std::string server_pkey = get_pubkey_from_srv();
|
std::string server_pkey = get_pubkey_from_srv();
|
||||||
/*if(server_pkey.empty()) {
|
/*if(server_pkey.empty()) { // Commented out error handling
|
||||||
std::cout << "Server is Down" << std::endl;
|
std::cout << "Server is Down" << std::endl;
|
||||||
heartbeat_state = false;
|
heartbeat_state = false;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
// Convert char arrays to strings for easier handling
|
||||||
std::string client_pkey_str(client_pkey);
|
std::string client_pkey_str(client_pkey);
|
||||||
std::string client_skey_str(client_skey);
|
std::string client_skey_str(client_skey);
|
||||||
|
|
||||||
// Multithreaded
|
// Start worker threads for commands and heartbeats
|
||||||
std::thread command_t(command_thread, server_pkey, client_pkey_str, client_skey_str);
|
std::thread command_t(command_thread, server_pkey, client_pkey_str, client_skey_str);
|
||||||
std::thread heartbeat_t(heartbeat_thread, server_pkey, client_pkey_str, client_skey_str);
|
std::thread heartbeat_t(heartbeat_thread, server_pkey, client_pkey_str, client_skey_str);
|
||||||
|
|
||||||
|
// Main monitoring loop - checks connection health
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!heartbeat_state.load()) {
|
if (!heartbeat_state.load()) { // If heartbeat has failed
|
||||||
std::cout << "Dead" << std::endl;
|
std::cout << "Dead" << std::endl;
|
||||||
abort_state==true;
|
abort_state = true; // Signal threads to terminate (note: this is using assignment, not comparison)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (connection_state.load() == false) {
|
if (connection_state.load() == false) { // If connection is down
|
||||||
std::cout << "Dead2" << std::endl;
|
std::cout << "Dead2" << std::endl;
|
||||||
abort_state==true;
|
abort_state = true; // Signal threads to terminate (note: this is using assignment, not comparison)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Check every half-second
|
||||||
}
|
}
|
||||||
abort_state==true;
|
|
||||||
command_t.join();
|
abort_state = true; // Signal threads to terminate (note: this is using assignment, not comparison)
|
||||||
heartbeat_t.join();
|
command_t.join(); // Wait for command thread to finish
|
||||||
|
heartbeat_t.join(); // Wait for heartbeat thread to finish
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
266
server.cpp
266
server.cpp
@ -1,153 +1,176 @@
|
|||||||
#include <iostream>
|
/**
|
||||||
#include <fstream>
|
* ZeroMQ Server Implementation
|
||||||
#include <thread>
|
*
|
||||||
#include <zmq.hpp>
|
* This program creates a server that:
|
||||||
#include <vector>
|
* - Uses ZeroMQ (zmq) for network communication
|
||||||
#include <chrono>
|
* - Handles commands from clients through a command socket
|
||||||
|
* - Monitors client connections through a heartbeat socket
|
||||||
|
* - Provides secure communication with curve cryptography
|
||||||
|
* - Supports file transfer operations (send/get)
|
||||||
|
*/
|
||||||
|
|
||||||
#include <sstream> // For string streams
|
#include <iostream> // For input/output operations (cout, cerr)
|
||||||
#include <atomic>
|
#include <fstream> // For file operations (ifstream, ofstream)
|
||||||
|
#include <thread> // For creating and managing threads
|
||||||
|
#include <zmq.hpp> // ZeroMQ C++ binding for network communication
|
||||||
|
#include <vector> // For dynamic arrays (vectors)
|
||||||
|
#include <chrono> // For time-related functions
|
||||||
|
|
||||||
#include <thread>
|
#include <sstream> // For string stream operations
|
||||||
|
#include <atomic> // For thread-safe variables
|
||||||
|
|
||||||
std::atomic<bool> heartbeat_state (true);
|
#include <thread> // Already included above, redundant
|
||||||
std::atomic<bool> client_state (false);
|
|
||||||
|
|
||||||
|
// Atomic variables for thread-safe state tracking across multiple threads
|
||||||
|
std::atomic<bool> heartbeat_state (true); // Tracks if heartbeat is active
|
||||||
|
std::atomic<bool> client_state (false); // Tracks if a client is connected
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all client commands on a separate thread
|
||||||
|
*
|
||||||
|
* @param server_skey The server's secret key for secure communication
|
||||||
|
*/
|
||||||
void command_thread(std::string server_skey) {
|
void command_thread(std::string server_skey) {
|
||||||
|
|
||||||
zmq::context_t ctx;
|
// ZeroMQ setup
|
||||||
zmq::socket_t command_sock(ctx, zmq::socket_type::rep);
|
zmq::context_t ctx; // Create a ZeroMQ context (required for all sockets)
|
||||||
//command_sock.set(zmq::sockopt::sndtimeo, 10000);
|
zmq::socket_t command_sock(ctx, zmq::socket_type::rep); // Create a REP (reply) socket
|
||||||
|
//command_sock.set(zmq::sockopt::sndtimeo, 10000); // Commented out timeout settings
|
||||||
//command_sock.set(zmq::sockopt::rcvtimeo, 10000);
|
//command_sock.set(zmq::sockopt::rcvtimeo, 10000);
|
||||||
command_sock.set(zmq::sockopt::curve_server, true);
|
command_sock.set(zmq::sockopt::curve_server, true); // Enable secure curve encryption
|
||||||
command_sock.set(zmq::sockopt::curve_secretkey, server_skey);
|
command_sock.set(zmq::sockopt::curve_secretkey, server_skey); // Set the server's secret key
|
||||||
//command_sock.set(zmq::sockopt::linger, 100); // Close immediately on disconnect
|
//command_sock.set(zmq::sockopt::linger, 100); // Commented out linger option
|
||||||
command_sock.bind("tcp://*:5556");
|
command_sock.bind("tcp://*:5556"); // Bind socket to port 5556 on all interfaces
|
||||||
zmq::message_t client_response;
|
zmq::message_t client_response;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// File paths for operations
|
||||||
std::string write_file = "./oomer.bsz";
|
std::string write_file = "./oomer.bsz";
|
||||||
std::string read_file = "./oomer.png";
|
std::string read_file = "./oomer.png";
|
||||||
const size_t chunk_size = 65536;
|
const size_t chunk_size = 65536; // 64KB chunks for file transfers
|
||||||
std::vector<char> sftp_buffer(chunk_size); // Buffer to hold each chunk
|
std::vector<char> sftp_buffer(chunk_size); // Buffer to hold each chunk
|
||||||
std::ofstream binaryOutputFile;// for writing
|
std::ofstream binaryOutputFile; // File stream for writing
|
||||||
std::ifstream binaryInputFile;// for reading
|
std::ifstream binaryInputFile; // File stream for reading
|
||||||
|
|
||||||
|
// Main command processing loop
|
||||||
while (true) {
|
while (true) {
|
||||||
zmq::message_t msg_command;
|
zmq::message_t msg_command;
|
||||||
//ZIN<<<
|
//ZIN<<< (This comment indicates receiving data from the network)
|
||||||
command_sock.recv(msg_command, zmq::recv_flags::none);
|
command_sock.recv(msg_command, zmq::recv_flags::none); // Wait for a command from client
|
||||||
std::string client_command = msg_command.to_string();
|
std::string client_command = msg_command.to_string(); // Convert message to string
|
||||||
std::cout << "Command: " << client_command << std::endl;
|
std::cout << "Command: " << client_command << std::endl;
|
||||||
|
|
||||||
if(client_command == "hello"){
|
// Process different commands
|
||||||
|
if(client_command == "hello"){ // Basic hello/bye command
|
||||||
std::cout << "bye" << std::endl;
|
std::cout << "bye" << std::endl;
|
||||||
//>>>ZOUT
|
//>>>ZOUT (This comment indicates sending data to the network)
|
||||||
command_sock.send(zmq::message_t("bye"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("bye"), zmq::send_flags::none);
|
||||||
} else if (client_command == "exit") {
|
} else if (client_command == "exit") { // Exit command
|
||||||
std::cout << "exit" << std::endl;
|
std::cout << "exit" << std::endl;
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none); // Send acknowledgment
|
||||||
// RENDER
|
// RENDER command
|
||||||
} else if (client_command == "render") {
|
} else if (client_command == "render") {
|
||||||
//engine.scene().read("./oomer.bsz");
|
//engine.scene().read("./oomer.bsz"); // Commented out rendering code
|
||||||
//engine.scene().camera()["resolution"] = Vec2 {200, 200};
|
//engine.scene().camera()["resolution"] = Vec2 {200, 200};
|
||||||
//engine.start();
|
//engine.start();
|
||||||
std::cout << "start render" << std::endl;
|
std::cout << "start render" << std::endl;
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
||||||
|
|
||||||
//GET
|
//GET command - sends a file to the client
|
||||||
} else if (client_command == "get") { //REP mode
|
} else if (client_command == "get") { //REP mode
|
||||||
std::string read_file = "./oomer.png";
|
std::string read_file = "./oomer.png";
|
||||||
std::cout << "Executing get command\n";
|
std::cout << "Executing get command\n";
|
||||||
std::ifstream binaryInputFile;
|
std::ifstream binaryInputFile;
|
||||||
binaryInputFile.open(read_file, std::ios::binary);// for reading
|
binaryInputFile.open(read_file, std::ios::binary); // Open file in binary mode
|
||||||
if (!binaryInputFile.is_open()) {
|
if (!binaryInputFile.is_open()) {
|
||||||
std::cerr << "Error opening file for read" << std::endl;
|
std::cerr << "Error opening file for read" << std::endl;
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("ERR"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("ERR"), zmq::send_flags::none); // Send error if file can't be opened
|
||||||
} else {
|
} else {
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("RDY"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("RDY"), zmq::send_flags::none); // Tell client we're ready to send
|
||||||
std::vector<char> send_buffer(chunk_size);
|
std::vector<char> send_buffer(chunk_size);
|
||||||
std::streamsize bytes_read_in_chunk;
|
std::streamsize bytes_read_in_chunk;
|
||||||
while (true) {
|
while (true) {
|
||||||
zmq::message_t z_in;
|
zmq::message_t z_in;
|
||||||
//ZIN
|
//ZIN
|
||||||
command_sock.recv(z_in); // Block until zGo, or any message
|
command_sock.recv(z_in); // Wait for client to request next chunk
|
||||||
binaryInputFile.read(send_buffer.data(), chunk_size); // read the file into the buffer
|
binaryInputFile.read(send_buffer.data(), chunk_size); // Read file chunk into buffer
|
||||||
bytes_read_in_chunk = binaryInputFile.gcount(); // Actual bytes read
|
bytes_read_in_chunk = binaryInputFile.gcount(); // Get actual bytes read
|
||||||
if(bytes_read_in_chunk > 0){
|
if(bytes_read_in_chunk > 0){
|
||||||
std::cout << bytes_read_in_chunk << std::endl;
|
std::cout << bytes_read_in_chunk << std::endl;
|
||||||
zmq::message_t message(send_buffer.data(), bytes_read_in_chunk);
|
zmq::message_t message(send_buffer.data(), bytes_read_in_chunk); // Create message with chunk data
|
||||||
//ZOUT
|
//ZOUT
|
||||||
command_sock.send(message, zmq::send_flags::none);
|
command_sock.send(message, zmq::send_flags::none); // Send chunk to client
|
||||||
} else {
|
} else {
|
||||||
//ZOUT
|
//ZOUT
|
||||||
command_sock.send(zmq::message_t("EOF"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("EOF"), zmq::send_flags::none); // Signal end of file
|
||||||
std::cout << "EOF" << std::endl;
|
std::cout << "EOF" << std::endl;
|
||||||
break; // Exit when 0 bytes read
|
break; // Exit when file is fully sent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// STAT command - read and send log file contents
|
||||||
} else if (client_command == "stat") {
|
} else if (client_command == "stat") {
|
||||||
std::ifstream log_file("logfile.txt");
|
std::ifstream log_file("logfile.txt");
|
||||||
if (log_file.is_open()) {
|
if (log_file.is_open()) {
|
||||||
std::string log_line;
|
std::string log_line;
|
||||||
if (std::getline(log_file, log_line)) { // Reads the entire line, including spaces
|
if (std::getline(log_file, log_line)) { // Read a line from the log file
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t(log_line), zmq::send_flags::none);
|
command_sock.send(zmq::message_t(log_line), zmq::send_flags::none); // Send log line to client
|
||||||
} else {
|
} else {
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none); // Empty log file
|
||||||
}
|
}
|
||||||
log_file.close();
|
log_file.close();
|
||||||
}
|
}
|
||||||
|
// SEND command - receive a file from client
|
||||||
} else if (client_command == "send") {
|
} else if (client_command == "send") {
|
||||||
std::ofstream output_file("oomer.bsz", std::ios::binary); // Open file in binary mode
|
std::ofstream output_file("oomer.bsz", std::ios::binary); // Open file for writing in binary mode
|
||||||
if (!output_file.is_open()) {
|
if (!output_file.is_open()) {
|
||||||
std::cerr << "Error opening file for writing" << std::endl;
|
std::cerr << "Error opening file for writing" << std::endl;
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("ERR"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("ERR"), zmq::send_flags::none); // Send error if file can't be opened
|
||||||
} else { // File handle open and ready
|
} else { // File handle open and ready
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("RDY"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("RDY"), zmq::send_flags::none); // Tell client we're ready to receive
|
||||||
while (true) {
|
while (true) {
|
||||||
zmq::message_t recv_data;
|
zmq::message_t recv_data;
|
||||||
//ZIN<<<
|
//ZIN<<<
|
||||||
command_sock.recv(recv_data, zmq::recv_flags::none);
|
command_sock.recv(recv_data, zmq::recv_flags::none); // Receive chunk from client
|
||||||
if(recv_data.size() < 8) { // data and signals sent on same socket
|
if(recv_data.size() < 8) { // Check if this is a signal rather than data
|
||||||
// Allow for signals up to 8 bytes, EOF, ERR
|
// Small messages (under 8 bytes) are likely signals
|
||||||
// messages are null terminated requiring -1
|
|
||||||
std::string response_str(static_cast<char*>(recv_data.data()), recv_data.size()-1);
|
std::string response_str(static_cast<char*>(recv_data.data()), recv_data.size()-1);
|
||||||
if (response_str=="EOF") {
|
if (response_str=="EOF") { // End of file signal
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
||||||
break; // End of file
|
break; // Done receiving file
|
||||||
} else if(response_str=="ERR") {
|
} else if(response_str=="ERR") { // Error signal
|
||||||
std::cout << "ERR on client" << std::endl;
|
std::cout << "ERR on client" << std::endl;
|
||||||
//>>>ZOUT
|
//>>>ZOUT
|
||||||
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
||||||
break; // End of file
|
break; // End transfer due to error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// File write
|
// Write received data to file
|
||||||
output_file.write(static_cast<char*>(recv_data.data()), recv_data.size());
|
output_file.write(static_cast<char*>(recv_data.data()), recv_data.size());
|
||||||
//>>ZOUT
|
//>>ZOUT
|
||||||
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none); // Acknowledge chunk received
|
||||||
}
|
}
|
||||||
output_file.close();
|
output_file.close();
|
||||||
}
|
}
|
||||||
} else { // A unknown REQ sent, acknowledge because req-rep pattern is blocking
|
} else { // Unknown command
|
||||||
//>>ZOUT
|
//>>ZOUT
|
||||||
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none);
|
command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none); // Still need to respond in REQ-REP pattern
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Small delay between commands
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const zmq::error_t& e) {
|
} catch (const zmq::error_t& e) {
|
||||||
// Handle ZMQ-specific exceptions
|
// Handle ZeroMQ-specific exceptions
|
||||||
std::cerr << "ZMQ error: " << e.what() << std::endl;
|
std::cerr << "ZMQ error: " << e.what() << std::endl;
|
||||||
ctx.close();
|
ctx.close();
|
||||||
command_sock.close();
|
command_sock.close();
|
||||||
@ -159,97 +182,108 @@ void command_thread(std::string server_skey) {
|
|||||||
// Catch any other exceptions
|
// Catch any other exceptions
|
||||||
std::cerr << "Unknown exception caught." << std::endl;
|
std::cerr << "Unknown exception caught." << std::endl;
|
||||||
}
|
}
|
||||||
command_sock.close();
|
command_sock.close(); // Clean up socket
|
||||||
ctx.close();
|
ctx.close(); // Clean up context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors client connection through regular heartbeat messages
|
||||||
|
*
|
||||||
|
* @param server_skey The server's secret key for secure communication
|
||||||
|
*/
|
||||||
void heartbeat_thread(std::string server_skey) {
|
void heartbeat_thread(std::string server_skey) {
|
||||||
heartbeat_state = true;
|
heartbeat_state = true; // Initialize heartbeat as active
|
||||||
std::cout << "new heartbeat_thread" << std::endl;
|
std::cout << "new heartbeat_thread" << std::endl;
|
||||||
|
|
||||||
|
// ZeroMQ setup
|
||||||
zmq::context_t ctx;
|
zmq::context_t ctx;
|
||||||
zmq::socket_t heartbeat_sock (ctx, zmq::socket_type::rep);
|
zmq::socket_t heartbeat_sock (ctx, zmq::socket_type::rep); // Create a REP socket for heartbeats
|
||||||
heartbeat_sock.set(zmq::sockopt::curve_server, true);
|
heartbeat_sock.set(zmq::sockopt::curve_server, true); // Enable encryption
|
||||||
heartbeat_sock.set(zmq::sockopt::curve_secretkey, server_skey);
|
heartbeat_sock.set(zmq::sockopt::curve_secretkey, server_skey); // Set secret key
|
||||||
heartbeat_sock.bind("tcp://*:5555");
|
heartbeat_sock.bind("tcp://*:5555"); // Listen on port 5555
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
|
// Only start checking heartbeats once a client connects
|
||||||
//Start polling heartbeats once client connects
|
|
||||||
if (client_state == true) {
|
if (client_state == true) {
|
||||||
|
// Set up polling to check for incoming messages with timeout
|
||||||
zmq::pollitem_t response_item = { heartbeat_sock, 0, ZMQ_POLLIN, 0 };
|
zmq::pollitem_t response_item = { heartbeat_sock, 0, ZMQ_POLLIN, 0 };
|
||||||
zmq::poll(&response_item, 1, 25000); // Wait for response with timeout
|
zmq::poll(&response_item, 1, 25000); // Wait for heartbeat with 25 second timeout
|
||||||
|
|
||||||
if (response_item.revents & ZMQ_POLLIN) {
|
if (response_item.revents & ZMQ_POLLIN) { // If we received a message
|
||||||
zmq::message_t message;
|
zmq::message_t message;
|
||||||
//ZIN<<<
|
//ZIN<<<
|
||||||
heartbeat_sock.recv(message, zmq::recv_flags::none);
|
heartbeat_sock.recv(message, zmq::recv_flags::none); // Receive heartbeat
|
||||||
//ZOUT>>>
|
//ZOUT>>>
|
||||||
heartbeat_sock.send(zmq::message_t("ACK"), zmq::send_flags::dontwait); // No block
|
heartbeat_sock.send(zmq::message_t("ACK"), zmq::send_flags::dontwait); // Acknowledge without blocking
|
||||||
} else { //timeout
|
} else { // Timeout - no heartbeat received
|
||||||
std::cout << "Bella Client Lost" << std::endl;
|
std::cout << "Bella Client Lost" << std::endl;
|
||||||
heartbeat_state = false;
|
heartbeat_state = false; // Mark client as disconnected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Small delay between checks
|
||||||
}
|
}
|
||||||
heartbeat_sock.close();
|
heartbeat_sock.close(); // Clean up socket
|
||||||
ctx.close();
|
ctx.close(); // Clean up context
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocking zmq rep socket to pass server_public_key
|
/**
|
||||||
|
* Serves the public key to clients that request it
|
||||||
|
* This is how clients get the key needed to establish a secure connection
|
||||||
|
*
|
||||||
|
* @param pub_key The server's public key to share with clients
|
||||||
|
*/
|
||||||
void pkey_server(const std::string& pub_key) {
|
void pkey_server(const std::string& pub_key) {
|
||||||
zmq::context_t ctx;
|
zmq::context_t ctx;
|
||||||
zmq::socket_t sock(ctx, zmq::socket_type::rep);
|
zmq::socket_t sock(ctx, zmq::socket_type::rep); // Create REP socket
|
||||||
sock.bind("tcp://*:9555"); //[TODO] args to set port
|
sock.bind("tcp://*:9555"); // Bind to port 9555
|
||||||
|
|
||||||
zmq::message_t z_in;
|
zmq::message_t z_in;
|
||||||
std::cout << "Entered: Public Key Serving Mode" << std::endl;
|
std::cout << "Entered: Public Key Serving Mode" << std::endl;
|
||||||
//ZIN<<<
|
//ZIN<<<
|
||||||
sock.recv(z_in);
|
sock.recv(z_in); // Wait for client request
|
||||||
|
|
||||||
|
// Check if the request contains the correct passphrase
|
||||||
if (z_in.to_string().compare("Bellarender123") == 0) {
|
if (z_in.to_string().compare("Bellarender123") == 0) {
|
||||||
zmq::message_t z_out(pub_key);
|
zmq::message_t z_out(pub_key); // Create message with the public key
|
||||||
//ZOUT>>>
|
//ZOUT>>>
|
||||||
sock.send(z_out, zmq::send_flags::none);
|
sock.send(z_out, zmq::send_flags::none); // Send the public key
|
||||||
client_state = true;
|
client_state = true; // Mark that a client has connected
|
||||||
}
|
}
|
||||||
sock.close();
|
sock.close(); // Clean up socket
|
||||||
ctx.close();
|
ctx.close(); // Clean up context
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will use the dl_core main helper here. This gives us a helpful Args instance to use, and
|
/**
|
||||||
// also hides the confusing details of dealing with main vs. WinMain on windows, and gives us
|
* Main function - program entry point
|
||||||
// utf8-encoded args when the application is unicode.
|
* Sets up security, creates threads, and manages client connections
|
||||||
//
|
*/
|
||||||
//#include "dl_core/dl_main.inl"
|
|
||||||
//int DL_main(Args& args)
|
|
||||||
//{
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
// Generate brand new keypair on launch
|
// Generate brand new cryptographic keypair on launch for security
|
||||||
// [TODO] Add client side public key fingerprinting for added security
|
char skey[128] = { 0 }; // Secret key buffer (private key)
|
||||||
char skey[128] = { 0 };
|
char pkey[128] = { 0 }; // Public key buffer
|
||||||
char pkey[128] = { 0 };
|
if ( zmq_curve_keypair(&pkey[0], &skey[0])) { // Generate the keypair
|
||||||
if ( zmq_curve_keypair(&pkey[0], &skey[0])) {
|
// 1 is failure
|
||||||
// 1 is fail
|
|
||||||
std::cout << "\ncurve keypair gen failed.";
|
std::cout << "\ncurve keypair gen failed.";
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE); // Exit program if key generation fails
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi threading
|
// Start worker threads for handling commands and heartbeats
|
||||||
std::thread command_t(command_thread, skey);
|
std::thread command_t(command_thread, skey); // Thread for handling commands
|
||||||
std::thread heartbeat_t(heartbeat_thread, skey);
|
std::thread heartbeat_t(heartbeat_thread, skey); // Thread for monitoring heartbeats
|
||||||
|
|
||||||
//
|
// Main loop that handles client connections
|
||||||
while(true) { // awaiting new client loop
|
while(true) { // Infinite loop to accept new clients when old ones disconnect
|
||||||
heartbeat_state = true;
|
heartbeat_state = true; // Reset heartbeat state for new client
|
||||||
pkey_server(pkey); // blocking wait client to get public key on port 5555
|
pkey_server(pkey); // Wait for client to request public key (blocking call)
|
||||||
std::cout << "Client connected" << std::endl;
|
std::cout << "Client connected" << std::endl;
|
||||||
|
|
||||||
while(true) { // inner loop
|
// Loop while client is connected
|
||||||
if (heartbeat_state.load()==false) {
|
while(true) { // Monitor client connection
|
||||||
|
if (heartbeat_state.load()==false) { // Check if heartbeat is still active
|
||||||
std::cout << "Client connectiono dead" << std::endl;
|
std::cout << "Client connectiono dead" << std::endl;
|
||||||
break;
|
break; // Exit inner loop if client disconnected
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Small delay to prevent CPU spinning
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user