diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e67634..b0fbbf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(PROJECT_NAME CppSocketLibrary) -project(${PROJECT_NAME} VERSION 0.5.3) +project(${PROJECT_NAME} VERSION 0.5.4) set(HEADERS src/serversocket/ServerSocket.h diff --git a/Environment-Test-Dockerfile b/Environment-Test-Dockerfile index 12f4d28..ac73fa9 100644 --- a/Environment-Test-Dockerfile +++ b/Environment-Test-Dockerfile @@ -1,3 +1,5 @@ +# docker build -f .\Environment-Test-Dockerfile . + FROM alpine:3.20.0 AS alpine WORKDIR /alpine diff --git a/src/serversocket/ServerSocket.h b/src/serversocket/ServerSocket.h index 6e8cf5f..c1e6f8c 100644 --- a/src/serversocket/ServerSocket.h +++ b/src/serversocket/ServerSocket.h @@ -54,7 +54,6 @@ namespace kt void initialisePortNumber(); public: - ServerSocket() = default; ServerSocket(const kt::SocketType, const std::optional& = std::nullopt, const unsigned short& = 0, const unsigned int& = 20, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); ServerSocket(const kt::ServerSocket&); kt::ServerSocket& operator=(const kt::ServerSocket&); diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index 5b79d2c..04eaa6c 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -84,6 +84,11 @@ namespace kt #endif + if (preBindSocketOperation.has_value()) + { + preBindSocketOperation.value()(this->receiveSocket); + } + int bindResult = ::bind(this->receiveSocket, &firstAddress.address, kt::getAddressLength(firstAddress)); this->bound = bindResult != -1; if (!this->bound) @@ -223,6 +228,11 @@ namespace kt this->preSendSocketOperation = std::make_optional(newOperation); } + void UDPSocket::setPreBindSocketOperation(std::function newOperation) + { + this->preBindSocketOperation = std::make_optional(newOperation); + } + int UDPSocket::pollSocket(SOCKET socket, const long& timeout) const { if (kt::isInvalidSocket(socket)) diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h index d0f37ab..0c4f63c 100644 --- a/src/socket/UDPSocket.h +++ b/src/socket/UDPSocket.h @@ -51,6 +51,7 @@ namespace kt kt::InternetProtocolVersion protocolVersion = kt::InternetProtocolVersion::Any; std::optional listeningPort = std::nullopt; std::optional> preSendSocketOperation = std::nullopt; + std::optional> preBindSocketOperation = std::nullopt; int pollSocket(SOCKET socket, const long& = 1000) const; void initialiseListeningPortNumber(); @@ -79,6 +80,7 @@ namespace kt std::optional getListeningPort() const; void setPreSendSocketOperation(std::function); + void setPreBindSocketOperation(std::function); }; } // End namespace kt diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c7f83b5..44b61ae 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,6 +25,8 @@ set(SOURCE socket/BluetoothSocketTest.cpp address/SocketAddressTest.cpp + + socket/ScenarioTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCE}) diff --git a/tests/socket/ScenarioTest.cpp b/tests/socket/ScenarioTest.cpp new file mode 100644 index 0000000..cc93c34 --- /dev/null +++ b/tests/socket/ScenarioTest.cpp @@ -0,0 +1,85 @@ + +#include + +#include "../../src/socket/UDPSocket.h" +#include "../../src/serversocket/ServerSocket.h" + +namespace kt +{ + /** + * Ensure that we can bind to the same port using TCP and UDP sockets. + * + * In this case, when UDP binds first. + */ + TEST(ScenarioTest, UDPThenTCPBindSamePort) + { + kt::UDPSocket socket; + std::pair bindResult = socket.bind(); + ASSERT_TRUE(bindResult.first); + ASSERT_NE(std::nullopt, socket.getListeningPort()); + + kt::ServerSocket server(SocketType::Wifi, std::nullopt, socket.getListeningPort().value()); + + ASSERT_EQ(server.getPort(), socket.getListeningPort().value()); + + server.close(); + socket.close(); + } + + /** + * Ensure that we can bind to the same port using TCP and UDP sockets. + * + * In this case, when TCP binds first. + */ + TEST(ScenarioTest, TCPThenUDPBindSamePort) + { + kt::ServerSocket server(SocketType::Wifi); + + kt::UDPSocket socket; + std::pair bindResult = socket.bind(std::nullopt, server.getPort()); + ASSERT_TRUE(bindResult.first); + ASSERT_NE(std::nullopt, socket.getListeningPort()); + + ASSERT_EQ(server.getPort(), socket.getListeningPort().value()); + + server.close(); + socket.close(); + } + + /** + * Ensure that we can bind two UDP sockets to the same address by setting SO_REUSEADDR in the pre-bind handler. + */ + TEST(ScenarioTest, TwoUDPSocketsBindingToSamePort) + { + std::function setReuseAddrOption = [](SOCKET& s) { + const int enableOption = 1; + ASSERT_EQ(0, setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&enableOption, sizeof(enableOption))); + }; + + kt::UDPSocket socket; + socket.setPreBindSocketOperation(setReuseAddrOption); + std::pair bindResult = socket.bind(); + ASSERT_TRUE(bindResult.first); + + kt::UDPSocket socket2; + socket2.setPreBindSocketOperation(setReuseAddrOption); + bindResult = socket2.bind(std::nullopt, socket.getListeningPort().value()); + ASSERT_TRUE(bindResult.first); + + ASSERT_EQ(socket.getListeningPort().value(), socket2.getListeningPort().value()); + + kt::UDPSocket sendSocket; + const std::string data = "TwoUDPSocketsBindingToSamePort"; + std::pair, kt::SocketAddress> sendResult = sendSocket.sendTo("localhost", socket.getListeningPort().value(), data); + ASSERT_TRUE(sendResult.first.first); + + // Make sure only one of the sockets is ready to read, not both + ASSERT_FALSE(socket.ready() && socket2.ready()); + ASSERT_TRUE(socket2.ready() || socket.ready()); + + socket.close(); + socket2.close(); + + sendSocket.close(); + } +}