diff --git a/README.md b/README.md index 96f4e82..5773f4c 100644 --- a/README.md +++ b/README.md @@ -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 + + +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 + ``` diff --git a/client.cpp b/client.cpp index b72d682..ab96d90 100644 --- a/client.cpp +++ b/client.cpp @@ -1,23 +1,42 @@ -#include -#include -#include -#include -#include -#include +/** + * ZeroMQ Client Implementation + * + * This program creates a client that: + * - Connects to a ZeroMQ server for network communication + * - Sends commands and processes responses + * - Uses secure communication with curve cryptography + * - Supports file transfer operations (send/get) + * - Maintains connection health through heartbeats + */ -#include -#include -#include -#include +#include // For input/output operations (cout, cerr) +#include // For file operations (ifstream, ofstream) +#include // For filesystem operations +#include // For random number generation +#include // For creating and managing threads +#include // ZeroMQ C++ binding for network communication -#include -#include -#include +#include // For string handling +#include // For dynamic arrays (vectors) +#include // For time-related functions +#include // Redundant include -std::atomic heartbeat_state (true); -std::atomic connection_state (false); -std::atomic abort_state (false); +#include // For thread-safe variables +#include // For thread synchronization +#include // For thread synchronization +// Atomic variables for thread-safe state tracking across multiple threads +std::atomic heartbeat_state (true); // Tracks if heartbeat communication is active +std::atomic connection_state (false); // Tracks if connection to server is established +std::atomic 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) { if (str.length() >= 4) { 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; } +/** + * 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) { - const size_t chunk_size = 65536; - zmq::context_t ctx; - zmq::socket_t command_sock (ctx, zmq::socket_type::req); - //command_sock.set(zmq::sockopt::sndtimeo, 10000); + const size_t chunk_size = 65536; // 64KB chunks for file transfers + + // ZeroMQ setup + 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::curve_serverkey, server_pkey); - command_sock.set(zmq::sockopt::curve_publickey, client_pkey); - command_sock.set(zmq::sockopt::curve_secretkey, client_skey); - command_sock.set(zmq::sockopt::linger, 1); // Close immediately on disconnect - command_sock.connect("tcp://localhost:5556"); + + // Set up security for the socket + command_sock.set(zmq::sockopt::curve_serverkey, server_pkey); // Server's public key + command_sock.set(zmq::sockopt::curve_publickey, client_pkey); // Our public key + 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; while (true) { + // Check if we should terminate this thread if(abort_state.load()==true) { break; } + + // Get command from user 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::vector args; + std::vector args; // Store each word as a separate argument while (ss >> 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(); std::string command; if (num_args > 0) { - command = args[0]; + command = args[0]; // First argument is the command + + // Handle SEND command 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; 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; continue; } 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; continue; } - } else if (command == "exit") { + } + // Handle EXIT command + else if (command == "exit") { std::cout << "now" << std::endl; - break; - } else if (command == "render") { + break; // Exit the command loop + } + // Handle RENDER command + else if (command == "render") { std::string compoundArg; if(num_args > 1) { + // Combine remaining args into a single string for (size_t i = 1; i < args.size(); ++i) { compoundArg += args[i]; if (i < args.size() - 1) { - compoundArg += " "; // Add spaces between arguments + compoundArg += " "; // Add spaces between arguments } } std::cout << compoundArg << std::endl; } - } else if (command == "hello") { - ; - } else { + } + // Handle HELLO command + else if (command == "hello") { + ; // No special validation needed + } + // Handle unknown commands + else { std::cout << "unknown" << std::endl; - continue; + continue; // Skip sending to server } } - // Sanity check input complete - // Push to server over encrypted socket + // Send validated command to server over encrypted socket zmq::message_t server_response; zmq::message_t msg_command(command); - //>>>ZOUT - command_sock.send(msg_command, zmq::send_flags::none); //SEND + //>>>ZOUT (This comment indicates sending data to the network) + command_sock.send(msg_command, zmq::send_flags::none); // Send command to server std::cout << "Sent: " << input.data() << std::endl; - //ZIN<<< - command_sock.recv(server_response, zmq::recv_flags::none); //RECV + //ZIN<<< (This comment indicates receiving data from the network) + command_sock.recv(server_response, zmq::recv_flags::none); // Wait for server response std::string response_str(static_cast(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; + + // Handle different command types if(input == "exit") { - break; - // RENDER + break; // Exit command - terminate the thread + // RENDER command } else if(command == "render") { //>>>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<<< - 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") { //>>>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<<< - 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") { - 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"; if (!output_file.is_open()) { std::cerr << "Error opening file for writing" << 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 { + // File transfer loop - receive file in chunks while (true) { //>>>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; //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 - if (recv_data.size() < 8) { + // Check if we received a status message instead of data + if (recv_data.size() < 8) { // Small messages are likely status signals std::string recv_string(static_cast(recv_data.data()), recv_data.size()-1); - //std::string recv_string = recv_data.to_string(); - if (recv_string == "EOF") { + if (recv_string == "EOF") { // End of file signal std::cout << "EOF" << std::endl; - break; // End of file - } else if(recv_string == "ERR") { //LIKELY ERR\0 from client, can't find file + break; // Done receiving file + } else if(recv_string == "ERR") { // Error signal (file not found, etc.) std::cout << "ERR client read ACK" << std::endl; - break; // Err + break; // Stop due to error } else { - std::cout << "HUH" << recv_string << std::endl; + std::cout << "HUH" << recv_string << std::endl; // Unexpected response break; } } - // by reaching this point we assume binary data ( even 8 bytes will reach here ) - std::cout << recv_data.size() << std::endl; - output_file.write(static_cast(recv_data.data()), recv_data.size()); + + // Process binary data chunk (larger messages are assumed to be file data) + std::cout << recv_data.size() << std::endl; // Print chunk size + output_file.write(static_cast(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") { - std::string read_file = "./orange-juice.bsz"; + std::string read_file = "./orange-juice.bsz"; // Hardcoded filename to send std::cout << "sending\n"; 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()) { std::cerr << "Error opening file for read" << std::endl; //>>>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<<< - command_sock.recv(server_response, zmq::recv_flags::none); + command_sock.recv(server_response, zmq::recv_flags::none); // Get acknowledgment } else { - std::vector send_buffer(chunk_size); + // File sending loop - send file in chunks + std::vector send_buffer(chunk_size); // Buffer for file chunks std::streamsize bytes_read_in_chunk; while (true) { - binaryInputFile.read(send_buffer.data(), chunk_size); // read the file into the buffer - bytes_read_in_chunk = binaryInputFile.gcount(); // Actual bytes read - if(bytes_read_in_chunk > 0){ - zmq::message_t message(send_buffer.data(), bytes_read_in_chunk); + binaryInputFile.read(send_buffer.data(), chunk_size); // Read chunk from file + bytes_read_in_chunk = binaryInputFile.gcount(); // Get actual bytes read + if(bytes_read_in_chunk > 0){ // If we read data + zmq::message_t message(send_buffer.data(), bytes_read_in_chunk); // Create message with data //>>>ZOUT - command_sock.send(message, zmq::send_flags::none); + command_sock.send(message, zmq::send_flags::none); // Send chunk to server //ZIN<<< - command_sock.recv(server_response, zmq::recv_flags::none); + command_sock.recv(server_response, zmq::recv_flags::none); // Get acknowledgment } else { - break; + break; // Exit when file is fully read } } //<<>> - command_sock.recv(server_response, zmq::recv_flags::none); + command_sock.recv(server_response, zmq::recv_flags::none); // Get final acknowledgment } } } else { + // For simple commands, just print the server's response std::cout << "Server response: " << response_str << std::endl; } } - command_sock.close(); - ctx.close(); + command_sock.close(); // Clean up socket + 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) { + // ZeroMQ setup zmq::context_t ctx; - zmq::socket_t heartbeat_sock (ctx, zmq::socket_type::req); - heartbeat_sock.set(zmq::sockopt::curve_serverkey, server_pkey); - heartbeat_sock.set(zmq::sockopt::curve_publickey, client_pkey); - heartbeat_sock.set(zmq::sockopt::curve_secretkey, client_skey); - heartbeat_sock.set(zmq::sockopt::linger, 1); // Close immediately on disconnect - heartbeat_sock.connect("tcp://localhost:5555"); + zmq::socket_t heartbeat_sock (ctx, zmq::socket_type::req); // Create REQ socket for heartbeats + + // Set up security for the socket + heartbeat_sock.set(zmq::sockopt::curve_serverkey, server_pkey); // Server's public key + heartbeat_sock.set(zmq::sockopt::curve_publickey, client_pkey); // Our public key + 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; std::vector items = {}; + while (true) { + // Check if we should terminate this thread if(abort_state.load()==true) { 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) { + // Send heartbeat to server 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::poll(&response_item, 1, 100); // Wait for response with timeout - if (response_item.revents & ZMQ_POLLIN) { + zmq::poll(&response_item, 1, 100); // Poll with 100ms timeout + + if (response_item.revents & ZMQ_POLLIN) { // If we got a response zmq::message_t msg_response; - heartbeat_sock.recv(msg_response, zmq::recv_flags::none); - //std::cout << "Heartbeat Response: " << std::endl; - } else { + heartbeat_sock.recv(msg_response, zmq::recv_flags::none); // Receive it + //std::cout << "Heartbeat Response: " << std::endl; // Commented out debug print + } else { // No response within timeout std::cout << "Bella Server is unavailable" << std::endl; - heartbeat_state = false; - connection_state = false; - break; + heartbeat_state = false; // Mark heartbeat as failed + connection_state = false; // Mark connection as down + break; // Exit heartbeat loop } } } - heartbeat_sock.close(); - ctx.close(); + heartbeat_sock.close(); // Clean up socket + 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() { - // No authentication is used, server will give out pubkey to anybody - // Could use a unique message but since socket is unencrypted this provides - // 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 + // Note: This initial connection is not encrypted, but subsequent connections will be + // ZeroMQ will establish perfect forward secrecy after initial handshake zmq::context_t ctx; - zmq::socket_t pubkey_sock(ctx, zmq::socket_type::req); - pubkey_sock.connect("tcp://127.0.0.1:9555"); + zmq::socket_t pubkey_sock(ctx, zmq::socket_type::req); // Create REQ socket + 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")); + // Send the passphrase to request the public key try { zmq::send_result_t send_result = pubkey_sock.send(z_out, zmq::send_flags::none); } catch (const zmq::error_t& e) { @@ -257,59 +328,71 @@ std::string get_pubkey_from_srv() { } std::cout << "bellazmq connecting to server..." << std::endl; + + // Receive the server's public key zmq::message_t z_in; pubkey_sock.recv(z_in); std::string pub_key = z_in.to_string(); + + // Clean up resources pubkey_sock.close(); ctx.close(); + std::cout << "connection successful" << std::endl; - connection_state = true; + connection_state = true; // Mark connection as established return pub_key; } +/** + * Main function - program entry point + * Sets up security, creates threads, and manages the overall connection + */ int main() { - const size_t chunk_size = 32768; - // Dynamically create keypair, every run is bespoke - // [TODO] send pubkey to server, mkdir, render to that dir - char client_skey[41] = { 0 }; - char client_pkey[41] = { 0 }; - if ( zmq_curve_keypair(&client_pkey[0], &client_skey[0])) { - // 1 is fail + const size_t chunk_size = 32768; // 32KB chunk size (not used in main) + + // Generate a unique cryptographic keypair for this client + char client_skey[41] = { 0 }; // Secret key buffer (private key) + char client_pkey[41] = { 0 }; // Public key buffer + if ( zmq_curve_keypair(&client_pkey[0], &client_skey[0])) { // Generate the keypair + // 1 is failure 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(); - /*if(server_pkey.empty()) { + /*if(server_pkey.empty()) { // Commented out error handling std::cout << "Server is Down" << std::endl; heartbeat_state = false; }*/ + // Convert char arrays to strings for easier handling std::string client_pkey_str(client_pkey); 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 heartbeat_t(heartbeat_thread, server_pkey, client_pkey_str, client_skey_str); + // Main monitoring loop - checks connection health while (true) { - if (!heartbeat_state.load()) { + if (!heartbeat_state.load()) { // If heartbeat has failed std::cout << "Dead" << std::endl; - abort_state==true; + abort_state = true; // Signal threads to terminate (note: this is using assignment, not comparison) break; } - if (connection_state.load() == false) { + if (connection_state.load() == false) { // If connection is down std::cout << "Dead2" << std::endl; - abort_state==true; + abort_state = true; // Signal threads to terminate (note: this is using assignment, not comparison) 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(); - heartbeat_t.join(); + + abort_state = true; // Signal threads to terminate (note: this is using assignment, not comparison) + command_t.join(); // Wait for command thread to finish + heartbeat_t.join(); // Wait for heartbeat thread to finish return 0; } diff --git a/server.cpp b/server.cpp index 5a9d6a9..4923f84 100644 --- a/server.cpp +++ b/server.cpp @@ -1,153 +1,176 @@ -#include -#include -#include -#include -#include -#include +/** + * ZeroMQ Server Implementation + * + * This program creates a server that: + * - Uses ZeroMQ (zmq) for network communication + * - 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 // For string streams -#include +#include // For input/output operations (cout, cerr) +#include // For file operations (ifstream, ofstream) +#include // For creating and managing threads +#include // ZeroMQ C++ binding for network communication +#include // For dynamic arrays (vectors) +#include // For time-related functions -#include +#include // For string stream operations +#include // For thread-safe variables -std::atomic heartbeat_state (true); -std::atomic client_state (false); +#include // Already included above, redundant +// Atomic variables for thread-safe state tracking across multiple threads +std::atomic heartbeat_state (true); // Tracks if heartbeat is active +std::atomic 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) { - zmq::context_t ctx; - zmq::socket_t command_sock(ctx, zmq::socket_type::rep); - //command_sock.set(zmq::sockopt::sndtimeo, 10000); + // ZeroMQ setup + zmq::context_t ctx; // Create a ZeroMQ context (required for all sockets) + 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::curve_server, true); - command_sock.set(zmq::sockopt::curve_secretkey, server_skey); - //command_sock.set(zmq::sockopt::linger, 100); // Close immediately on disconnect - command_sock.bind("tcp://*:5556"); + command_sock.set(zmq::sockopt::curve_server, true); // Enable secure curve encryption + command_sock.set(zmq::sockopt::curve_secretkey, server_skey); // Set the server's secret key + //command_sock.set(zmq::sockopt::linger, 100); // Commented out linger option + command_sock.bind("tcp://*:5556"); // Bind socket to port 5556 on all interfaces zmq::message_t client_response; try { + // File paths for operations std::string write_file = "./oomer.bsz"; std::string read_file = "./oomer.png"; - const size_t chunk_size = 65536; - std::vector sftp_buffer(chunk_size); // Buffer to hold each chunk - std::ofstream binaryOutputFile;// for writing - std::ifstream binaryInputFile;// for reading + const size_t chunk_size = 65536; // 64KB chunks for file transfers + std::vector sftp_buffer(chunk_size); // Buffer to hold each chunk + std::ofstream binaryOutputFile; // File stream for writing + std::ifstream binaryInputFile; // File stream for reading + + // Main command processing loop while (true) { zmq::message_t msg_command; - //ZIN<<< - command_sock.recv(msg_command, zmq::recv_flags::none); - std::string client_command = msg_command.to_string(); + //ZIN<<< (This comment indicates receiving data from the network) + command_sock.recv(msg_command, zmq::recv_flags::none); // Wait for a command from client + std::string client_command = msg_command.to_string(); // Convert message to string 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; - //>>>ZOUT + //>>>ZOUT (This comment indicates sending data to the network) 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; //>>>ZOUT - command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none); - // RENDER + command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none); // Send acknowledgment + // RENDER command } 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.start(); std::cout << "start render" << std::endl; //>>>ZOUT 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 std::string read_file = "./oomer.png"; std::cout << "Executing get command\n"; 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()) { std::cerr << "Error opening file for read" << std::endl; //>>>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 { //>>>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 send_buffer(chunk_size); std::streamsize bytes_read_in_chunk; while (true) { zmq::message_t z_in; //ZIN - command_sock.recv(z_in); // Block until zGo, or any message - binaryInputFile.read(send_buffer.data(), chunk_size); // read the file into the buffer - bytes_read_in_chunk = binaryInputFile.gcount(); // Actual bytes read + command_sock.recv(z_in); // Wait for client to request next chunk + binaryInputFile.read(send_buffer.data(), chunk_size); // Read file chunk into buffer + bytes_read_in_chunk = binaryInputFile.gcount(); // Get actual bytes read if(bytes_read_in_chunk > 0){ 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 - command_sock.send(message, zmq::send_flags::none); + command_sock.send(message, zmq::send_flags::none); // Send chunk to client } else { //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; - 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") { std::ifstream log_file("logfile.txt"); if (log_file.is_open()) { 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 - 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 { //>>>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(); } + // SEND command - receive a file from client } 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()) { std::cerr << "Error opening file for writing" << std::endl; //>>>ZOUT - command_sock.send(zmq::message_t("ERR"), zmq::send_flags::none); - } else { // File handle open and ready + 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 //>>>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) { zmq::message_t recv_data; //ZIN<<< - command_sock.recv(recv_data, zmq::recv_flags::none); - if(recv_data.size() < 8) { // data and signals sent on same socket - // Allow for signals up to 8 bytes, EOF, ERR - // messages are null terminated requiring -1 + command_sock.recv(recv_data, zmq::recv_flags::none); // Receive chunk from client + if(recv_data.size() < 8) { // Check if this is a signal rather than data + // Small messages (under 8 bytes) are likely signals std::string response_str(static_cast(recv_data.data()), recv_data.size()-1); - if (response_str=="EOF") { + if (response_str=="EOF") { // End of file signal //>>>ZOUT command_sock.send(zmq::message_t("ACK"), zmq::send_flags::none); - break; // End of file - } else if(response_str=="ERR") { + break; // Done receiving file + } else if(response_str=="ERR") { // Error signal std::cout << "ERR on client" << std::endl; //>>>ZOUT 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(recv_data.data()), recv_data.size()); //>>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(); } - } else { // A unknown REQ sent, acknowledge because req-rep pattern is blocking + } else { // Unknown command //>>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) { - // Handle ZMQ-specific exceptions + // Handle ZeroMQ-specific exceptions std::cerr << "ZMQ error: " << e.what() << std::endl; ctx.close(); command_sock.close(); @@ -159,97 +182,108 @@ void command_thread(std::string server_skey) { // Catch any other exceptions std::cerr << "Unknown exception caught." << std::endl; } - command_sock.close(); - ctx.close(); + command_sock.close(); // Clean up socket + 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) { - heartbeat_state = true; + heartbeat_state = true; // Initialize heartbeat as active std::cout << "new heartbeat_thread" << std::endl; + + // ZeroMQ setup zmq::context_t ctx; - zmq::socket_t heartbeat_sock (ctx, zmq::socket_type::rep); - heartbeat_sock.set(zmq::sockopt::curve_server, true); - heartbeat_sock.set(zmq::sockopt::curve_secretkey, server_skey); - heartbeat_sock.bind("tcp://*:5555"); + zmq::socket_t heartbeat_sock (ctx, zmq::socket_type::rep); // Create a REP socket for heartbeats + heartbeat_sock.set(zmq::sockopt::curve_server, true); // Enable encryption + heartbeat_sock.set(zmq::sockopt::curve_secretkey, server_skey); // Set secret key + heartbeat_sock.bind("tcp://*:5555"); // Listen on port 5555 + while(true) { - - //Start polling heartbeats once client connects + // Only start checking heartbeats once a client connects 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::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; //ZIN<<< - heartbeat_sock.recv(message, zmq::recv_flags::none); + heartbeat_sock.recv(message, zmq::recv_flags::none); // Receive heartbeat //ZOUT>>> - heartbeat_sock.send(zmq::message_t("ACK"), zmq::send_flags::dontwait); // No block - } else { //timeout + heartbeat_sock.send(zmq::message_t("ACK"), zmq::send_flags::dontwait); // Acknowledge without blocking + } else { // Timeout - no heartbeat received 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(); - ctx.close(); + heartbeat_sock.close(); // Clean up socket + 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) { zmq::context_t ctx; - zmq::socket_t sock(ctx, zmq::socket_type::rep); - sock.bind("tcp://*:9555"); //[TODO] args to set port + zmq::socket_t sock(ctx, zmq::socket_type::rep); // Create REP socket + sock.bind("tcp://*:9555"); // Bind to port 9555 zmq::message_t z_in; std::cout << "Entered: Public Key Serving Mode" << std::endl; //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) { - zmq::message_t z_out(pub_key); + zmq::message_t z_out(pub_key); // Create message with the public key //ZOUT>>> - sock.send(z_out, zmq::send_flags::none); - client_state = true; + sock.send(z_out, zmq::send_flags::none); // Send the public key + client_state = true; // Mark that a client has connected } - sock.close(); - ctx.close(); + sock.close(); // Clean up socket + 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 -// utf8-encoded args when the application is unicode. -// -//#include "dl_core/dl_main.inl" -//int DL_main(Args& args) -//{ - +/** + * Main function - program entry point + * Sets up security, creates threads, and manages client connections + */ int main() { - // Generate brand new keypair on launch - // [TODO] Add client side public key fingerprinting for added security - char skey[128] = { 0 }; - char pkey[128] = { 0 }; - if ( zmq_curve_keypair(&pkey[0], &skey[0])) { - // 1 is fail + // Generate brand new cryptographic keypair on launch for security + char skey[128] = { 0 }; // Secret key buffer (private key) + char pkey[128] = { 0 }; // Public key buffer + if ( zmq_curve_keypair(&pkey[0], &skey[0])) { // Generate the keypair + // 1 is failure std::cout << "\ncurve keypair gen failed."; - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); // Exit program if key generation fails } - // Multi threading - std::thread command_t(command_thread, skey); - std::thread heartbeat_t(heartbeat_thread, skey); + // Start worker threads for handling commands and heartbeats + std::thread command_t(command_thread, skey); // Thread for handling commands + std::thread heartbeat_t(heartbeat_thread, skey); // Thread for monitoring heartbeats - // - while(true) { // awaiting new client loop - heartbeat_state = true; - pkey_server(pkey); // blocking wait client to get public key on port 5555 + // Main loop that handles client connections + while(true) { // Infinite loop to accept new clients when old ones disconnect + heartbeat_state = true; // Reset heartbeat state for new client + pkey_server(pkey); // Wait for client to request public key (blocking call) std::cout << "Client connected" << std::endl; - while(true) { // inner loop - if (heartbeat_state.load()==false) { + // Loop while client is connected + while(true) { // Monitor client connection + if (heartbeat_state.load()==false) { // Check if heartbeat is still active 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 } } }