diff --git a/slsSupportLib/src/DataSocket.cpp b/slsSupportLib/src/DataSocket.cpp index 2d423f0af..4b90aa293 100644 --- a/slsSupportLib/src/DataSocket.cpp +++ b/slsSupportLib/src/DataSocket.cpp @@ -1,6 +1,7 @@ // 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" @@ -52,6 +53,7 @@ int DataSocket::Receive(void *buffer, size_t size) { int bytes_expected = static_cast(size); // signed size int bytes_read = 0; ssize_t this_read = 0; // last read result, kept for diagnostics + Timer timer; while (bytes_read < bytes_expected) { this_read = ::read(getSocketId(), reinterpret_cast(buffer) + bytes_read, @@ -72,6 +74,7 @@ int DataSocket::Receive(void *buffer, size_t size) { ss << ": connection closed by peer (EOF)"; else if (this_read < 0) ss << ": read error: " << std::strerror(err); + ss << " after " << timer.elapsed_ms() << " ms"; throw SocketError(ss.str()); } } diff --git a/slsSupportLib/tests/test-Sockets.cpp b/slsSupportLib/tests/test-Sockets.cpp index b41c44cb4..825d66c12 100644 --- a/slsSupportLib/tests/test-Sockets.cpp +++ b/slsSupportLib/tests/test-Sockets.cpp @@ -3,26 +3,34 @@ #include "catch.hpp" #include "sls/ClientSocket.h" #include "sls/ServerSocket.h" +#include "sls/sls_detector_exceptions.h" +#include "sls/sls_detector_funcs.h" #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 (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; } @@ -33,7 +41,8 @@ TEST_CASE("The server recive the same message as we send", "[support]") { 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()); @@ -50,4 +59,71 @@ TEST_CASE("throws on no server", "[support]") { CHECK_THROWS(DetectorSocket("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:")); +} + } // namespace sls