From 9b46d486cbd6cc744fbf99abbfb5c48e567d71bb Mon Sep 17 00:00:00 2001 From: Kyle Schwarz Date: Wed, 8 Mar 2023 18:32:26 +0000 Subject: [PATCH] Driver: Add TCP support Device: Close Driver in heartbeat thread on disconnection --- CMakeLists.txt | 13 + api/icsneocpp/event.cpp | 15 + ci/build-windows.bat | 2 +- device/device.cpp | 4 +- device/devicefinder.cpp | 8 + include/icsneo/api/event.h | 5 + include/icsneo/platform/tcp.h | 57 ++++ platform/posix/pcap.cpp | 9 +- platform/tcp.cpp | 564 ++++++++++++++++++++++++++++++++++ platform/windows/pcap.cpp | 9 +- 10 files changed, 674 insertions(+), 12 deletions(-) create mode 100644 include/icsneo/platform/tcp.h create mode 100644 platform/tcp.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b4944b4..1f46726 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ option(LIBICSNEO_ENABLE_FIRMIO "Enable communication between Linux and CoreMini option(LIBICSNEO_ENABLE_RAW_ETHERNET "Enable devices which communicate over raw ethernet" ON) option(LIBICSNEO_ENABLE_CDCACM "Enable devices which communicate over USB CDC ACM" ON) option(LIBICSNEO_ENABLE_FTDI "Enable devices which communicate over USB FTDI2XX" ON) +option(LIBICSNEO_ENABLE_TCP "Enable devices which communicate over TCP" OFF) if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 17) @@ -157,6 +158,12 @@ else() # Darwin or Linux endif() endif() +if(LIBICSNEO_ENABLE_TCP) + list(APPEND PLATFORM_SRC + platform/tcp.cpp + ) +endif() + if(LIBICSNEO_BUILD_EXAMPLES) add_subdirectory(examples) endif() @@ -284,6 +291,12 @@ endif() if(LIBICSNEO_ENABLE_FTDI) target_compile_definitions(icsneocpp PRIVATE ICSNEO_ENABLE_FTDI) endif() +if(LIBICSNEO_ENABLE_TCP) + target_compile_definitions(icsneocpp PRIVATE ICSNEO_ENABLE_TCP) + if(WIN32) + target_link_libraries(icsneocpp PRIVATE ws2_32) + endif() +endif() # fatfs add_subdirectory(third-party/fatfs) diff --git a/api/icsneocpp/event.cpp b/api/icsneocpp/event.cpp index 9a17694..27eb9ba 100644 --- a/api/icsneocpp/event.cpp +++ b/api/icsneocpp/event.cpp @@ -125,6 +125,11 @@ static constexpr const char* DEVICE_IN_USE = "The device is currently in use by static constexpr const char* PCAP_COULD_NOT_START = "The PCAP driver could not be started. Ethernet devices will not be found."; static constexpr const char* PCAP_COULD_NOT_FIND_DEVICES = "The PCAP driver failed to find devices. Ethernet devices will not be found."; static constexpr const char* PACKET_DECODING = "There was an error decoding a packet from the device."; +static constexpr const char* SOCKET_FAILED_TO_OPEN = "Unable to open new socket."; +static constexpr const char* FAILED_TO_BIND = "Unable to bind socket."; +static constexpr const char* ERROR_SETTING_SOCKET_OPTION = "A call to setsockopt() failed."; +static constexpr const char* GETIFADDRS_ERROR = "A call to getifaddrs() failed."; +static constexpr const char* SEND_TO_ERROR = "A call to sendto() failed."; static constexpr const char* TOO_MANY_EVENTS = "Too many events have occurred. The list has been truncated."; static constexpr const char* UNKNOWN = "An unknown internal error occurred."; @@ -266,6 +271,16 @@ const char* APIEvent::DescriptionForType(Type type) { return PCAP_COULD_NOT_FIND_DEVICES; case Type::PacketDecodingError: return PACKET_DECODING; + case Type::SocketFailedToOpen: + return SOCKET_FAILED_TO_OPEN; + case Type::FailedToBind: + return FAILED_TO_BIND; + case Type::ErrorSettingSocketOption: + return ERROR_SETTING_SOCKET_OPTION; + case Type::GetIfAddrsError: + return GETIFADDRS_ERROR; + case Type::SendToError: + return SEND_TO_ERROR; // Other Errors case Type::TooManyEvents: diff --git a/ci/build-windows.bat b/ci/build-windows.bat index 5451d07..b708583 100644 --- a/ci/build-windows.bat +++ b/ci/build-windows.bat @@ -6,7 +6,7 @@ REM build cd build set CFLAGS=/WX set CXXFLAGS=/WX -cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DLIBICSNEO_BUILD_TESTS=ON .. +cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DLIBICSNEO_BUILD_TESTS=ON -DLIBICSNEO_ENABLE_TCP=ON .. if %errorlevel% neq 0 exit /b %errorlevel% cmake --build . if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/device/device.cpp b/device/device.cpp index 2e6fd5e..4ae1fca 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -271,8 +271,10 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) { if(receivedMessage) { receivedMessage = false; } else { - if(!stopHeartbeatThread && !isDisconnected()) + if(!stopHeartbeatThread && !isDisconnected()) { report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + com->driver->close(); + } break; } } diff --git a/device/devicefinder.cpp b/device/devicefinder.cpp index 7f15f8d..d62a874 100644 --- a/device/devicefinder.cpp +++ b/device/devicefinder.cpp @@ -19,6 +19,10 @@ #include "icsneo/platform/ftdi.h" #endif +#ifdef ICSNEO_ENABLE_TCP +#include "icsneo/platform/tcp.h" +#endif + using namespace icsneo; template @@ -47,6 +51,10 @@ std::vector> DeviceFinder::FindAll() { FirmIO::Find(newDriverFoundDevices); #endif + #ifdef ICSNEO_ENABLE_TCP + TCP::Find(newDriverFoundDevices); + #endif + #ifdef ICSNEO_ENABLE_RAW_ETHERNET PCAP::Find(newDriverFoundDevices); #endif diff --git a/include/icsneo/api/event.h b/include/icsneo/api/event.h index 84a2752..85767eb 100644 --- a/include/icsneo/api/event.h +++ b/include/icsneo/api/event.h @@ -103,6 +103,11 @@ public: PCAPCouldNotStart = 0x3102, PCAPCouldNotFindDevices = 0x3103, PacketDecodingError = 0x3104, + SocketFailedToOpen = 0x3105, + FailedToBind = 0x3106, + ErrorSettingSocketOption = 0x3107, + GetIfAddrsError = 0x3108, + SendToError = 0x3109, NoErrorFound = 0xFFFFFFFD, TooManyEvents = 0xFFFFFFFE, diff --git a/include/icsneo/platform/tcp.h b/include/icsneo/platform/tcp.h new file mode 100644 index 0000000..8b0c2a5 --- /dev/null +++ b/include/icsneo/platform/tcp.h @@ -0,0 +1,57 @@ +#ifndef __TCP_H_ +#define __TCP_H_ + +#ifdef __cplusplus + +#include + +#include "icsneo/communication/driver.h" +#include "icsneo/device/founddevice.h" + +namespace icsneo { + +class TCP : public Driver { +public: + static void Find(std::vector& foundDevices); + + struct NetworkInterface { + const std::string name; + const uint32_t ip; + }; + + TCP(const device_eventhandler_t& err, NetworkInterface on, uint32_t dstIP, uint16_t dstPort); + ~TCP() override { if(isOpen()) close(); } + bool open() override; + bool isOpen() override; + bool close() override; + bool isEthernet() const override { return true; } +private: + #ifdef _WIN32 + typedef size_t SocketFileDescriptor; + #else + typedef int SocketFileDescriptor; + #endif + + class Socket { + public: + Socket(int domain, int type, int protocol, bool nonblocking = true); + ~Socket(); + explicit operator bool() const { return fd != -1; } + operator SocketFileDescriptor() const { return fd; } + private: + SocketFileDescriptor fd; + }; + + NetworkInterface interface; + uint32_t dstIP; + uint16_t dstPort; + std::optional socket; + void readTask() override; + void writeTask() override; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/platform/posix/pcap.cpp b/platform/posix/pcap.cpp index 9ffc4d2..a43058c 100644 --- a/platform/posix/pcap.cpp +++ b/platform/posix/pcap.cpp @@ -168,20 +168,19 @@ void PCAP::Find(std::vector& found) { if(!decoder.decode(message, packet)) continue; - const neodevice_handle_t handle = (neodevice_handle_t)((i << 24) | (decoded.srcMAC[3] << 16) | (decoded.srcMAC[4] << 8) | (decoded.srcMAC[5])); - if(std::any_of(found.begin(), found.end(), [&handle](const auto& found) { return handle == found.handle; })) - continue; // We already have this device on this interface - const auto serial = std::dynamic_pointer_cast(message); if(!serial || serial->deviceSerial.size() != 6) continue; FoundDevice foundDevice; - foundDevice.handle = handle; + foundDevice.handle = (neodevice_handle_t)((i << 24) | (decoded.srcMAC[3] << 16) | (decoded.srcMAC[4] << 8) | (decoded.srcMAC[5])); foundDevice.productId = decoded.srcMAC[2]; memcpy(foundDevice.serial, serial->deviceSerial.c_str(), sizeof(foundDevice.serial) - 1); foundDevice.serial[sizeof(foundDevice.serial) - 1] = '\0'; + if(std::any_of(found.begin(), found.end(), [&](const auto& found) { return ::strncmp(foundDevice.serial, found.serial, sizeof(foundDevice.serial)) == 0; })) + continue; // We already have this device on this interface + foundDevice.makeDriver = [](const device_eventhandler_t& report, neodevice_t& device) { return std::unique_ptr(new PCAP(report, device)); }; diff --git a/platform/tcp.cpp b/platform/tcp.cpp new file mode 100644 index 0000000..53b146c --- /dev/null +++ b/platform/tcp.cpp @@ -0,0 +1,564 @@ + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "icsneo/platform/tcp.h" + +#ifdef _WIN32 +#define WIN_INT(a) static_cast(a) +#else +#define WIN_INT(a) a +#endif + +#ifdef __APPLE__ +#define APPLE_SIN_LEN(a) a.sin_len = sizeof(struct sockaddr_in); +#else +#define APPLE_SIN_LEN(a) +#endif + +using namespace icsneo; + +TCP::Socket::Socket(int domain, int type, int protocol, bool nonblocking) { + #ifdef _WIN32 + class WSAState { + public: + WSAState() { + WSADATA wsaData = {}; + valid = ::WSAStartup(MAKEWORD(2, 2), &wsaData) == 0; + } + ~WSAState() { + ::WSACleanup(); + } + explicit operator bool() const { return valid; } + private: + bool valid = false; + }; + static const WSAState WSA_STATE; + if(!WSA_STATE) + return; + #endif + fd = ::socket(domain, type, protocol); + if(nonblocking) { + #ifdef _WIN32 + unsigned long param = 1; + ::ioctlsocket(fd, FIONBIO, ¶m); + #else + const int flags = fcntl(fd, F_GETFL, 0); + ::fcntl(fd, F_SETFL, flags | O_NONBLOCK); + #endif + } +} + +TCP::Socket::~Socket() { + #ifdef _WIN32 + ::closesocket(fd); + #else + ::close(fd); + #endif +} + +void TCP::Find(std::vector& found) { + static const auto MDNS_PORT = htons((unsigned short)5353); + static const auto MDNS_IP = htonl((((uint32_t)224U) << 24U) | ((uint32_t)251U)); + + class IFAddresses { + public: + #ifdef _WIN32 + typedef IP_ADAPTER_ADDRESSES* InterfaceHandle; + #else + typedef ifaddrs* InterfaceHandle; + #endif + + class Interface { + public: + Interface(InterfaceHandle handle) : handle(handle) {} + Interface next() { + #ifdef _WIN32 + return Interface(handle->Next); + #else + return Interface(handle->ifa_next); + #endif + } + unsigned flags() { + #ifdef _WIN32 + return handle->Flags; + #else + return handle->ifa_flags; + #endif + } + explicit operator bool() { + return handle; + } + bool validType() { + #ifdef _WIN32 + return + handle && + (handle->TunnelType != TUNNEL_TYPE_TEREDO) && + (handle->OperStatus == IfOperStatusUp) && + (address()->sa_family == AF_INET); + #else + return + handle && + handle->ifa_addr && + (flags() & IFF_UP) && + (flags() & IFF_MULTICAST) && + !(flags() & IFF_LOOPBACK) && + !(flags() & IFF_POINTOPOINT) && + (handle->ifa_addr->sa_family == AF_INET) && + (((sockaddr_in*)address())->sin_addr.s_addr != htonl(INADDR_LOOPBACK)); + #endif + + } + InterfaceHandle operator->() { + return handle; + } + sockaddr* address() const { + #ifdef _WIN32 + return handle->FirstUnicastAddress->Address.lpSockaddr; + #else + return handle->ifa_addr; + #endif + } + std::string_view name() const { + #ifdef _WIN32 + return handle->AdapterName; + #else + return handle->ifa_name; + #endif + } + private: + InterfaceHandle handle; + }; + + IFAddresses() { + #ifdef _WIN32 + unsigned long ret; + unsigned long size = 15'000; + do { + storage.resize(size); + ret = ::GetAdaptersAddresses(AF_INET, 0, NULL, (InterfaceHandle)storage.data(), &size); + } while (ret == ERROR_BUFFER_OVERFLOW); + front = (InterfaceHandle)storage.data(); + #else + ::getifaddrs(&front); + #endif + } + ~IFAddresses() { + #ifdef _WIN32 + #else + ::freeifaddrs(front); + #endif + } + + explicit operator bool() { + return front; + } + Interface begin() const { + return Interface(front); + } + private: + #ifdef _WIN32 + std::vector storage; + #endif + InterfaceHandle front; + }; + + IFAddresses interfaces; + if(!interfaces) { + EventManager::GetInstance().add(APIEvent::Type::GetIfAddrsError, APIEvent::Severity::EventWarning); + return; + } + + for(auto interface = interfaces.begin(); interface; interface = interface.next()) { + if(!interface.validType()) + continue; + Socket socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if(!socket) { + EventManager::GetInstance().add(APIEvent::Type::SocketFailedToOpen, APIEvent::Severity::EventWarning); + return; + } + + { + unsigned int reuse = 1; + if(::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) { + EventManager::GetInstance().add(APIEvent::Type::ErrorSettingSocketOption, APIEvent::Severity::EventWarning); + continue; + } + #ifndef _WIN32 + if(::setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) { + EventManager::GetInstance().add(APIEvent::Type::ErrorSettingSocketOption, APIEvent::Severity::EventWarning); + continue; + } + #ifndef __APPLE__ + if(::setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, interface.name().data(), interface.name().size()) < 0) { + EventManager::GetInstance().add(APIEvent::Type::ErrorSettingSocketOption, APIEvent::Severity::EventWarning); + continue; + } + #endif + #endif + } + auto ifAddrIn = (sockaddr_in*)interface.address(); + ifAddrIn->sin_port = MDNS_PORT; + + { + sockaddr_in addr = *ifAddrIn; + APPLE_SIN_LEN(addr); + ::setsockopt(socket, IPPROTO_IP, IP_MULTICAST_IF, (const char*)&addr.sin_addr, sizeof(addr.sin_addr)); + #ifndef _WIN32 + addr.sin_addr.s_addr = INADDR_ANY; + #endif + if(::bind(socket, (sockaddr*)&addr, sizeof(addr)) == -1) { + EventManager::GetInstance().add(APIEvent::Type::FailedToBind, APIEvent::Severity::EventWarning); + continue; + } + } + + { + ip_mreq req = {}; + req.imr_multiaddr.s_addr = htonl((((uint32_t)224U) << 24U) | ((uint32_t)251U)); + req.imr_interface = ifAddrIn->sin_addr; + ::setsockopt(socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&req, sizeof(req)); + } + + std::array query = { + 0x00, 0x00, /* id */ + 0x00, 0x00, /* flags */ + 0x00, 0x01, /* query count */ + 0x00, 0x00, /* answer count */ + 0x00, 0x00, /* auth count */ + 0x00, 0x00, /* additional count */ + 0x06, '_', 'n', 'e', 'o', 'v', 'i', 0x04, '_', 't', 'c', 'p', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, + 0x00, 0x0c, /* type*/ + 0x00, 0x01 /* class */ + }; + sockaddr_in addr = {}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = MDNS_IP; + addr.sin_port = MDNS_PORT; + APPLE_SIN_LEN(addr); + if(::sendto(socket, (char*)query.data(), WIN_INT(query.size()), 0, (sockaddr*)&addr, sizeof(addr)) < 0) { + EventManager::GetInstance().add(APIEvent::Type::SendToError, APIEvent::Severity::EventWarning); + continue; + } + + timeval timeout = {}; + timeout.tv_usec = 50000; + fd_set readfs; + FD_ZERO(&readfs); + int nfds = WIN_INT(socket) + 1; + FD_SET(socket, &readfs); + while(true) { + static constexpr size_t bufferLen = 2048; + uint8_t buffer[bufferLen]; + ::select(nfds, &readfs, 0, 0, &timeout); // timeout is intentially not reset, we want timeout.tv_usec _total_ + const auto recvRet = ::recv(socket, (char*)buffer, bufferLen, 0); + static constexpr auto headerLength = 12; + if(recvRet < headerLength) { + break; + } + uint8_t* bufferEnd = buffer + recvRet; + const auto flags = ntohs(*(uint16_t*)(buffer + 2)); + const bool isResponse = flags >> 15; + if(!isResponse) + continue; + const auto answerCount = ntohs(*(uint16_t*)(buffer + 6)); + const auto additionalCount = ntohs(*(uint16_t*)(buffer + 10)); + if(answerCount < 1 || additionalCount < 1) { + continue; + } + std::function(uint8_t*,std::vector&)> parseStrings = [&](uint8_t* start, std::vector& strings) -> std::optional { + uint8_t* origStart = start; + for(size_t i = 0; i < 10 /* infinite loop prevention */; ++i) { + if(start >= bufferEnd) + return std::nullopt; + static constexpr uint8_t isCompressed = 0xC0; + if(*start & isCompressed) { + if(start + 2 > bufferEnd) + return std::nullopt; + uint16_t offset = ntohs(*(uint16_t*)start) & 0x3FFF; + if(buffer + offset > bufferEnd) + return std::nullopt; + parseStrings(buffer + offset, strings); + start += 2; + return start - origStart; + } else if(*start == 0) { + return start + 1 - origStart; + } else { + if(start + 1 /* skip the length */ + *start > bufferEnd - 1 /* every string ends with '\0' */) + return std::nullopt; + strings.emplace_back((char*)(start + 1), *start); + start += 1 + *start; + } + } + return std::nullopt; + }; + struct Record { + enum class Type { + PTR = 0x000C, + SRV = 0x0021, + A = 0x0001, + }; + std::vector name; + Type type; + uint8_t* data; + uint16_t dataLength; + }; + const auto parseRecord = [&](uint8_t* start, Record& parsedRecord) -> std::optional { + uint8_t* origStart = start; + const auto nameLength = parseStrings(start, parsedRecord.name); + if(!nameLength) + return std::nullopt; + if(start + *nameLength + 10 /* type + flags + TTL + data length */ > bufferEnd) + return std::nullopt; + start += *nameLength; + parsedRecord.type = Record::Type(ntohs(*(uint16_t*)start)); + start += 8; /* type + flags + TTL */ + parsedRecord.dataLength = ntohs(*(uint16_t*)start); + start += 2; + parsedRecord.data = start; + start += parsedRecord.dataLength; + return start - origStart; + }; + + const auto parseRecords = [&](uint8_t* start, std::vector& parsed) -> std::optional { + uint8_t* origStart = start; + for(auto& record : parsed) { + const auto recordLength = parseRecord(start, record); + if(!recordLength || start + *recordLength > bufferEnd) + return std::nullopt; + start += *recordLength; + } + return start - origStart; + }; + std::vector answers(answerCount); + const auto answersLength = parseRecords(buffer + headerLength, answers); + if(!answersLength) + continue; + + std::vector additional(additionalCount); + const auto additionalLength = parseRecords(buffer + headerLength + *answersLength, additional); + if(!additionalLength) + continue; + FoundDevice foundDevice; + + const auto fillSerial = [&]() -> bool { + for(const auto& record : answers) { + if(record.name.size() == 3 && record.name[0] == "_neovi" && record.name[1] == "_tcp" && record.name[2] == "local") { + constexpr size_t deviceSerialBufferLength = sizeof(foundDevice.serial); + std::copy(record.data + 1 /* length field */, record.data + deviceSerialBufferLength, foundDevice.serial); + foundDevice.serial[deviceSerialBufferLength - 1] = '\0'; + return true; + } + } + return false; + }; + + if(!fillSerial()) + continue; + + std::optional devIP; + std::optional devPort; + const auto fillDevEndpoint = [&]() -> bool { + for(const auto& record : additional) { + if(record.name.size() == 4 && record.name[1] == "_neovi" && record.name[2] == "_tcp" && record.name[3] == "local") { + if(record.type == Record::Type::A) { + if(record.dataLength != 4) + return false; + devIP.emplace(ntohl(*(uint32_t*)record.data)); + } else if(record.type == Record::Type::SRV) { + if(record.dataLength != 8 /* priority + weight + port */) + return false; + devPort.emplace(ntohs(*(uint16_t*)(record.data + 4))); + } + } + } + return devIP && devPort; + }; + + if(!fillDevEndpoint()) + continue; + + NetworkInterface on = { + std::string(interface.name()), + ntohl(ifAddrIn->sin_addr.s_addr) + }; + + foundDevice.makeDriver = [=](const device_eventhandler_t& eh, neodevice_t&) { + return std::unique_ptr(new TCP(eh, on, *devIP, *devPort)); + }; + found.push_back(std::move(foundDevice)); + } + } +} + +TCP::TCP(const device_eventhandler_t& err, NetworkInterface on, uint32_t dstIP, uint16_t dstPort) : + Driver(err), interface(on), dstIP(dstIP), dstPort(dstPort) { +} + +bool TCP::open() { + if(socket) { + report(APIEvent::Type::DeviceCurrentlyOpen, APIEvent::Severity::Error); + return false; + } + + socket.emplace(AF_INET, SOCK_STREAM, IPPROTO_TCP); + #if !defined(_WIN32) && !defined(__APPLE__) + if(::setsockopt(*socket, SOL_SOCKET, SO_BINDTODEVICE, interface.name.c_str(), interface.name.size()) < 0) { + report(APIEvent::Type::ErrorSettingSocketOption, APIEvent::Severity::Error); + return false; + } + #endif + + { + sockaddr_in addr = {}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(interface.ip); + APPLE_SIN_LEN(addr); + if(::bind(*socket, (sockaddr*)&addr, sizeof(addr)) < 0) { + report(APIEvent::Type::FailedToBind, APIEvent::Severity::Error); + return false; + } + } + + { + sockaddr_in addr = {}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(dstIP); + addr.sin_port = htons(dstPort); + APPLE_SIN_LEN(addr); + + // the socket is non-blocking so it's expected that the first connect will fail + if(::connect(*socket, (sockaddr*)&addr, sizeof(addr)) == 0) { + report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + return false; + } + #ifdef _WIN32 + if(::WSAGetLastError() != WSAEWOULDBLOCK) { + report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + return false; + } + #else + if(errno != EINPROGRESS) { + report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + return false; + } + #endif + + timeval timeout = {}; + timeout.tv_sec = 1; + fd_set writefs; + FD_ZERO(&writefs); + int nfds = WIN_INT(*socket) + 1; + FD_SET(*socket, &writefs); + ::select(nfds, 0, &writefs, 0, &timeout); + + if(::connect(*socket, (sockaddr*)&addr, sizeof(addr)) < 0) { + #ifdef _WIN32 + if(::WSAGetLastError() != WSAEISCONN) { + report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + return false; + } + #elif defined(__APPLE__) + if(errno != EISCONN) { + report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + return false; + } + #else + report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + return false; + #endif + } + } + + readThread = std::thread(&TCP::readTask, this); + writeThread = std::thread(&TCP::writeTask, this); + return true; +} + +bool TCP::isOpen() { + return socket.has_value(); +} + +bool TCP::close() { + if(!isOpen() && !isDisconnected()) { + report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); + return false; + } + + closing = true; + disconnected = false; + + if(readThread.joinable()) + readThread.join(); + if(writeThread.joinable()) + writeThread.join(); + + uint8_t flush; + WriteOperation flushop; + while(readQueue.try_dequeue(flush)) {} + while(writeQueue.try_dequeue(flushop)) {} + + socket.reset(); + + return true; +} + +void TCP::readTask() { + EventManager::GetInstance().downgradeErrorsOnCurrentThread(); + + const int nfds = WIN_INT(*socket) + 1; + fd_set readfs; + FD_ZERO(&readfs); + FD_SET(*socket, &readfs); + timeval timeout; + + constexpr size_t READ_BUFFER_SIZE = 2048; + uint8_t readbuf[READ_BUFFER_SIZE]; + while(!closing) { + if(const auto received = ::recv(*socket, (char*)readbuf, READ_BUFFER_SIZE, 0); received > 0) { + readQueue.enqueue_bulk(readbuf, received); + } else { + timeout.tv_sec = 0; + timeout.tv_usec = 50'000; + ::select(nfds, &readfs, 0, 0, &timeout); + } + } +} + +void TCP::writeTask() { + EventManager::GetInstance().downgradeErrorsOnCurrentThread(); + + const int nfds = WIN_INT(*socket) + 1; + fd_set writefs; + FD_ZERO(&writefs); + FD_SET(*socket, &writefs); + timeval timeout; + + WriteOperation writeOp; + while(!closing) { + if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100))) + continue; + + while(!closing) { + if(::send(*socket, (char*)writeOp.bytes.data(), WIN_INT(writeOp.bytes.size()), 0) > 0) + break; + timeout.tv_sec = 0; + timeout.tv_usec = 100'000; + ::select(nfds, 0, &writefs, 0, &timeout); + } + } +} diff --git a/platform/windows/pcap.cpp b/platform/windows/pcap.cpp index fb1a513..c22592c 100644 --- a/platform/windows/pcap.cpp +++ b/platform/windows/pcap.cpp @@ -152,20 +152,19 @@ void PCAP::Find(std::vector& found) { if(!decoder.decode(message, packet)) continue; - const neodevice_handle_t handle = (neodevice_handle_t)((i << 24) | (decoded.srcMAC[3] << 16) | (decoded.srcMAC[4] << 8) | (decoded.srcMAC[5])); - if(std::any_of(found.begin(), found.end(), [&handle](const auto& found) { return handle == found.handle; })) - continue; // We already have this device on this interface - const auto serial = std::dynamic_pointer_cast(message); if(!serial || serial->deviceSerial.size() != 6) continue; FoundDevice foundDevice; - foundDevice.handle = handle; + foundDevice.handle = (neodevice_handle_t)((i << 24) | (decoded.srcMAC[3] << 16) | (decoded.srcMAC[4] << 8) | (decoded.srcMAC[5])); foundDevice.productId = decoded.srcMAC[2]; memcpy(foundDevice.serial, serial->deviceSerial.c_str(), sizeof(foundDevice.serial) - 1); foundDevice.serial[sizeof(foundDevice.serial) - 1] = '\0'; + if(std::any_of(found.begin(), found.end(), [&](const auto& found) { return ::strncmp(foundDevice.serial, found.serial, sizeof(foundDevice.serial)) == 0; })) + continue; + foundDevice.makeDriver = [](const device_eventhandler_t& reportFn, neodevice_t& device) { return std::unique_ptr(new PCAP(reportFn, device)); };