diff --git a/CMakeLists.txt b/CMakeLists.txt index d8e5747..60472a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,18 @@ add_executable(bootstrap_contract bootstrap-contract/bootstrap_contract.cpp ) +#-------Sashi CLI------- + +add_executable(sashi + sashi-cli/cli-manager.cpp + sashi-cli/main.cpp +) + +target_link_libraries(sashi + libboost_stacktrace_backtrace.a + ${CMAKE_DL_LIBS} # Needed for stacktrace support +) + #-------Sashimono Agent------- add_subdirectory(src/killswitch) @@ -47,6 +59,7 @@ target_link_libraries(sagent add_dependencies(sagent bootstrap_contract + sashi ) add_custom_command(TARGET sagent POST_BUILD @@ -63,7 +76,7 @@ target_precompile_headers(sagent PUBLIC src/pchheader.hpp) # Add target to generate the installer setup. add_custom_target(installer COMMAND mkdir -p ./build/sashimono-installer - COMMAND bash -c "cp -r ./build/{sagent,hpfs,user-install.sh,user-uninstall.sh,contract_template} ./build/sashimono-installer/" + COMMAND bash -c "cp -r ./build/{sagent,sashi,hpfs,user-install.sh,user-uninstall.sh,contract_template} ./build/sashimono-installer/" COMMAND bash -c "cp -r ./installer/{docker-install.sh,registry-install.sh,registry-uninstall.sh,sashimono-install.sh,sashimono-uninstall.sh} ./build/sashimono-installer/" COMMAND bash -c "cp -r ./dependencies/{user-cgcreate.sh,libblake3.so} ./build/sashimono-installer/" COMMAND tar cfz ./build/sashimono-installer.tar.gz --directory=./build/ sashimono-installer diff --git a/examples/message-board/message-board.js b/examples/message-board/message-board.js index f4eaa3a..787aa5e 100644 --- a/examples/message-board/message-board.js +++ b/examples/message-board/message-board.js @@ -1,4 +1,3 @@ -const WebSocket = require('ws'); const https = require('https'); const http = require('http'); const fs = require('fs'); @@ -7,39 +6,30 @@ const { v4: uuidv4 } = require('uuid'); const { execSync } = require("child_process"); const express = require('express'); -// Generate tls keys if not found. -if (!fs.existsSync('./tlskey.pem')) { - console.log("TLS key files not detected. Generating.."); - execSync("openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout tlskey.pem -out tlscert.pem -subj \"/C=SA/ST=SA/L=SA/O=SA/CN=SA\""); - console.log("New tls key files generated.") +const cliDevPath = "../../build/sashi"; +const cliProdPath = "/usr/bin/sashi"; +let cliPath; +let args = process.argv; +if (args.length == 3 && args[2] == 'prod') + cliPath = cliProdPath; +else if (args.length == 2 || (args.length == 3 && args[2] == 'dev')) + cliPath = cliDevPath; +else { + console.log("Arguments mismatch.\n Usage: node message-board (optional)"); + process.exit(0); } -let restServer, websocketServer; -const reqMap = {}; // Store response object vs message id to deliver response back when the SA responses. + +if (!fs.existsSync(cliPath)) { + console.error(`Sashi CLI does not exist in ${cliPath}.`) + process.exit(0); +} + +let restServer; /** * Interactive interface to get message from the command line and sent it to all the connected agents. */ const interatctiveInterface = async () => { - const server = https.createServer({ - cert: fs.readFileSync('./tlscert.pem'), - key: fs.readFileSync('./tlskey.pem') - }); - - websocketServer = new WebSocket.Server({ server }); - - websocketServer.on('connection', (ws) => { - ws.on('message', (msg) => { - try { - const message = JSON.parse(Buffer.from(msg).toString()); - console.log('Received: ', message); - reqMap[message.reply_for] && reqMap[message.reply_for].status(message.type == "error" ? 500 : 200).send(message); - - } catch (error) { - console.error("Error occured in json parsing." + error); - } - }); - }); - // start listening for stdin const rl = readLine.createInterface({ input: process.stdin, @@ -49,9 +39,7 @@ const interatctiveInterface = async () => { // On ctrl + c we should close SA connection gracefully. rl.on('SIGINT', () => { console.log('SIGINT received...'); - websocketServer.close(); rl.close(); - server.close(); restServer && restServer.close(); }); @@ -63,135 +51,149 @@ const interatctiveInterface = async () => { }) } - server.listen(5000, () => { - console.log(`wss://localhost:${server.address().port}`) - console.log("Ready to accept inputs."); + console.log("Ready to accept inputs."); - const inputPump = () => { - rl.question('', async (inp) => { - - if (inp.length > 0) { - if (websocketServer.clients.size == 0) { - console.log('No Sashimano agents connected yet.') - } - else { - switch (inp) { - case 'create': - contractId = await askForInput('Contract ID (default:uuidv4)', uuidv4()); - image = await askForInput('Image: 1=ubuntu(default) | 2=nodejs', "1"); - if (image != "1" && image != "2") { - console.error('Invalid image. (Should be "1" or "2").') - break; - } - - sendToAllAgents(JSON.stringify({ - id: uuidv4(), - type: 'create', - owner_pubkey: 'ed5cb83404120ac759609819591ef839b7d222c84f1f08b3012f490586159d2b50', - contract_id: contractId, - image: (image == "1" ? "ubt.20.04" : "ubt.20.04-njs.14") - })); - break; - case 'initiate': - containerName = await askForInput('Container Name'); - role = await askForInput('Role: validator(default) | observer', "validator"); - if (role != 'validator' && role != 'observer') { - console.error('Invalid role. (Should be "validator" or "observer").') - break; - } - - history = await askForInput('History <{full|custom},max_primary_shards,max_raw_shards> (custom,1,1)', "custom,1,1"); - split = []; - if (history) { - split = history.split(','); - if (split.length == 0 || split.length == 0 > 3) { - console.error('Invalid history.') - break; - } - else if (split[0] != 'full' && split[0] != 'custom') { - console.error('Invalid history. (Should be "full" or "custom").') - break; - } - } - peers = await askForInput('Comma seperated Peer List ,,...'); - unl = await askForInput('Comma seperated UNL ,,...'); - sendToAllAgents(JSON.stringify({ - id: uuidv4(), - type: 'initiate', - container_name: containerName, - peers: peers ? peers.split(',') : [], - unl: unl ? unl.split(',') : [], - role: role, - history: split.length > 0 ? split[0] : '', - max_primary_shards: split.length > 1 ? parseInt(split[1]) : '', - max_raw_shards: split.length > 2 ? parseInt(split[2]) : '' - })); - break; - case 'destroy': - containerName = await askForInput('Container Name'); - sendToAllAgents(JSON.stringify({ - id: uuidv4(), - type: 'destroy', - container_name: containerName - })) - break; - case 'start': - containerName = await askForInput('Container Name'); - sendToAllAgents(JSON.stringify({ - id: uuidv4(), - type: 'start', - container_name: containerName - })) - break; - case 'stop': - containerName = await askForInput('Container Name'); - sendToAllAgents(JSON.stringify({ - id: uuidv4(), - type: 'stop', - container_name: containerName - })) - break; - - default: - console.error('Invalid command. Only valid [create, initiate, destroy, start and stop]'); - break; + const inputPump = () => { + rl.question('', async (inp) => { + if (inp.length > 0) { + switch (inp) { + case 'status': + checkAgentStatus(); + break; + case 'create': + contractId = await askForInput('Contract ID (default:uuidv4)', uuidv4()); + image = await askForInput('Image: 1=ubuntu(default) | 2=nodejs', "1"); + if (image != "1" && image != "2") { + console.error('Invalid image. (Should be "1" or "2").') + break; } - } + sendToAgent(JSON.stringify({ + id: uuidv4(), + type: 'create', + owner_pubkey: 'ed5cb83404120ac759609819591ef839b7d222c84f1f08b3012f490586159d2b50', + contract_id: contractId, + image: (image == "1" ? "ubt.20.04" : "ubt.20.04-njs.14") + })); + break; + case 'initiate': + containerName = await askForInput('Container Name'); + role = await askForInput('Role: validator(default) | observer', "validator"); + if (role != 'validator' && role != 'observer') { + console.error('Invalid role. (Should be "validator" or "observer").') + break; + } + history = await askForInput('History <{full|custom},max_primary_shards,max_raw_shards> (custom,1,1)', "custom,1,1"); + split = []; + if (history) { + split = history.split(','); + if (split.length == 0 || split.length == 0 > 3) { + console.error('Invalid history.') + break; + } + else if (split[0] != 'full' && split[0] != 'custom') { + console.error('Invalid history. (Should be "full" or "custom").') + break; + } + } + peers = await askForInput('Comma seperated Peer List ,,...'); + unl = await askForInput('Comma seperated UNL ,,...'); + sendToAgent(JSON.stringify({ + id: uuidv4(), + type: 'initiate', + container_name: containerName, + peers: peers ? peers.split(',') : [], + unl: unl ? unl.split(',') : [], + role: role, + history: split.length > 0 ? split[0] : '', + max_primary_shards: split.length > 1 ? parseInt(split[1]) : '', + max_raw_shards: split.length > 2 ? parseInt(split[2]) : '' + })); + break; + case 'destroy': + containerName = await askForInput('Container Name'); + sendToAgent(JSON.stringify({ + id: uuidv4(), + type: 'destroy', + container_name: containerName + })) + break; + case 'start': + containerName = await askForInput('Container Name'); + sendToAgent(JSON.stringify({ + id: uuidv4(), + type: 'start', + container_name: containerName + })) + break; + case 'stop': + containerName = await askForInput('Container Name'); + sendToAgent(JSON.stringify({ + id: uuidv4(), + type: 'stop', + container_name: containerName + })) + break; + + default: + console.error('Invalid command. Only valid [create, initiate, destroy, start and stop]'); + break; } - inputPump(); - }) - } - inputPump(); - }); + } + + inputPump(); + }) + } + inputPump(); } -const sendToAllAgents = (msg) => { - websocketServer && websocketServer.clients.forEach(ws => { - ws.send(msg); - }); +const sendToAgent = (msg, res = null) => { + try { + let output = execSync(`${cliPath} json '${msg}'`, { stdio: 'pipe' }); + let message = Buffer.from(output).toString(); + message = JSON.parse(message.substring(0, message.length - 2)); // Skipping the \n from the result. + console.log('Received: ', message); + res && res.status((message.content && typeof message.content == 'string' && message.content.endsWith("error")) ? 500 : 200).send(message); + } + catch (e) { + console.error(`Message sending error. ${e}`); + res && res.status(500).send(`Message sending error. ${e}`); + } +} + +const checkAgentStatus = (res = null) => { + try { + let output = execSync(`${cliPath} status`, { stdio: 'pipe' }); + let message = Buffer.from(output).toString(); + message = message.substring(0, message.length - 1); // Skipping the \n from the result. + console.log(`Socket ${message} is online.`); + res && res.status(200).send(message); + return true; + } + catch (e) { + console.error(`Socket is offline. ${e}`); + res && res.status(500).send(`Socket is offline. ${e}`); + return false; + } } const webServerProtocol = (process.env.SSL === "true") ? "https" : "http"; const webServerPort = 5001; const restApi = async () => { + // Generate tls keys if not found. + if (webServerProtocol == "https" && !fs.existsSync('./tlskey.pem')) { + console.log("TLS key files not detected. Generating.."); + execSync("openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout tlskey.pem -out tlscert.pem -subj \"/C=SA/ST=SA/L=SA/O=SA/CN=SA\""); + console.log("New tls key files generated.") + } + const app = express(); app.use(express.json()); - // Handle errors before forward to processing. - app.use((req, res, next) => { - if (websocketServer.clients.size == 0) { - console.log('No Sashimano agents connected yet.') - res.status(404).send('No Sashimano agents connected yet.'); - } - else { - next(); - } - }); app.post("/status", (req, res) => { - res.send("Message board running..."); + checkAgentStatus(res); }); app.post("/create", (req, res) => { const id = uuidv4(); @@ -202,58 +204,49 @@ const restApi = async () => { contract_id: (req.body.contract_id === "") ? uuidv4() : req.body.contract_id, image: req.body.image ? req.body.image : "ubt.20.04" }; - reqMap[id] = res; - sendToAllAgents(JSON.stringify(msg)); + sendToAgent(JSON.stringify(msg), res); }); app.post("/initiate", (req, res) => { const id = uuidv4(); const msg = { id, type: 'initiate', - owner_pubkey: req.body.owner_pubkey, container_name: req.body.container_name, - peers: req.body.peers ? req.body.peers: [], - unl: req.body.unl ? req.body.unl: [], - role: req.body.role? req.body.role: 'validator', - history: req.body.history? req.body.history: 'custom', - max_primary_shards: req.body.max_primary_shards? req.body.max_primary_shards: 1, - max_raw_shards: req.body.max_raw_shards? req.body.max_raw_shards: 1 + peers: req.body.peers ? req.body.peers : [], + unl: req.body.unl ? req.body.unl : [], + role: req.body.role ? req.body.role : 'validator', + history: req.body.history ? req.body.history : 'custom', + max_primary_shards: req.body.max_primary_shards ? req.body.max_primary_shards : 1, + max_raw_shards: req.body.max_raw_shards ? req.body.max_raw_shards : 1 }; - reqMap[id] = res; - sendToAllAgents(JSON.stringify(msg)); + sendToAgent(JSON.stringify(msg), res); }); app.post("/start", (req, res) => { const id = uuidv4(); const msg = { id, type: 'start', - owner_pubkey: req.body.owner_pubkey, container_name: req.body.container_name }; - reqMap[id] = res; - sendToAllAgents(JSON.stringify(msg)); + sendToAgent(JSON.stringify(msg), res); }); app.post("/stop", (req, res) => { const id = uuidv4(); const msg = { id, type: 'stop', - owner_pubkey: req.body.owner_pubkey, container_name: req.body.container_name }; - reqMap[id] = res; - sendToAllAgents(JSON.stringify(msg)); + sendToAgent(JSON.stringify(msg), res); }); app.post("/destroy", (req, res) => { const id = uuidv4(); const msg = { id, type: 'destroy', - owner_pubkey: req.body.owner_pubkey, container_name: req.body.container_name }; - reqMap[id] = res; - sendToAllAgents(JSON.stringify(msg)); + sendToAgent(JSON.stringify(msg), res); }); restServer = (webServerProtocol == "https") ? https.createServer({ @@ -266,6 +259,8 @@ const restApi = async () => { } (async () => { - await restApi(); - await interatctiveInterface(); + if (checkAgentStatus()) { + await restApi(); + await interatctiveInterface(); + } })(); diff --git a/installer/sashimono-install.sh b/installer/sashimono-install.sh index df614f9..4133c17 100755 --- a/installer/sashimono-install.sh +++ b/installer/sashimono-install.sh @@ -2,6 +2,7 @@ # Sashimono agent installation script. # This must be executed with root privileges. +user_bin=/usr/bin sashimono_bin=/usr/bin/sashimono-agent docker_bin=/usr/bin/sashimono-agent/dockerbin sashimono_data=/etc/sashimono @@ -62,6 +63,9 @@ function rollback() { cp "$script_dir"/{sagent,hpfs,user-cgcreate.sh,user-install.sh,user-uninstall.sh} $sashimono_bin chmod -R +x $sashimono_bin +# Install Sashimono CLI binaries into user bin dir. +cp "$script_dir"/sashi $user_bin + # Download and install rootless dockerd. "$script_dir"/docker-install.sh $docker_bin diff --git a/installer/sashimono-uninstall.sh b/installer/sashimono-uninstall.sh index c987fbb..6385662 100755 --- a/installer/sashimono-uninstall.sh +++ b/installer/sashimono-uninstall.sh @@ -2,6 +2,7 @@ # Sashimono agent uninstall script. # -q for non-interactive (quiet) mode +user_bin=/usr/bin sashimono_bin=/usr/bin/sashimono-agent docker_bin=/usr/bin/sashimono-agent/dockerbin sashimono_data=/etc/sashimono @@ -74,6 +75,9 @@ rm /etc/systemd/system/$sashimono_service.service echo "Deleting binaries..." rm -r $sashimono_bin +echo "Deleting Sashimono CLI..." +rm $user_bin/sashi + echo "Deleting data folder..." rm -r $sashimono_data diff --git a/sashi-cli/cli-manager.cpp b/sashi-cli/cli-manager.cpp new file mode 100644 index 0000000..0a1d690 --- /dev/null +++ b/sashi-cli/cli-manager.cpp @@ -0,0 +1,143 @@ +#include "pchheader.hpp" +#include "cli-manager.hpp" + +namespace cli +{ + constexpr const char *SOCKET_NAME = "sa.sock"; // Name of the sashimono socket. + constexpr const char *DATA_DIR = "/etc/sashimono"; // Sashimono data directory. + constexpr const int BUFFER_SIZE = 1024; // Max read buffer size. + + cli_context ctx; + + bool init_success = false; + + /** + * Initialize the socket and connect. + * @return 0 on success, -1 on error. + */ + int init(std::string_view sashi_dir) + { + ctx.sashi_dir = sashi_dir; + + // Get the socket path from available location. + if (get_socket_path(ctx.socket_path) == -1) + return -1; + + // Create the seq paket socket. + ctx.socket_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (ctx.socket_fd == -1) + { + std::cerr << errno << " :Error while creating the sashimono socket.\n"; + return -1; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, ctx.socket_path.data(), sizeof(addr.sun_path) - 1); + + if (connect(ctx.socket_fd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) + { + // If permission denied, show a custom error. + if (errno == EACCES) + std::cerr << "Permission denied: Only root or users in 'sashiadmin' group can access the sashimono socket.\n"; + else + std::cerr << errno << " :Error while connecting to the sashimono socket.\n"; + close(ctx.socket_fd); + return -1; + } + + init_success = true; + return 0; + } + + /** + * Locate and return the sashimono agent socket path according predefined rules. + * If sa.sock found on the same path as the cli binary, use that. (to support dev testing) + * Else sa.sock found on /etc/sashimono, use that. + * Else show error. + * @param socket_path Socket path to be populated. + * @return 0 on success, -1 on error. + */ + int get_socket_path(std::string &socket_path) + { + // Check whether socket exists in exec path. + std::string path = ctx.sashi_dir + std::string("/") + SOCKET_NAME; + struct stat st; + if (stat(path.data(), &st) == 0 && S_ISSOCK(st.st_mode)) + { + socket_path = path; + return 0; + } + + // Otherwise check in the data dir. + path = DATA_DIR + std::string("/") + SOCKET_NAME; + memset(&st, 0, sizeof(struct stat)); + if (stat(path.data(), &st) == 0 && S_ISSOCK(st.st_mode)) + { + socket_path = path; + return 0; + } + + std::cerr << SOCKET_NAME << " is not found.\n"; + return -1; + } + + /** + * Write a given message into the sashimono socket. + * @param message Message to be write. + * @return 0 on success, -1 on error. + */ + int write_to_socket(std::string_view message) + { + if (!init_success) + { + std::cerr << "Sashimono socket is not initialized.\n"; + return -1; + } + + if (write(ctx.socket_fd, message.data(), message.size()) == -1) + { + std::cerr << errno << " :Error while wrting to the sashimono socket.\n"; + return -1; + } + + return 0; + } + + /** + * Read message from the sashimono socket. + * @param message Message to be read. + * @return Read message length on success, -1 on error. + */ + int read_from_socket(std::string &message) + { + if (!init_success) + { + std::cerr << "Sashimono socket is not initialized.\n"; + return -1; + } + + // Resize the message to max length and resize to original read length after reading. + message.resize(BUFFER_SIZE); + const int res = read(ctx.socket_fd, message.data(), message.length()); + if (res == -1) + { + std::cerr << errno << " :Error while reading from the sashimono socket.\n"; + return -1; + } + message.resize(res); + + return res; + } + + /** + * Close the socket and deinitialize. + */ + void deinit() + { + if (init_success) + close(ctx.socket_fd); + } +} \ No newline at end of file diff --git a/sashi-cli/cli-manager.hpp b/sashi-cli/cli-manager.hpp new file mode 100644 index 0000000..efb1a21 --- /dev/null +++ b/sashi-cli/cli-manager.hpp @@ -0,0 +1,26 @@ +#ifndef _CLI_MANAGER_ +#define _CLI_MANAGER_ + +namespace cli +{ + struct cli_context + { + std::string sashi_dir; // Path of the Sashi CLI executable. + std::string socket_path; // Path of the sashimono socket. + int socket_fd = -1; // File descriptor of the socket. + }; + + extern cli_context ctx; + + int init(std::string_view sashi_dir); + + int get_socket_path(std::string &socket_path); + + int write_to_socket(std::string_view message); + + int read_from_socket(std::string &message); + + void deinit(); +} + +#endif diff --git a/sashi-cli/main.cpp b/sashi-cli/main.cpp new file mode 100644 index 0000000..fe3e466 --- /dev/null +++ b/sashi-cli/main.cpp @@ -0,0 +1,113 @@ +/** + Entry point for Sashi-cli +**/ +#include "pchheader.hpp" +#include "cli-manager.hpp" + +#define PARSE_ERROR \ + { \ + std::cerr << "Arguments mismatch.\n"; \ + std::cerr << "Usage:\n"; \ + std::cerr << "sashi status\n"; \ + std::cerr << "sashi json \n"; \ + std::cerr << "Example: sashi json '{\"container_name\":\"\", ...}'\n"; \ + return -1; \ + } + +/** + * Performs any cleanup on graceful application termination. + */ +void deinit() +{ + cli::deinit(); +} + +void segfault_handler(int signum) +{ + std::cout << boost::stacktrace::stacktrace() << std::endl; + exit(SIGABRT); +} + +/** + * Global exception handler for std exceptions. + */ +void std_terminate() noexcept +{ + const std::exception_ptr exptr = std::current_exception(); + if (exptr != 0) + { + try + { + std::rethrow_exception(exptr); + } + catch (std::exception &ex) + { + std::cerr << "std error: " << ex.what() << std::endl; + } + catch (...) + { + std::cerr << "std error: Terminated due to unknown exception\n"; + } + } + else + { + std::cerr << "std error: Terminated due to unknown reason\n"; + } + + std::cerr << boost::stacktrace::stacktrace() << std::endl; + + exit(1); +} + +int main(int argc, char **argv) +{ + // Register exception and segfault handlers. + std::set_terminate(&std_terminate); + signal(SIGSEGV, &segfault_handler); + signal(SIGABRT, &segfault_handler); + + // Disable SIGPIPE to avoid crashing on broken pipe IO. + { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGPIPE); + // sigprocmask is used instead of pthread_sigmask since this is single threaded. + sigprocmask(SIG_BLOCK, &mask, NULL); + } + + if (argc > 1) + { + // Take the realpath of sash exec path. + std::array buffer; + ::realpath(argv[0], buffer.data()); + buffer[PATH_MAX] = '\0'; + const std::string exec_dir = dirname(buffer.data()); + + const std::string command = argv[1]; + + if (command == "status") + { + if (cli::init(exec_dir) == -1) + return -1; + + std::cout << cli::ctx.socket_path << std::endl; + cli::deinit(); + return 0; + } + else if (command == "json" && argc == 3) + { + std::string output; + if (cli::init(exec_dir) == -1 || cli::write_to_socket(argv[2]) == -1 || cli::read_from_socket(output) == -1) + { + cli::deinit(); + return -1; + } + + std::cout << output << std::endl; + cli::deinit(); + return 0; + } + } + + PARSE_ERROR +} \ No newline at end of file diff --git a/sashi-cli/pchheader.hpp b/sashi-cli/pchheader.hpp new file mode 100644 index 0000000..35dfd29 --- /dev/null +++ b/sashi-cli/pchheader.hpp @@ -0,0 +1,13 @@ +#ifndef _CLI_PCHHEADER_ +#define _CLI_PCHHEADER_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#endif \ No newline at end of file diff --git a/src/comm/comm_handler.cpp b/src/comm/comm_handler.cpp index ef2a88f..2641639 100644 --- a/src/comm/comm_handler.cpp +++ b/src/comm/comm_handler.cpp @@ -260,14 +260,15 @@ namespace comm **/ int read_socket(std::string &message) { - char buffer[BUFFER_SIZE]; - const int ret = read(ctx.data_socket, buffer, BUFFER_SIZE); + // Resize the message to max length and resize to original read length after reading. + message.resize(BUFFER_SIZE); + const int ret = read(ctx.data_socket, message.data(), message.length()); if (ret == -1) { LOG_ERROR << errno << ": Error receiving data."; return -1; } - message = std::string(buffer); + message.resize(ret); return ret; } } // namespace comm