From dd9edb0acf1417eb41e55dba810e9cce7d9f4140 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Tue, 25 Jun 2024 19:04:45 +0900 Subject: [PATCH 01/21] Refactor the world, creating separate classes for each socket type to make the interface much cleaner, more specialised and easier for end users to use and drive the sockets. --- CMakeLists.txt | 6 + src/address/SocketAddress.cpp | 39 +- src/address/SocketAddress.h | 13 +- src/serversocket/ServerSocket.cpp | 73 +- src/serversocket/ServerSocket.h | 12 +- src/socket/BluetoothSocket.cpp | 251 +++++ src/socket/BluetoothSocket.h | 90 ++ src/socket/Socket.cpp | 868 +----------------- src/socket/Socket.h | 88 +- src/socket/TCPSocket.cpp | 283 ++++++ src/socket/TCPSocket.h | 80 ++ src/socket/UDPSocket.cpp | 236 +++++ src/socket/UDPSocket.h | 78 ++ tests/CMakeLists.txt | 8 +- ...SocketTest.cpp => ServerSocketTCPTest.cpp} | 34 +- tests/socket/BluetoothSocketTest.cpp | 59 ++ tests/socket/SocketBluetoothTest.cpp | 59 -- tests/socket/SocketUDPTest.cpp | 335 ------- .../{SocketTCPTest.cpp => TCPSocketTest.cpp} | 75 +- tests/socket/UDPSocketTest.cpp | 146 +++ 20 files changed, 1363 insertions(+), 1470 deletions(-) create mode 100644 src/socket/BluetoothSocket.cpp create mode 100644 src/socket/BluetoothSocket.h create mode 100644 src/socket/TCPSocket.cpp create mode 100644 src/socket/TCPSocket.h create mode 100644 src/socket/UDPSocket.cpp create mode 100644 src/socket/UDPSocket.h rename tests/serversocket/{ServerSocketTest.cpp => ServerSocketTCPTest.cpp} (72%) create mode 100644 tests/socket/BluetoothSocketTest.cpp delete mode 100644 tests/socket/SocketBluetoothTest.cpp delete mode 100644 tests/socket/SocketUDPTest.cpp rename tests/socket/{SocketTCPTest.cpp => TCPSocketTest.cpp} (63%) create mode 100644 tests/socket/UDPSocketTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2906dfe..6470b70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,9 @@ project(${PROJECT_NAME} VERSION 1.0) set(HEADERS src/serversocket/ServerSocket.h src/socket/Socket.h + src/socket/TCPSocket.h + src/socket/UDPSocket.h + src/socket/BluetoothSocket.h src/address/SocketAddress.h src/socketexceptions/BindingException.hpp src/socketexceptions/SocketException.hpp @@ -23,6 +26,9 @@ set(HEADERS set(SOURCE src/serversocket/ServerSocket.cpp src/socket/Socket.cpp + src/socket/TCPSocket.cpp + src/socket/UDPSocket.cpp + src/socket/BluetoothSocket.cpp src/socketexceptions/SocketError.cpp src/address/SocketAddress.cpp ) diff --git a/src/address/SocketAddress.cpp b/src/address/SocketAddress.cpp index 95e10a2..ea55105 100644 --- a/src/address/SocketAddress.cpp +++ b/src/address/SocketAddress.cpp @@ -2,6 +2,7 @@ #include #include +#include namespace kt { @@ -22,7 +23,7 @@ namespace kt return htonl(address.ipv4.sin_port); } - std::optional resolveToAddress(const kt::SocketAddress& address) + std::optional getAddress(const kt::SocketAddress& address) { const kt::InternetProtocolVersion protocolVersion = getInternetProtocolVersion(address); const size_t addressLength = protocolVersion == kt::InternetProtocolVersion::IPV6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN; @@ -47,4 +48,40 @@ namespace kt // Since we zero out the address, we need to check its not default initialised return !asString.empty() && asString != "0.0.0.0" && asString != "::" ? std::optional{asString} : std::nullopt; } + + std::pair, int> socketToAddress(const SOCKET& socket) + { + kt::SocketAddress address{}; + socklen_t socketSize = sizeof(address); + int result = getsockname(socket, &address.address, &socketSize); + return std::make_pair(std::optional{ address }, result); + } + + std::pair, int> resolveToAddresses(const std::optional& hostname, const unsigned int& port, addrinfo& hints) + { + std::vector addresses; + addrinfo* resolvedAddresses = nullptr; + + int result = getaddrinfo(hostname.has_value() ? hostname.value().c_str() : nullptr, std::to_string(port).c_str(), &hints, &resolvedAddresses); + if (result != 0 || resolvedAddresses == nullptr) + { + if (resolvedAddresses != nullptr) + { + freeaddrinfo(resolvedAddresses); + } + return std::make_pair(addresses, result); + } + + // We need to iterate over the resolved address and attempt to connect to each of them, if a connection attempt is succesful + // we will return, otherwise we will throw is we are unable to connect to any. + for (addrinfo* addr = resolvedAddresses; addr != nullptr; addr = addr->ai_next) + { + kt::SocketAddress address = {}; + std::memcpy(&address, addr->ai_addr, addr->ai_addrlen); + addresses.push_back(address); + } + freeaddrinfo(resolvedAddresses); + + return std::make_pair(addresses, result); + } } diff --git a/src/address/SocketAddress.h b/src/address/SocketAddress.h index 373addf..7f8cf82 100644 --- a/src/address/SocketAddress.h +++ b/src/address/SocketAddress.h @@ -5,6 +5,8 @@ #include #include +#include +#include #ifdef _WIN32 @@ -24,6 +26,11 @@ #include #include #include +#include +#include + +// Typedef to match the windows typedef since they are different underlying types +typedef int SOCKET; #endif @@ -43,5 +50,9 @@ namespace kt long getPortNumber(const kt::SocketAddress&); - std::optional resolveToAddress(const kt::SocketAddress&); + std::optional getAddress(const kt::SocketAddress&); + + std::pair, int> socketToAddress(const SOCKET&); + + std::pair, int> resolveToAddresses(const std::optional&, const unsigned int&, addrinfo&); } diff --git a/src/serversocket/ServerSocket.cpp b/src/serversocket/ServerSocket.cpp index 7f9cfc9..c4009b3 100644 --- a/src/serversocket/ServerSocket.cpp +++ b/src/serversocket/ServerSocket.cpp @@ -7,6 +7,7 @@ #include "../enums/SocketProtocol.h" #include "../enums/SocketType.h" #include "../socketexceptions/SocketError.h" +#include "../address/SocketAddress.h" #include #include @@ -189,8 +190,7 @@ namespace kt } #endif - size_t socketSize = initialiseServerAddress(); - + initialiseServerAddress(); this->socketDescriptor = socket(static_cast(this->protocolVersion), socketType, socketProtocol); if (isInvalidSocket(this->socketDescriptor)) { @@ -215,7 +215,7 @@ namespace kt } } #endif - + socklen_t socketSize = sizeof(this->serverAddress); if (bind(this->socketDescriptor, &this->serverAddress.address, socketSize) == -1) { this->close(); @@ -234,26 +234,25 @@ namespace kt } } - size_t kt::ServerSocket::initialiseServerAddress() + void kt::ServerSocket::initialiseServerAddress() { - addrinfo hint{}; + addrinfo hints{}; memset(&this->serverAddress, 0, sizeof(this->serverAddress)); - hint.ai_flags = AI_PASSIVE; - hint.ai_family = static_cast(this->protocolVersion); - hint.ai_socktype = SOCK_STREAM; - hint.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE; + hints.ai_family = static_cast(this->protocolVersion); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; - addrinfo *addresses = nullptr; - if (getaddrinfo(nullptr, std::to_string(this->port).c_str(), &hint, &addresses) != 0) + std::pair, int> resolveAddresses = kt::resolveToAddresses(std::nullopt, this->port, hints); + + if (resolveAddresses.second != 0 || resolveAddresses.first.empty()) { - freeaddrinfo(addresses); throw kt::SocketException("Failed to retrieve address info of local hostname. " + getErrorCode()); } - this->protocolVersion = static_cast(addresses->ai_family); - std::memcpy(&this->serverAddress, addresses->ai_addr, addresses->ai_addrlen); - freeaddrinfo(addresses); - return addresses->ai_addrlen; + kt::SocketAddress address = resolveAddresses.first.at(0); + this->protocolVersion = static_cast(address.address.sa_family); + std::memcpy(&this->serverAddress, &address, sizeof(this->serverAddress)); } void kt::ServerSocket::initialisePortNumber() @@ -318,31 +317,7 @@ namespace kt return this->protocolVersion; } - /** - * Used to accept a connection on the specific port. - * Upon accepting a new connection it will return a Socket object used to communicate with the receiver. - * - * @param timeout - indicates how long (in microseconds) the socket should be polled for before assuming there is no response. Default is 0 (unlimited). - * - * @returns kt::Socket object of the receiver who has just connected to the kt::ServerSocket. - */ - kt::Socket kt::ServerSocket::accept(const long& timeout) - { - if (this->type == kt::SocketType::Wifi) - { - return this->acceptWifiConnection(timeout); - } - else if (this->type == kt::SocketType::Bluetooth) - { - return this->acceptBluetoothConnection(timeout); - } - else - { - throw kt::SocketException("Cannot accept connection with SocketType set as SocketType::None"); - } - } - - kt::Socket kt::ServerSocket::acceptWifiConnection(const long& timeout) + kt::TCPSocket kt::ServerSocket::acceptTCPConnection(const long& timeout) const { if (timeout > 0) { @@ -366,16 +341,16 @@ namespace kt } unsigned int portNum = this->getInternetProtocolVersion() == kt::InternetProtocolVersion::IPV6 ? htons(acceptedAddress.ipv6.sin6_port) : htons(acceptedAddress.ipv4.sin_port); - std::optional hostname = kt::resolveToAddress(acceptedAddress); + std::optional hostname = kt::getAddress(acceptedAddress); if (!hostname.has_value()) { throw kt::SocketException("Unable to resolve accepted hostname from accepted socket."); } - return kt::Socket(temp, this->type, kt::SocketProtocol::TCP, hostname.value(), portNum, this->getInternetProtocolVersion()); + return kt::TCPSocket(temp, hostname.value(), portNum, this->getInternetProtocolVersion(), acceptedAddress); } - kt::Socket kt::ServerSocket::acceptBluetoothConnection(const long& timeout) + kt::BluetoothSocket kt::ServerSocket::acceptBluetoothConnection(const long& timeout) { if (timeout > 0) { @@ -390,8 +365,8 @@ namespace kt } } + throw kt::SocketException("acceptBluetoothConnection() - Not yet implemented."); #ifdef __linux__ - throw kt::SocketException("Not yet implemented."); // Remove bluetooth related code // sockaddr_rc remoteDevice = { 0 }; @@ -417,13 +392,7 @@ namespace kt */ void ServerSocket::close() { -#ifdef _WIN32 - closesocket(this->socketDescriptor); - -#elif __linux__ - ::close(this->socketDescriptor); - -#endif + kt::close(this->socketDescriptor); } } // End namespace kt diff --git a/src/serversocket/ServerSocket.h b/src/serversocket/ServerSocket.h index b1af08b..3eefea7 100644 --- a/src/serversocket/ServerSocket.h +++ b/src/serversocket/ServerSocket.h @@ -4,7 +4,8 @@ #include "../address/SocketAddress.h" -#include "../socket/Socket.h" +#include "../socket/BluetoothSocket.h" +#include "../socket/TCPSocket.h" #include "../enums/SocketProtocol.h" #include "../enums/SocketType.h" @@ -50,10 +51,7 @@ namespace kt void constructBluetoothSocket(const unsigned int&); void constructWifiSocket(const unsigned int&); void initialisePortNumber(); - size_t initialiseServerAddress(); - - kt::Socket acceptWifiConnection(const long& = 0); - kt::Socket acceptBluetoothConnection(const long& = 0); + void initialiseServerAddress(); public: ServerSocket() = default; @@ -61,11 +59,13 @@ namespace kt ServerSocket(const kt::ServerSocket&); kt::ServerSocket& operator=(const kt::ServerSocket&); + kt::TCPSocket acceptTCPConnection(const long& = 0) const; + kt::BluetoothSocket acceptBluetoothConnection(const long& = 0); + kt::SocketType getType() const; kt::InternetProtocolVersion getInternetProtocolVersion() const; unsigned int getPort() const; - kt::Socket accept(const long& = 0); void close(); }; diff --git a/src/socket/BluetoothSocket.cpp b/src/socket/BluetoothSocket.cpp new file mode 100644 index 0000000..df93723 --- /dev/null +++ b/src/socket/BluetoothSocket.cpp @@ -0,0 +1,251 @@ +#include "BluetoothSocket.h" +#include "../socketexceptions/SocketException.hpp" + +namespace kt +{ + void BluetoothSocket::constructBluetoothSocket() + { + throw kt::SocketException("Socket:constructBluetoothSocket() is not supported."); +#ifdef _WIN32 + + /*this->socketDescriptor = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); + if (isInvalidSocket(this->socketDescriptor)) + { + throw SocketException("Error establishing Bluetooth socket: " + std::string(std::strerror(errno))); + } + + this->bluetoothAddress.addressFamily = AF_BTH; + this->bluetoothAddress.btAddr = std::stoull(this->hostname); + this->bluetoothAddress.port = this->port; + + if (connect(this->socketDescriptor, (sockaddr*)&this->bluetoothAddress, sizeof(SOCKADDR_BTH)) == -1) + { + throw SocketException("Error connecting to Bluetooth server: " + std::string(std::strerror(errno))); + }*/ + +#elif __linux__ + /*this->socketDescriptor = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + + if (isInvalidSocket(this->socketDescriptor)) + { + throw SocketException("Error establishing Bluetooth socket: " + std::string(std::strerror(errno))); + } + + this->bluetoothAddress.rc_family = AF_BLUETOOTH; + this->bluetoothAddress.rc_channel = (uint8_t)port; + str2ba(this->hostname.c_str(), &this->bluetoothAddress.rc_bdaddr); + + if (connect(this->socketDescriptor, (sockaddr*)&this->bluetoothAddress, sizeof(this->bluetoothAddress)) == -1) + { + throw SocketException("Error connecting to Bluetooth server: " + std::string(std::strerror(errno))); + }*/ +#endif + } + + int BluetoothSocket::pollSocket(SOCKET socket, const long& timeout) const + { + return kt::pollSocket(socket, timeout); + } + + void BluetoothSocket::close(SOCKET socket) + { + kt::close(socket); + } + + BluetoothSocket::BluetoothSocket(const std::string& hostname, const unsigned int& port) + { + this->hostname = hostname; + this->port = port; + } + + void BluetoothSocket::close() + { + this->close(this->socketDescriptor); + } + + bool BluetoothSocket::send(const std::string&, int) + { + return false; + } + + unsigned int BluetoothSocket::getPort() const + { + return this->port; + } + + std::string BluetoothSocket::getHostname() const + { + return this->hostname; + } + + std::optional BluetoothSocket::get(const int& flags) const + { + std::string received = this->receiveAmount(1, flags); + if (received.empty()) + { + return std::nullopt; + } + return received[0]; + } + + std::string BluetoothSocket::receiveAmount(const unsigned int& amount, const int& flags) const + { + return std::string(); + } + + /** + * **In progress** + * + * Scans for bluetooth devices and returns a std::vector<std::pair<std::string, std::string>> of the device names and addresses. + * + * @param duration - The duration for which the scan should take to discover nearby bluetooth devices. + * + * @return A std::vector<std::pair<std::string, std::string>> where .first is the devices address, and .second is the device name. + */ + std::vector > BluetoothSocket::scanDevices(unsigned int duration) + { + throw kt::SocketException("Socket::scanDevices(int) is not supported."); + +#ifdef _WIN32 + + /*WSADATA wsaData; + int res = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (res != 0) + { + throw SocketException("WSAStartup Failed: " + std::to_string(res)); + }*/ + + /*WSAQUERYSET wsaQuery; + HANDLE hLoopUp; + LPWSAQUERYSET pQuerySet = nullptr; + SOCKADDR_BTH tempAddress; + DWORD dwSize = 5000 * sizeof(unsigned char); + memset(&wsaQuery, 0, sizeof(WSAQUERYSET)); + wsaQuery.dwSize = sizeof(WSAQUERYSET); + wsaQuery.dwNameSpace = NS_BTH; + wsaQuery.lpcsaBuffer = nullptr; + + int res = WSALookupServiceBegin(&wsaQuery, LUP_CONTAINERS, &hLoopUp); + if (res == -1) + { + throw SocketException("Unable to search for devices. Could not begin search."); + } + + memset(&pQuerySet, 0, sizeof(WSAQUERYSET)); + pQuerySet->dwSize = sizeof(WSAQUERYSET); + pQuerySet->dwNameSpace = NS_BTH; + pQuerySet->lpBlob = nullptr; + + while (WSALookupServiceNext(hLoopUp, LUP_RETURN_NAME | LUP_RETURN_ADDR, &dwSize, pQuerySet) == 0) + { + tempAddress = ((SOCKADDR_BTH*) pQuerySet->lpcsaBuffer->RemoteAddr.lpSockaddr)->btAddr; + // std::cout << pQuerySet->lpszServiceInstanceName << " : " << GET_NAP(tempAddress) << " - " << GET_SAP(tempAddress) << " ~ " << pQuerySet->dwNameSpace << std::endl; + }*/ + +#elif __linux__ + + /*std::vector > devices; + std::pair tempPair; + + inquiry_info* ii = nullptr; + int maxResponse = 255, numberOfResponses, ownId, tempSocket, flags; + char deviceAddress[19]; + char deviceName[248]; + + ownId = hci_get_route(nullptr); + tempSocket = hci_open_dev(ownId); + if (ownId < 0 || tempSocket < 0) + { + throw SocketException("Error opening Bluetooth socket for scanning..."); + } + + flags = IREQ_CACHE_FLUSH; + ii = new inquiry_info[maxResponse * sizeof(inquiry_info)]; + + numberOfResponses = hci_inquiry(ownId, duration, maxResponse, nullptr, &ii, flags); + if (numberOfResponses < 0) + { + delete[]ii; + throw SocketException("Error scanning for bluetooth devices"); + } + + for (int i = 0; i < numberOfResponses; i++) + { + ba2str(&(ii + i)->bdaddr, deviceAddress); + memset(&deviceName, '\0', sizeof(deviceName)); + if (hci_read_remote_name(tempSocket, &(ii + i)->bdaddr, sizeof(deviceName), deviceName, 0) < 0) + { + strcpy(deviceName, "[unknown]"); + } + + tempPair = std::make_pair(deviceAddress, deviceName); + devices.push_back(tempPair); + + } + + delete[]ii; + ::close(tempSocket); + + return devices;*/ +#endif + } + + std::optional kt::BluetoothSocket::getLocalMACAddress() + { + throw kt::SocketException("Socket::getLocalMACAddress() is not supported."); + +#ifdef _WIN32 + + // Up to 20 Interfaces + /*IP_ADAPTER_INFO AdapterInfo[20]; + DWORD dwBufLen = sizeof(AdapterInfo); + DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen); + PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo; + + while (std::string(pAdapterInfo->Description).find("Bluetooth") == std::string::npos) + { + pAdapterInfo = pAdapterInfo->Next; + } + + std::stringstream ss; + for (int i = 0; i < 6; i++) + { + ss << std::hex << std::setfill('0'); + ss << std::setw(2) << static_cast(pAdapterInfo->Address[i]); + + if (i != 5) + { + ss << ":"; + } + } + + return ss.str();*/ + +#elif __linux__ + // int id; + // bdaddr_t btaddr; + // char localMACAddress[18]; + + // // Get id of local device + // if ((id = hci_get_route(nullptr)) < 0) + // { + // return std::nullopt; + // } + + // // Get local bluetooth address + // if (hci_dev_req(id, &btaddr) < 0) + // { + // return std::nullopt; + // } + + // // Convert address to string + // if (ba2str(&btaddr, localMACAddress) < 0) + // { + // return std::nullopt; + // } + + // return std::optional{std::string(localMACAddress)}; +#endif + } +} + diff --git a/src/socket/BluetoothSocket.h b/src/socket/BluetoothSocket.h new file mode 100644 index 0000000..cac007c --- /dev/null +++ b/src/socket/BluetoothSocket.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include + +#include "../enums/SocketProtocol.h" +#include "../enums/SocketType.h" +#include "../enums/InternetProtocolVersion.h" +#include "../address/SocketAddress.h" +#include "../socketexceptions/SocketError.h" + +#include "Socket.h" + +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#define _WIN32_WINNT 0x0600 + +#include +#include +#include + +#elif __linux__ + +#include +#include +#include +#include +#include +#include + +// Typedef to match the windows typedef since they are different underlying types +typedef int SOCKET; + +#endif + +namespace kt +{ + class BluetoothSocket + { + protected: + std::string hostname; + unsigned int port; + SOCKET socketDescriptor; + +#ifdef _WIN32 + //SOCKADDR_BTH bluetoothAddress; + +#elif __linux__ + sockaddr_rc bluetoothAddress; // For Bluetooth + +#endif + + void constructBluetoothSocket(); + int pollSocket(SOCKET socket, const long& = 1000) const; + + void close(SOCKET socket); + + public: + BluetoothSocket() = default; + BluetoothSocket(const std::string&, const unsigned int&); + //BluetoothSocket(const SOCKET&, const kt::SocketType, const kt::SocketProtocol, const std::string&, const unsigned int&, const kt::InternetProtocolVersion); + + //BluetoothSocket(const kt::BluetoothSocket&); + //kt::BluetoothSocket& operator=(const kt::BluetoothSocket&); + + void close(); + + //bool ready(const unsigned long = 1000) const; + //bool connected(const unsigned long = 1000) const; + bool send(const std::string&, int = 0); + + unsigned int getPort() const; + std::string getHostname() const; + + std::optional get(const int&) const; + std::string receiveAmount(const unsigned int&, const int& = 0) const; + //std::string receiveToDelimiter(const char&, unsigned int = 0); + //std::string receiveAll(const unsigned long = 1000); + + static std::vector > scanDevices(unsigned int = 5); + static std::optional getLocalMACAddress(); + }; + +} // End namespace kt diff --git a/src/socket/Socket.cpp b/src/socket/Socket.cpp index a891a00..d119017 100644 --- a/src/socket/Socket.cpp +++ b/src/socket/Socket.cpp @@ -50,390 +50,6 @@ namespace kt { - /** - * A constructor which will immediately attempt to connect to the host via the port specified. - * - * @param hostname - The hostname of the device to connect to. - * @param port - The port number. - * @param type - Determines whether this socket is a wifi or bluetooth socket. - * @param protocol - Indicates the protocol being used by this socket, for Wifi this value can be *kt::SocketProtocol::TCP* or *kt::SocketProtocol::UDP* Default value is *kt::SocketProtocol::None*. - * - * @throw SocketException - If the Socket is unable to be instanciated or connect to server. - * @throw BindingException - If the Socket is unable to bind to the port specified. - */ - kt::Socket::Socket(const std::string& hostname, const unsigned int& port, const kt::SocketType type, const kt::SocketProtocol protocol) - { - this->hostname = hostname; - this->port = port; - this->type = type; - this->protocol = protocol; - - memset(&this->receiveAddress, '\0', sizeof(this->receiveAddress)); - memset(&this->serverAddress, '\0', sizeof(this->serverAddress)); - -#ifdef __linux__ - this->bluetoothAddress = { 0 }; - -#endif - - if (this->type == kt::SocketType::Wifi && this->protocol == kt::SocketProtocol::None) - { - throw SocketException("Unable to set protocol to 'None' for a Wifi Socket."); - } - else if (this->type == kt::SocketType::Bluetooth && this->protocol != kt::SocketProtocol::None) - { - // No protocol should be set when using a bluetooth socket - throw kt::SocketException("Bluetooth socket Protocol should be 'None'."); - } - - if (type == kt::SocketType::Wifi) - { - this->constructWifiSocket(this->port); - } - else if (type == kt::SocketType::Bluetooth) - { - this->constructBluetoothSocket(); - } - else - { - // kt::SocketType::None - throw kt::SocketException("Unable to build Socket with 'None' as its SocketType"); - } - } - - /** - * A constructor used by ServerSocket to create and copy of a currently connected socket. **This should not be used directly**. - * - * @param socketDescriptor - Is the file descriptor for the connection. - * @param type - Determines whether this socket is a wifi or bluetooth socket. - * @param protocol - Indicates the protocol being used by this socket, for Wifi this value can be *kt::SocketProtocol::TCP* or *kt::SocketProtocol::UDP* Default value is *kt::SocketProtocol::None*. - * @param hostname - the hostname of the socket to copy. - * @param port - the port number of the socket to copy. - * @param protocolVersion - the protocol version that the socket will use. - */ - kt::Socket::Socket(const SOCKET& socketDescriptor, const kt::SocketType type, const kt::SocketProtocol protocol, const std::string& hostname, const unsigned int& port, const kt::InternetProtocolVersion protocolVersion) - { - this->hostname = hostname; - this->port = port; - this->protocol = protocol; - this->socketDescriptor = socketDescriptor; - this->type = type; - this->protocolVersion = protocolVersion; - memset(&this->receiveAddress, '\0', sizeof(this->receiveAddress)); - memset(&this->serverAddress, '\0', sizeof(this->serverAddress)); - } - - /** - * A copy constructor for the Socket class. Will copy the object members and assume that it is already connected to the endpoint. - * - * @param socket - The Socket object to be copied. - */ - kt::Socket::Socket(const kt::Socket& socket) - { - this->socketDescriptor = socket.socketDescriptor; - this->udpSendSocket = socket.udpSendSocket; - this->hostname = socket.hostname; - this->protocol = socket.protocol; - this->port = socket.port; - this->type = socket.type; - this->protocolVersion = socket.protocolVersion; - - this->receiveAddress = socket.receiveAddress; - this->serverAddress = socket.serverAddress; -#ifdef __linux__ - this->bluetoothAddress = socket.bluetoothAddress; - -#endif - - } - - /** - * An assignment operator for the Socket object. Will make a copy of the appropriate socket. - * - * @param socket - The Socket object to be copied. - * - * @return kt::Socket the copied socket - */ - kt::Socket& kt::Socket::operator=(const kt::Socket& socket) - { - this->socketDescriptor = socket.socketDescriptor; - this->udpSendSocket = socket.udpSendSocket; - this->hostname = socket.hostname; - this->protocol = socket.protocol; - this->port = socket.port; - this->type = socket.type; - this->protocolVersion = socket.protocolVersion; - - this->receiveAddress = socket.receiveAddress; - this->serverAddress = socket.serverAddress; -#ifdef __linux__ - this->bluetoothAddress = socket.bluetoothAddress; - -#endif - - return *this; - } - - void kt::Socket::constructBluetoothSocket() - { -#ifdef _WIN32 - throw kt::SocketException("Socket:constructBluetoothSocket() is not supported on Windows."); - - /*this->socketDescriptor = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); - if (isInvalidSocket(this->socketDescriptor)) - { - throw SocketException("Error establishing Bluetooth socket: " + std::string(std::strerror(errno))); - } - - this->bluetoothAddress.addressFamily = AF_BTH; - this->bluetoothAddress.btAddr = std::stoull(this->hostname); - this->bluetoothAddress.port = this->port; - - if (connect(this->socketDescriptor, (sockaddr*)&this->bluetoothAddress, sizeof(SOCKADDR_BTH)) == -1) - { - throw SocketException("Error connecting to Bluetooth server: " + std::string(std::strerror(errno))); - }*/ - -#elif __linux__ - this->socketDescriptor = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); - - if (isInvalidSocket(this->socketDescriptor)) - { - throw SocketException("Error establishing Bluetooth socket: " + std::string(std::strerror(errno))); - } - - this->bluetoothAddress.rc_family = AF_BLUETOOTH; - this->bluetoothAddress.rc_channel = (uint8_t) port; - str2ba(this->hostname.c_str(), &this->bluetoothAddress.rc_bdaddr); - - if (connect(this->socketDescriptor, (sockaddr*) &this->bluetoothAddress, sizeof(this->bluetoothAddress)) == -1) - { - throw SocketException("Error connecting to Bluetooth server: " + std::string(std::strerror(errno))); - } -#endif - } - - void kt::Socket::constructWifiSocket(unsigned int& newPort) - { - const int socketType = this->protocol == kt::SocketProtocol::TCP ? SOCK_STREAM : SOCK_DGRAM; - const int socketProtocol = this->protocol == kt::SocketProtocol::TCP ? IPPROTO_TCP : IPPROTO_UDP; - - memset(&this->serverAddress, 0, sizeof(this->serverAddress)); - -#ifdef _WIN32 - WSADATA wsaData{}; - if (int res = WSAStartup(MAKEWORD(2, 2), &wsaData); res != 0) - { - throw kt::SocketException("WSAStartup Failed. " + std::to_string(res)); - } - -#endif - - addrinfo* resolvedAddresses = nullptr; - addrinfo hints{}; - hints.ai_family = static_cast(this->protocolVersion); - hints.ai_socktype = socketType; - hints.ai_protocol = socketProtocol; - int res = getaddrinfo(this->hostname.c_str(), std::to_string(newPort).c_str(), &hints, &resolvedAddresses); - if (res != 0 || this->hostname.empty() || resolvedAddresses == nullptr) - { - if (resolvedAddresses != nullptr) - { - freeaddrinfo(resolvedAddresses); - } - - throw kt::SocketException("Unable to resolve IP of destination address with hostname: [" + this->hostname + "]. Look up response code: [" + std::to_string(res) + "]. " + getErrorCode()); - } - - // We need to iterate over the resolved address and attempt to connect to each of them, if a connection attempt is succesful - // we will return, otherwise we will throw is we are unable to connect to any. - if (this->protocol == kt::SocketProtocol::TCP) - { - for (addrinfo* addr = resolvedAddresses; addr != nullptr; addr = addr->ai_next) - { - this->socketDescriptor = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); - if (!isInvalidSocket(this->socketDescriptor)) - { - int connectionResult = connect(this->socketDescriptor, addr->ai_addr, addr->ai_addrlen); - if (connectionResult == 0) - { - std::memcpy(&this->serverAddress, addr->ai_addr, addr->ai_addrlen); - this->protocolVersion = static_cast(addr->ai_family); - freeaddrinfo(resolvedAddresses); - // Return once we successfully connect to one - return; - } - } - - this->close(); - this->socketDescriptor = getInvalidSocketValue(); - } - freeaddrinfo(resolvedAddresses); - throw kt::SocketException("Error connecting to Wifi server: [" + std::to_string(res) + "] " + getErrorCode()); - } - else - { - this->udpSendSocket = socket(resolvedAddresses->ai_family, resolvedAddresses->ai_socktype, resolvedAddresses->ai_protocol); - this->protocolVersion = static_cast(resolvedAddresses->ai_family); - std::memcpy(&this->serverAddress, resolvedAddresses->ai_addr, resolvedAddresses->ai_addrlen); - freeaddrinfo(resolvedAddresses); - } - } - - /** - * Closes the existing connection. If no connection is open, then it will do nothing. - * This method should be called before the object itself is distructed. - */ - void kt::Socket::close() - { - this->close(this->socketDescriptor); - this->close(this->udpSendSocket); - - this->bound = false; - } - - /** - * This method is required for kt::SocketProtocol::UDP sockets. - * The socket that is listening for new connections will need to call this before they begin listening (accepting connections). - * This ensures the socket is bound to the port and can receive new connections. *Only a single process can be bound to a single port at one time*. - * - * @return bool - true if the socket was bound successfully, otherwise false - * - * @throw BindingException - if the socket fails to bind - */ - bool kt::Socket::bind(const kt::InternetProtocolVersion protocolVersion) - { - if (this->protocol == kt::SocketProtocol::UDP) - { - // Clear client address - memset(&this->receiveAddress, '\0', sizeof(this->receiveAddress)); - - const int socketType = SOCK_DGRAM; - const int socketProtocol = IPPROTO_UDP; - - addrinfo hint{}; - hint.ai_flags = AI_PASSIVE; - hint.ai_family = static_cast(protocolVersion); - hint.ai_socktype = socketType; - hint.ai_protocol = socketProtocol; - - addrinfo *addresses = nullptr; - if (getaddrinfo(nullptr, std::to_string(this->port).c_str(), &hint, &addresses) != 0) - { - freeaddrinfo(addresses); - throw kt::SocketException("Failed to retrieve address info of local host. " + getErrorCode()); - } - this->protocolVersion = static_cast(addresses->ai_family); - this->socketDescriptor = socket(addresses->ai_family, addresses->ai_socktype, addresses->ai_protocol); - -#ifdef _WIN32 - if (this->protocolVersion == kt::InternetProtocolVersion::IPV6) - { - const int disableOption = 0; - if (setsockopt(this->socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&disableOption, sizeof(disableOption)) != 0) - { - throw kt::SocketException("Failed to set IPV6_V6ONLY socket option: " + getErrorCode()); - } - } -#endif - - this->bound = ::bind(this->socketDescriptor, addresses->ai_addr, addresses->ai_addrlen) != -1; - freeaddrinfo(addresses); - if (!this->bound) - { - throw kt::BindingException("Error binding connection, the port " + std::to_string(this->port) + " is already being used: " + getErrorCode()); - } - - if (this->port == 0) - { - this->initialiseListeningPortNumber(); - } - - return this->bound; - } - return false; - } - - void kt::Socket::initialiseListeningPortNumber() - { - socklen_t socketSize = sizeof(this->serverAddress); - if (getsockname(this->socketDescriptor, &this->serverAddress.address, &socketSize) != 0) - { - this->close(); - throw kt::BindingException("Unable to retrieve randomly bound port number during socket creation. " + getErrorCode()); - } - - if (this->protocolVersion == kt::InternetProtocolVersion::IPV6) - { - this->port = ntohs(this->serverAddress.ipv6.sin6_port); - } - else - { - this->port = ntohs(this->serverAddress.ipv4.sin_port); - } - } - - void Socket::close(SOCKET socket) - { -#ifdef _WIN32 - closesocket(socket); - -#elif __linux__ - ::close(socket); -#endif - } - - /** - * Sends input std::string to the receiver via the configured socket. - * - * @param message - The message to send to the receiver. - * @param flag - A flag value to specify additional behaviour for this message. *Defaults to 0 if no argument is passed*. - * - * @return true if the message was sent without error, else false. - */ - bool kt::Socket::send(const std::string& message, int flag) - { - if (!message.empty()) - { - if (this->protocol == kt::SocketProtocol::TCP) - { - return ::send(this->socketDescriptor, message.c_str(), message.size(), flag) != -1; - } - else if (this->protocol == kt::SocketProtocol::UDP) - { - std::optional address = this->getUDPSendAddress(); - if (address.has_value()) - { - int result = ::sendto(this->udpSendSocket, message.c_str(), message.size(), flag, &(address.value().address), sizeof(address.value())); - return result != -1; - } - return false; - } - } - return false; - } - - int kt::Socket::pollSocket(SOCKET socket, const long& timeout) const - { - timeval timeOutVal{}; - int res = kt::pollSocket(socket, timeout, &timeOutVal); -#ifdef __linux__ - if (res == 0) - { - if (timeOutVal.tv_usec == 0) - { - return 0; - } - else - { - return 1; - } - } -#endif - - return res; - } - /** * Poll the provided socket descriptor for the provided timeout in microseconds. */ @@ -455,491 +71,13 @@ namespace kt return select(socketDescriptor + 1, &sReady, nullptr, nullptr, timeOutVal); } - /** - * Determines whether the stream has data to read. - * - * @param timeout - The timeout duration in *micro seconds*. Default is 1000 microseconds. - * - * @return true if there is data to read otherwise false. - */ - bool kt::Socket::ready(const unsigned long timeout) const - { - int result = this->pollSocket(this->socketDescriptor, timeout); - // 0 indicates that there is no data - return result > 0; - } - - /** - * Determines whether the stream is open. - * - * @param timeout - The timeout duration in *micro seconds*. Default is 1000 microseconds. - * - * @return true if the stream is open otherwise false. - * - * **NOTE:** This method is still in BETA, and cannot detect if the connection has been closed by the remote device. - */ - bool kt::Socket::connected(const unsigned long timeout) const - { - // UDP is connectionless - if (this->protocol == kt::SocketProtocol::UDP) - { - return false; - } - - int result = this->pollSocket(this->socketDescriptor, timeout); - - // -1 indicates that the connection is not available - return result != -1; - } - - /** - * This method is intended only for kt::SocketProtcol::UDP kt::Sockets. - * Usage on a *kt::SocketProtocol::TCP* socket will always return *false*. - * - * @return true if this socket is bound, otherwise false. - */ - bool kt::Socket::isUdpBound() const - { - return this->bound; - } - - /** - * Reads and returns a single character from the reciever. - * - * @return The character read. - */ - std::optional kt::Socket::get() - { - std::string received = this->receiveAmount(1); - if (received.empty()) - { - return std::nullopt; - } - return received[0]; - } - - /** - * @return the port number used by this socket. - */ - unsigned int kt::Socket::getPort() const - { - return this->port; - } - - /** - * @return the kt::SocketType for this kt::Socket. - */ - kt::SocketType kt::Socket::getType() const - { - return this->type; - } - - /** - * @return the kt::InternetProtocolVersion for this kt::Socket. - */ - kt::InternetProtocolVersion kt::Socket::getInternetProtocolVersion() const - { - return this->protocolVersion; - } - - /** - * @return the kt::SocketProtocol configured for this kt::Socket. - */ - kt::SocketProtocol kt::Socket::getProtocol() const - { - return this->protocol; - } - - /** - * This method is intended only for kt::SocketProtcol::UDP kt::Sockets. - * Use on a kt::SocketProtcol::TCP will result in an empty string. - * - * @return when using *kt::SocketProtocol::UDP* the address of the last device who sent the data that was most recently read. Always returns an empty string for kt::SocketProtocol::TCP kt::Sockets. - */ - std::optional kt::Socket::getLastUDPRecievedAddress() const - { - if (this->protocol == kt::SocketProtocol::UDP) - { - return kt::resolveToAddress(this->receiveAddress); - } - - return std::nullopt; - } - - std::optional kt::Socket::getLastUDPReceivedAddress() const - { - if (this->protocol == kt::SocketProtocol::UDP) - { - kt::SocketAddress address{}; - std::memcpy(&address, &this->receiveAddress, sizeof(address)); - return std::optional{ address }; - } - return std::nullopt; - } - - /** - * @return the hostname configured for this socket. - */ - std::string kt::Socket::getHostname() const - { - return this->hostname; - } - - std::optional kt::Socket::getUDPSendAddress() const - { - if (this->protocol == kt::SocketProtocol::UDP) - { - kt::SocketAddress newAddress{}; - // If the current server address is empty then we should return a null optional - if (std::memcmp(&newAddress, &this->serverAddress, sizeof(newAddress)) == 0) - { - return std::nullopt; - } - - memcpy(&newAddress, &this->serverAddress, sizeof(this->serverAddress)); - return std::optional{ newAddress }; - } - return std::nullopt; - } - - void Socket::setUDPSendAddress(std::string newHostname, unsigned int newPort, kt::InternetProtocolVersion newProtocolVersion) - { - this->protocolVersion = newProtocolVersion; - this->hostname = newHostname; - this->constructWifiSocket(newPort); - } - - void Socket::setUDPSendAddress(kt::SocketAddress newSocketAddress) - { - if (this->getProtocol() == kt::SocketProtocol::UDP) - { - kt::InternetProtocolVersion newVersion = kt::getInternetProtocolVersion(newSocketAddress); - if (newVersion != this->getInternetProtocolVersion()) - { - this->close(this->udpSendSocket); - this->udpSendSocket = socket(newSocketAddress.address.sa_family, SOCK_DGRAM, IPPROTO_UDP); - this->protocolVersion = newVersion; - } - std::memcpy(&this->serverAddress, &newSocketAddress, sizeof(this->serverAddress)); - } - } - - /** - * Reads in a specific amount of character from the input stream and returns them as a std::string. - * This method will return early if there is no more data to send or the other party closes the connection. - * - * @param amountToReceive - The amount of characters to read from the sender. - * - * @return A std::string of the specified size with the respective character read in. - */ - std::string kt::Socket::receiveAmount(const unsigned int amountToReceive) - { - if (amountToReceive == 0 || !this->ready()) - { - return ""; - } - - std::string data; - data.resize(amountToReceive); - - std::string result; - result.reserve(amountToReceive); - - if (this->protocol == kt::SocketProtocol::TCP) - { - unsigned int counter = 0; - - do - { - int flag = recv(this->socketDescriptor, &data[0], static_cast(amountToReceive - counter), 0); - if (flag < 1) - { - return result; - } - - // Need to substring to remove null terminating byte - result += data.substr(0, flag); - - data.clear(); - counter += flag; - } while (counter < amountToReceive && this->ready()); - } - else if (this->protocol == kt::SocketProtocol::UDP) - { - // UDP is odd, and will consume the entire datagram after a single read even if not all bytes are read - memset(&this->receiveAddress, '\0', sizeof(this->receiveAddress)); - socklen_t addressLength = sizeof(this->receiveAddress); - int flag = recvfrom(this->socketDescriptor, &data[0], static_cast(amountToReceive), 0, &this->receiveAddress.address, &addressLength); - if (flag < 1) - { - // This is for Windows, in some scenarios Windows will return a -1 flag but the buffer is populated properly - // The code it is returning is 10040 this is indicating that the provided buffer is too small for the incoming - // message, there is probably some settings we can tweak, however I think this is okay to return for now. - return data; - } - - // Need to substring to remove null terminating byte - result += data.substr(0, flag); - } - return result; - } - - /** - * Reads from the sender until the passed in delimiter is reached. The delimiter is discarded and the characters preceeding it are returned as a std::string. - * - * @param delimiter The delimiter that will be used to mark the end of the read in process. - * @param udpMaxAmountToRead Only used when using UDP, it is set as a packet read limit, which the delimiter is scanned for then the remaining data after it is truncated and lost. - * - * @return A std::string with all of the characters preceeding the delimiter. - * - * @throw SocketException - if the delimiter is '\0'. - */ - std::string kt::Socket::receiveToDelimiter(const char& delimiter, unsigned int udpMaxAmountToRead) - { - if (delimiter == '\0') - { - throw kt::SocketException("The null terminator '\\0' is an invalid delimiter."); - } - - std::string data; - if (!this->ready()) - { - return data; - } - - if (this->protocol == kt::SocketProtocol::TCP) - { - std::optional character; - do - { - character = this->get(); - if (character.has_value() && *character != delimiter) - { - data += *character; - } - } while (character.has_value() && *character != delimiter && this->ready()); - - return data; - } - else if (this->protocol == kt::SocketProtocol::UDP) - { - std::string temp; - if (udpMaxAmountToRead == 0) - { - udpMaxAmountToRead = DEFAULT_UDP_BUFFER_SIZE; - } - temp.resize(udpMaxAmountToRead); - memset(&this->receiveAddress, '\0', sizeof(this->receiveAddress)); - socklen_t addressLength = sizeof(this->receiveAddress); - - int flag = recvfrom(this->socketDescriptor, &temp[0], static_cast(udpMaxAmountToRead), 0, &this->receiveAddress.address, &addressLength); - if (flag < 1) - { - return data; - } - - // Need to substring to remove null terminating byte - data += temp.substr(0, flag); - size_t delimiterIndex = data.find_first_of(delimiter); - if (delimiterIndex != std::string::npos) - { - return data.substr(0, delimiterIndex); - } - } - - return data; - } - - /** - * Reads data while the stream is *ready()*. - * - * @return A std::string containing the characters read while the stream was *ready()*. - * - * **NOTE:** This method can take a long time to execute. If you know the desired size and/or a delimiter, the other methods may be more fitting. - * This method may take some time for the receiver to retreive the whole message due to the inability to flush streams when using sockets. - */ - std::string kt::Socket::receiveAll(const unsigned long timeout) - { - if (this->protocol == kt::SocketProtocol::UDP) - { - throw kt::SocketException("Socket::receiveAll(unsigned long) is not supported for UDP socket configuration. Please use Socket::receiveAmount(unsigned int) instead."); - } - - std::string result; - result.reserve(1024); - bool hitEOF = false; - - if (!this->ready()) - { - return ""; - } - - while (this->ready(timeout) && !hitEOF) - { - std::string res = receiveAmount(this->pollSocket(this->socketDescriptor, timeout)); - if (!res.empty() && res[0] == '\0') - { - hitEOF = true; - } - else - { - result += res; - } - } - result.shrink_to_fit(); - return result; - } - - /** - * **In progress** - * - * Scans for bluetooth devices and returns a std::vector<std::pair<std::string, std::string>> of the device names and addresses. - * - * @param duration - The duration for which the scan should take to discover nearby bluetooth devices. - * - * @return A std::vector<std::pair<std::string, std::string>> where .first is the devices address, and .second is the device name. - */ - std::vector > kt::Socket::scanDevices(unsigned int duration) + void close(SOCKET socket) { #ifdef _WIN32 - throw kt::SocketException("Socket::scanDevices(int) is not supported on Windows."); - - /*WSADATA wsaData; - int res = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (res != 0) - { - throw SocketException("WSAStartup Failed: " + std::to_string(res)); - }*/ - - /*WSAQUERYSET wsaQuery; - HANDLE hLoopUp; - LPWSAQUERYSET pQuerySet = nullptr; - SOCKADDR_BTH tempAddress; - DWORD dwSize = 5000 * sizeof(unsigned char); - memset(&wsaQuery, 0, sizeof(WSAQUERYSET)); - wsaQuery.dwSize = sizeof(WSAQUERYSET); - wsaQuery.dwNameSpace = NS_BTH; - wsaQuery.lpcsaBuffer = nullptr; - - int res = WSALookupServiceBegin(&wsaQuery, LUP_CONTAINERS, &hLoopUp); - if (res == -1) - { - throw SocketException("Unable to search for devices. Could not begin search."); - } - - memset(&pQuerySet, 0, sizeof(WSAQUERYSET)); - pQuerySet->dwSize = sizeof(WSAQUERYSET); - pQuerySet->dwNameSpace = NS_BTH; - pQuerySet->lpBlob = nullptr; - - while (WSALookupServiceNext(hLoopUp, LUP_RETURN_NAME | LUP_RETURN_ADDR, &dwSize, pQuerySet) == 0) - { - tempAddress = ((SOCKADDR_BTH*) pQuerySet->lpcsaBuffer->RemoteAddr.lpSockaddr)->btAddr; - // std::cout << pQuerySet->lpszServiceInstanceName << " : " << GET_NAP(tempAddress) << " - " << GET_SAP(tempAddress) << " ~ " << pQuerySet->dwNameSpace << std::endl; - }*/ - -#elif __linux__ - - std::vector > devices; - std::pair tempPair; - - inquiry_info *ii = nullptr; - int maxResponse = 255, numberOfResponses, ownId, tempSocket, flags; - char deviceAddress[19]; - char deviceName[248]; - - ownId = hci_get_route(nullptr); - tempSocket = hci_open_dev( ownId ); - if (ownId < 0 || tempSocket < 0) - { - throw SocketException("Error opening Bluetooth socket for scanning..."); - } - - flags = IREQ_CACHE_FLUSH; - ii = new inquiry_info[maxResponse * sizeof(inquiry_info)]; - - numberOfResponses = hci_inquiry(ownId, duration, maxResponse, nullptr, &ii, flags); - if( numberOfResponses < 0 ) - { - delete []ii; - throw SocketException("Error scanning for bluetooth devices"); - } - - for (int i = 0; i < numberOfResponses; i++) - { - ba2str( &(ii + i)->bdaddr, deviceAddress); - memset(&deviceName, '\0', sizeof(deviceName)); - if (hci_read_remote_name(tempSocket, &(ii+i)->bdaddr, sizeof(deviceName), deviceName, 0) < 0) - { - strcpy(deviceName, "[unknown]"); - } - - tempPair = std::make_pair(deviceAddress, deviceName); - devices.push_back(tempPair); - - } - - delete []ii; - ::close( tempSocket ); - - return devices; -#endif - } - - std::optional kt::Socket::getLocalMACAddress() - { -#ifdef _WIN32 - throw kt::SocketException("Socket::getLocalMACAddress() is not supported on Windows."); - - // Up to 20 Interfaces - /*IP_ADAPTER_INFO AdapterInfo[20]; - DWORD dwBufLen = sizeof(AdapterInfo); - DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen); - PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo; - - while (std::string(pAdapterInfo->Description).find("Bluetooth") == std::string::npos) - { - pAdapterInfo = pAdapterInfo->Next; - } - - std::stringstream ss; - for (int i = 0; i < 6; i++) - { - ss << std::hex << std::setfill('0'); - ss << std::setw(2) << static_cast(pAdapterInfo->Address[i]); - - if (i != 5) - { - ss << ":"; - } - } - - return ss.str();*/ + closesocket(socket); #elif __linux__ - int id; - bdaddr_t btaddr; - char localMACAddress[18]; - - // Get id of local device - if ((id = hci_get_route(nullptr)) < 0) - { - return std::nullopt; - } - - // Get local bluetooth address - if (hci_devba(id, &btaddr) < 0) - { - return std::nullopt; - } - - // Convert address to string - if (ba2str(&btaddr, localMACAddress) < 0) - { - return std::nullopt; - } - - return std::optional{std::string(localMACAddress)}; + ::close(socket); #endif } diff --git a/src/socket/Socket.h b/src/socket/Socket.h index 23c81f6..f6af84e 100644 --- a/src/socket/Socket.h +++ b/src/socket/Socket.h @@ -1,16 +1,5 @@ #pragma once -#include -#include -#include -#include - -#include "../enums/SocketProtocol.h" -#include "../enums/SocketType.h" -#include "../enums/InternetProtocolVersion.h" -#include "../address/SocketAddress.h" -#include "../socketexceptions/SocketError.h" - #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -20,17 +9,10 @@ #define _WIN32_WINNT 0x0600 #include -#include -#include #elif __linux__ #include -#include -#include -#include -#include -#include // Typedef to match the windows typedef since they are different underlying types typedef int SOCKET; @@ -39,74 +21,8 @@ typedef int SOCKET; namespace kt { - const unsigned int DEFAULT_UDP_BUFFER_SIZE = 10240; // 10 kilobytes - - class Socket - { - protected: - std::string hostname; - unsigned int port; - kt::SocketProtocol protocol = kt::SocketProtocol::None; - kt::SocketType type = kt::SocketType::None; - kt::InternetProtocolVersion protocolVersion = kt::InternetProtocolVersion::Any; - bool bound = false; - kt::SocketAddress serverAddress = {}; // For Wifi - kt::SocketAddress receiveAddress = {}; // For UDP, stores the client address of the last message received - SOCKET udpSendSocket = getInvalidSocketValue(); - SOCKET socketDescriptor = getInvalidSocketValue(); - -#ifdef _WIN32 - //SOCKADDR_BTH bluetoothAddress; - -#elif __linux__ - sockaddr_rc bluetoothAddress; // For Bluetooth - -#endif - - void constructBluetoothSocket(); - void constructWifiSocket(unsigned int&); - int pollSocket(SOCKET socket, const long& = 1000) const; - void initialiseListeningPortNumber(); - - void close(SOCKET socket); - - public: - Socket() = default; - Socket(const std::string&, const unsigned int&, const kt::SocketType, const kt::SocketProtocol = kt::SocketProtocol::None); // Create Wi-Fi/Bluetooth Socket - Socket(const SOCKET&, const kt::SocketType, const kt::SocketProtocol, const std::string&, const unsigned int&, const kt::InternetProtocolVersion); - - Socket(const kt::Socket&); - kt::Socket& operator=(const kt::Socket&); - - bool bind(const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); - void close(); - - bool ready(const unsigned long = 1000) const; - bool connected(const unsigned long = 1000) const; - bool send(const std::string&, int = 0); - - unsigned int getPort() const; - bool isUdpBound() const; - kt::SocketProtocol getProtocol() const; - kt::SocketType getType() const; - kt::InternetProtocolVersion getInternetProtocolVersion() const; - std::string getHostname() const; - - std::optional getLastUDPRecievedAddress() const; - std::optional getLastUDPReceivedAddress() const; - std::optional getUDPSendAddress() const; - void setUDPSendAddress(std::string, unsigned int, kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); - void setUDPSendAddress(kt::SocketAddress); - - std::optional get(); - std::string receiveAmount(const unsigned int); - std::string receiveToDelimiter(const char&, unsigned int = 0); - std::string receiveAll(const unsigned long = 1000); - - static std::vector > scanDevices(unsigned int = 5); - static std::optional getLocalMACAddress(); - }; - int pollSocket(const SOCKET& socketDescriptor, const long& timeout, timeval* timeOutVal = nullptr); + void close(SOCKET socket); + } // End namespace kt diff --git a/src/socket/TCPSocket.cpp b/src/socket/TCPSocket.cpp new file mode 100644 index 0000000..f9cf25c --- /dev/null +++ b/src/socket/TCPSocket.cpp @@ -0,0 +1,283 @@ + +#include "TCPSocket.h" +#include "../socketexceptions/SocketException.hpp" + +#include + +namespace kt +{ + TCPSocket::TCPSocket(const std::string& hostname, const unsigned int& port) + { + this->hostname = hostname; + this->port = port; + + memset(&this->serverAddress, '\0', sizeof(this->serverAddress)); + + constructWifiSocket(); + } + + TCPSocket::TCPSocket(const SOCKET& socket, const std::string& hostname, const unsigned int& port, const kt::InternetProtocolVersion protocolVersion, const kt::SocketAddress& acceptedAddress) + { + this->socketDescriptor = socket; + this->hostname = hostname; + this->port = port; + this->protocolVersion = protocolVersion; + std::memcpy(&this->serverAddress, &acceptedAddress, sizeof(this->serverAddress)); + } + + TCPSocket::TCPSocket(const kt::TCPSocket& socket) + { + this->socketDescriptor = socket.socketDescriptor; + this->hostname = socket.hostname; + this->port = socket.port; + this->protocolVersion = socket.protocolVersion; + std::memcpy(&this->serverAddress, &socket.serverAddress, sizeof(this->serverAddress)); + } + + TCPSocket& TCPSocket::operator=(const kt::TCPSocket& socket) + { + this->socketDescriptor = socket.socketDescriptor; + this->hostname = socket.hostname; + this->port = socket.port; + this->protocolVersion = socket.protocolVersion; + std::memcpy(&this->serverAddress, &socket.serverAddress, sizeof(this->serverAddress)); + + return *this; + } + + void TCPSocket::constructWifiSocket() + { + memset(&this->serverAddress, 0, sizeof(this->serverAddress)); + +#ifdef _WIN32 + WSADATA wsaData{}; + if (int res = WSAStartup(MAKEWORD(2, 2), &wsaData); res != 0) + { + throw kt::SocketException("WSAStartup Failed. " + std::to_string(res)); + } + +#endif + + addrinfo* resolvedAddresses = nullptr; + addrinfo hints{}; + hints.ai_family = static_cast(this->protocolVersion); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + int res = getaddrinfo(this->hostname.c_str(), std::to_string(this->port).c_str(), &hints, &resolvedAddresses); + if (res != 0 || this->hostname.empty() || resolvedAddresses == nullptr) + { + if (resolvedAddresses != nullptr) + { + freeaddrinfo(resolvedAddresses); + } + + throw kt::SocketException("Unable to resolve IP of destination address with hostname: [" + this->hostname + ":" + std::to_string(this->port) + "]. Look up response code: [" + std::to_string(res) + "]. " + getErrorCode()); + } + + // We need to iterate over the resolved address and attempt to connect to each of them, if a connection attempt is succesful + // we will return, otherwise we will throw is we are unable to connect to any. + for (addrinfo* addr = resolvedAddresses; addr != nullptr; addr = addr->ai_next) + { + this->socketDescriptor = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (!isInvalidSocket(this->socketDescriptor)) + { + int connectionResult = connect(this->socketDescriptor, addr->ai_addr, addr->ai_addrlen); + if (connectionResult == 0) + { + std::memcpy(&this->serverAddress, addr->ai_addr, addr->ai_addrlen); + this->protocolVersion = static_cast(addr->ai_family); + freeaddrinfo(resolvedAddresses); + // Return once we successfully connect to one + return; + } + } + + this->close(); + this->socketDescriptor = getInvalidSocketValue(); + } + freeaddrinfo(resolvedAddresses); + throw kt::SocketException("Error connecting to TCP server: [" + std::to_string(res) + "] " + getErrorCode()); + } + + int TCPSocket::pollSocket(SOCKET socket, const long& timeout) const + { + timeval timeOutVal{}; + int res = kt::pollSocket(socket, timeout, &timeOutVal); +#ifdef __linux__ + if (res == 0) + { + if (timeOutVal.tv_usec == 0) + { + return 0; + } + else + { + return 1; + } + } +#endif + + return res; + } + + void TCPSocket::close() const + { + kt::close(this->socketDescriptor); + } + + bool TCPSocket::ready(const unsigned long timeout) const + { + int result = this->pollSocket(this->socketDescriptor, timeout); + // 0 indicates that there is no data + return result > 0; + } + + bool TCPSocket::connected(const unsigned long timeout) const + { + int result = this->pollSocket(this->socketDescriptor, timeout); + // -1 indicates that the connection is not available + return result != -1; + } + + bool TCPSocket::send(const std::string& message, const int& flags) const + { + int result = ::send(this->socketDescriptor, message.c_str(), message.size(), flags); + return result != -1; + } + + std::string TCPSocket::getHostname() const + { + return this->hostname; + } + + unsigned int TCPSocket::getPort() const + { + return this->port; + } + + kt::InternetProtocolVersion TCPSocket::getInternetProtocolVersion() const + { + return this->protocolVersion; + } + + kt::SocketAddress TCPSocket::getSocketAddress() const + { + return this->serverAddress; + } + + std::optional TCPSocket::get(const int& flags) const + { + std::string received = this->receiveAmount(1, flags); + if (received.empty()) + { + return std::nullopt; + } + return received[0]; + } + + /** + * Reads in a specific amount of character from the input stream and returns them as a std::string. + * This method will return early if there is no more data to send or the other party closes the connection. + * + * @param amountToReceive - The amount of characters to read from the sender. + * + * @return A std::string of the specified size with the respective character read in. + */ + std::string kt::TCPSocket::receiveAmount(const unsigned int amountToReceive, const int& flags) const + { + if (amountToReceive == 0 || !this->ready()) + { + return ""; + } + + std::string data; + data.resize(amountToReceive); + + std::string resultantString; + resultantString.reserve(amountToReceive); + + unsigned int counter = 0; + do + { + int amountReceived = recv(this->socketDescriptor, &data[0], static_cast(amountToReceive - counter), flags); + if (amountReceived < 1) + { + return resultantString; + } + + // Need to substring to remove null terminating byte + resultantString += data.substr(0, amountReceived); + + data.clear(); + counter += amountReceived; + } while (counter < amountToReceive && this->ready()); + + + return resultantString; + } + + /** + * Reads from the sender until the passed in delimiter is reached. The delimiter is discarded and the characters preceeding it are returned as a std::string. + * + * @param delimiter The delimiter that will be used to mark the end of the read in process. + * + * @return A std::string with all of the characters preceeding the delimiter. + * + * @throw SocketException - if the delimiter is '\0'. + */ + std::string kt::TCPSocket::receiveToDelimiter(const char& delimiter, const int& flags) + { + if (delimiter == '\0') + { + throw kt::SocketException("The null terminator '\\0' is an invalid delimiter."); + } + + std::string data; + if (!this->ready()) + { + return data; + } + + std::optional character; + do + { + character = this->get(flags); + if (character.has_value() && *character != delimiter) + { + data += *character; + } + } while (character.has_value() && *character != delimiter && this->ready()); + + return data; + } + + /** + * Reads data while the stream is *ready()*. + * + * @return A std::string containing the characters read while the stream was *ready()*. + * + * **NOTE:** This method can take a long time to execute. If you know the desired size and/or a delimiter, the other methods may be more fitting. + * This method may take some time for the receiver to retreive the whole message due to the inability to flush streams when using sockets. + */ + std::string kt::TCPSocket::receiveAll(const unsigned long timeout, const int& flags) + { + std::string result; + result.reserve(1024); + bool hitEOF = false; + + while (this->ready(timeout) && !hitEOF) + { + std::string res = receiveAmount(this->pollSocket(this->socketDescriptor, timeout), flags); + if (!res.empty() && res[0] == '\0') + { + hitEOF = true; + } + else + { + result += res; + } + } + result.shrink_to_fit(); + return result; + } +} diff --git a/src/socket/TCPSocket.h b/src/socket/TCPSocket.h new file mode 100644 index 0000000..6e6f33f --- /dev/null +++ b/src/socket/TCPSocket.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include + +#include "../enums/SocketProtocol.h" +#include "../enums/SocketType.h" +#include "../enums/InternetProtocolVersion.h" +#include "../address/SocketAddress.h" +#include "../socketexceptions/SocketError.h" + +#include "Socket.h" + +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif + +#define _WIN32_WINNT 0x0600 + +#include +#include +#include + +#elif __linux__ + +#include +#include +#include +#include +#include +#include + +// Typedef to match the windows typedef since they are different underlying types +typedef int SOCKET; + +#endif + +namespace kt +{ class TCPSocket + { + protected: + SOCKET socketDescriptor = getInvalidSocketValue(); + std::string hostname; + unsigned int port; + kt::InternetProtocolVersion protocolVersion = kt::InternetProtocolVersion::Any; + kt::SocketAddress serverAddress = {}; // The remote address that we will be connected to + + void constructWifiSocket(); + + public: + TCPSocket() = default; + TCPSocket(const std::string&, const unsigned int&); + TCPSocket(const SOCKET&, const std::string&, const unsigned int&, const kt::InternetProtocolVersion, const kt::SocketAddress&); + + TCPSocket(const kt::TCPSocket&); + kt::TCPSocket& operator=(const kt::TCPSocket&); + + void close() const; + + int pollSocket(SOCKET socket, const long& = 1000) const; + bool ready(const unsigned long = 1000) const; + bool connected(const unsigned long = 1000) const; + bool send(const std::string&, const int& = 0) const; + + std::string getHostname() const; + unsigned int getPort() const; + kt::InternetProtocolVersion getInternetProtocolVersion() const; + kt::SocketAddress getSocketAddress() const; + + std::optional get(const int& = 0) const; + std::string receiveAmount(const unsigned int, const int& = 0) const; + std::string receiveToDelimiter(const char&, const int& = 0); + std::string receiveAll(const unsigned long = 1000, const int& = 0); + }; + +} // End namespace kt diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp new file mode 100644 index 0000000..b4182e4 --- /dev/null +++ b/src/socket/UDPSocket.cpp @@ -0,0 +1,236 @@ +#include "UDPSocket.h" + +#include "../socketexceptions/SocketException.hpp" +#include "../socketexceptions/BindingException.hpp" + +namespace kt +{ + UDPSocket::UDPSocket(const kt::UDPSocket& socket) + { + this->bound = socket.bound; + this->receiveSocket = socket.receiveSocket; + } + + kt::UDPSocket& UDPSocket::operator=(const kt::UDPSocket& socket) + { + this->bound = socket.bound; + this->receiveSocket = socket.receiveSocket; + + return *this; + } + + std::pair UDPSocket::constructSocket(std::string& hostname, unsigned int& port, const kt::InternetProtocolVersion protocolVersion) + { + const int socketType = SOCK_DGRAM; + const int protocol = IPPROTO_UDP; + + addrinfo hints{}; + hints.ai_family = static_cast(protocolVersion); + hints.ai_socktype = socketType; + hints.ai_protocol = protocol; + + std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::optional{ hostname }, port, hints); + if (resolvedAddresses.second != 0 || resolvedAddresses.first.empty()) + { + throw kt::SocketException("Unable to resolve IP of destination address with hostname: [" + hostname + ":" + std::to_string(port) + "].Look up response code : [" + std::to_string(resolvedAddresses.second) + "] . " + getErrorCode()); + } + + kt::SocketAddress resolvedAddress = resolvedAddresses.first.at(0); + SOCKET udpSendSocket = socket(resolvedAddress.address.sa_family, socketType, protocol); + + return std::make_pair(udpSendSocket, resolvedAddress); + } + + /** + * This method is required for kt::SocketProtocol::UDP sockets. + * The socket that is listening for new connections will need to call this before they begin listening (accepting connections). + * This ensures the socket is bound to the port and can receive new connections. *Only a single process can be bound to a single port at one time*. + * + * @return bool - true if the socket was bound successfully, otherwise false + * + * @throw BindingException - if the socket fails to bind + */ + bool kt::UDPSocket::bind(const unsigned int& port, const kt::InternetProtocolVersion protocolVersion) + { + this->listeningPort = port; + + kt::SocketAddress receiveAddress{}; + const int socketType = SOCK_DGRAM; + const int socketProtocol = IPPROTO_UDP; + + addrinfo hints{}; + hints.ai_flags = AI_PASSIVE; + hints.ai_family = static_cast(protocolVersion); + hints.ai_socktype = socketType; + hints.ai_protocol = socketProtocol; + + std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::nullopt, this->listeningPort, hints); + if (resolvedAddresses.second != 0 || resolvedAddresses.first.empty()) + { + throw kt::SocketException("Failed to retrieve address info of local host. With error:" + std::to_string(resolvedAddresses.second) + " " + getErrorCode()); + } + + kt::SocketAddress firstAddress = resolvedAddresses.first.at(0); + this->protocolVersion = static_cast(firstAddress.address.sa_family); + this->receiveSocket = socket(firstAddress.address.sa_family, socketType, socketProtocol); + +#ifdef _WIN32 + if (this->protocolVersion == kt::InternetProtocolVersion::IPV6) + { + const int disableOption = 0; + if (setsockopt(this->receiveSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&disableOption, sizeof(disableOption)) != 0) + { + throw kt::SocketException("Failed to set IPV6_V6ONLY socket option: " + getErrorCode()); + } + } + + const int enableOption = 1; + if (setsockopt(this->receiveSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&enableOption, sizeof(enableOption)) != 0) + { + throw SocketException("Failed to set SO_REUSEADDR socket option: " + getErrorCode()); + } +#endif + + int bindResult = ::bind(this->receiveSocket, &firstAddress.address, sizeof(firstAddress.address)); + this->bound = bindResult != -1; + if (!this->bound) + { + throw kt::BindingException("Error binding connection, the port " + std::to_string(this->listeningPort) + " is already being used. Response code from ::bind()" + std::to_string(bindResult) + ". Latest Error code: " + getErrorCode()); + } + + if (this->listeningPort == 0) + { + this->initialiseListeningPortNumber(); + } + + return this->bound; + } + + void UDPSocket::close() + { + this->close(this->receiveSocket); + + this->bound = false; + } + + bool UDPSocket::ready(const unsigned long timeout) const + { + int result = this->pollSocket(this->receiveSocket, timeout); + // 0 indicates that there is no data + return result > 0; + } + + std::pair UDPSocket::sendTo(const std::string& message, const kt::SocketAddress& address, const int& flags) + { + SOCKET tempSocket = socket(address.address.sa_family, SOCK_DGRAM, IPPROTO_UDP); + if (!kt::isInvalidSocket(tempSocket)) + { + return std::make_pair(false, -2); + } + int result = ::sendto(tempSocket, message.c_str(), message.size(), flags, &(address.address), sizeof(address)); + return std::make_pair(result != -1, result); + } + + std::pair UDPSocket::sendTo(const std::string& hostname, const unsigned int& port, const std::string& message, const int& flags) + { + addrinfo hints{}; + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + + std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::optional{ hostname }, port, hints); + if (resolvedAddresses.first.empty() || resolvedAddresses.second != 0) + { + return std::make_pair(false, resolvedAddresses.second); + } + kt::SocketAddress firstAddress = resolvedAddresses.first.at(0); + return this->sendTo(message, firstAddress, flags); + } + + std::pair, kt::SocketAddress> UDPSocket::receiveFrom(const unsigned int& receiveLength, const int& flags) + { + kt::SocketAddress receiveAddress{}; + if (!this->bound || receiveLength == 0 || !this->ready()) + { + return std::make_pair(std::nullopt, receiveAddress); + } + std::string data; + data.resize(receiveLength); + + socklen_t addressLength = sizeof(receiveAddress); + int flag = recvfrom(this->listeningPort, &data[0], static_cast(receiveLength), flags, &receiveAddress.address, &addressLength); + +#ifdef _WIN32 + if (flag < 1) + { + // This is for Windows, in some scenarios Windows will return a -1 flag but the buffer is populated properly + // The code it is returning is 10040 this is indicating that the provided buffer is too small for the incoming + // message, there is probably some settings we can tweak, however I think this is okay to return for now. + return std::make_pair(std::make_optional(data), receiveAddress); + } +#endif + + // Need to substring to remove any null terminating bytes + data = data.substr(0, flag); + return std::make_pair(std::make_optional(data), receiveAddress); + } + + bool UDPSocket::isUdpBound() const + { + return this->bound; + } + + /** + * This is only applicable and useful if the UDPSocket is isUdpBound() returns *true*. + */ + kt::InternetProtocolVersion UDPSocket::getInternetProtocolVersion() const + { + return this->protocolVersion; + } + + unsigned int UDPSocket::getListeningPort() const + { + return this->listeningPort; + } + + int UDPSocket::pollSocket(SOCKET socket, const long& timeout) const + { + timeval timeOutVal{}; + int res = kt::pollSocket(socket, timeout, &timeOutVal); +#ifdef __linux__ + if (res == 0) + { + if (timeOutVal.tv_usec == 0) + { + return 0; + } + else + { + return 1; + } + } +#endif + + return res; + } + + void UDPSocket::initialiseListeningPortNumber() + { + std::pair, int> address = kt::socketToAddress(this->receiveSocket); + if (address.second != 0 && !address.first.has_value()) + { + this->close(); + throw kt::BindingException("Unable to retrieve randomly bound port number during socket creation. " + getErrorCode()); + } + + this->listeningPort = kt::getPortNumber(address.first.value()); + } + + void UDPSocket::close(SOCKET socket) + { + kt::close(socket); + } + +} + diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h new file mode 100644 index 0000000..ed56c75 --- /dev/null +++ b/src/socket/UDPSocket.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include + +#include "../enums/SocketProtocol.h" +#include "../enums/SocketType.h" +#include "../enums/InternetProtocolVersion.h" +#include "../address/SocketAddress.h" +#include "../socketexceptions/SocketError.h" + +#include "Socket.h" + +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#define _WIN32_WINNT 0x0600 + +#include +#include +#include + +#elif __linux__ + +#include +#include +#include +#include +#include +#include + +// Typedef to match the windows typedef since they are different underlying types +typedef int SOCKET; + +#endif + +namespace kt +{ + const unsigned int DEFAULT_UDP_BUFFER_SIZE = 10240; // 10 kilobytes + + class UDPSocket + { + protected: + bool bound = false; + SOCKET receiveSocket = getInvalidSocketValue(); + kt::InternetProtocolVersion protocolVersion = kt::InternetProtocolVersion::Any; + unsigned int listeningPort = 0; + + std::pair constructSocket(std::string&, unsigned int&, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); + int pollSocket(SOCKET socket, const long& = 1000) const; + void initialiseListeningPortNumber(); + + void close(SOCKET socket); + + public: + UDPSocket() = default; + UDPSocket(const kt::UDPSocket&); + kt::UDPSocket& operator=(const kt::UDPSocket&); + + bool bind(const unsigned int&, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); + void close(); + + bool ready(const unsigned long = 1000) const; + std::pair sendTo(const std::string&, const kt::SocketAddress&, const int& = 0); + std::pair sendTo(const std::string&, const unsigned int&, const std::string&, const int& = 0); + std::pair, kt::SocketAddress> receiveFrom(const unsigned int&, const int& = 0); + + bool isUdpBound() const; + kt::InternetProtocolVersion getInternetProtocolVersion() const; + unsigned int getListeningPort() const; + }; + +} // End namespace kt diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 873e2b1..db01bf2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,10 +19,10 @@ FetchContent_MakeAvailable(googletest) set(SOURCE - serversocket/ServerSocketTest.cpp - socket/SocketTCPTest.cpp - socket/SocketUDPTest.cpp - socket/SocketBluetoothTest.cpp + serversocket/ServerSocketTCPTest.cpp + socket/TCPSocketTest.cpp + socket/UDPSocketTest.cpp + socket/BluetoothSocketTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCE}) diff --git a/tests/serversocket/ServerSocketTest.cpp b/tests/serversocket/ServerSocketTCPTest.cpp similarity index 72% rename from tests/serversocket/ServerSocketTest.cpp rename to tests/serversocket/ServerSocketTCPTest.cpp index 86c6c4b..d1f44a0 100644 --- a/tests/serversocket/ServerSocketTest.cpp +++ b/tests/serversocket/ServerSocketTCPTest.cpp @@ -11,12 +11,12 @@ const int PORT_NUMBER = 87682; namespace kt { - class ServerSocketTest: public ::testing::Test + class ServerSocketTCPTest: public ::testing::Test { protected: ServerSocket serverSocket; protected: - ServerSocketTest() : serverSocket(SocketType::Wifi, PORT_NUMBER) {} + ServerSocketTCPTest() : serverSocket(SocketType::Wifi, PORT_NUMBER) {} // void SetUp() override { } void TearDown() override { @@ -27,7 +27,7 @@ namespace kt /* * Ensure the defaults are set correctly. */ - TEST_F(ServerSocketTest, TestDefaultConstructor) + TEST_F(ServerSocketTCPTest, TestDefaultConstructor) { ASSERT_EQ(SocketType::Wifi, serverSocket.getType()); ASSERT_NE(InternetProtocolVersion::Any, serverSocket.getInternetProtocolVersion()); @@ -37,7 +37,7 @@ namespace kt /* * Ensure that we throw a binding exception if the port is already used. */ - TEST_F(ServerSocketTest, TestConstructors) + TEST_F(ServerSocketTCPTest, TestConstructors) { EXPECT_THROW({ ServerSocket server2(SocketType::Wifi, serverSocket.getPort()); @@ -47,23 +47,23 @@ namespace kt /* * Ensure thatt if a copied serversocket is closed, that it closes the copied socket too since they shared the same underlying descriptor. */ - TEST_F(ServerSocketTest, TestCloseCopiedServerSocket) + TEST_F(ServerSocketTCPTest, TestCloseCopiedServerSocket) { ServerSocket copiedServer = serverSocket; serverSocket.close(); - ASSERT_THROW(copiedServer.accept(), SocketException); + ASSERT_THROW(copiedServer.acceptTCPConnection(), SocketException); } /* * Ensure the copy constructed server socket is able to connect to the client and receive messages. */ - TEST_F(ServerSocketTest, TestCopyConstructor) + TEST_F(ServerSocketTCPTest, TestCopyConstructor) { ServerSocket server2(serverSocket); - Socket client("127.0.0.1", serverSocket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::TCP); + TCPSocket client("127.0.0.1", serverSocket.getPort()); - Socket serverClient = server2.accept(); + TCPSocket serverClient = server2.acceptTCPConnection(); const std::string testString = "test"; ASSERT_TRUE(client.send(testString)); @@ -78,12 +78,12 @@ namespace kt /* * Ensure the server socket can be created using IPV6 and can accept a connection. */ - TEST_F(ServerSocketTest, TestServerSocketAsIPV6) + TEST_F(ServerSocketTCPTest, TestServerSocketAsIPV6) { ServerSocket ipv6Server(SocketType::Wifi, 0, 20, InternetProtocolVersion::IPV6); - Socket client("0000:0000:0000:0000:0000:0000:0000:0001", ipv6Server.getPort(), SocketType::Wifi, SocketProtocol::TCP); - Socket serverClient = ipv6Server.accept(); + TCPSocket client("0000:0000:0000:0000:0000:0000:0000:0001", ipv6Server.getPort()); + TCPSocket serverClient = ipv6Server.acceptTCPConnection(); const std::string testString = "test"; ASSERT_TRUE(client.send(testString)); @@ -98,7 +98,7 @@ namespace kt /* * Ensure the server socket cannot be connected to by a client using IPV4. */ - TEST_F(ServerSocketTest, TestServerSocketAsIPV4ServerAndIPV4Client) + TEST_F(ServerSocketTCPTest, TestServerSocketAsIPV4ServerAndIPV4Client) { serverSocket.close(); @@ -107,12 +107,12 @@ namespace kt // Attempt to connect to a local server using a IPV6 address (which is not being hosted) EXPECT_THROW({ - Socket client("::1", ipv4Server.getPort(), SocketType::Wifi, SocketProtocol::TCP); + TCPSocket client("::1", ipv4Server.getPort()); }, SocketException); // Make sure theres no incoming connections EXPECT_THROW({ - ipv4Server.accept(1000); + ipv4Server.acceptTCPConnection(1000); }, TimeoutException); ipv4Server.close(); @@ -121,11 +121,11 @@ namespace kt /** * Ensure that calls to ServerSocket.accept() with a provided timeout waits for atleast the provided timeout before throwing a TimeoutException. */ - TEST_F(ServerSocketTest, TestServerSocketAcceptTimeout) + TEST_F(ServerSocketTCPTest, TestServerSocketAcceptTimeout) { std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); EXPECT_THROW({ - Socket serverClient = serverSocket.accept(1 * 1000000); + TCPSocket serverClient = serverSocket.acceptTCPConnection(1 * 1000000); }, TimeoutException); std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); diff --git a/tests/socket/BluetoothSocketTest.cpp b/tests/socket/BluetoothSocketTest.cpp new file mode 100644 index 0000000..07f304b --- /dev/null +++ b/tests/socket/BluetoothSocketTest.cpp @@ -0,0 +1,59 @@ + +#include +#include + +#include + +#include "../../src/socket/BluetoothSocket.h" +#include "../../src/serversocket/ServerSocket.h" + +namespace kt +{ + class DISABLED_BluetoothSocketTest : public ::testing::Test + { + protected: + ServerSocket serverSocket; + BluetoothSocket socket; + + protected: + DISABLED_BluetoothSocketTest() : serverSocket(SocketType::Bluetooth), socket(BluetoothSocket::getLocalMACAddress().value(), serverSocket.getPort()) { } + // void TearDown() override + // { + // socket.close(); + // serverSocket.close(); + // } + }; + + TEST_F(DISABLED_BluetoothSocketTest, BluetoothGetLocalMacAddress) + { + GTEST_SKIP(); + + ASSERT_NE(std::nullopt, BluetoothSocket::getLocalMACAddress()); + } + + TEST_F(DISABLED_BluetoothSocketTest, BluetoothScanDevices) + { + GTEST_SKIP(); + + std::vector > devices = BluetoothSocket::scanDevices(); + for (const std::pair& p : devices) + { + std::cout << p.first << " - " << p.second << std::endl; + } + } + + TEST_F(DISABLED_BluetoothSocketTest, BluetoothSend) + { + GTEST_SKIP(); + + BluetoothSocket server = serverSocket.acceptBluetoothConnection(); + const std::string toSend = "TestBluetooth"; + + ASSERT_TRUE(socket.send(toSend)); + + std::string response = server.receiveAmount(toSend.size()); + ASSERT_EQ(response, toSend); + + server.close(); + } +} \ No newline at end of file diff --git a/tests/socket/SocketBluetoothTest.cpp b/tests/socket/SocketBluetoothTest.cpp deleted file mode 100644 index 7b85a2a..0000000 --- a/tests/socket/SocketBluetoothTest.cpp +++ /dev/null @@ -1,59 +0,0 @@ - -#include -#include - -#include - -#include "../../src/socket/Socket.h" -#include "../../src/serversocket/ServerSocket.h" - -namespace kt -{ - class DISABLED_SocketBluetoothTest : public ::testing::Test - { - protected: - ServerSocket serverSocket; - Socket socket; - - protected: - DISABLED_SocketBluetoothTest() : serverSocket(SocketType::Bluetooth), socket(Socket::getLocalMACAddress().value(), serverSocket.getPort(), SocketType::Bluetooth) { } - void TearDown() override - { - socket.close(); - serverSocket.close(); - } - }; - - TEST_F(DISABLED_SocketBluetoothTest, BluetoothGetLocalMacAddress) - { - GTEST_SKIP(); - - ASSERT_NE(std::nullopt, Socket::getLocalMACAddress()); - } - - TEST_F(DISABLED_SocketBluetoothTest, BluetoothScanDevices) - { - GTEST_SKIP(); - - std::vector > devices = Socket::scanDevices(); - for (const std::pair& p : devices) - { - std::cout << p.first << " - " << p.second << std::endl; - } - } - - TEST_F(DISABLED_SocketBluetoothTest, BluetoothSend) - { - GTEST_SKIP(); - - Socket server = serverSocket.accept(); - const std::string toSend = "TestBluetooth"; - - ASSERT_TRUE(socket.send(toSend)); - - std::string response = server.receiveAmount(toSend.size()); - ASSERT_EQ(response, toSend); - - server.close(); - } -} \ No newline at end of file diff --git a/tests/socket/SocketUDPTest.cpp b/tests/socket/SocketUDPTest.cpp deleted file mode 100644 index 266e47b..0000000 --- a/tests/socket/SocketUDPTest.cpp +++ /dev/null @@ -1,335 +0,0 @@ - -#include -#include - -#include - -#include "../../src/socket/Socket.h" -#include "../../src/socketexceptions/BindingException.hpp" - -const std::string LOCALHOST = "localhost"; - -namespace kt -{ - class SocketUDPTest : public ::testing::Test - { - protected: - Socket socket; - - protected: - SocketUDPTest() : socket(LOCALHOST, 0, SocketType::Wifi, SocketProtocol::UDP) { } - void TearDown() override - { - this->socket.close(); - } - }; - - /* - * Test the kt::Socket constructors and exception handling for UDP. This covers the following scenarios: - * - Constructing a socket and ensuring its default values are set correctly - */ - TEST_F(SocketUDPTest, UDPConstructors) - { - ASSERT_EQ(socket.getType(), SocketType::Wifi); - ASSERT_EQ(socket.getProtocol(), SocketProtocol::UDP); - ASSERT_FALSE(socket.connected()); - ASSERT_FALSE(socket.ready()); - ASSERT_FALSE(socket.isUdpBound()); - ASSERT_EQ(LOCALHOST, socket.getHostname()); - ASSERT_EQ(std::nullopt, socket.getLastUDPRecievedAddress()); - } - - /* - * Ensure that we cannot receive any messages when we are not bound. - */ - TEST_F(SocketUDPTest, UDPBind_NotCalled) - { - ASSERT_FALSE(socket.isUdpBound()); - - kt::Socket client(LOCALHOST, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - - ASSERT_FALSE(socket.ready()); - client.send("test"); - ASSERT_FALSE(socket.ready()); - } - - /* - * Ensure that multiple calls to Socket.bind() fails if another socket is already bound to that port. - */ - TEST_F(SocketUDPTest, UDPBindAndBound_MultipleCalls) - { - ASSERT_FALSE(socket.isUdpBound()); - ASSERT_TRUE(socket.bind()); - ASSERT_TRUE(socket.isUdpBound()); - - kt::Socket newServer(LOCALHOST, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - ASSERT_FALSE(newServer.isUdpBound()); - EXPECT_THROW(newServer.bind(), BindingException); - } - - /* - * Ensure that multiple calls to Socket.bind() fails if another socket is already bound to that port. - */ - TEST_F(SocketUDPTest, UDPBind_WithoutSpecifiedPort) - { - ASSERT_FALSE(socket.isUdpBound()); - - unsigned int port = 0; - kt::Socket newServer(LOCALHOST, port, kt::SocketType::Wifi, kt::SocketProtocol::UDP); - ASSERT_FALSE(newServer.isUdpBound()); - newServer.bind(); - ASSERT_TRUE(newServer.isUdpBound()); - ASSERT_NE(port, newServer.getPort()); // Make sure we have looked up and resolved the port number upon successful binding - - newServer.close(); - } - - /* - * Test Socket.send(std::string) to ensure that the server socket receives data. - */ - TEST_F(SocketUDPTest, UDPSendAndReady) - { - ASSERT_TRUE(socket.bind()); - - Socket client(LOCALHOST, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - - ASSERT_FALSE(socket.ready()); - const std::string testString = "test"; - ASSERT_TRUE(client.send(testString)); - ASSERT_TRUE(socket.ready()); - - client.close(); - } - - /* - * Call receiveAmount to make sure the correct amount is read. - */ - TEST_F(SocketUDPTest, UDPReceiveAmount) - { - ASSERT_TRUE(socket.bind()); - - Socket client(LOCALHOST, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - const std::string testString = "test"; - ASSERT_TRUE(client.send(testString)); - - ASSERT_TRUE(socket.ready()); - std::string recieved = socket.receiveAmount(testString.size()); - ASSERT_FALSE(socket.ready()); - ASSERT_EQ(testString, recieved); - - client.close(); - } - - /* - * Ensure that receiveAmount reads the specified amount even when more is available in the buffer. - * Also confirm that the remaining data is lost if not read. - */ - TEST_F(SocketUDPTest, UDPReceiveAmount_NotEnoughRead) - { - ASSERT_TRUE(socket.bind()); - - Socket client(LOCALHOST, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - const std::string testString = "test"; - ASSERT_FALSE(socket.ready()); - ASSERT_TRUE(client.send(testString)); - - ASSERT_TRUE(socket.ready()); - std::string recieved = socket.receiveAmount(testString.size() - 1); - ASSERT_FALSE(socket.ready()); - ASSERT_EQ(testString.substr(0, testString.size() - 1), recieved); - - client.close(); - } - - /* - * Ensure that the receiveAmount read the correct amount when less data is provided than expected. - */ - TEST_F(SocketUDPTest, UDPReceiveAmount_TooMuchRead) - { - ASSERT_TRUE(socket.bind()); - - Socket client(LOCALHOST, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - const std::string testString = "test"; - ASSERT_TRUE(client.send(testString)); - - ASSERT_TRUE(socket.ready()); - std::string recieved = socket.receiveAmount(testString.size() + 1); - ASSERT_FALSE(socket.ready()); - ASSERT_EQ(testString, recieved); - - client.close(); - } - - /* - * Ensure that the last received address is set accordingly after data is read. - */ - TEST_F(SocketUDPTest, UDPGetAndLastReceivedAddress) - { - ASSERT_TRUE(socket.bind(InternetProtocolVersion::IPV4)); - - const std::string address = "127.0.0.1"; - Socket client(address, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - std::string testString = "t"; - ASSERT_TRUE(client.send(testString)); - - ASSERT_TRUE(socket.ready()); - std::string received = std::string(1, *socket.get()); - ASSERT_FALSE(socket.ready()); - ASSERT_EQ(testString, received); - - ASSERT_EQ(address, socket.getLastUDPRecievedAddress()); - kt::SocketAddress recievedAddress = socket.getLastUDPReceivedAddress().value(); - ASSERT_EQ(InternetProtocolVersion::IPV4, kt::getInternetProtocolVersion(recievedAddress)); - - client.close(); - } - - /* - * Ensure that the last received address is set accordingly after data is read using IPV6. - */ - TEST_F(SocketUDPTest, UDPGetAndLastReceivedAddress_IPV6) - { - ASSERT_TRUE(socket.bind(InternetProtocolVersion::IPV6)); - - const std::string address = "::1"; - Socket client(address, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - std::string testString = "t"; - ASSERT_TRUE(client.send(testString)); - - ASSERT_TRUE(socket.ready()); - std::string received = std::string(1, *socket.get()); - ASSERT_FALSE(socket.ready()); - ASSERT_EQ(testString, received); - - ASSERT_EQ(address, socket.getLastUDPRecievedAddress()); - ASSERT_NE(std::nullopt, socket.getLastUDPReceivedAddress()); - kt::SocketAddress recievedAddress = socket.getLastUDPReceivedAddress().value(); - ASSERT_EQ(InternetProtocolVersion::IPV6, kt::getInternetProtocolVersion(recievedAddress)); - - client.close(); - } - - /* - * Throw in ReceiveAll for UDP, since ReceiveAmount should be used instead. - */ - TEST_F(SocketUDPTest, UDPReceiveAll) - { - ASSERT_TRUE(socket.bind()); - - Socket client(LOCALHOST, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - const std::string testString = "testString"; - ASSERT_TRUE(client.send(testString)); - - ASSERT_TRUE(socket.ready()); - ASSERT_THROW({ - std::string response = socket.receiveAll(); - ASSERT_FALSE(socket.ready()); - }, SocketException); - - client.close(); - } - - /* - * Ensure receive to delimiter receives until the delimiter. - */ - TEST_F(SocketUDPTest, UDPReceiveToDelimiter) - { - ASSERT_TRUE(socket.bind()); - - Socket client(LOCALHOST, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - - const std::string testString = "testString"; - const char delimiter = '~'; - ASSERT_TRUE(client.send(testString + delimiter + testString)); - - ASSERT_TRUE(socket.ready()); - std::string response = socket.receiveToDelimiter(delimiter); - ASSERT_FALSE(socket.ready()); // Parts of the message past the delimiter will be lost in UDP - ASSERT_EQ(response, testString); - - client.close(); - } - - TEST_F(SocketUDPTest, UDPSendToIPV4ThenToIPV6UsingSetUDPSendAddress) - { - ASSERT_TRUE(socket.bind()); - Socket ipv6Socket("::1", 0, kt::SocketType::Wifi, kt::SocketProtocol::UDP); - ASSERT_TRUE(ipv6Socket.bind(kt::InternetProtocolVersion::IPV6)); - - Socket client(LOCALHOST, socket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::UDP); - - // Confirm we sent the message to the ipv4 udp socket - std::string testString = "testString"; - ASSERT_TRUE(client.send(testString)); - - ASSERT_TRUE(socket.ready()); - std::string response = socket.receiveAmount(testString.size()); - ASSERT_FALSE(socket.ready()); // Parts of the message past the delimiter will be lost in UDP - ASSERT_EQ(response, testString); - - std::optional ipv4Address = client.getUDPSendAddress(); - ASSERT_NE(std::nullopt, ipv4Address); - - // Confirm we sent the message to the ipv6 udp socket by resolving it - client.setUDPSendAddress("::1", ipv6Socket.getPort()); - testString += "2"; - ASSERT_TRUE(client.send(testString)); - - ASSERT_TRUE(ipv6Socket.ready()); - response = ipv6Socket.receiveAmount(testString.size()); - ASSERT_FALSE(ipv6Socket.ready()); - ASSERT_EQ(response, testString); - - // Switch back to the ipv4 socket using a different override and confirm we can send to that socket - client.setUDPSendAddress(ipv4Address.value()); - testString += "45"; - ASSERT_TRUE(client.send(testString)); - - ASSERT_TRUE(socket.ready()); - response = socket.receiveAmount(testString.size()); - ASSERT_FALSE(socket.ready()); - ASSERT_EQ(response, testString); - - client.close(); - ipv6Socket.close(); - } - - TEST_F(SocketUDPTest, UDPReturnMessageToSender) - { - ASSERT_TRUE(socket.bind(kt::InternetProtocolVersion::IPV4)); - Socket ipv6Socket(LOCALHOST, 0, kt::SocketType::Wifi, kt::SocketProtocol::UDP); - ASSERT_TRUE(ipv6Socket.bind(kt::InternetProtocolVersion::IPV6)); - ipv6Socket.setUDPSendAddress(LOCALHOST, socket.getPort(), kt::InternetProtocolVersion::IPV4); - - // Send from ipv6 to ipv4 (since ipv6 has solved the ipv4 host in the constructor) - std::string payload = "my_payload"; - ASSERT_TRUE(ipv6Socket.send(payload)); - - ASSERT_TRUE(socket.ready()); - std::string response = socket.receiveAmount(payload.size()); - ASSERT_FALSE(socket.ready()); - ASSERT_EQ(response, payload); - - std::optional receivedAddressOpt = socket.getLastUDPReceivedAddress(); - ASSERT_NE(std::nullopt, receivedAddressOpt); - kt::SocketAddress receivedAddress = receivedAddressOpt.value(); - // Because socket is an ipv4 listening socket, the receive address will also be ipv4 - ASSERT_EQ(kt::InternetProtocolVersion::IPV4, kt::getInternetProtocolVersion(receivedAddress)); - - // If we want to use the received address in the below call we need to inline edit the address to be an ipv6 address - // We can do this by adding: "::ffff:" as a prefix to the existing address, but would want to do it directly in the struct - // socket.setUDPSendAddress(receivedAddress); - socket.setUDPSendAddress("::1", ipv6Socket.getPort(), kt::InternetProtocolVersion::IPV6); - - ASSERT_TRUE(socket.send(response)); - - ASSERT_TRUE(ipv6Socket.ready()); - payload = ipv6Socket.receiveAmount(payload.size()); - ASSERT_FALSE(ipv6Socket.ready()); - ASSERT_EQ(response, payload); - - ipv6Socket.close(); - } - - // TODO: large payload tests -} diff --git a/tests/socket/SocketTCPTest.cpp b/tests/socket/TCPSocketTest.cpp similarity index 63% rename from tests/socket/SocketTCPTest.cpp rename to tests/socket/TCPSocketTest.cpp index 197dbb4..809f130 100644 --- a/tests/socket/SocketTCPTest.cpp +++ b/tests/socket/TCPSocketTest.cpp @@ -3,7 +3,7 @@ #include -#include "../../src/socket/Socket.h" +#include "../../src/socket/TCPSocket.h" #include "../../src/serversocket/ServerSocket.h" #include "../../src/socketexceptions/BindingException.hpp" @@ -11,14 +11,14 @@ const std::string LOCALHOST = "localhost"; //"127.0.0.1"; namespace kt { - class SocketTCPTest : public ::testing::Test + class TCPSocketTest : public ::testing::Test { protected: ServerSocket serverSocket; - Socket socket; + TCPSocket socket; protected: - SocketTCPTest() : serverSocket(SocketType::Wifi, 0), socket(LOCALHOST, serverSocket.getPort(), SocketType::Wifi, SocketProtocol::TCP) { } + TCPSocketTest() : serverSocket(SocketType::Wifi, 0), socket(LOCALHOST, serverSocket.getPort()) { } void TearDown() override { socket.close(); @@ -29,67 +29,54 @@ namespace kt /* * Ensure that the default construtor properties are set correct when the socket connects successfully. */ - TEST_F(SocketTCPTest, TCPConstructor) + TEST_F(TCPSocketTest, TCPConstructor) { - ASSERT_FALSE(socket.isUdpBound()); ASSERT_TRUE(socket.connected()); - ASSERT_EQ(socket.getType(), SocketType::Wifi); - ASSERT_EQ(socket.getProtocol(), SocketProtocol::TCP); ASSERT_FALSE(socket.ready()); ASSERT_EQ(LOCALHOST, socket.getHostname()); - ASSERT_EQ(std::nullopt, socket.getLastUDPRecievedAddress()); - } - - /* - * Ensure that a SocketException is thrown when a Wifi socket with a protocol set to None is created. - */ - TEST_F(SocketTCPTest, TCPConstructor_NoProtocol) - { - ASSERT_THROW({ - Socket failedSocket(LOCALHOST, serverSocket.getPort(), SocketType::Wifi); - }, SocketException); + ASSERT_EQ(serverSocket.getInternetProtocolVersion(), socket.getInternetProtocolVersion()); } /* * Ensure that a SocketException is thrown when there is no hostname provided. */ - TEST_F(SocketTCPTest, TCPConstructor_NoHostname) + TEST_F(TCPSocketTest, TCPConstructor_NoHostname) { ASSERT_THROW({ - Socket failedSocket("", serverSocket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::TCP); + TCPSocket failedSocket("", serverSocket.getPort()); }, SocketException); } /* * Ensure a SocketException is thrown when there is no listening server. */ - TEST_F(SocketTCPTest, TCPConstructor_NoListeningServerSocket) + TEST_F(TCPSocketTest, TCPConstructor_NoListeningServerSocket) { serverSocket.close(); socket.close(); ASSERT_THROW({ - Socket failedSocket(LOCALHOST, serverSocket.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::TCP); + TCPSocket failedSocket(LOCALHOST, serverSocket.getPort()); }, SocketException); } /* * Ensure a SocketException is thrown if you attempt to connect to a port where no server is listening. */ - TEST_F(SocketTCPTest, TCPConstructor_IncorrectPort) + TEST_F(TCPSocketTest, TCPConstructor_IncorrectPort) { ASSERT_THROW({ - Socket failedSocket(LOCALHOST, serverSocket.getPort() + 1, kt::SocketType::Wifi, kt::SocketProtocol::TCP); + TCPSocket failedSocket(LOCALHOST, serverSocket.getPort() + 1); }, SocketException); } /* * Ensure that a Socket created from the copy constructor is still able to send and receive from the copied socket. */ - TEST_F(SocketTCPTest, TCPCopyConstructor) + TEST_F(TCPSocketTest, TCPCopyConstructor) { - Socket server = serverSocket.accept(); - Socket copiedSocket(socket); + TCPSocket server = serverSocket.acceptTCPConnection(); + TCPSocket copiedSocket(socket); const std::string testString = "Test"; ASSERT_TRUE(copiedSocket.send(testString)); @@ -103,18 +90,18 @@ namespace kt /* * Ensure that a connected socket and accepted socket are correcly marked as connected. */ - TEST_F(SocketTCPTest, TCPConnected) + TEST_F(TCPSocketTest, TCPConnected) { - Socket server = serverSocket.accept(); + TCPSocket server = serverSocket.acceptTCPConnection(); ASSERT_TRUE(socket.connected()); ASSERT_TRUE(server.connected()); server.close(); } - TEST_F(SocketTCPTest, TCPReceiveAmount) + TEST_F(TCPSocketTest, TCPReceiveAmount) { - Socket server = serverSocket.accept(); + TCPSocket server = serverSocket.acceptTCPConnection(); const std::string testString = "test"; ASSERT_FALSE(server.ready()); ASSERT_TRUE(socket.send(testString)); @@ -125,9 +112,9 @@ namespace kt server.close(); } - TEST_F(SocketTCPTest, TCPReceiveAll) + TEST_F(TCPSocketTest, TCPReceiveAll) { - Socket server = serverSocket.accept(); + TCPSocket server = serverSocket.acceptTCPConnection(); const std::string testString = "test"; ASSERT_FALSE(server.ready()); ASSERT_TRUE(socket.send(testString + testString + testString)); @@ -138,9 +125,9 @@ namespace kt server.close(); } - TEST_F(SocketTCPTest, TCPReceiveToDelimiter) + TEST_F(TCPSocketTest, TCPReceiveToDelimiter) { - Socket server = serverSocket.accept(); + TCPSocket server = serverSocket.acceptTCPConnection(); const std::string testString = "test"; char delimiter = '&'; ASSERT_FALSE(socket.ready()); @@ -153,14 +140,14 @@ namespace kt server.close(); } - TEST_F(SocketTCPTest, TCPReceiveToDelimiter_InvalidDelimiter) + TEST_F(TCPSocketTest, TCPReceiveToDelimiter_InvalidDelimiter) { ASSERT_THROW(socket.receiveToDelimiter('\0'), SocketException); } - TEST_F(SocketTCPTest, TCPGet) + TEST_F(TCPSocketTest, TCPGet) { - Socket server = serverSocket.accept(); + TCPSocket server = serverSocket.acceptTCPConnection(); const std::string testString = "test"; ASSERT_FALSE(socket.ready()); ASSERT_TRUE(server.send(testString)); @@ -184,23 +171,23 @@ namespace kt server.close(); } - TEST_F(SocketTCPTest, TCPClose) + TEST_F(TCPSocketTest, TCPClose) { - Socket server = serverSocket.accept(); + TCPSocket server = serverSocket.acceptTCPConnection(); ASSERT_TRUE(socket.connected()); socket.close(); ASSERT_FALSE(socket.connected()); server.close(); } - TEST_F(SocketTCPTest, IPV6Address) + TEST_F(TCPSocketTest, IPV6Address) { ServerSocket ipv6ServerSocket(SocketType::Wifi, 0, 20, InternetProtocolVersion::IPV6); - Socket ipv6Socket("0:0:0:0:0:0:0:1", ipv6ServerSocket.getPort(), SocketType::Wifi, SocketProtocol::TCP); + TCPSocket ipv6Socket("0:0:0:0:0:0:0:1", ipv6ServerSocket.getPort()); // Accept ipv6 connnection - Socket ipv6Server = ipv6ServerSocket.accept(); + TCPSocket ipv6Server = ipv6ServerSocket.acceptTCPConnection(); ASSERT_TRUE(ipv6Server.connected()); const std::string testString = "Test"; diff --git a/tests/socket/UDPSocketTest.cpp b/tests/socket/UDPSocketTest.cpp new file mode 100644 index 0000000..5bb9932 --- /dev/null +++ b/tests/socket/UDPSocketTest.cpp @@ -0,0 +1,146 @@ + +#include +#include + +#include + +#include "../../src/socket/UDPSocket.h" +#include "../../src/socketexceptions/BindingException.hpp" + +const std::string LOCALHOST = "localhost"; + +namespace kt +{ + class UDPSocketTest : public ::testing::Test + { + protected: + UDPSocket socket; + + protected: + UDPSocketTest() : socket() { } + void TearDown() override + { + this->socket.close(); + } + }; + + /* + * Test the kt::UDPSocket constructors and exception handling for UDP. This covers the following scenarios: + * - Constructing a socket and ensuring its default values are set correctly + */ + TEST_F(UDPSocketTest, UDPConstructors) + { + ASSERT_FALSE(socket.ready()); + ASSERT_FALSE(socket.isUdpBound()); + ASSERT_EQ(0, socket.getListeningPort()); + ASSERT_EQ(kt::InternetProtocolVersion::Any, socket.getInternetProtocolVersion()); + } + + /* + * Ensure that multiple calls to UDPSocket.bind() fails if another socket is already bound to that port. + */ + TEST_F(UDPSocketTest, UDPBindAndBound_MultipleCalls) + { + ASSERT_FALSE(socket.isUdpBound()); + ASSERT_TRUE(socket.bind(0)); + ASSERT_TRUE(socket.isUdpBound()); + + kt::UDPSocket newServer; + ASSERT_FALSE(newServer.isUdpBound()); + EXPECT_THROW(newServer.bind(socket.getListeningPort()), BindingException); + } + + /* + * Ensure that calling UDPSocket.bind() with 0 as the port will resolve to another port from the OS. + */ + TEST_F(UDPSocketTest, UDPBind_WithoutSpecifiedPort) + { + const unsigned int port = 0; + + ASSERT_FALSE(socket.isUdpBound()); + socket.bind(port); + ASSERT_TRUE(socket.isUdpBound()); + ASSERT_NE(port, socket.getListeningPort()); + } + + /* + * Test UDPSocket.sendTo() to ensure that the listening socket's UDPSocket.ready() call returns *true*. + */ + TEST_F(UDPSocketTest, UDPSendAndReady) + { + ASSERT_TRUE(socket.bind(0)); + + UDPSocket client; + + ASSERT_FALSE(socket.ready()); + const std::string testString = "test"; + ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort(), testString).first); + ASSERT_TRUE(socket.ready()); + + client.close(); + } + + /* + * Call UDPSocket.receiveFrom() to make sure the correct amount of data is read. + */ + TEST_F(UDPSocketTest, UDPReceiveFrom) + { + ASSERT_TRUE(socket.bind(0)); + + UDPSocket client; + const std::string testString = "test"; + ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort(), testString).first); + + ASSERT_TRUE(socket.ready()); + std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size()); + ASSERT_FALSE(socket.ready()); + ASSERT_NE(std::nullopt, recieved.first); + ASSERT_EQ(testString, recieved.first.value()); + + client.close(); + } + + /** + * Ensure that receiveAmount reads the specified amount even when more is available in the buffer. + * Also confirm that the remaining data is lost if not read. + */ + TEST_F(UDPSocketTest, UDPReceiveAmount_NotEnoughRead) + { + ASSERT_TRUE(socket.bind(0)); + + UDPSocket client; + const std::string testString = "test"; + ASSERT_FALSE(socket.ready()); + ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort(), testString).first); + + ASSERT_TRUE(socket.ready()); + std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size() - 1); + ASSERT_FALSE(socket.ready()); + ASSERT_NE(std::nullopt, recieved.first); + ASSERT_EQ(testString.substr(0, testString.size() - 1), recieved.first.value()); + + client.close(); + } + + /* + * Ensure that the receiveAmount reads the correct amount when less data is provided than expected. + */ + TEST_F(UDPSocketTest, UDPReceiveAmount_TooMuchRead) + { + ASSERT_TRUE(socket.bind(0)); + + UDPSocket client; + const std::string testString = "test"; + ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort(), testString).first); + + ASSERT_TRUE(socket.ready()); + std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size() + 1); + ASSERT_FALSE(socket.ready()); + ASSERT_NE(std::nullopt, recieved.first); + ASSERT_EQ(testString, recieved.first.value()); + + client.close(); + } + + // TODO: large payload tests +} From 3f2c03db4689b80758923f30f7918807f681d5c1 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Tue, 25 Jun 2024 21:23:34 +0900 Subject: [PATCH 02/21] Check bindings and hints for different scenarios. --- src/socket/UDPSocket.cpp | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index b4182e4..e15747f 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -21,6 +21,15 @@ namespace kt std::pair UDPSocket::constructSocket(std::string& hostname, unsigned int& port, const kt::InternetProtocolVersion protocolVersion) { +#ifdef _WIN32 + WSADATA wsaData{}; + if (int res = WSAStartup(MAKEWORD(2, 2), &wsaData); res != 0) + { + throw kt::SocketException("WSAStartup Failed. " + std::to_string(res)); + } + +#endif + const int socketType = SOCK_DGRAM; const int protocol = IPPROTO_UDP; @@ -52,6 +61,20 @@ namespace kt */ bool kt::UDPSocket::bind(const unsigned int& port, const kt::InternetProtocolVersion protocolVersion) { + if (this->isUdpBound()) + { + return true; + } + +#ifdef _WIN32 + WSADATA wsaData{}; + if (int res = WSAStartup(MAKEWORD(2, 2), &wsaData); res != 0) + { + throw kt::SocketException("WSAStartup Failed. " + std::to_string(res)); + } + +#endif + this->listeningPort = port; kt::SocketAddress receiveAddress{}; @@ -67,12 +90,16 @@ namespace kt std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::nullopt, this->listeningPort, hints); if (resolvedAddresses.second != 0 || resolvedAddresses.first.empty()) { - throw kt::SocketException("Failed to retrieve address info of local host. With error:" + std::to_string(resolvedAddresses.second) + " " + getErrorCode()); + throw kt::BindingException("Failed to resolve bind address with the provided port: " + std::to_string(this->listeningPort) + ". Error message from code: " + gai_strerror(resolvedAddresses.second)); } kt::SocketAddress firstAddress = resolvedAddresses.first.at(0); this->protocolVersion = static_cast(firstAddress.address.sa_family); this->receiveSocket = socket(firstAddress.address.sa_family, socketType, socketProtocol); + if (kt::isInvalidSocket(this->receiveSocket)) + { + throw kt::SocketException("Unable to construct socket from local host details. " + getErrorCode()); + } #ifdef _WIN32 if (this->protocolVersion == kt::InternetProtocolVersion::IPV6) @@ -123,7 +150,7 @@ namespace kt std::pair UDPSocket::sendTo(const std::string& message, const kt::SocketAddress& address, const int& flags) { SOCKET tempSocket = socket(address.address.sa_family, SOCK_DGRAM, IPPROTO_UDP); - if (!kt::isInvalidSocket(tempSocket)) + if (kt::isInvalidSocket(tempSocket)) { return std::make_pair(false, -2); } @@ -134,7 +161,6 @@ namespace kt std::pair UDPSocket::sendTo(const std::string& hostname, const unsigned int& port, const std::string& message, const int& flags) { addrinfo hints{}; - hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; From 4c349b81e9e53baa2d310805c516b6f2c5bc8866 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Tue, 25 Jun 2024 23:16:56 +0900 Subject: [PATCH 03/21] Fix port and return type for port number to resolve test issues. --- src/address/SocketAddress.cpp | 6 +++--- src/address/SocketAddress.h | 2 +- src/socket/UDPSocket.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/address/SocketAddress.cpp b/src/address/SocketAddress.cpp index ea55105..96ce821 100644 --- a/src/address/SocketAddress.cpp +++ b/src/address/SocketAddress.cpp @@ -11,16 +11,16 @@ namespace kt return static_cast(address.address.sa_family); } - long getPortNumber(const kt::SocketAddress& address) + unsigned int getPortNumber(const kt::SocketAddress& address) { kt::InternetProtocolVersion version = getInternetProtocolVersion(address); if (version == kt::InternetProtocolVersion::IPV6) { - return htonl(address.ipv6.sin6_port); + return htons(address.ipv6.sin6_port); } // I believe the address is in the same position for ipv4 and ipv6 structs, so it doesn't really matter. // Doing the checks anway to make sure its fine - return htonl(address.ipv4.sin_port); + return htons(address.ipv4.sin_port); } std::optional getAddress(const kt::SocketAddress& address) diff --git a/src/address/SocketAddress.h b/src/address/SocketAddress.h index 7f8cf82..1b13418 100644 --- a/src/address/SocketAddress.h +++ b/src/address/SocketAddress.h @@ -48,7 +48,7 @@ namespace kt kt::InternetProtocolVersion getInternetProtocolVersion(const kt::SocketAddress&); - long getPortNumber(const kt::SocketAddress&); + unsigned int getPortNumber(const kt::SocketAddress&); std::optional getAddress(const kt::SocketAddress&); diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index e15747f..b867df7 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -185,7 +185,7 @@ namespace kt data.resize(receiveLength); socklen_t addressLength = sizeof(receiveAddress); - int flag = recvfrom(this->listeningPort, &data[0], static_cast(receiveLength), flags, &receiveAddress.address, &addressLength); + int flag = recvfrom(this->receiveSocket, &data[0], static_cast(receiveLength), flags, &receiveAddress.address, &addressLength); #ifdef _WIN32 if (flag < 1) From 14ae74fe475b550f5ca4909d5310a270e637c7dc Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Tue, 25 Jun 2024 23:54:28 +0900 Subject: [PATCH 04/21] Add IP version check before and after bind for UDPSocket. --- tests/socket/UDPSocketTest.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/socket/UDPSocketTest.cpp b/tests/socket/UDPSocketTest.cpp index 5bb9932..1ffcc23 100644 --- a/tests/socket/UDPSocketTest.cpp +++ b/tests/socket/UDPSocketTest.cpp @@ -42,7 +42,9 @@ namespace kt TEST_F(UDPSocketTest, UDPBindAndBound_MultipleCalls) { ASSERT_FALSE(socket.isUdpBound()); + ASSERT_EQ(kt::InternetProtocolVersion::Any, socket.getInternetProtocolVersion()); ASSERT_TRUE(socket.bind(0)); + ASSERT_NE(kt::InternetProtocolVersion::Any, socket.getInternetProtocolVersion()); ASSERT_TRUE(socket.isUdpBound()); kt::UDPSocket newServer; From f738bfb1403f49b73bd8b659b5a5d6507d5183e3 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 09:08:34 +0900 Subject: [PATCH 05/21] Update sendTo() that takes a string for hostname and port to return the resolved address too, which can be used for future sends to that client without needed to re-resolve their address from the hostname again. Add test for above scenario. --- src/socket/UDPSocket.cpp | 7 ++++--- src/socket/UDPSocket.h | 2 +- tests/socket/UDPSocketTest.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index b867df7..e896a70 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -158,7 +158,7 @@ namespace kt return std::make_pair(result != -1, result); } - std::pair UDPSocket::sendTo(const std::string& hostname, const unsigned int& port, const std::string& message, const int& flags) + std::pair> UDPSocket::sendTo(const std::string& hostname, const unsigned int& port, const std::string& message, const int& flags) { addrinfo hints{}; hints.ai_family = AF_UNSPEC; @@ -168,10 +168,11 @@ namespace kt std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::optional{ hostname }, port, hints); if (resolvedAddresses.first.empty() || resolvedAddresses.second != 0) { - return std::make_pair(false, resolvedAddresses.second); + return std::make_pair(false, std::make_pair(resolvedAddresses.second, kt::SocketAddress{})); } kt::SocketAddress firstAddress = resolvedAddresses.first.at(0); - return this->sendTo(message, firstAddress, flags); + std::pair result = this->sendTo(message, firstAddress, flags); + return std::make_pair(result.first, std::make_pair(result.second, firstAddress)); } std::pair, kt::SocketAddress> UDPSocket::receiveFrom(const unsigned int& receiveLength, const int& flags) diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h index ed56c75..90ce429 100644 --- a/src/socket/UDPSocket.h +++ b/src/socket/UDPSocket.h @@ -67,7 +67,7 @@ namespace kt bool ready(const unsigned long = 1000) const; std::pair sendTo(const std::string&, const kt::SocketAddress&, const int& = 0); - std::pair sendTo(const std::string&, const unsigned int&, const std::string&, const int& = 0); + std::pair> sendTo(const std::string&, const unsigned int&, const std::string&, const int& = 0); std::pair, kt::SocketAddress> receiveFrom(const unsigned int&, const int& = 0); bool isUdpBound() const; diff --git a/tests/socket/UDPSocketTest.cpp b/tests/socket/UDPSocketTest.cpp index 1ffcc23..eb70e06 100644 --- a/tests/socket/UDPSocketTest.cpp +++ b/tests/socket/UDPSocketTest.cpp @@ -144,5 +144,35 @@ namespace kt client.close(); } + /** + * Ensure that sendTo() returns the send address properly and that it can be used in future calls to skip the address resolution step. + */ + TEST_F(UDPSocketTest, UDPSendToAddress) + { + ASSERT_TRUE(socket.bind(0)); + + UDPSocket client; + std::string testString = "test"; + std::pair> sendResult = client.sendTo(LOCALHOST, socket.getListeningPort(), testString); + ASSERT_TRUE(sendResult.first); + + ASSERT_TRUE(socket.ready()); + std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size()); + ASSERT_FALSE(socket.ready()); + ASSERT_NE(std::nullopt, recieved.first); + ASSERT_EQ(testString, recieved.first.value()); + + testString += testString + testString; + // Now send using the address resolved and returned from the first call to sendTo() + ASSERT_TRUE(client.sendTo(testString, sendResult.second.second).first); + ASSERT_TRUE(socket.ready()); + recieved = socket.receiveFrom(testString.size()); + ASSERT_FALSE(socket.ready()); + ASSERT_NE(std::nullopt, recieved.first); + ASSERT_EQ(testString, recieved.first.value()); + + client.close(); + } + // TODO: large payload tests } From 68cbb2b3fa2bc6e2f58ae70d2191244e3439d658 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 09:14:39 +0900 Subject: [PATCH 06/21] Use kt::socketToAddress() in kt::ServerSocket::initialisePortNumber(). --- src/serversocket/ServerSocket.cpp | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/serversocket/ServerSocket.cpp b/src/serversocket/ServerSocket.cpp index c4009b3..15cbc50 100644 --- a/src/serversocket/ServerSocket.cpp +++ b/src/serversocket/ServerSocket.cpp @@ -257,21 +257,14 @@ namespace kt void kt::ServerSocket::initialisePortNumber() { - socklen_t socketSize = sizeof(this->serverAddress); - if (getsockname(this->socketDescriptor, &this->serverAddress.address, &socketSize) != 0) - { - this->close(); - throw kt::BindingException("Unable to retrieve randomly bound port number during socket creation. " + getErrorCode()); - } + std::pair, int> address = kt::socketToAddress(this->socketDescriptor); + if (address.second != 0 && !address.first.has_value()) + { + this->close(); + throw kt::BindingException("Unable to retrieve randomly bound port number during socket creation. " + getErrorCode()); + } - if (this->protocolVersion == kt::InternetProtocolVersion::IPV6) - { - this->port = ntohs(this->serverAddress.ipv6.sin6_port); - } - else - { - this->port = ntohs(this->serverAddress.ipv4.sin_port); - } + this->port = kt::getPortNumber(address.first.value()); } From 191fdd606e8e4af313f5c168f9c873ffe43e9a21 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 10:32:44 +0900 Subject: [PATCH 07/21] Add kt::getAddressLength() to fix bind() calls with UDP on Windows. --- src/address/SocketAddress.cpp | 10 ++++++++++ src/address/SocketAddress.h | 6 ++++++ src/socket/UDPSocket.cpp | 7 +++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/address/SocketAddress.cpp b/src/address/SocketAddress.cpp index 96ce821..844dc3f 100644 --- a/src/address/SocketAddress.cpp +++ b/src/address/SocketAddress.cpp @@ -84,4 +84,14 @@ namespace kt return std::make_pair(addresses, result); } + +#ifdef _WIN32 + int getAddressLength(const kt::SocketAddress& address) +#else + socklen_t getAddressLength(const kt::SocketAddress& address) +#endif + { + const kt::InternetProtocolVersion protocolVersion = getInternetProtocolVersion(address); + return protocolVersion == kt::InternetProtocolVersion::IPV6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN; + } } diff --git a/src/address/SocketAddress.h b/src/address/SocketAddress.h index 1b13418..843d625 100644 --- a/src/address/SocketAddress.h +++ b/src/address/SocketAddress.h @@ -55,4 +55,10 @@ namespace kt std::pair, int> socketToAddress(const SOCKET&); std::pair, int> resolveToAddresses(const std::optional&, const unsigned int&, addrinfo&); + +#ifdef _WIN32 + int getAddressLength(const kt::SocketAddress&); +#else + socklen_t getAddressLength(const kt::SocketAddress&); +#endif } diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index e896a70..d61c95d 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -77,7 +77,6 @@ namespace kt this->listeningPort = port; - kt::SocketAddress receiveAddress{}; const int socketType = SOCK_DGRAM; const int socketProtocol = IPPROTO_UDP; @@ -90,7 +89,7 @@ namespace kt std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::nullopt, this->listeningPort, hints); if (resolvedAddresses.second != 0 || resolvedAddresses.first.empty()) { - throw kt::BindingException("Failed to resolve bind address with the provided port: " + std::to_string(this->listeningPort) + ". Error message from code: " + gai_strerror(resolvedAddresses.second)); + throw kt::BindingException("Failed to resolve bind address with the provided port: " + std::to_string(this->listeningPort)); } kt::SocketAddress firstAddress = resolvedAddresses.first.at(0); @@ -118,7 +117,7 @@ namespace kt } #endif - int bindResult = ::bind(this->receiveSocket, &firstAddress.address, sizeof(firstAddress.address)); + int bindResult = ::bind(this->receiveSocket, &firstAddress.address, kt::getAddressLength(firstAddress)); this->bound = bindResult != -1; if (!this->bound) { @@ -185,7 +184,7 @@ namespace kt std::string data; data.resize(receiveLength); - socklen_t addressLength = sizeof(receiveAddress); + auto addressLength = kt::getAddressLength(receiveAddress); int flag = recvfrom(this->receiveSocket, &data[0], static_cast(receiveLength), flags, &receiveAddress.address, &addressLength); #ifdef _WIN32 From 58ae0d9a281f6f4d02b68cb9e560932566874ab0 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 10:33:04 +0900 Subject: [PATCH 08/21] Fix _WIN32_WINNT redefinition warnings. --- src/address/SocketAddress.h | 4 +++- src/serversocket/ServerSocket.cpp | 6 ------ src/serversocket/ServerSocket.h | 4 +++- src/socket/BluetoothSocket.h | 6 ++++-- src/socket/Socket.cpp | 6 ------ src/socket/Socket.h | 4 +++- src/socket/TCPSocket.h | 4 +++- src/socket/UDPSocket.h | 4 +++- src/socketexceptions/SocketError.cpp | 6 ------ src/socketexceptions/SocketError.h | 4 +++- 10 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/address/SocketAddress.h b/src/address/SocketAddress.h index 843d625..1fd9e1e 100644 --- a/src/address/SocketAddress.h +++ b/src/address/SocketAddress.h @@ -14,7 +14,9 @@ #define WIN32_LEAN_AND_MEAN #endif -#define _WIN32_WINNT 0x0600 +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0600 +#endif #include #include diff --git a/src/serversocket/ServerSocket.cpp b/src/serversocket/ServerSocket.cpp index 15cbc50..33edd45 100644 --- a/src/serversocket/ServerSocket.cpp +++ b/src/serversocket/ServerSocket.cpp @@ -19,12 +19,6 @@ #ifdef _WIN32 -#ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN -#endif - -#define _WIN32_WINNT 0x0600 - #include #include #include diff --git a/src/serversocket/ServerSocket.h b/src/serversocket/ServerSocket.h index 3eefea7..612591d 100644 --- a/src/serversocket/ServerSocket.h +++ b/src/serversocket/ServerSocket.h @@ -17,7 +17,9 @@ #define WIN32_LEAN_AND_MEAN #endif -#define _WIN32_WINNT 0x0600 +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0600 +#endif #include #include diff --git a/src/socket/BluetoothSocket.h b/src/socket/BluetoothSocket.h index cac007c..545c901 100644 --- a/src/socket/BluetoothSocket.h +++ b/src/socket/BluetoothSocket.h @@ -16,10 +16,12 @@ #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN #endif -#define _WIN32_WINNT 0x0600 +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0600 +#endif #include #include diff --git a/src/socket/Socket.cpp b/src/socket/Socket.cpp index d119017..7c1728e 100644 --- a/src/socket/Socket.cpp +++ b/src/socket/Socket.cpp @@ -16,12 +16,6 @@ #ifdef _WIN32 -#ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN -#endif - -#define _WIN32_WINNT 0x0600 - #include #include #include diff --git a/src/socket/Socket.h b/src/socket/Socket.h index f6af84e..6b63a34 100644 --- a/src/socket/Socket.h +++ b/src/socket/Socket.h @@ -6,7 +6,9 @@ #define WIN32_LEAN_AND_MEAN #endif -#define _WIN32_WINNT 0x0600 +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0600 +#endif #include diff --git a/src/socket/TCPSocket.h b/src/socket/TCPSocket.h index 6e6f33f..edc911b 100644 --- a/src/socket/TCPSocket.h +++ b/src/socket/TCPSocket.h @@ -19,7 +19,9 @@ #define WIN32_LEAN_AND_MEAN #endif -#define _WIN32_WINNT 0x0600 +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0600 +#endif #include #include diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h index 90ce429..ff409bf 100644 --- a/src/socket/UDPSocket.h +++ b/src/socket/UDPSocket.h @@ -19,7 +19,9 @@ #define WIN32_LEAN_AND_MEAN #endif -#define _WIN32_WINNT 0x0600 +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0600 +#endif #include #include diff --git a/src/socketexceptions/SocketError.cpp b/src/socketexceptions/SocketError.cpp index e9bd4b1..2d12237 100644 --- a/src/socketexceptions/SocketError.cpp +++ b/src/socketexceptions/SocketError.cpp @@ -4,12 +4,6 @@ #ifdef _WIN32 -#ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN -#endif - -#define _WIN32_WINNT 0x0600 - #include #elif __linux__ diff --git a/src/socketexceptions/SocketError.h b/src/socketexceptions/SocketError.h index 10a36f5..371221d 100644 --- a/src/socketexceptions/SocketError.h +++ b/src/socketexceptions/SocketError.h @@ -8,7 +8,9 @@ #define WIN32_LEAN_AND_MEAN #endif -#define _WIN32_WINNT 0x0600 +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0600 +#endif #include From d8dabd2549390fe3341b515ccf1c586fe1c31c46 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 10:42:26 +0900 Subject: [PATCH 09/21] close the temporary socket we create in sendTo(). Remove close() calls on UDPSocket that only calls send and is not bound. Remove reuse option on socket. --- src/socket/UDPSocket.cpp | 6 +----- tests/socket/UDPSocketTest.cpp | 10 ---------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index d61c95d..23b0987 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -110,11 +110,6 @@ namespace kt } } - const int enableOption = 1; - if (setsockopt(this->receiveSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&enableOption, sizeof(enableOption)) != 0) - { - throw SocketException("Failed to set SO_REUSEADDR socket option: " + getErrorCode()); - } #endif int bindResult = ::bind(this->receiveSocket, &firstAddress.address, kt::getAddressLength(firstAddress)); @@ -154,6 +149,7 @@ namespace kt return std::make_pair(false, -2); } int result = ::sendto(tempSocket, message.c_str(), message.size(), flags, &(address.address), sizeof(address)); + this->close(tempSocket); return std::make_pair(result != -1, result); } diff --git a/tests/socket/UDPSocketTest.cpp b/tests/socket/UDPSocketTest.cpp index eb70e06..6f5ba66 100644 --- a/tests/socket/UDPSocketTest.cpp +++ b/tests/socket/UDPSocketTest.cpp @@ -78,8 +78,6 @@ namespace kt const std::string testString = "test"; ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort(), testString).first); ASSERT_TRUE(socket.ready()); - - client.close(); } /* @@ -98,8 +96,6 @@ namespace kt ASSERT_FALSE(socket.ready()); ASSERT_NE(std::nullopt, recieved.first); ASSERT_EQ(testString, recieved.first.value()); - - client.close(); } /** @@ -120,8 +116,6 @@ namespace kt ASSERT_FALSE(socket.ready()); ASSERT_NE(std::nullopt, recieved.first); ASSERT_EQ(testString.substr(0, testString.size() - 1), recieved.first.value()); - - client.close(); } /* @@ -140,8 +134,6 @@ namespace kt ASSERT_FALSE(socket.ready()); ASSERT_NE(std::nullopt, recieved.first); ASSERT_EQ(testString, recieved.first.value()); - - client.close(); } /** @@ -170,8 +162,6 @@ namespace kt ASSERT_FALSE(socket.ready()); ASSERT_NE(std::nullopt, recieved.first); ASSERT_EQ(testString, recieved.first.value()); - - client.close(); } // TODO: large payload tests From 9cae521ee4e0f2ec806b8eb5a85573b8c86a45fe Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 11:16:15 +0900 Subject: [PATCH 10/21] Remove unused UDPSocket::constructSocket(). Make the listeningPort an std::optional to make it clear when the socket is not actually able to listen or receive. Update tests for listening port change. --- src/socket/Socket.cpp | 2 +- src/socket/UDPSocket.cpp | 53 +++++++++++----------------------- src/socket/UDPSocket.h | 10 ++----- tests/socket/UDPSocketTest.cpp | 19 ++++++------ 4 files changed, 31 insertions(+), 53 deletions(-) diff --git a/src/socket/Socket.cpp b/src/socket/Socket.cpp index 7c1728e..6d00676 100644 --- a/src/socket/Socket.cpp +++ b/src/socket/Socket.cpp @@ -56,7 +56,7 @@ namespace kt timeOutVal = &timeoutVal; } - timeOutVal->tv_usec = static_cast(timeout); + timeOutVal->tv_usec = timeout; FD_ZERO(&sReady); FD_SET(socketDescriptor, &sReady); diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index 23b0987..e9b7e3b 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -6,21 +6,10 @@ namespace kt { UDPSocket::UDPSocket(const kt::UDPSocket& socket) - { - this->bound = socket.bound; - this->receiveSocket = socket.receiveSocket; - } - - kt::UDPSocket& UDPSocket::operator=(const kt::UDPSocket& socket) { this->bound = socket.bound; this->receiveSocket = socket.receiveSocket; - return *this; - } - - std::pair UDPSocket::constructSocket(std::string& hostname, unsigned int& port, const kt::InternetProtocolVersion protocolVersion) - { #ifdef _WIN32 WSADATA wsaData{}; if (int res = WSAStartup(MAKEWORD(2, 2), &wsaData); res != 0) @@ -29,25 +18,14 @@ namespace kt } #endif + } - const int socketType = SOCK_DGRAM; - const int protocol = IPPROTO_UDP; - - addrinfo hints{}; - hints.ai_family = static_cast(protocolVersion); - hints.ai_socktype = socketType; - hints.ai_protocol = protocol; - - std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::optional{ hostname }, port, hints); - if (resolvedAddresses.second != 0 || resolvedAddresses.first.empty()) - { - throw kt::SocketException("Unable to resolve IP of destination address with hostname: [" + hostname + ":" + std::to_string(port) + "].Look up response code : [" + std::to_string(resolvedAddresses.second) + "] . " + getErrorCode()); - } - - kt::SocketAddress resolvedAddress = resolvedAddresses.first.at(0); - SOCKET udpSendSocket = socket(resolvedAddress.address.sa_family, socketType, protocol); + kt::UDPSocket& UDPSocket::operator=(const kt::UDPSocket& socket) + { + this->bound = socket.bound; + this->receiveSocket = socket.receiveSocket; - return std::make_pair(udpSendSocket, resolvedAddress); + return *this; } /** @@ -75,8 +53,6 @@ namespace kt #endif - this->listeningPort = port; - const int socketType = SOCK_DGRAM; const int socketProtocol = IPPROTO_UDP; @@ -86,10 +62,10 @@ namespace kt hints.ai_socktype = socketType; hints.ai_protocol = socketProtocol; - std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::nullopt, this->listeningPort, hints); + std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::nullopt, port, hints); if (resolvedAddresses.second != 0 || resolvedAddresses.first.empty()) { - throw kt::BindingException("Failed to resolve bind address with the provided port: " + std::to_string(this->listeningPort)); + throw kt::BindingException("Failed to resolve bind address with the provided port: " + std::to_string(port)); } kt::SocketAddress firstAddress = resolvedAddresses.first.at(0); @@ -116,13 +92,17 @@ namespace kt this->bound = bindResult != -1; if (!this->bound) { - throw kt::BindingException("Error binding connection, the port " + std::to_string(this->listeningPort) + " is already being used. Response code from ::bind()" + std::to_string(bindResult) + ". Latest Error code: " + getErrorCode()); + throw kt::BindingException("Error binding connection, the port " + std::to_string(port) + " is already being used. Response code from ::bind()" + std::to_string(bindResult) + ". Latest Error code: " + getErrorCode()); } - if (this->listeningPort == 0) + if (port == 0) { this->initialiseListeningPortNumber(); } + else + { + this->listeningPort = std::optional{ port }; + } return this->bound; } @@ -132,6 +112,7 @@ namespace kt this->close(this->receiveSocket); this->bound = false; + this->listeningPort = std::nullopt; } bool UDPSocket::ready(const unsigned long timeout) const @@ -211,7 +192,7 @@ namespace kt return this->protocolVersion; } - unsigned int UDPSocket::getListeningPort() const + std::optional UDPSocket::getListeningPort() const { return this->listeningPort; } @@ -246,7 +227,7 @@ namespace kt throw kt::BindingException("Unable to retrieve randomly bound port number during socket creation. " + getErrorCode()); } - this->listeningPort = kt::getPortNumber(address.first.value()); + this->listeningPort = std::optional{ kt::getPortNumber(address.first.value()) }; } void UDPSocket::close(SOCKET socket) diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h index ff409bf..4c4d94a 100644 --- a/src/socket/UDPSocket.h +++ b/src/socket/UDPSocket.h @@ -43,20 +43,16 @@ typedef int SOCKET; namespace kt { - const unsigned int DEFAULT_UDP_BUFFER_SIZE = 10240; // 10 kilobytes - class UDPSocket { protected: bool bound = false; SOCKET receiveSocket = getInvalidSocketValue(); kt::InternetProtocolVersion protocolVersion = kt::InternetProtocolVersion::Any; - unsigned int listeningPort = 0; + std::optional listeningPort = std::nullopt; - std::pair constructSocket(std::string&, unsigned int&, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); int pollSocket(SOCKET socket, const long& = 1000) const; void initialiseListeningPortNumber(); - void close(SOCKET socket); public: @@ -70,11 +66,11 @@ namespace kt bool ready(const unsigned long = 1000) const; std::pair sendTo(const std::string&, const kt::SocketAddress&, const int& = 0); std::pair> sendTo(const std::string&, const unsigned int&, const std::string&, const int& = 0); - std::pair, kt::SocketAddress> receiveFrom(const unsigned int&, const int& = 0); + std::pair, kt::SocketAddress> receiveFrom(const unsigned int&, const int& = 0); bool isUdpBound() const; kt::InternetProtocolVersion getInternetProtocolVersion() const; - unsigned int getListeningPort() const; + std::optional getListeningPort() const; }; } // End namespace kt diff --git a/tests/socket/UDPSocketTest.cpp b/tests/socket/UDPSocketTest.cpp index 6f5ba66..769e10f 100644 --- a/tests/socket/UDPSocketTest.cpp +++ b/tests/socket/UDPSocketTest.cpp @@ -32,7 +32,7 @@ namespace kt { ASSERT_FALSE(socket.ready()); ASSERT_FALSE(socket.isUdpBound()); - ASSERT_EQ(0, socket.getListeningPort()); + ASSERT_EQ(std::nullopt, socket.getListeningPort()); ASSERT_EQ(kt::InternetProtocolVersion::Any, socket.getInternetProtocolVersion()); } @@ -49,7 +49,8 @@ namespace kt kt::UDPSocket newServer; ASSERT_FALSE(newServer.isUdpBound()); - EXPECT_THROW(newServer.bind(socket.getListeningPort()), BindingException); + ASSERT_NE(std::nullopt, socket.getListeningPort()); + EXPECT_THROW(newServer.bind(socket.getListeningPort().value()), BindingException); } /* @@ -66,9 +67,9 @@ namespace kt } /* - * Test UDPSocket.sendTo() to ensure that the listening socket's UDPSocket.ready() call returns *true*. + * Test UDPSocket.sendTo() to ensure that it can send correctly to the listening socket. */ - TEST_F(UDPSocketTest, UDPSendAndReady) + TEST_F(UDPSocketTest, UDPSendTo) { ASSERT_TRUE(socket.bind(0)); @@ -76,7 +77,7 @@ namespace kt ASSERT_FALSE(socket.ready()); const std::string testString = "test"; - ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort(), testString).first); + ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort().value(), testString).first); ASSERT_TRUE(socket.ready()); } @@ -89,7 +90,7 @@ namespace kt UDPSocket client; const std::string testString = "test"; - ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort(), testString).first); + ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort().value(), testString).first); ASSERT_TRUE(socket.ready()); std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size()); @@ -109,7 +110,7 @@ namespace kt UDPSocket client; const std::string testString = "test"; ASSERT_FALSE(socket.ready()); - ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort(), testString).first); + ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort().value(), testString).first); ASSERT_TRUE(socket.ready()); std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size() - 1); @@ -127,7 +128,7 @@ namespace kt UDPSocket client; const std::string testString = "test"; - ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort(), testString).first); + ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort().value(), testString).first); ASSERT_TRUE(socket.ready()); std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size() + 1); @@ -145,7 +146,7 @@ namespace kt UDPSocket client; std::string testString = "test"; - std::pair> sendResult = client.sendTo(LOCALHOST, socket.getListeningPort(), testString); + std::pair> sendResult = client.sendTo(LOCALHOST, socket.getListeningPort().value(), testString); ASSERT_TRUE(sendResult.first); ASSERT_TRUE(socket.ready()); From ddffdcd9a6b42dc1988ab412a8a64a819990b1c8 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 11:44:12 +0900 Subject: [PATCH 11/21] Add char* implementations for TCP send() and receiveAmount() and UDP sendTo() and receiveFrom(). Use std::make_optional() when creating optionals. --- src/address/SocketAddress.cpp | 2 +- src/socket/TCPSocket.cpp | 43 +++++++++++++++------------- src/socket/TCPSocket.h | 2 ++ src/socket/UDPSocket.cpp | 53 ++++++++++++++++++++++++----------- src/socket/UDPSocket.h | 3 ++ 5 files changed, 66 insertions(+), 37 deletions(-) diff --git a/src/address/SocketAddress.cpp b/src/address/SocketAddress.cpp index 844dc3f..597f8fe 100644 --- a/src/address/SocketAddress.cpp +++ b/src/address/SocketAddress.cpp @@ -54,7 +54,7 @@ namespace kt kt::SocketAddress address{}; socklen_t socketSize = sizeof(address); int result = getsockname(socket, &address.address, &socketSize); - return std::make_pair(std::optional{ address }, result); + return std::make_pair(std::make_optional(address), result); } std::pair, int> resolveToAddresses(const std::optional& hostname, const unsigned int& port, addrinfo& hints) diff --git a/src/socket/TCPSocket.cpp b/src/socket/TCPSocket.cpp index f9cf25c..7005f6f 100644 --- a/src/socket/TCPSocket.cpp +++ b/src/socket/TCPSocket.cpp @@ -139,12 +139,17 @@ namespace kt return result != -1; } - bool TCPSocket::send(const std::string& message, const int& flags) const + bool TCPSocket::send(const char* message, const int& messageLength, const int& flags) const { - int result = ::send(this->socketDescriptor, message.c_str(), message.size(), flags); + int result = ::send(this->socketDescriptor, message, messageLength, flags); return result != -1; } + bool TCPSocket::send(const std::string& message, const int& flags) const + { + return this->send(message.c_str(), message.size(), flags); + } + std::string TCPSocket::getHostname() const { return this->hostname; @@ -185,35 +190,33 @@ namespace kt */ std::string kt::TCPSocket::receiveAmount(const unsigned int amountToReceive, const int& flags) const { - if (amountToReceive == 0 || !this->ready()) - { - return ""; - } - std::string data; data.resize(amountToReceive); - std::string resultantString; - resultantString.reserve(amountToReceive); + int amountReceived = this->receiveAmount(&data[0], amountToReceive, flags); + return data.substr(0, amountReceived); + } - unsigned int counter = 0; + int TCPSocket::receiveAmount(char* buffer, const unsigned int amountToReceive, const int& flags) const + { + int counter = 0; + + if (amountToReceive == 0 || !this->ready()) + { + return counter; + } + do { - int amountReceived = recv(this->socketDescriptor, &data[0], static_cast(amountToReceive - counter), flags); + int amountReceived = recv(this->socketDescriptor, &buffer[counter], static_cast(amountToReceive - counter), flags); if (amountReceived < 1) { - return resultantString; + return counter; } - - // Need to substring to remove null terminating byte - resultantString += data.substr(0, amountReceived); - - data.clear(); counter += amountReceived; } while (counter < amountToReceive && this->ready()); - - - return resultantString; + + return counter; } /** diff --git a/src/socket/TCPSocket.h b/src/socket/TCPSocket.h index edc911b..28652e0 100644 --- a/src/socket/TCPSocket.h +++ b/src/socket/TCPSocket.h @@ -66,6 +66,7 @@ namespace kt int pollSocket(SOCKET socket, const long& = 1000) const; bool ready(const unsigned long = 1000) const; bool connected(const unsigned long = 1000) const; + bool send(const char*, const int&, const int& = 0) const; bool send(const std::string&, const int& = 0) const; std::string getHostname() const; @@ -75,6 +76,7 @@ namespace kt std::optional get(const int& = 0) const; std::string receiveAmount(const unsigned int, const int& = 0) const; + int receiveAmount(char*, const unsigned int, const int& = 0) const; std::string receiveToDelimiter(const char&, const int& = 0); std::string receiveAll(const unsigned long = 1000, const int& = 0); }; diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index e9b7e3b..dbce904 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -101,7 +101,7 @@ namespace kt } else { - this->listeningPort = std::optional{ port }; + this->listeningPort = std::make_optional(port); } return this->bound; @@ -123,60 +123,81 @@ namespace kt } std::pair UDPSocket::sendTo(const std::string& message, const kt::SocketAddress& address, const int& flags) + { + return this->sendTo(&message[0], message.size(), address, flags); + } + + std::pair UDPSocket::sendTo(const char* buffer, const int& bufferLength, const kt::SocketAddress& address, const int& flags) { SOCKET tempSocket = socket(address.address.sa_family, SOCK_DGRAM, IPPROTO_UDP); if (kt::isInvalidSocket(tempSocket)) { return std::make_pair(false, -2); } - int result = ::sendto(tempSocket, message.c_str(), message.size(), flags, &(address.address), sizeof(address)); + int result = ::sendto(tempSocket, buffer, bufferLength, flags, &(address.address), sizeof(address)); this->close(tempSocket); return std::make_pair(result != -1, result); } std::pair> UDPSocket::sendTo(const std::string& hostname, const unsigned int& port, const std::string& message, const int& flags) + { + return this->sendTo(hostname, port, &message[0], message.size(), flags); + } + + std::pair> UDPSocket::sendTo(const std::string& hostname, const unsigned int& port, const char* buffer, const int& bufferLength, const int& flags) { addrinfo hints{}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; - std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::optional{ hostname }, port, hints); + std::pair, int> resolvedAddresses = kt::resolveToAddresses(std::make_optional(hostname), port, hints); if (resolvedAddresses.first.empty() || resolvedAddresses.second != 0) { return std::make_pair(false, std::make_pair(resolvedAddresses.second, kt::SocketAddress{})); } kt::SocketAddress firstAddress = resolvedAddresses.first.at(0); - std::pair result = this->sendTo(message, firstAddress, flags); + std::pair result = this->sendTo(buffer, bufferLength, firstAddress, flags); return std::make_pair(result.first, std::make_pair(result.second, firstAddress)); } std::pair, kt::SocketAddress> UDPSocket::receiveFrom(const unsigned int& receiveLength, const int& flags) { - kt::SocketAddress receiveAddress{}; - if (!this->bound || receiveLength == 0 || !this->ready()) - { - return std::make_pair(std::nullopt, receiveAddress); - } std::string data; data.resize(receiveLength); - auto addressLength = kt::getAddressLength(receiveAddress); - int flag = recvfrom(this->receiveSocket, &data[0], static_cast(receiveLength), flags, &receiveAddress.address, &addressLength); + std::pair result = this->receiveFrom(&data[0], receiveLength, flags); #ifdef _WIN32 - if (flag < 1) + if (result.first < 1) { // This is for Windows, in some scenarios Windows will return a -1 flag but the buffer is populated properly // The code it is returning is 10040 this is indicating that the provided buffer is too small for the incoming // message, there is probably some settings we can tweak, however I think this is okay to return for now. - return std::make_pair(std::make_optional(data), receiveAddress); + return std::make_pair(std::make_optional(data), result.second); } #endif // Need to substring to remove any null terminating bytes - data = data.substr(0, flag); - return std::make_pair(std::make_optional(data), receiveAddress); + if (result.first < receiveLength) + { + data = data.substr(0, result.first); + } + + return std::make_pair(std::make_optional(data), result.second); + } + + std::pair UDPSocket::receiveFrom(char* buffer, const unsigned int& receiveLength, const int& flags) const + { + kt::SocketAddress receiveAddress{}; + if (!this->bound || receiveLength == 0 || !this->ready()) + { + return std::make_pair(0, receiveAddress); + } + + auto addressLength = kt::getAddressLength(receiveAddress); + int flag = recvfrom(this->receiveSocket, buffer, static_cast(receiveLength), flags, &receiveAddress.address, &addressLength); + return std::make_pair(flag, receiveAddress); } bool UDPSocket::isUdpBound() const @@ -227,7 +248,7 @@ namespace kt throw kt::BindingException("Unable to retrieve randomly bound port number during socket creation. " + getErrorCode()); } - this->listeningPort = std::optional{ kt::getPortNumber(address.first.value()) }; + this->listeningPort = std::make_optional(kt::getPortNumber(address.first.value())); } void UDPSocket::close(SOCKET socket) diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h index 4c4d94a..32bdf5a 100644 --- a/src/socket/UDPSocket.h +++ b/src/socket/UDPSocket.h @@ -65,8 +65,11 @@ namespace kt bool ready(const unsigned long = 1000) const; std::pair sendTo(const std::string&, const kt::SocketAddress&, const int& = 0); + std::pair sendTo(const char*, const int&, const kt::SocketAddress&, const int& = 0); std::pair> sendTo(const std::string&, const unsigned int&, const std::string&, const int& = 0); + std::pair> sendTo(const std::string&, const unsigned int&, const char*, const int&, const int& = 0); std::pair, kt::SocketAddress> receiveFrom(const unsigned int&, const int& = 0); + std::pair receiveFrom(char*, const unsigned int&, const int& = 0) const; bool isUdpBound() const; kt::InternetProtocolVersion getInternetProtocolVersion() const; From e964393f6ca74d40746922ccdcdd41388f759981 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 15:22:45 +0900 Subject: [PATCH 12/21] Remove SocketProtocol.h --- .gitignore | 2 +- CMakeLists.txt | 1 - src/enums/SocketProtocol.h | 11 ----------- src/serversocket/ServerSocket.cpp | 1 - src/serversocket/ServerSocket.h | 1 - src/socket/BluetoothSocket.h | 1 - src/socket/Socket.cpp | 1 - src/socket/TCPSocket.h | 1 - src/socket/UDPSocket.h | 4 ++-- 9 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 src/enums/SocketProtocol.h diff --git a/.gitignore b/.gitignore index cfc6a32..f1b2f28 100644 --- a/.gitignore +++ b/.gitignore @@ -47,7 +47,7 @@ TestBluetooth # Build folder **/build/ -**/build-linux/ +**/build-*/ cmake-build-debug/* CMakeFiles/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 6470b70..40f9878 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,6 @@ set(HEADERS src/socketexceptions/TimeoutException.hpp src/socketexceptions/SocketError.h - src/enums/SocketProtocol.h src/enums/SocketType.h src/enums/InternetProtocolVersion.h ) diff --git a/src/enums/SocketProtocol.h b/src/enums/SocketProtocol.h deleted file mode 100644 index 99d6f34..0000000 --- a/src/enums/SocketProtocol.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -namespace kt -{ - enum class SocketProtocol - { - None, - TCP, - UDP - }; -} diff --git a/src/serversocket/ServerSocket.cpp b/src/serversocket/ServerSocket.cpp index 33edd45..0443398 100644 --- a/src/serversocket/ServerSocket.cpp +++ b/src/serversocket/ServerSocket.cpp @@ -4,7 +4,6 @@ #include "../socketexceptions/SocketException.hpp" #include "../socketexceptions/BindingException.hpp" #include "../socketexceptions/TimeoutException.hpp" -#include "../enums/SocketProtocol.h" #include "../enums/SocketType.h" #include "../socketexceptions/SocketError.h" #include "../address/SocketAddress.h" diff --git a/src/serversocket/ServerSocket.h b/src/serversocket/ServerSocket.h index 612591d..a7ac40d 100644 --- a/src/serversocket/ServerSocket.h +++ b/src/serversocket/ServerSocket.h @@ -7,7 +7,6 @@ #include "../socket/BluetoothSocket.h" #include "../socket/TCPSocket.h" -#include "../enums/SocketProtocol.h" #include "../enums/SocketType.h" #include "../enums/InternetProtocolVersion.h" diff --git a/src/socket/BluetoothSocket.h b/src/socket/BluetoothSocket.h index 545c901..e89f9aa 100644 --- a/src/socket/BluetoothSocket.h +++ b/src/socket/BluetoothSocket.h @@ -5,7 +5,6 @@ #include #include -#include "../enums/SocketProtocol.h" #include "../enums/SocketType.h" #include "../enums/InternetProtocolVersion.h" #include "../address/SocketAddress.h" diff --git a/src/socket/Socket.cpp b/src/socket/Socket.cpp index 6d00676..12eaa52 100644 --- a/src/socket/Socket.cpp +++ b/src/socket/Socket.cpp @@ -2,7 +2,6 @@ #include "Socket.h" #include "../socketexceptions/SocketException.hpp" #include "../socketexceptions/BindingException.hpp" -#include "../enums/SocketProtocol.h" #include "../enums/SocketType.h" #include "../socketexceptions/SocketError.h" diff --git a/src/socket/TCPSocket.h b/src/socket/TCPSocket.h index 28652e0..cedfd04 100644 --- a/src/socket/TCPSocket.h +++ b/src/socket/TCPSocket.h @@ -5,7 +5,6 @@ #include #include -#include "../enums/SocketProtocol.h" #include "../enums/SocketType.h" #include "../enums/InternetProtocolVersion.h" #include "../address/SocketAddress.h" diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h index 32bdf5a..8c77042 100644 --- a/src/socket/UDPSocket.h +++ b/src/socket/UDPSocket.h @@ -5,7 +5,6 @@ #include #include -#include "../enums/SocketProtocol.h" #include "../enums/SocketType.h" #include "../enums/InternetProtocolVersion.h" #include "../address/SocketAddress.h" @@ -62,12 +61,13 @@ namespace kt bool bind(const unsigned int&, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); void close(); - bool ready(const unsigned long = 1000) const; + std::pair sendTo(const std::string&, const kt::SocketAddress&, const int& = 0); std::pair sendTo(const char*, const int&, const kt::SocketAddress&, const int& = 0); std::pair> sendTo(const std::string&, const unsigned int&, const std::string&, const int& = 0); std::pair> sendTo(const std::string&, const unsigned int&, const char*, const int&, const int& = 0); + std::pair, kt::SocketAddress> receiveFrom(const unsigned int&, const int& = 0); std::pair receiveFrom(char*, const unsigned int&, const int& = 0) const; From 29eeff660ddec382ea8956a527e4ca0af1985ef3 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 15:43:03 +0900 Subject: [PATCH 13/21] Update examples in readme to match interface changes. --- README.md | 87 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index dc4444e..aa0cb4f 100644 --- a/README.md +++ b/README.md @@ -32,44 +32,65 @@ The following **linux** dependencies are required: - TCP Example using IPV6: ```cpp -// Create a new Wifi ServerSocket -kt::ServerSocket server(kt::SocketType::Wifi, 56756, 20, InternetProtocolVersion::IPV6); - -// Create new TCP socket -kt::Socket client("::1", server.getPort(), kt::SocketType::Wifi, kt::SocketProtocol::TCP); - -// Accept connection to server -kt::Socket serverSocket = server.accept(); - -const std::string testString = "Test"; -serverSocket.send(testString); -const std::string response = client.receiveAmount(testString.size()); -// Compare received and sent string values -assert(response == testString); - -client.close(); -serverSocket.close(); -server.close(); +void tcpExample() +{ + // Create a new Wifi ServerSocket + kt::ServerSocket server(kt::SocketType::Wifi, 56756, 20, kt::InternetProtocolVersion::IPV6); + + // Create new TCP socket + kt::TCPSocket client("::1", server.getPort()); + + // Accept the incoming connection at the server + kt::TCPSocket serverSocket = server.acceptTCPConnection(); + + // Send string with text before and after the delimiter + const std::string testString = "TCP Delimiter Test"; + const char delimiter = '~'; + if (!socket.send(testString + delimiter + "other string")) + { + std::cout << "Failed to send test string" << std::endl; + return; + } + + if (serverSocket.ready()) + { + std::string response = serverSocket.receiveToDelimiter(delimiter); + // Check that the received string is the same as the string sent by the client + ASSERT_EQ(response, testString); + } + + // Close all sockets + client.close(); + serverSocket.close(); + server.close(); +} ``` - UDP Example using IPV4 (the default protocol version - so protocol related arguments are omitted): ```cpp -kt::Socket serverSocket("127.0.0.1", 43567, kt::SocketType::Wifi, kt::SocketProtocol::UDP); -// Which ever socket is acting as the "server" needs to bind, only a single process can be bound -// to a specific port at a time -serverSocket.bind(InternetProtocolVersion::IPV4); // This argument can be removed as the default is `InternetProtocolVersion::IPV6`, setting arg here explicitly for clarity. - -kt::Socket client("127.0.0.1", 43567, kt::SocketType::Wifi, kt::SocketProtocol::UDP); - -const std::string testString = "UDP Test"; -const char delimiter = '~'; -client.send(testString + delimiter); -const std::string response = serverSocket.receiveToDelimiter(delimiter); -assert(response == testString); - -serverSocket.close(); -client.close(); +void udpExample() +{ + // The socket receiving data must first be bound + kt::UDPSocket socket; + socket.bind(87893, kt::InternetProtocolVersion::IPV4); + + kt::UDPSocket client; + const std::string testString = "UDP test string"; + if (!client.sendTo("localhost", 87893, testString).first) + { + std::cout << "Failed to send to address." << std::endl; + return; + } + + if (socket.ready()) + { + std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size()); + ASSERT_EQ(testString, recieved.first.value()); + } + + socket.close(); +} ``` ## Known Issues From 47760189d5000566600fd667d08d06438ef5ea12 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 15:43:13 +0900 Subject: [PATCH 14/21] Add new BindAfterMessageIsSent test for UDP. --- tests/socket/UDPSocketTest.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/socket/UDPSocketTest.cpp b/tests/socket/UDPSocketTest.cpp index 769e10f..103f294 100644 --- a/tests/socket/UDPSocketTest.cpp +++ b/tests/socket/UDPSocketTest.cpp @@ -165,5 +165,26 @@ namespace kt ASSERT_EQ(testString, recieved.first.value()); } + /** + * Test a scenario where we bind and read from the socket after the message has been sent. + */ + TEST_F(UDPSocketTest, BindAfterMessageIsSent) + { + const unsigned int PORT = 87893; + kt::UDPSocket client; + + const std::string testString = "BindAfterMessageIsSent"; + ASSERT_TRUE(client.sendTo(LOCALHOST, PORT, testString).first); + + kt::UDPSocket socket; + ASSERT_TRUE(socket.bind(PORT, kt::InternetProtocolVersion::IPV4)); + ASSERT_TRUE(socket.ready()); + std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size()); + ASSERT_NE(std::nullopt, recieved.first); + ASSERT_EQ(testString, recieved.first.value()); + + socket.close(); + } + // TODO: large payload tests } From ec05fa82fcb97bbcfa2cda25c47f7b5c61d974a2 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 16:42:21 +0900 Subject: [PATCH 15/21] Return status code in std::string version of recieveFrom(). Update readme and tests. --- README.md | 2 +- src/socket/Socket.cpp | 3 ++- src/socket/UDPSocket.cpp | 6 +++--- src/socket/UDPSocket.h | 2 +- tests/socket/UDPSocketTest.cpp | 10 +++++----- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index aa0cb4f..3261779 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ void udpExample() if (socket.ready()) { - std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size()); + std::pair, std::pair> recieved = socket.receiveFrom(testString.size()); ASSERT_EQ(testString, recieved.first.value()); } diff --git a/src/socket/Socket.cpp b/src/socket/Socket.cpp index 12eaa52..17804ee 100644 --- a/src/socket/Socket.cpp +++ b/src/socket/Socket.cpp @@ -61,7 +61,8 @@ namespace kt FD_SET(socketDescriptor, &sReady); // Need this->socketDescriptor + 1 here - return select(socketDescriptor + 1, &sReady, nullptr, nullptr, timeOutVal); + int result = select(socketDescriptor + 1, &sReady, nullptr, nullptr, timeOutVal); + return result; } void close(SOCKET socket) diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index dbce904..e8ad627 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -161,7 +161,7 @@ namespace kt return std::make_pair(result.first, std::make_pair(result.second, firstAddress)); } - std::pair, kt::SocketAddress> UDPSocket::receiveFrom(const unsigned int& receiveLength, const int& flags) + std::pair, std::pair> UDPSocket::receiveFrom(const unsigned int& receiveLength, const int& flags) { std::string data; data.resize(receiveLength); @@ -174,7 +174,7 @@ namespace kt // This is for Windows, in some scenarios Windows will return a -1 flag but the buffer is populated properly // The code it is returning is 10040 this is indicating that the provided buffer is too small for the incoming // message, there is probably some settings we can tweak, however I think this is okay to return for now. - return std::make_pair(std::make_optional(data), result.second); + return std::make_pair(data.empty() ? std::nullopt : std::make_optional(data), result); } #endif @@ -184,7 +184,7 @@ namespace kt data = data.substr(0, result.first); } - return std::make_pair(std::make_optional(data), result.second); + return std::make_pair(data.empty() ? std::nullopt : std::make_optional(data), result); } std::pair UDPSocket::receiveFrom(char* buffer, const unsigned int& receiveLength, const int& flags) const diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h index 8c77042..13f5b38 100644 --- a/src/socket/UDPSocket.h +++ b/src/socket/UDPSocket.h @@ -68,7 +68,7 @@ namespace kt std::pair> sendTo(const std::string&, const unsigned int&, const std::string&, const int& = 0); std::pair> sendTo(const std::string&, const unsigned int&, const char*, const int&, const int& = 0); - std::pair, kt::SocketAddress> receiveFrom(const unsigned int&, const int& = 0); + std::pair, std::pair> receiveFrom(const unsigned int&, const int& = 0); std::pair receiveFrom(char*, const unsigned int&, const int& = 0) const; bool isUdpBound() const; diff --git a/tests/socket/UDPSocketTest.cpp b/tests/socket/UDPSocketTest.cpp index 103f294..c21131f 100644 --- a/tests/socket/UDPSocketTest.cpp +++ b/tests/socket/UDPSocketTest.cpp @@ -93,7 +93,7 @@ namespace kt ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort().value(), testString).first); ASSERT_TRUE(socket.ready()); - std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size()); + std::pair, std::pair> recieved = socket.receiveFrom(testString.size()); ASSERT_FALSE(socket.ready()); ASSERT_NE(std::nullopt, recieved.first); ASSERT_EQ(testString, recieved.first.value()); @@ -113,7 +113,7 @@ namespace kt ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort().value(), testString).first); ASSERT_TRUE(socket.ready()); - std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size() - 1); + std::pair, std::pair> recieved = socket.receiveFrom(testString.size() - 1); ASSERT_FALSE(socket.ready()); ASSERT_NE(std::nullopt, recieved.first); ASSERT_EQ(testString.substr(0, testString.size() - 1), recieved.first.value()); @@ -131,7 +131,7 @@ namespace kt ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort().value(), testString).first); ASSERT_TRUE(socket.ready()); - std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size() + 1); + std::pair, std::pair> recieved = socket.receiveFrom(testString.size() + 1); ASSERT_FALSE(socket.ready()); ASSERT_NE(std::nullopt, recieved.first); ASSERT_EQ(testString, recieved.first.value()); @@ -150,7 +150,7 @@ namespace kt ASSERT_TRUE(sendResult.first); ASSERT_TRUE(socket.ready()); - std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size()); + std::pair, std::pair> recieved = socket.receiveFrom(testString.size()); ASSERT_FALSE(socket.ready()); ASSERT_NE(std::nullopt, recieved.first); ASSERT_EQ(testString, recieved.first.value()); @@ -179,7 +179,7 @@ namespace kt kt::UDPSocket socket; ASSERT_TRUE(socket.bind(PORT, kt::InternetProtocolVersion::IPV4)); ASSERT_TRUE(socket.ready()); - std::pair, kt::SocketAddress> recieved = socket.receiveFrom(testString.size()); + std::pair, std::pair> recieved = socket.receiveFrom(testString.size()); ASSERT_NE(std::nullopt, recieved.first); ASSERT_EQ(testString, recieved.first.value()); From 2a30d1aa31c006a16e02a67980c0a70e46b5ab3b Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Wed, 26 Jun 2024 17:41:38 +0900 Subject: [PATCH 16/21] Revert "Add new BindAfterMessageIsSent test for UDP." This reverts commit 47760189d5000566600fd667d08d06438ef5ea12. --- tests/socket/UDPSocketTest.cpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/tests/socket/UDPSocketTest.cpp b/tests/socket/UDPSocketTest.cpp index c21131f..401175b 100644 --- a/tests/socket/UDPSocketTest.cpp +++ b/tests/socket/UDPSocketTest.cpp @@ -165,26 +165,5 @@ namespace kt ASSERT_EQ(testString, recieved.first.value()); } - /** - * Test a scenario where we bind and read from the socket after the message has been sent. - */ - TEST_F(UDPSocketTest, BindAfterMessageIsSent) - { - const unsigned int PORT = 87893; - kt::UDPSocket client; - - const std::string testString = "BindAfterMessageIsSent"; - ASSERT_TRUE(client.sendTo(LOCALHOST, PORT, testString).first); - - kt::UDPSocket socket; - ASSERT_TRUE(socket.bind(PORT, kt::InternetProtocolVersion::IPV4)); - ASSERT_TRUE(socket.ready()); - std::pair, std::pair> recieved = socket.receiveFrom(testString.size()); - ASSERT_NE(std::nullopt, recieved.first); - ASSERT_EQ(testString, recieved.first.value()); - - socket.close(); - } - // TODO: large payload tests } From 4d90dd9c650a630b50b401d6412f7b75ba2b322d Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Fri, 28 Jun 2024 11:03:21 +0900 Subject: [PATCH 17/21] Add default port to UDP bind as 0. Update Readme and add comments in select(). Remove special case handling in select() for linux. Update receiveFrom signature to accept only int to match the sys call signature. --- README.md | 2 +- src/socket/Socket.cpp | 6 ++++-- src/socket/TCPSocket.cpp | 13 ------------- src/socket/UDPSocket.cpp | 19 +++---------------- src/socket/UDPSocket.h | 6 +++--- tests/socket/UDPSocketTest.cpp | 23 ++++++++++++----------- 6 files changed, 23 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 3261779..a914713 100644 --- a/README.md +++ b/README.md @@ -113,4 +113,4 @@ signal(SIGPIPE, SIG_IGN); ### NOTE: UDP Read Sizes -- Take care when reading UDP messages. If you do not read the entire length of the message the rest of the data will be lost. Try using `receiveAll()`/`recieveToDelimiter()`/`receiveAmount()` instead of `get()`, unless you know the amount of data that you are expecting. +- Take care when reading UDP messages. If you do not read the entire length of the message the rest of the data will be lost. diff --git a/src/socket/Socket.cpp b/src/socket/Socket.cpp index 17804ee..8cfe263 100644 --- a/src/socket/Socket.cpp +++ b/src/socket/Socket.cpp @@ -60,8 +60,10 @@ namespace kt FD_ZERO(&sReady); FD_SET(socketDescriptor, &sReady); - // Need this->socketDescriptor + 1 here - int result = select(socketDescriptor + 1, &sReady, nullptr, nullptr, timeOutVal); + // On windows: "Ignored. The nfds (the first arg) parameter is included only for compatibility with Berkeley sockets." + // On linux: "ndfs (the first arg) is the highest-numbered file descriptor in any of the three sets, plus 1." + // So we will use the linux required value since it is ignored in the windows API. + int result = select(static_cast(socketDescriptor + 1), &sReady, nullptr, nullptr, timeOutVal); return result; } diff --git a/src/socket/TCPSocket.cpp b/src/socket/TCPSocket.cpp index 7005f6f..32298d9 100644 --- a/src/socket/TCPSocket.cpp +++ b/src/socket/TCPSocket.cpp @@ -103,19 +103,6 @@ namespace kt { timeval timeOutVal{}; int res = kt::pollSocket(socket, timeout, &timeOutVal); -#ifdef __linux__ - if (res == 0) - { - if (timeOutVal.tv_usec == 0) - { - return 0; - } - else - { - return 1; - } - } -#endif return res; } diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index e8ad627..7a9158a 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -161,7 +161,7 @@ namespace kt return std::make_pair(result.first, std::make_pair(result.second, firstAddress)); } - std::pair, std::pair> UDPSocket::receiveFrom(const unsigned int& receiveLength, const int& flags) + std::pair, std::pair> UDPSocket::receiveFrom(const int& receiveLength, const int& flags) { std::string data; data.resize(receiveLength); @@ -187,7 +187,7 @@ namespace kt return std::make_pair(data.empty() ? std::nullopt : std::make_optional(data), result); } - std::pair UDPSocket::receiveFrom(char* buffer, const unsigned int& receiveLength, const int& flags) const + std::pair UDPSocket::receiveFrom(char* buffer, const int& receiveLength, const int& flags) const { kt::SocketAddress receiveAddress{}; if (!this->bound || receiveLength == 0 || !this->ready()) @@ -196,7 +196,7 @@ namespace kt } auto addressLength = kt::getAddressLength(receiveAddress); - int flag = recvfrom(this->receiveSocket, buffer, static_cast(receiveLength), flags, &receiveAddress.address, &addressLength); + int flag = recvfrom(this->receiveSocket, buffer, receiveLength, flags, &receiveAddress.address, &addressLength); return std::make_pair(flag, receiveAddress); } @@ -222,19 +222,6 @@ namespace kt { timeval timeOutVal{}; int res = kt::pollSocket(socket, timeout, &timeOutVal); -#ifdef __linux__ - if (res == 0) - { - if (timeOutVal.tv_usec == 0) - { - return 0; - } - else - { - return 1; - } - } -#endif return res; } diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h index 13f5b38..6e4905d 100644 --- a/src/socket/UDPSocket.h +++ b/src/socket/UDPSocket.h @@ -59,7 +59,7 @@ namespace kt UDPSocket(const kt::UDPSocket&); kt::UDPSocket& operator=(const kt::UDPSocket&); - bool bind(const unsigned int&, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); + bool bind(const unsigned int& = 0, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); void close(); bool ready(const unsigned long = 1000) const; @@ -68,8 +68,8 @@ namespace kt std::pair> sendTo(const std::string&, const unsigned int&, const std::string&, const int& = 0); std::pair> sendTo(const std::string&, const unsigned int&, const char*, const int&, const int& = 0); - std::pair, std::pair> receiveFrom(const unsigned int&, const int& = 0); - std::pair receiveFrom(char*, const unsigned int&, const int& = 0) const; + std::pair, std::pair> receiveFrom(const int&, const int& = 0); + std::pair receiveFrom(char*, const int&, const int& = 0) const; bool isUdpBound() const; kt::InternetProtocolVersion getInternetProtocolVersion() const; diff --git a/tests/socket/UDPSocketTest.cpp b/tests/socket/UDPSocketTest.cpp index 401175b..813e78e 100644 --- a/tests/socket/UDPSocketTest.cpp +++ b/tests/socket/UDPSocketTest.cpp @@ -43,7 +43,7 @@ namespace kt { ASSERT_FALSE(socket.isUdpBound()); ASSERT_EQ(kt::InternetProtocolVersion::Any, socket.getInternetProtocolVersion()); - ASSERT_TRUE(socket.bind(0)); + ASSERT_TRUE(socket.bind()); ASSERT_NE(kt::InternetProtocolVersion::Any, socket.getInternetProtocolVersion()); ASSERT_TRUE(socket.isUdpBound()); @@ -58,12 +58,10 @@ namespace kt */ TEST_F(UDPSocketTest, UDPBind_WithoutSpecifiedPort) { - const unsigned int port = 0; - ASSERT_FALSE(socket.isUdpBound()); - socket.bind(port); + socket.bind(0); ASSERT_TRUE(socket.isUdpBound()); - ASSERT_NE(port, socket.getListeningPort()); + ASSERT_NE(0, socket.getListeningPort()); } /* @@ -71,7 +69,7 @@ namespace kt */ TEST_F(UDPSocketTest, UDPSendTo) { - ASSERT_TRUE(socket.bind(0)); + ASSERT_TRUE(socket.bind()); UDPSocket client; @@ -86,7 +84,8 @@ namespace kt */ TEST_F(UDPSocketTest, UDPReceiveFrom) { - ASSERT_TRUE(socket.bind(0)); + ASSERT_TRUE(socket.bind()); + ASSERT_FALSE(socket.ready()); UDPSocket client; const std::string testString = "test"; @@ -105,11 +104,11 @@ namespace kt */ TEST_F(UDPSocketTest, UDPReceiveAmount_NotEnoughRead) { - ASSERT_TRUE(socket.bind(0)); + ASSERT_TRUE(socket.bind()); + ASSERT_FALSE(socket.ready()); UDPSocket client; const std::string testString = "test"; - ASSERT_FALSE(socket.ready()); ASSERT_TRUE(client.sendTo(LOCALHOST, socket.getListeningPort().value(), testString).first); ASSERT_TRUE(socket.ready()); @@ -124,7 +123,8 @@ namespace kt */ TEST_F(UDPSocketTest, UDPReceiveAmount_TooMuchRead) { - ASSERT_TRUE(socket.bind(0)); + ASSERT_TRUE(socket.bind()); + ASSERT_FALSE(socket.ready()); UDPSocket client; const std::string testString = "test"; @@ -142,7 +142,8 @@ namespace kt */ TEST_F(UDPSocketTest, UDPSendToAddress) { - ASSERT_TRUE(socket.bind(0)); + ASSERT_TRUE(socket.bind()); + ASSERT_FALSE(socket.ready()); UDPSocket client; std::string testString = "test"; From a0ef138b8eb5c6ba145016c959f7c4436775caba Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Fri, 28 Jun 2024 15:18:15 +0900 Subject: [PATCH 18/21] Reduce default poll time from 1000 micro seconds to 100 microseconds. Some clean up. --- src/address/SocketAddress.cpp | 2 +- src/serversocket/ServerSocket.cpp | 2 -- src/socket/TCPSocket.cpp | 6 ++---- src/socket/TCPSocket.h | 8 ++++---- src/socket/UDPSocket.cpp | 14 +++++++------- src/socket/UDPSocket.h | 2 +- 6 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/address/SocketAddress.cpp b/src/address/SocketAddress.cpp index 597f8fe..49d7ffc 100644 --- a/src/address/SocketAddress.cpp +++ b/src/address/SocketAddress.cpp @@ -92,6 +92,6 @@ namespace kt #endif { const kt::InternetProtocolVersion protocolVersion = getInternetProtocolVersion(address); - return protocolVersion == kt::InternetProtocolVersion::IPV6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN; + return protocolVersion == kt::InternetProtocolVersion::IPV6 ? sizeof(address.ipv6) : sizeof(address.ipv4); } } diff --git a/src/serversocket/ServerSocket.cpp b/src/serversocket/ServerSocket.cpp index 0443398..5290833 100644 --- a/src/serversocket/ServerSocket.cpp +++ b/src/serversocket/ServerSocket.cpp @@ -230,8 +230,6 @@ namespace kt void kt::ServerSocket::initialiseServerAddress() { addrinfo hints{}; - memset(&this->serverAddress, 0, sizeof(this->serverAddress)); - hints.ai_flags = AI_PASSIVE; hints.ai_family = static_cast(this->protocolVersion); hints.ai_socktype = SOCK_STREAM; diff --git a/src/socket/TCPSocket.cpp b/src/socket/TCPSocket.cpp index 32298d9..08f50be 100644 --- a/src/socket/TCPSocket.cpp +++ b/src/socket/TCPSocket.cpp @@ -11,7 +11,7 @@ namespace kt this->hostname = hostname; this->port = port; - memset(&this->serverAddress, '\0', sizeof(this->serverAddress)); + memset(&this->serverAddress, 0, sizeof(this->serverAddress)); constructWifiSocket(); } @@ -47,8 +47,6 @@ namespace kt void TCPSocket::constructWifiSocket() { - memset(&this->serverAddress, 0, sizeof(this->serverAddress)); - #ifdef _WIN32 WSADATA wsaData{}; if (int res = WSAStartup(MAKEWORD(2, 2), &wsaData); res != 0) @@ -195,7 +193,7 @@ namespace kt do { - int amountReceived = recv(this->socketDescriptor, &buffer[counter], static_cast(amountToReceive - counter), flags); + int amountReceived = ::recv(this->socketDescriptor, &buffer[counter], static_cast(amountToReceive - counter), flags); if (amountReceived < 1) { return counter; diff --git a/src/socket/TCPSocket.h b/src/socket/TCPSocket.h index cedfd04..efb3faa 100644 --- a/src/socket/TCPSocket.h +++ b/src/socket/TCPSocket.h @@ -62,9 +62,9 @@ namespace kt void close() const; - int pollSocket(SOCKET socket, const long& = 1000) const; - bool ready(const unsigned long = 1000) const; - bool connected(const unsigned long = 1000) const; + int pollSocket(SOCKET socket, const long& = 100) const; + bool ready(const unsigned long = 100) const; + bool connected(const unsigned long = 100) const; bool send(const char*, const int&, const int& = 0) const; bool send(const std::string&, const int& = 0) const; @@ -77,7 +77,7 @@ namespace kt std::string receiveAmount(const unsigned int, const int& = 0) const; int receiveAmount(char*, const unsigned int, const int& = 0) const; std::string receiveToDelimiter(const char&, const int& = 0); - std::string receiveAll(const unsigned long = 1000, const int& = 0); + std::string receiveAll(const unsigned long = 100, const int& = 0); }; } // End namespace kt diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index 7a9158a..6d03592 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -41,7 +41,7 @@ namespace kt { if (this->isUdpBound()) { - return true; + this->close(); } #ifdef _WIN32 @@ -174,17 +174,17 @@ namespace kt // This is for Windows, in some scenarios Windows will return a -1 flag but the buffer is populated properly // The code it is returning is 10040 this is indicating that the provided buffer is too small for the incoming // message, there is probably some settings we can tweak, however I think this is okay to return for now. - return std::make_pair(data.empty() ? std::nullopt : std::make_optional(data), result); + return std::make_pair(result.first <= 0 ? std::nullopt : std::make_optional(data), result); } #endif // Need to substring to remove any null terminating bytes - if (result.first < receiveLength) + if (result.first >= 0 && result.first < receiveLength) { data = data.substr(0, result.first); } - - return std::make_pair(data.empty() ? std::nullopt : std::make_optional(data), result); + + return std::make_pair(result.first <= 0 ? std::nullopt : std::make_optional(data), result); } std::pair UDPSocket::receiveFrom(char* buffer, const int& receiveLength, const int& flags) const @@ -192,11 +192,11 @@ namespace kt kt::SocketAddress receiveAddress{}; if (!this->bound || receiveLength == 0 || !this->ready()) { - return std::make_pair(0, receiveAddress); + return std::make_pair(-1, receiveAddress); } auto addressLength = kt::getAddressLength(receiveAddress); - int flag = recvfrom(this->receiveSocket, buffer, receiveLength, flags, &receiveAddress.address, &addressLength); + int flag = ::recvfrom(this->receiveSocket, buffer, receiveLength, flags, &receiveAddress.address, &addressLength); return std::make_pair(flag, receiveAddress); } diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h index 6e4905d..58b38ec 100644 --- a/src/socket/UDPSocket.h +++ b/src/socket/UDPSocket.h @@ -61,7 +61,7 @@ namespace kt bool bind(const unsigned int& = 0, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); void close(); - bool ready(const unsigned long = 1000) const; + bool ready(const unsigned long = 100) const; std::pair sendTo(const std::string&, const kt::SocketAddress&, const int& = 0); std::pair sendTo(const char*, const int&, const kt::SocketAddress&, const int& = 0); From 773c1f76131fb8239811d6bcb0162c5939805fd5 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Fri, 28 Jun 2024 15:25:33 +0900 Subject: [PATCH 19/21] Add tests for SocketAddress functions. Refactor all port numbers to be an unsigned short to match spec and as per getPortNumber tests. --- src/address/SocketAddress.cpp | 8 +- src/serversocket/ServerSocket.cpp | 4 +- src/serversocket/ServerSocket.h | 6 +- src/socket/TCPSocket.cpp | 6 +- src/socket/TCPSocket.h | 8 +- src/socket/UDPSocket.cpp | 4 +- src/socket/UDPSocket.h | 6 +- tests/CMakeLists.txt | 2 + tests/address/SocketAddressTest.cpp | 103 +++++++++++++++++++++ tests/serversocket/ServerSocketTCPTest.cpp | 2 +- 10 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 tests/address/SocketAddressTest.cpp diff --git a/src/address/SocketAddress.cpp b/src/address/SocketAddress.cpp index 49d7ffc..86ef28a 100644 --- a/src/address/SocketAddress.cpp +++ b/src/address/SocketAddress.cpp @@ -8,7 +8,13 @@ namespace kt { kt::InternetProtocolVersion getInternetProtocolVersion(const kt::SocketAddress& address) { - return static_cast(address.address.sa_family); + kt::InternetProtocolVersion resolvedVersion = static_cast(address.address.sa_family); + + if (resolvedVersion == kt::InternetProtocolVersion::IPV4 || resolvedVersion == kt::InternetProtocolVersion::IPV6) + { + return resolvedVersion; + } + return kt::InternetProtocolVersion::Any; } unsigned int getPortNumber(const kt::SocketAddress& address) diff --git a/src/serversocket/ServerSocket.cpp b/src/serversocket/ServerSocket.cpp index 5290833..abd68c5 100644 --- a/src/serversocket/ServerSocket.cpp +++ b/src/serversocket/ServerSocket.cpp @@ -52,7 +52,7 @@ namespace kt * @throw SocketException - If the ServerSocket is unable to be instanciated or begin listening. * @throw BindingException - If the ServerSocket is unable to bind to the specific port specified. */ - kt::ServerSocket::ServerSocket(const kt::SocketType type, const unsigned int& port, const unsigned int& connectionBacklogSize, const kt::InternetProtocolVersion protocolVersion) + kt::ServerSocket::ServerSocket(const kt::SocketType type, const unsigned short& port, const unsigned int& connectionBacklogSize, const kt::InternetProtocolVersion protocolVersion) { this->socketDescriptor = getInvalidSocketValue(); this->port = port; @@ -288,7 +288,7 @@ namespace kt * Used to get the port number that the ServerSocket is listening on. * @return An unsigned int of the port number that the ServerSocket is listening on. */ - unsigned int kt::ServerSocket::getPort() const + unsigned short kt::ServerSocket::getPort() const { return this->port; } diff --git a/src/serversocket/ServerSocket.h b/src/serversocket/ServerSocket.h index a7ac40d..c50d994 100644 --- a/src/serversocket/ServerSocket.h +++ b/src/serversocket/ServerSocket.h @@ -41,7 +41,7 @@ namespace kt class ServerSocket { protected: - unsigned int port; + unsigned short port; kt::SocketType type = kt::SocketType::None; kt::InternetProtocolVersion protocolVersion = kt::InternetProtocolVersion::Any; kt::SocketAddress serverAddress = {}; @@ -56,7 +56,7 @@ namespace kt public: ServerSocket() = default; - ServerSocket(const kt::SocketType, const unsigned int& = 0, const unsigned int& = 20, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); + ServerSocket(const kt::SocketType, const unsigned short& = 0, const unsigned int& = 20, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); ServerSocket(const kt::ServerSocket&); kt::ServerSocket& operator=(const kt::ServerSocket&); @@ -65,7 +65,7 @@ namespace kt kt::SocketType getType() const; kt::InternetProtocolVersion getInternetProtocolVersion() const; - unsigned int getPort() const; + unsigned short getPort() const; void close(); }; diff --git a/src/socket/TCPSocket.cpp b/src/socket/TCPSocket.cpp index 08f50be..6c12a67 100644 --- a/src/socket/TCPSocket.cpp +++ b/src/socket/TCPSocket.cpp @@ -6,7 +6,7 @@ namespace kt { - TCPSocket::TCPSocket(const std::string& hostname, const unsigned int& port) + TCPSocket::TCPSocket(const std::string& hostname, const unsigned short& port) { this->hostname = hostname; this->port = port; @@ -16,7 +16,7 @@ namespace kt constructWifiSocket(); } - TCPSocket::TCPSocket(const SOCKET& socket, const std::string& hostname, const unsigned int& port, const kt::InternetProtocolVersion protocolVersion, const kt::SocketAddress& acceptedAddress) + TCPSocket::TCPSocket(const SOCKET& socket, const std::string& hostname, const unsigned short& port, const kt::InternetProtocolVersion protocolVersion, const kt::SocketAddress& acceptedAddress) { this->socketDescriptor = socket; this->hostname = hostname; @@ -140,7 +140,7 @@ namespace kt return this->hostname; } - unsigned int TCPSocket::getPort() const + unsigned short TCPSocket::getPort() const { return this->port; } diff --git a/src/socket/TCPSocket.h b/src/socket/TCPSocket.h index efb3faa..e0ca7d4 100644 --- a/src/socket/TCPSocket.h +++ b/src/socket/TCPSocket.h @@ -46,7 +46,7 @@ namespace kt protected: SOCKET socketDescriptor = getInvalidSocketValue(); std::string hostname; - unsigned int port; + unsigned short port; kt::InternetProtocolVersion protocolVersion = kt::InternetProtocolVersion::Any; kt::SocketAddress serverAddress = {}; // The remote address that we will be connected to @@ -54,8 +54,8 @@ namespace kt public: TCPSocket() = default; - TCPSocket(const std::string&, const unsigned int&); - TCPSocket(const SOCKET&, const std::string&, const unsigned int&, const kt::InternetProtocolVersion, const kt::SocketAddress&); + TCPSocket(const std::string&, const unsigned short&); + TCPSocket(const SOCKET&, const std::string&, const unsigned short&, const kt::InternetProtocolVersion, const kt::SocketAddress&); TCPSocket(const kt::TCPSocket&); kt::TCPSocket& operator=(const kt::TCPSocket&); @@ -69,7 +69,7 @@ namespace kt bool send(const std::string&, const int& = 0) const; std::string getHostname() const; - unsigned int getPort() const; + unsigned short getPort() const; kt::InternetProtocolVersion getInternetProtocolVersion() const; kt::SocketAddress getSocketAddress() const; diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index 6d03592..2173239 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -37,7 +37,7 @@ namespace kt * * @throw BindingException - if the socket fails to bind */ - bool kt::UDPSocket::bind(const unsigned int& port, const kt::InternetProtocolVersion protocolVersion) + bool kt::UDPSocket::bind(const unsigned short& port, const kt::InternetProtocolVersion protocolVersion) { if (this->isUdpBound()) { @@ -213,7 +213,7 @@ namespace kt return this->protocolVersion; } - std::optional UDPSocket::getListeningPort() const + std::optional UDPSocket::getListeningPort() const { return this->listeningPort; } diff --git a/src/socket/UDPSocket.h b/src/socket/UDPSocket.h index 58b38ec..17a86c3 100644 --- a/src/socket/UDPSocket.h +++ b/src/socket/UDPSocket.h @@ -48,7 +48,7 @@ namespace kt bool bound = false; SOCKET receiveSocket = getInvalidSocketValue(); kt::InternetProtocolVersion protocolVersion = kt::InternetProtocolVersion::Any; - std::optional listeningPort = std::nullopt; + std::optional listeningPort = std::nullopt; int pollSocket(SOCKET socket, const long& = 1000) const; void initialiseListeningPortNumber(); @@ -59,7 +59,7 @@ namespace kt UDPSocket(const kt::UDPSocket&); kt::UDPSocket& operator=(const kt::UDPSocket&); - bool bind(const unsigned int& = 0, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); + bool bind(const unsigned short& = 0, const kt::InternetProtocolVersion = kt::InternetProtocolVersion::Any); void close(); bool ready(const unsigned long = 100) const; @@ -73,7 +73,7 @@ namespace kt bool isUdpBound() const; kt::InternetProtocolVersion getInternetProtocolVersion() const; - std::optional getListeningPort() const; + std::optional getListeningPort() const; }; } // End namespace kt diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index db01bf2..c7f83b5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,6 +23,8 @@ set(SOURCE socket/TCPSocketTest.cpp socket/UDPSocketTest.cpp socket/BluetoothSocketTest.cpp + + address/SocketAddressTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCE}) diff --git a/tests/address/SocketAddressTest.cpp b/tests/address/SocketAddressTest.cpp new file mode 100644 index 0000000..1bb2b84 --- /dev/null +++ b/tests/address/SocketAddressTest.cpp @@ -0,0 +1,103 @@ +#include +#include +#include + +#include "../../src/address/SocketAddress.h" +#include "../../src/enums/InternetProtocolVersion.h" + +namespace kt +{ + TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersion_DefaultStruct) + { + kt::SocketAddress address{}; + ASSERT_EQ(kt::InternetProtocolVersion::Any, kt::getInternetProtocolVersion(address)); + } + + TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersion_IPV4) + { + const std::string localhost = "localhost"; + const unsigned int port = 0; + kt::InternetProtocolVersion version = kt::InternetProtocolVersion::IPV4; + + addrinfo hints{}; + hints.ai_family = static_cast(version); + + std::pair, int> results = kt::resolveToAddresses(std::make_optional(localhost), port, hints); + ASSERT_FALSE(results.first.empty()); + kt::SocketAddress firstAddress = results.first.at(0); + ASSERT_EQ(version, kt::getInternetProtocolVersion(firstAddress)); + } + + TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersion_IPV6) + { + const std::string localhost = "localhost"; + const unsigned int port = 0; + kt::InternetProtocolVersion version = kt::InternetProtocolVersion::IPV6; + + addrinfo hints{}; + hints.ai_family = static_cast(version); + + std::pair, int> results = kt::resolveToAddresses(std::make_optional(localhost), port, hints); + ASSERT_FALSE(results.first.empty()); + kt::SocketAddress firstAddress = results.first.at(0); + ASSERT_EQ(version, kt::getInternetProtocolVersion(firstAddress)); + } + + TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersion_RandomSocketFamily) + { + kt::SocketAddress address{}; + address.address.sa_family = 1; + + ASSERT_NE(address.address.sa_family, static_cast(kt::InternetProtocolVersion::Any)); + ASSERT_NE(address.address.sa_family, static_cast(kt::InternetProtocolVersion::IPV4)); + ASSERT_NE(address.address.sa_family, static_cast(kt::InternetProtocolVersion::IPV6)); + + ASSERT_EQ(kt::InternetProtocolVersion::Any, kt::getInternetProtocolVersion(address)); + } + + TEST(SocketAddressTest, SocketAddressGetPortNumber_NoPortNumberSet) + { + kt::SocketAddress address{}; + ASSERT_EQ(0, kt::getPortNumber(address)); + } + + TEST(SocketAddressTest, SocketAddressGetPortNumber_IPV4PortAndFamily) + { + const unsigned short port = 57323; + kt::SocketAddress address{}; + address.address.sa_family = static_cast(kt::InternetProtocolVersion::IPV4); + + address.ipv4.sin_port = htons(port); + ASSERT_EQ(port, kt::getPortNumber(address)); + } + + TEST(SocketAddressTest, SocketAddressGetPortNumber_IPV4PortAndIPV6Family) + { + const unsigned short port = 3248; + kt::SocketAddress address{}; + address.address.sa_family = static_cast(kt::InternetProtocolVersion::IPV6); + + address.ipv4.sin_port = htons(port); + ASSERT_EQ(port, kt::getPortNumber(address)); + } + + TEST(SocketAddressTest, SocketAddressGetPortNumber_IPV6PortAndFamily) + { + const unsigned short port = 64123; + kt::SocketAddress address{}; + address.address.sa_family = static_cast(kt::InternetProtocolVersion::IPV6); + + address.ipv6.sin6_port = htons(port); + ASSERT_EQ(port, kt::getPortNumber(address)); + } + + TEST(SocketAddressTest, SocketAddressGetPortNumber_IPV6PortAndIPV4Family) + { + const unsigned short port = 37652; + kt::SocketAddress address{}; + address.address.sa_family = static_cast(kt::InternetProtocolVersion::IPV4); + + address.ipv6.sin6_port = htons(port); + ASSERT_EQ(port, kt::getPortNumber(address)); + } +} diff --git a/tests/serversocket/ServerSocketTCPTest.cpp b/tests/serversocket/ServerSocketTCPTest.cpp index d1f44a0..401214f 100644 --- a/tests/serversocket/ServerSocketTCPTest.cpp +++ b/tests/serversocket/ServerSocketTCPTest.cpp @@ -7,7 +7,7 @@ #include "../../src/socketexceptions/BindingException.hpp" #include "../../src/socketexceptions/TimeoutException.hpp" -const int PORT_NUMBER = 87682; +const unsigned short PORT_NUMBER = 87682; namespace kt { From 6f061e9d456186f3d3c6f7817eb50385e234c749 Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Fri, 28 Jun 2024 15:57:53 +0900 Subject: [PATCH 20/21] Continue working on SocketAddress tests. Resolve issue with socket polling on Windows. Change port number in serversocket test to be an unsigned short as per the previous commit. --- src/address/SocketAddress.cpp | 2 +- src/socket/UDPSocket.cpp | 14 +---- tests/address/SocketAddressTest.cpp | 66 +++++++++++++++++++++- tests/serversocket/ServerSocketTCPTest.cpp | 2 +- 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/address/SocketAddress.cpp b/src/address/SocketAddress.cpp index 86ef28a..d56a974 100644 --- a/src/address/SocketAddress.cpp +++ b/src/address/SocketAddress.cpp @@ -98,6 +98,6 @@ namespace kt #endif { const kt::InternetProtocolVersion protocolVersion = getInternetProtocolVersion(address); - return protocolVersion == kt::InternetProtocolVersion::IPV6 ? sizeof(address.ipv6) : sizeof(address.ipv4); + return protocolVersion == kt::InternetProtocolVersion::IPV4 ? sizeof(address.ipv4) : sizeof(address.ipv6); } } diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index 2173239..1c48d1f 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -167,16 +167,6 @@ namespace kt data.resize(receiveLength); std::pair result = this->receiveFrom(&data[0], receiveLength, flags); - -#ifdef _WIN32 - if (result.first < 1) - { - // This is for Windows, in some scenarios Windows will return a -1 flag but the buffer is populated properly - // The code it is returning is 10040 this is indicating that the provided buffer is too small for the incoming - // message, there is probably some settings we can tweak, however I think this is okay to return for now. - return std::make_pair(result.first <= 0 ? std::nullopt : std::make_optional(data), result); - } -#endif // Need to substring to remove any null terminating bytes if (result.first >= 0 && result.first < receiveLength) @@ -184,7 +174,7 @@ namespace kt data = data.substr(0, result.first); } - return std::make_pair(result.first <= 0 ? std::nullopt : std::make_optional(data), result); + return std::make_pair(data.size() == 0 ? std::nullopt : std::make_optional(data), result); } std::pair UDPSocket::receiveFrom(char* buffer, const int& receiveLength, const int& flags) const @@ -195,6 +185,8 @@ namespace kt return std::make_pair(-1, receiveAddress); } + // Using auto here since the "addressLength" argument for "::recvfrom()" has differing types depending what platform + // we are on, so I am letting the definition of kt::getAddressLength() drive this type via auto auto addressLength = kt::getAddressLength(receiveAddress); int flag = ::recvfrom(this->receiveSocket, buffer, receiveLength, flags, &receiveAddress.address, &addressLength); return std::make_pair(flag, receiveAddress); diff --git a/tests/address/SocketAddressTest.cpp b/tests/address/SocketAddressTest.cpp index 1bb2b84..eced757 100644 --- a/tests/address/SocketAddressTest.cpp +++ b/tests/address/SocketAddressTest.cpp @@ -7,13 +7,19 @@ namespace kt { + /** + * Ensure a default initialised SocketAddress has "Any" set as the IP version. + */ TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersion_DefaultStruct) { kt::SocketAddress address{}; ASSERT_EQ(kt::InternetProtocolVersion::Any, kt::getInternetProtocolVersion(address)); } - TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersion_IPV4) + /** + * Ensure that a SocketAddress with socket family set to IPV4 is resolved to the IPV4 enum. + */ + TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersionAndGetAddress_IPV4) { const std::string localhost = "localhost"; const unsigned int port = 0; @@ -26,9 +32,14 @@ namespace kt ASSERT_FALSE(results.first.empty()); kt::SocketAddress firstAddress = results.first.at(0); ASSERT_EQ(version, kt::getInternetProtocolVersion(firstAddress)); + + ASSERT_EQ("127.0.0.1", kt::getAddress(firstAddress).value()); } - TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersion_IPV6) + /** + * Ensure that a SocketAddress with socket family set to IPV6 is resolved to the IPV6 enum. + */ + TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersionAndGetAddress_IPV6) { const std::string localhost = "localhost"; const unsigned int port = 0; @@ -41,8 +52,13 @@ namespace kt ASSERT_FALSE(results.first.empty()); kt::SocketAddress firstAddress = results.first.at(0); ASSERT_EQ(version, kt::getInternetProtocolVersion(firstAddress)); + + ASSERT_EQ("::1", kt::getAddress(firstAddress).value()); } + /** + * Ensure that a SocketAddress with an undefined socket family is resolved to Any. + */ TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersion_RandomSocketFamily) { kt::SocketAddress address{}; @@ -55,12 +71,18 @@ namespace kt ASSERT_EQ(kt::InternetProtocolVersion::Any, kt::getInternetProtocolVersion(address)); } + /** + * Ensure a default SocketAddress resolves to 0 as the port number. + */ TEST(SocketAddressTest, SocketAddressGetPortNumber_NoPortNumberSet) { kt::SocketAddress address{}; ASSERT_EQ(0, kt::getPortNumber(address)); } + /** + * Ensure that the port number can be retrieved if its set in the IPV4 struct and the family it set to IPV4. + */ TEST(SocketAddressTest, SocketAddressGetPortNumber_IPV4PortAndFamily) { const unsigned short port = 57323; @@ -71,6 +93,11 @@ namespace kt ASSERT_EQ(port, kt::getPortNumber(address)); } + /** + * Ensure that the port number can be retrieved if its set in the IPV4 struct and the family it set to IPV6. + * + * This is confirming that the port is in the same position in the struct no matter which representation we use. + */ TEST(SocketAddressTest, SocketAddressGetPortNumber_IPV4PortAndIPV6Family) { const unsigned short port = 3248; @@ -81,6 +108,9 @@ namespace kt ASSERT_EQ(port, kt::getPortNumber(address)); } + /** + * Ensure that the port number can be retrieved if its set in the IPV6 struct and the family it set to IPV6. + */ TEST(SocketAddressTest, SocketAddressGetPortNumber_IPV6PortAndFamily) { const unsigned short port = 64123; @@ -91,6 +121,11 @@ namespace kt ASSERT_EQ(port, kt::getPortNumber(address)); } + /** + * Ensure that the port number can be retrieved if its set in the IPV6 struct and the family it set to IPV4. + * + * This is confirming that the port is in the same position in the struct no matter which representation we use. + */ TEST(SocketAddressTest, SocketAddressGetPortNumber_IPV6PortAndIPV4Family) { const unsigned short port = 37652; @@ -100,4 +135,31 @@ namespace kt address.ipv6.sin6_port = htons(port); ASSERT_EQ(port, kt::getPortNumber(address)); } + + TEST(SocketAddressTest, SocketAddressGetAddressLength_DefaultAddress) + { + kt::SocketAddress address{}; + ASSERT_EQ(sizeof(address.ipv6), kt::getAddressLength(address)); + } + + TEST(SocketAddressTest, SocketAddressGetAddressLength_IPV6Family) + { + kt::SocketAddress address{}; + address.address.sa_family = static_cast(kt::InternetProtocolVersion::IPV6); + ASSERT_EQ(sizeof(address.ipv6), kt::getAddressLength(address)); + } + + TEST(SocketAddressTest, SocketAddressGetAddressLength_IPV4Family) + { + kt::SocketAddress address{}; + address.address.sa_family = static_cast(kt::InternetProtocolVersion::IPV4); + ASSERT_EQ(sizeof(address.ipv4), kt::getAddressLength(address)); + } + + TEST(SocketAddressTest, SocketAddressGetAddress_DefaultAddress) + { + kt::SocketAddress address{}; + std::optional result = kt::getAddress(address); + ASSERT_EQ(std::nullopt, result); + } } diff --git a/tests/serversocket/ServerSocketTCPTest.cpp b/tests/serversocket/ServerSocketTCPTest.cpp index 401214f..4e4ba74 100644 --- a/tests/serversocket/ServerSocketTCPTest.cpp +++ b/tests/serversocket/ServerSocketTCPTest.cpp @@ -7,7 +7,7 @@ #include "../../src/socketexceptions/BindingException.hpp" #include "../../src/socketexceptions/TimeoutException.hpp" -const unsigned short PORT_NUMBER = 87682; +const unsigned short PORT_NUMBER = 47682; namespace kt { From aba069829d88268ac38872edcb5f187474fc4e8f Mon Sep 17 00:00:00 2001 From: Kilemonn Date: Fri, 28 Jun 2024 16:15:18 +0900 Subject: [PATCH 21/21] Continue with adding tests for SocketAddress and tweaking behaviour as needed to behave as I need. --- src/address/SocketAddress.cpp | 4 +-- src/address/SocketAddress.h | 2 +- src/socket/UDPSocket.cpp | 4 +++ src/socketexceptions/SocketError.cpp | 2 ++ tests/address/SocketAddressTest.cpp | 48 ++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/address/SocketAddress.cpp b/src/address/SocketAddress.cpp index d56a974..679764f 100644 --- a/src/address/SocketAddress.cpp +++ b/src/address/SocketAddress.cpp @@ -60,10 +60,10 @@ namespace kt kt::SocketAddress address{}; socklen_t socketSize = sizeof(address); int result = getsockname(socket, &address.address, &socketSize); - return std::make_pair(std::make_optional(address), result); + return std::make_pair(result == -1 ? std::nullopt : std::make_optional(address), result); } - std::pair, int> resolveToAddresses(const std::optional& hostname, const unsigned int& port, addrinfo& hints) + std::pair, int> resolveToAddresses(const std::optional& hostname, const unsigned short& port, addrinfo& hints) { std::vector addresses; addrinfo* resolvedAddresses = nullptr; diff --git a/src/address/SocketAddress.h b/src/address/SocketAddress.h index 1fd9e1e..429c1b4 100644 --- a/src/address/SocketAddress.h +++ b/src/address/SocketAddress.h @@ -56,7 +56,7 @@ namespace kt std::pair, int> socketToAddress(const SOCKET&); - std::pair, int> resolveToAddresses(const std::optional&, const unsigned int&, addrinfo&); + std::pair, int> resolveToAddresses(const std::optional&, const unsigned short&, addrinfo&); #ifdef _WIN32 int getAddressLength(const kt::SocketAddress&); diff --git a/src/socket/UDPSocket.cpp b/src/socket/UDPSocket.cpp index 1c48d1f..f8a1abd 100644 --- a/src/socket/UDPSocket.cpp +++ b/src/socket/UDPSocket.cpp @@ -188,6 +188,10 @@ namespace kt // Using auto here since the "addressLength" argument for "::recvfrom()" has differing types depending what platform // we are on, so I am letting the definition of kt::getAddressLength() drive this type via auto auto addressLength = kt::getAddressLength(receiveAddress); + + // In some scenarios Windows will return a -1 flag value but the buffer is populated properly with the correct length + // The code it is returning is 10040 this is indicating that the provided buffer is too small for the incoming + // message, there is probably some settings we can tweak, however I think this is okay to return for now. int flag = ::recvfrom(this->receiveSocket, buffer, receiveLength, flags, &receiveAddress.address, &addressLength); return std::make_pair(flag, receiveAddress); } diff --git a/src/socketexceptions/SocketError.cpp b/src/socketexceptions/SocketError.cpp index 2d12237..2add99a 100644 --- a/src/socketexceptions/SocketError.cpp +++ b/src/socketexceptions/SocketError.cpp @@ -2,6 +2,8 @@ #include #include +#include "SocketError.h" + #ifdef _WIN32 #include diff --git a/tests/address/SocketAddressTest.cpp b/tests/address/SocketAddressTest.cpp index eced757..a41f545 100644 --- a/tests/address/SocketAddressTest.cpp +++ b/tests/address/SocketAddressTest.cpp @@ -2,6 +2,7 @@ #include #include +#include "../../src/socketexceptions/SocketError.h" #include "../../src/address/SocketAddress.h" #include "../../src/enums/InternetProtocolVersion.h" @@ -18,6 +19,7 @@ namespace kt /** * Ensure that a SocketAddress with socket family set to IPV4 is resolved to the IPV4 enum. + * Ensure the address returned is "127.0.0.1" for this test. */ TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersionAndGetAddress_IPV4) { @@ -38,6 +40,7 @@ namespace kt /** * Ensure that a SocketAddress with socket family set to IPV6 is resolved to the IPV6 enum. + * Ensure the address returned is "::1" for this test. */ TEST(SocketAddressTest, SocketAddressGetInternetProtocolVersionAndGetAddress_IPV6) { @@ -136,12 +139,18 @@ namespace kt ASSERT_EQ(port, kt::getPortNumber(address)); } + /** + * Ensure a default address gives the bigger IPV6 address size. + */ TEST(SocketAddressTest, SocketAddressGetAddressLength_DefaultAddress) { kt::SocketAddress address{}; ASSERT_EQ(sizeof(address.ipv6), kt::getAddressLength(address)); } + /** + * Ensure an IPV6 family address returns the IPV6 address size. + */ TEST(SocketAddressTest, SocketAddressGetAddressLength_IPV6Family) { kt::SocketAddress address{}; @@ -149,6 +158,9 @@ namespace kt ASSERT_EQ(sizeof(address.ipv6), kt::getAddressLength(address)); } + /** + * Ensure an IPV4 family address returns the IPV4 address size. + */ TEST(SocketAddressTest, SocketAddressGetAddressLength_IPV4Family) { kt::SocketAddress address{}; @@ -156,10 +168,46 @@ namespace kt ASSERT_EQ(sizeof(address.ipv4), kt::getAddressLength(address)); } + /** + * Ensure a nullopt is returned with a default address. + */ TEST(SocketAddressTest, SocketAddressGetAddress_DefaultAddress) { kt::SocketAddress address{}; std::optional result = kt::getAddress(address); ASSERT_EQ(std::nullopt, result); } + + /** + * Ensure we get nullopt when we provide an invalid socket. + */ + TEST(SocketAddressTest, SocketAddressSocketToAddress_InvalidSocket) + { + SOCKET socket = kt::getInvalidSocketValue(); + std::pair, int> result = kt::socketToAddress(socket); + ASSERT_EQ(std::nullopt, result.first); + ASSERT_EQ(-1, result.second); + } + + TEST(SocketAddressTest, SocketAddressResolveToAddresses_InvalidAddress) + { + std::string hostname = "cpp-socket-library.test.hostname"; + unsigned short port = 0; + addrinfo hints{}; + + std::pair, int> results = kt::resolveToAddresses(std::make_optional(hostname), port, hints); + ASSERT_NE(0, results.second); + ASSERT_TRUE(results.first.empty()); + } + + TEST(SocketAddressTest, SocketAddressResolveToAddresses_ResolveLocalhost) + { + std::string localhost = "localhost"; + unsigned short port = 0; + addrinfo hints{}; + + std::pair, int> results = kt::resolveToAddresses(std::make_optional(localhost), port, hints); + ASSERT_EQ(0, results.second); + ASSERT_FALSE(results.first.empty()); + } }