diff --git a/slsSupportLib/include/sls/ClientSocket.h b/slsSupportLib/include/sls/ClientSocket.h index dcfc02b25..4ef8e5da4 100644 --- a/slsSupportLib/include/sls/ClientSocket.h +++ b/slsSupportLib/include/sls/ClientSocket.h @@ -21,6 +21,7 @@ class ClientSocket : public DataSocket { private: void readReply(int &ret, void *retval, size_t retval_size); + [[noreturn]] void throwError(const std::string &msg) const; struct sockaddr_in serverAddr {}; std::string socketType; }; diff --git a/slsSupportLib/include/sls/DataSocket.h b/slsSupportLib/include/sls/DataSocket.h index 31d9afa72..c8c7fc75e 100644 --- a/slsSupportLib/include/sls/DataSocket.h +++ b/slsSupportLib/include/sls/DataSocket.h @@ -77,8 +77,6 @@ class DataSocket { std::string Receive(size_t length); - int read(void *buffer, size_t size); - int write(void *buffer, size_t size); int setTimeOut(int t_seconds); int setReceiveTimeout(int us); void close(); @@ -88,6 +86,8 @@ class DataSocket { private: int sockfd_ = -1; int fnum_{0}; + + std::string_view errno_name(int e); }; }; // namespace sls diff --git a/slsSupportLib/include/sls/string_utils.h b/slsSupportLib/include/sls/string_utils.h index dc1c037ac..abeed79a7 100644 --- a/slsSupportLib/include/sls/string_utils.h +++ b/slsSupportLib/include/sls/string_utils.h @@ -96,4 +96,6 @@ bool replace_first(std::string *s, const std::string &substr, std::pair ParseHostPort(const std::string &s); +std::string to_lower(const std::string &s); + } // namespace sls diff --git a/slsSupportLib/src/ClientSocket.cpp b/slsSupportLib/src/ClientSocket.cpp index 117e7621c..6dc6aac42 100644 --- a/slsSupportLib/src/ClientSocket.cpp +++ b/slsSupportLib/src/ClientSocket.cpp @@ -5,9 +5,11 @@ #include "sls/sls_detector_defs.h" #include "sls/sls_detector_exceptions.h" #include "sls/sls_detector_funcs.h" +#include "sls/string_utils.h" #include #include #include +#include #include #include #include @@ -24,9 +26,10 @@ ClientSocket::ClientSocket(std::string stype, const std::string &host, hints.ai_flags |= AI_CANONNAME; if (getaddrinfo(host.c_str(), nullptr, &hints, &result) != 0) { - std::string msg = "ClientSocket cannot decode host:" + host + - " on port " + std::to_string(port) + "\n"; - throw SocketError(msg); + + + auto msg = fmt::format("Cannot resolve {} hostname: '{}'", to_lower(socketType), host); + throwError(msg); } // TODO! Erik, results could have multiple entries do we need to loop @@ -40,10 +43,11 @@ ClientSocket::ClientSocket(std::string stype, const std::string &host, if (::connect(getSocketId(), (struct sockaddr *)&serverAddr, sizeof(serverAddr)) != 0) { freeaddrinfo(result); - std::string msg = "ClientSocket: Cannot connect to " + socketType + - ":" + host + " on port " + std::to_string(port) + - "\n"; - throw SocketError(msg); + auto msg = fmt::format( + "Cannot connect to {} on {}:{}\n", + to_lower(socketType), host, port); + + throwError(msg); } freeaddrinfo(result); } @@ -54,10 +58,18 @@ ClientSocket::ClientSocket(std::string sType, struct sockaddr_in addr) if (::connect(getSocketId(), (struct sockaddr *)&addr, sizeof(addr)) != 0) { char address[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &addr.sin_addr, address, INET_ADDRSTRLEN); - std::string msg = "ClientSocket: Cannot connect to " + socketType + - ":" + address + " on port " + - std::to_string(addr.sin_port) + "\n"; - throw SocketError(msg); + auto msg = fmt::format("Cannot connect to {} on {}:{}", to_lower(socketType), address, addr.sin_port); + throwError(msg); + } +} + +void ClientSocket::throwError(const std::string &msg) const { + if (socketType == "Receiver") { + throw ReceiverError(msg); + } else if (socketType == "Detector") { + throw DetectorError(msg); + } else { + throw GuiError(msg); } } @@ -80,26 +92,15 @@ void ClientSocket::readReply(int &ret, void *retval, size_t retval_size) { std::string mess = readErrorMessage(); // Do we need to know hostname here? // In that case save it??? - if (socketType == "Receiver") { - throw ReceiverError("Receiver returned: " + std::string(mess)); - } else if (socketType == "Detector") { - throw DetectorError("Detector returned: " + std::string(mess)); - } else { - throw GuiError(mess); - } + throwError(socketType + " returned: " + mess); } // get retval Receive(retval, retval_size); } // debugging catch (SocketError &e) { - if (socketType == "Receiver") { - throw ReceiverError("Receiver returned: " + std::string(e.what())); - } else if (socketType == "Detector") { - throw DetectorError("Detector returned: " + std::string(e.what())); - } else { - throw GuiError(e.what()); - } + auto msg = fmt::format("While reading reply from {} {}", to_lower(socketType), e.what()); + throwError(msg); } } diff --git a/slsSupportLib/src/DataSocket.cpp b/slsSupportLib/src/DataSocket.cpp index 25d016d0b..a8b4d2bfd 100644 --- a/slsSupportLib/src/DataSocket.cpp +++ b/slsSupportLib/src/DataSocket.cpp @@ -1,12 +1,14 @@ // SPDX-License-Identifier: LGPL-3.0-or-other // Copyright (C) 2021 Contributors to the SLS Detector Package #include "sls/DataSocket.h" +#include "sls/Timer.h" #include "sls/logger.h" #include "sls/sls_detector_exceptions.h" #include "sls/sls_detector_funcs.h" #include #include #include +#include #include #include #include @@ -21,6 +23,14 @@ namespace sls { DataSocket::DataSocket(int socketId) : sockfd_(socketId) { int value = 1; setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)); +#ifdef SO_NOSIGPIPE + // macOS/BSD: suppress SIGPIPE when sending to a peer that closed the + // connection, so a failed send returns an error instead of killing the + // process. On Linux we instead pass MSG_NOSIGNAL to send() (see Send()). + int nosigpipe = 1; + setsockopt(sockfd_, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, + sizeof(nosigpipe)); +#endif } DataSocket::~DataSocket() { @@ -36,6 +46,7 @@ DataSocket::~DataSocket() { void DataSocket::swap(DataSocket &other) noexcept { std::swap(sockfd_, other.sockfd_); + std::swap(fnum_, other.fnum_); } DataSocket::DataSocket(DataSocket &&move) noexcept { move.swap(*this); } @@ -48,23 +59,34 @@ void DataSocket::setFnum(const int fnum) { fnum_ = fnum; } int DataSocket::Receive(void *buffer, size_t size) { // TODO!(Erik) Add sleep? how many reties? - int bytes_expected = static_cast(size); // signed size - int bytes_read = 0; + ssize_t bytes_expected = static_cast(size); + ssize_t bytes_read = 0; + ssize_t this_read = 0; // last read result, kept for diagnostics + Timer timer; while (bytes_read < bytes_expected) { - auto this_read = + this_read = ::read(getSocketId(), reinterpret_cast(buffer) + bytes_read, bytes_expected - bytes_read); + if (this_read < 0 && errno == EINTR) + continue; // interrupted by a signal, retry if (this_read <= 0) break; bytes_read += this_read; } if (bytes_read == bytes_expected) { - return bytes_read; + return static_cast(bytes_read); } else { + int err = errno; // capture before any other call can clobber it std::ostringstream ss; ss << "TCP socket read " << bytes_read << " bytes instead of " << bytes_expected << " bytes (" << getFunctionNameFromEnum(static_cast(fnum_)) << ')'; + if (this_read == 0) + ss << ": connection closed by peer (EOF)"; + else if (this_read < 0) + ss << ": read error: " << std::strerror(err) << " (" + << errno_name(err) << ")"; + ss << " after " << timer.elapsed_ms() << " ms"; throw SocketError(ss.str()); } } @@ -78,38 +100,51 @@ std::string DataSocket::Receive(size_t length) { return buff; } int DataSocket::Send(const void *buffer, size_t size) { - int bytes_sent = 0; - int data_size = static_cast(size); // signed size - while (bytes_sent < (data_size)) { - auto this_send = ::write(getSocketId(), buffer, size); + ssize_t bytes_expected = static_cast(size); + ssize_t bytes_sent = 0; + ssize_t this_send = 0; // last write result, kept for diagnostics + // Linux: avoid SIGPIPE on a broken connection by using send() with + // MSG_NOSIGNAL. macOS/BSD lack the flag and use SO_NOSIGPIPE instead + // (set in the constructor). +#ifdef MSG_NOSIGNAL + const int send_flags = MSG_NOSIGNAL; +#else + const int send_flags = 0; +#endif + Timer timer; + while (bytes_sent < bytes_expected) { + this_send = ::send( + getSocketId(), + reinterpret_cast(buffer) + bytes_sent, + bytes_expected - bytes_sent, send_flags); + if (this_send < 0 && errno == EINTR) + continue; // interrupted by a signal, retry if (this_send <= 0) break; bytes_sent += this_send; } - if (bytes_sent != data_size) { + if (bytes_sent == bytes_expected) { + return static_cast(bytes_sent); + } else { + int err = errno; // capture before any other call can clobber it std::ostringstream ss; ss << "TCP socket sent " << bytes_sent << " bytes instead of " - << data_size << " bytes (" + << bytes_expected << " bytes (" << getFunctionNameFromEnum(static_cast(fnum_)) << ')'; + if (this_send < 0) + ss << ": write error: " << std::strerror(err) << " (" + << errno_name(err) << ")"; + ss << " after " << timer.elapsed_ms() << " ms"; throw SocketError(ss.str()); } - return bytes_sent; } int DataSocket::Send(const std::string &s) { return Send(&s[0], s.size()); } -int DataSocket::write(void *buffer, size_t size) { - return ::write(getSocketId(), buffer, size); -} - -int DataSocket::read(void *buffer, size_t size) { - return ::read(getSocketId(), buffer, size); -} - int DataSocket::setReceiveTimeout(int us) { timeval t{}; - t.tv_sec = 0; - t.tv_usec = us; + t.tv_sec = us / 1000000; + t.tv_usec = us % 1000000; return ::setsockopt(getSocketId(), SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval)); } @@ -140,7 +175,9 @@ int DataSocket::setTimeOut(int t_seconds) { void DataSocket::close() { if (sockfd_ > 0) { if (::close(sockfd_)) { - throw SocketError("could not close socket"); + std::stringstream ss; + ss << "could not close socket (fd: " << sockfd_ << ")"; + throw SocketError(ss.str()); } sockfd_ = -1; } else { @@ -155,4 +192,61 @@ void DataSocket::shutDownSocket() { void DataSocket::shutdown() { ::shutdown(sockfd_, SHUT_RDWR); } +std::string_view DataSocket::errno_name(int e) { + switch (e) { +#ifdef EACCES + case EACCES: + return "EACCES"; +#endif +#ifdef EAGAIN + case EAGAIN: + return "EAGAIN"; +#endif +#ifdef EBADF + case EBADF: + return "EBADF"; +#endif +#ifdef ECONNABORTED + case ECONNABORTED: + return "ECONNABORTED"; +#endif +#ifdef ECONNREFUSED + case ECONNREFUSED: + return "ECONNREFUSED"; +#endif +#ifdef ECONNRESET + case ECONNRESET: + return "ECONNRESET"; +#endif +#ifdef EINPROGRESS + case EINPROGRESS: + return "EINPROGRESS"; +#endif +#ifdef EINTR + case EINTR: + return "EINTR"; +#endif +#ifdef EINVAL + case EINVAL: + return "EINVAL"; +#endif +#ifdef EPIPE + case EPIPE: + return "EPIPE"; +#endif +#ifdef ETIMEDOUT + case ETIMEDOUT: + return "ETIMEDOUT"; +#endif +#ifdef EWOULDBLOCK +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: + return "EWOULDBLOCK"; +#endif +#endif + default: + return "UNKNOWN_ERRNO"; + } +} + } // namespace sls diff --git a/slsSupportLib/src/ServerInterface.cpp b/slsSupportLib/src/ServerInterface.cpp index 92e3d7a67..8e2d4a503 100644 --- a/slsSupportLib/src/ServerInterface.cpp +++ b/slsSupportLib/src/ServerInterface.cpp @@ -10,16 +10,16 @@ namespace sls { int ServerInterface::sendResult(int ret, void *retval, int retvalSize, char *mess) { - write(&ret, sizeof(ret)); + Send(&ret, sizeof(ret)); if (ret == defs::FAIL) { if (mess != nullptr) { - write(mess, MAX_STR_LENGTH); + Send(mess, MAX_STR_LENGTH); } else { LOG(logERROR) << "No error message provided for this " "failure. Will mess up TCP\n"; } } else { - write(retval, retvalSize); + Send(retval, retvalSize); } return ret; } diff --git a/slsSupportLib/src/string_utils.cpp b/slsSupportLib/src/string_utils.cpp index 1426049ff..68e389c99 100644 --- a/slsSupportLib/src/string_utils.cpp +++ b/slsSupportLib/src/string_utils.cpp @@ -75,4 +75,11 @@ std::pair ParseHostPort(const std::string &s) { return std::make_pair(host, port); } +std::string to_lower(const std::string &s) { + std::string result = s; + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return std::tolower(c); }); + return result; +} + }; // namespace sls \ No newline at end of file diff --git a/slsSupportLib/tests/test-Sockets.cpp b/slsSupportLib/tests/test-Sockets.cpp index b41c44cb4..211df5858 100644 --- a/slsSupportLib/tests/test-Sockets.cpp +++ b/slsSupportLib/tests/test-Sockets.cpp @@ -3,37 +3,117 @@ #include "catch.hpp" #include "sls/ClientSocket.h" #include "sls/ServerSocket.h" +#include "sls/Timer.h" +#include "sls/sls_detector_defs.h" +#include "sls/sls_detector_exceptions.h" +#include "sls/sls_detector_funcs.h" +#include #include #include #include +#include +#include +#include #include +#include namespace sls { -std::vector server() { - std::cout << "starting server\n"; - auto server = ServerSocket(1950); +// One configurable test server: accept a connection, read a 100-byte request, +// reply with `bytes_to_send` bytes (first two set to 'O','K'), optionally wait +// `hold` so the client can time out, then close. Returns the received request. +std::vector echo_server(uint16_t port, size_t bytes_to_send, + std::chrono::milliseconds hold) { + std::cout << "starting server on port " << port << '\n'; + auto server = ServerSocket(port); auto s = server.accept(); std::vector buffer(100, '\0'); s.Receive(buffer.data(), buffer.size()); - std::cout << "ServerReceived: " << std::string(buffer.begin(), buffer.end()) - << '\n'; - std::vector to_send(100, '\0'); - to_send[0] = 'O'; - to_send[1] = 'K'; - s.Send(to_send.data(), to_send.size()); + if (port==1960){ + struct linger ling = { + .l_onoff = 1, + .l_linger = 0 + }; + + auto fd = s.getSocketId(); + setsockopt(fd, SOL_SOCKET, SO_LINGER, &ling, sizeof ling); + ::close(fd); + return buffer; + } + + if (bytes_to_send > 0) { + std::vector to_send(bytes_to_send, '\0'); + to_send[0] = 'O'; + to_send[1] = 'K'; + s.Send(to_send.data(), to_send.size()); + } + std::this_thread::sleep_for(hold); s.close(); return buffer; } +// Minimal command server speaking the same protocol as sendCommandThenRead: +// accept a connection, read the function number and a single int argument, +// then reply via ServerInterface::sendResult with OK and (arg * 2). Returns +// the {fnum, arg} pair the server received so the test can verify them. +std::pair command_server(uint16_t port) { + auto server = ServerSocket(port); + auto s = server.accept(); + int fnum = -1; + int arg = 0; + s.Receive(&fnum, sizeof(fnum)); + s.Receive(&arg, sizeof(arg)); + int retval = arg * 2; + s.sendResult(slsDetectorDefs::OK, &retval, sizeof(retval)); + s.close(); + return {fnum, arg}; +} + +// Command server that replies with a truncated message: it reads the request, +// sends the OK return code, but then sends fewer retval bytes than the client +// expects before closing, so the client's Receive hits EOF mid-read. +void short_reply_server(uint16_t port, size_t retval_bytes_to_send) { + auto server = ServerSocket(port); + auto s = server.accept(); + int fnum = -1; + int arg = 0; + s.Receive(&fnum, sizeof(fnum)); + s.Receive(&arg, sizeof(arg)); + int ret = slsDetectorDefs::OK; + s.Send(&ret, sizeof(ret)); + if (retval_bytes_to_send > 0) { + std::vector partial(retval_bytes_to_send, '\0'); + s.Send(partial.data(), partial.size()); + } + s.close(); +} + +// Server that accepts a connection but never reads from it, so a client +// trying to send more than fits in the kernel buffers will stall. A small +// receive buffer keeps the amount the test must send modest. Stays open until +// the client signals it is done (or a safety timeout) so it never closes +// mid-transfer and races the client's Send. +void non_reading_server(uint16_t port, std::atomic *client_done) { + auto server = ServerSocket(port); + auto s = server.accept(); + int rcvbuf = 1024; + setsockopt(s.getSocketId(), SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); + // Intentionally never Receive(): let the client's send path back up. + Timer t; + while (!client_done->load() && t.elapsed_ms() < 10000) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + s.close(); +} + TEST_CASE("The server recive the same message as we send", "[support]") { std::vector received_message(100, '\0'); std::vector sent_message(100, '\0'); const char m[]{"some message"}; std::copy(std::begin(m), std::end(m), sent_message.data()); - auto s = std::async(std::launch::async, server); + auto s = std::async(std::launch::async, echo_server, 1950, 100, + std::chrono::milliseconds(0)); std::this_thread::sleep_for(std::chrono::milliseconds(100)); auto client = DetectorSocket("localhost", 1950); client.Send(sent_message.data(), sent_message.size()); @@ -48,6 +128,207 @@ TEST_CASE("The server recive the same message as we send", "[support]") { TEST_CASE("throws on no server", "[support]") { CHECK_THROWS(DetectorSocket("localhost", 1950)); + CHECK_THROWS(ReceiverSocket("localhost", 1950)); + CHECK_THROWS(GuiSocket("localhost", 1950)); +} + +TEST_CASE("Receiving a too short message throws and reports EOF", "[support]") { + std::vector received_message(100, '\0'); + std::vector sent_message(100, '\0'); + const char m[]{"some message"}; + std::copy(std::begin(m), std::end(m), sent_message.data()); + + // Server replies with only 10 of the 100 expected bytes, then closes. + auto s = std::async(std::launch::async, echo_server, 1951, 10, + std::chrono::milliseconds(0)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto client = DetectorSocket("localhost", 1951); + client.Send(sent_message.data(), sent_message.size()); + + client.setFnum(F_GET_SERVER_VERSION); + + // The server only sends 10 of the 100 expected bytes and then closes the + // connection, so Receive must throw a SocketError reporting the EOF. + std::string error_message; + try { + client.Receive(received_message.data(), received_message.size()); + FAIL("Receive should have thrown on a too short message"); + } catch (const SocketError &e) { + error_message = e.what(); + } + client.close(); + s.get(); + + CHECK_THAT(error_message, + Catch::Matchers::Contains("read 10 bytes instead of 100 bytes")); + CHECK_THAT(error_message, + Catch::Matchers::Contains("connection closed by peer (EOF)")); +} + +TEST_CASE("Receiving with a socket error throws and reports the error", + "[support]") { + std::vector received_message(100, '\0'); + std::vector sent_message(100, '\0'); + const char m[]{"some message"}; + std::copy(std::begin(m), std::end(m), sent_message.data()); + + // Server stays silent (sends nothing) but keeps the connection open long + // enough for the client to time out, so the read fails with an error. + auto s = std::async(std::launch::async, echo_server, 1952, 0, + std::chrono::milliseconds(500)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto client = DetectorSocket("localhost", 1952); + client.Send(sent_message.data(), sent_message.size()); + + // Force read() to fail with EAGAIN/EWOULDBLOCK instead of returning EOF by + // setting a short receive timeout while the server stays silent. + client.setReceiveTimeout(100000); // 100 ms + + std::string error_message; + try { + client.Receive(received_message.data(), received_message.size()); + FAIL("Receive should have thrown on a socket error"); + } catch (const SocketError &e) { + error_message = e.what(); + } + client.close(); + s.get(); + + CHECK_THAT(error_message, + Catch::Matchers::Contains("read 0 bytes instead of 100 bytes")); + CHECK_THAT(error_message, Catch::Matchers::Contains("read error:")); +} + + +TEST_CASE("Socket crash?", "[support]") { + std::vector received_message(100, '\0'); + std::vector sent_message(100, '\0'); + const char m[]{"some message"}; + std::copy(std::begin(m), std::end(m), sent_message.data()); + + auto s = std::async(std::launch::async, echo_server, 1960, 100, + std::chrono::milliseconds(0)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto client = DetectorSocket("localhost", 1960); + client.Send(sent_message.data(), sent_message.size()); + + + REQUIRE_THROWS(client.Receive(received_message.data(), received_message.size())); + + //Now try to send more + // client.Send(sent_message.data(), sent_message.size()); + + + +} + + +TEST_CASE("ClientSocket throws on invalid hostname", "[support]") { + CHECK_THROWS(ReceiverSocket("invalidhostname", 1950)); + CHECK_THROWS(DetectorSocket("invalidhostname", 1950)); + CHECK_THROWS(GuiSocket("invalidhostname", 1950)); +} + + +TEST_CASE("Using DetectorSocket to talk to a Server Socket", "[support]") { + constexpr uint16_t port = 1961; + constexpr int fnum = F_GET_DETECTOR_TYPE; + constexpr int arg = 21; + + auto s = std::async(std::launch::async, command_server, port); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + auto client = DetectorSocket("localhost", port); + int retval = 0; + int ret = + client.sendCommandThenRead(fnum, &arg, sizeof(arg), &retval, + sizeof(retval)); + client.close(); + + auto server_received = s.get(); + + // Client got OK and the expected return value back from the server + CHECK(ret == slsDetectorDefs::OK); + CHECK(retval == arg * 2); + // Server received the function number and argument we sent + CHECK(server_received.first == fnum); + CHECK(server_received.second == arg); + // close() resets the underlying fd + CHECK(client.getSocketId() == -1); +} + +TEST_CASE("ServerSocket replies with a too short message", "[support]") { + constexpr uint16_t port = 1962; + constexpr int fnum = F_GET_DETECTOR_TYPE; + constexpr int arg = 21; + + // Server sends the OK return code, then only 1 of the 4 expected retval + // bytes before closing. + auto s = std::async(std::launch::async, short_reply_server, port, 1); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + auto client = DetectorSocket("localhost", port); + int retval = 0; + + std::string error_message; + try { + client.sendCommandThenRead(fnum, &arg, sizeof(arg), &retval, + sizeof(retval)); + FAIL("sendCommandThenRead should have thrown on a too short message"); + } catch (const DetectorError &e) { + error_message = e.what(); + } + client.close(); + s.get(); + + // The client read only 1 of the 4 expected retval bytes and then hit EOF. + CHECK_THAT(error_message, + Catch::Matchers::Contains("read 1 bytes instead of 4 bytes")); + CHECK_THAT(error_message, + Catch::Matchers::Contains("connection closed by peer (EOF)")); +} + +TEST_CASE("Client cannot send the expected number of bytes", "[support]") { + constexpr uint16_t port = 1963; + + // Server accepts but never reads; it stays open until we tell it the + // client is done, so it cannot close mid-transfer. + std::atomic client_done{false}; + auto s = std::async(std::launch::async, non_reading_server, port, + &client_done); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + auto client = DetectorSocket("localhost", port); + + // Shrink the send buffer and add a short send timeout so the write stalls + // and returns before all the data is sent. + int sndbuf = 4096; + setsockopt(client.getSocketId(), SOL_SOCKET, SO_SNDBUF, &sndbuf, + sizeof(sndbuf)); + struct timeval tv {}; + tv.tv_sec = 0; + tv.tv_usec = 300000; // 300 ms + setsockopt(client.getSocketId(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + + // Much more than fits in the (shrunken) send + receive buffers, so the + // send cannot complete while the server refuses to read. + std::vector big_message(8 * 1024 * 1024, '\0'); + + std::string error_message; + try { + client.Send(big_message.data(), big_message.size()); + FAIL("Send should have thrown when it could not send all bytes"); + } catch (const SocketError &e) { + error_message = e.what(); + } + client_done = true; + client.close(); + s.get(); + + // Fewer bytes were sent than expected, reported as a write error (the + // send timed out with EAGAIN/EWOULDBLOCK). + CHECK_THAT(error_message, Catch::Matchers::Contains("bytes instead of")); + CHECK_THAT(error_message, Catch::Matchers::Contains("write error:")); } } // namespace sls diff --git a/slsSupportLib/tests/test-string_utils.cpp b/slsSupportLib/tests/test-string_utils.cpp index 15dcb7dbc..21200eca9 100644 --- a/slsSupportLib/tests/test-string_utils.cpp +++ b/slsSupportLib/tests/test-string_utils.cpp @@ -123,6 +123,33 @@ TEST_CASE("port missing") { REQUIRE(res.second == 0); } +TEST_CASE("to_lower converts uppercase to lowercase") { + REQUIRE(to_lower("HELLO") == "hello"); + REQUIRE(to_lower("Hello World") == "hello world"); +} + +TEST_CASE("to_lower leaves an already lowercase string unchanged") { + REQUIRE(to_lower("already lower") == "already lower"); +} + +TEST_CASE("to_lower only affects alphabetic characters") { + REQUIRE(to_lower("ABC123!?_-XYZ") == "abc123!?_-xyz"); +} + +TEST_CASE("to_lower on an empty string returns an empty string") { + REQUIRE(to_lower("").empty()); +} + +TEST_CASE("to_lower does not modify the original string") { + std::string original = "MixedCase"; + auto result = to_lower(original); + REQUIRE(result == "mixedcase"); + // the source string must be untouched + REQUIRE(original == "MixedCase"); +} + + + // TEST_CASE("concat things not being strings") } // namespace sls