diff --git a/CMakeLists.txt b/CMakeLists.txt index b7e0364..e26becd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ option(LIBICSNEO_BUILD_ICSNEOC_STATIC "Build static C library" ON) option(LIBICSNEO_BUILD_ICSNEOLEGACY "Build icsnVC40 compatibility library" ON) set(LIBICSNEO_NPCAP_INCLUDE_DIR "" CACHE STRING "Npcap include directory; set to build with Npcap") +option(LIBICSNEO_USE_DEVICE_SHARING "Interact with devices through the sharing server" ON) + # Device Drivers # You almost certainly don't want firmio for your build, # it is only relevant for communication between Linux and @@ -18,13 +20,13 @@ option(LIBICSNEO_ENABLE_RAW_ETHERNET "Enable devices which communicate over raw 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) -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 17) -endif() - include(GNUInstallDirs) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Enable Warnings if(MSVC) @@ -113,6 +115,11 @@ if(WIN32) platform/windows/vcp.cpp ) endif() + + list(APPEND PLATFORM_SRC + platform/windows/sharedmemory.cpp + platform/windows/sharedsemaphore.cpp + ) else() # Darwin or Linux set(PLATFORM_SRC) @@ -155,6 +162,11 @@ else() # Darwin or Linux endif() endif() endif() + + list(APPEND PLATFORM_SRC + platform/posix/sharedmemory.cpp + platform/posix/sharedsemaphore.cpp + ) endif() if(LIBICSNEO_BUILD_EXAMPLES) @@ -192,6 +204,9 @@ set(SRC_FILES communication/multichannelcommunication.cpp communication/communication.cpp communication/driver.cpp + communication/interprocessmailbox.cpp + communication/sdio.cpp + communication/socket.cpp device/extensions/flexray/extension.cpp device/extensions/flexray/controller.cpp device/idevicesettings.cpp @@ -265,8 +280,7 @@ target_include_directories(icsneocpp ${CMAKE_CURRENT_SOURCE_DIR}/include ${LIBICSNEO_EXTENSION_INCLUDE_PATHS} ) -set_property(TARGET icsneocpp PROPERTY POSITION_INDEPENDENT_CODE ON) -target_compile_features(icsneocpp PUBLIC cxx_auto_type cxx_constexpr cxx_lambdas cxx_nullptr cxx_range_for cxx_rvalue_references cxx_sizeof_member cxx_strong_enums) + message("Loaded extensions: " ${LIBICSNEO_EXTENSION_TARGETS}) target_link_libraries(icsneocpp PUBLIC ${LIBICSNEO_EXTENSION_TARGETS}) if(LIBICSNEO_ENABLE_FIRMIO) @@ -281,10 +295,17 @@ endif() if(LIBICSNEO_ENABLE_FTDI) target_compile_definitions(icsneocpp PRIVATE ICSNEO_ENABLE_FTDI) endif() +if(LIBICSNEO_USE_DEVICE_SHARING) + target_compile_definitions(icsneocpp PRIVATE ICSNEO_ENABLE_DEVICE_SHARING) +endif() + +# socket +if(WIN32) + target_link_libraries(icsneocpp PRIVATE ws2_32) +endif() # fatfs add_subdirectory(third-party/fatfs) -set_property(TARGET fatfs PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(icsneocpp PRIVATE fatfs) # libftdi @@ -329,6 +350,11 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") target_link_libraries(icsneocpp PUBLIC "-framework CoreFoundation" "-framework IOKit") endif() +# For SharedMemory and SharedSemaphore +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + target_link_libraries(icsneocpp PUBLIC rt) +endif() + if(LIBICSNEO_BUILD_ICSNEOC) add_library(icsneoc SHARED api/icsneoc/icsneoc.cpp ${CMAKE_CURRENT_BINARY_DIR}/generated/icsneoc/version.rc) target_include_directories(icsneoc @@ -393,6 +419,8 @@ if(LIBICSNEO_BUILD_TESTS) test/diskdriverwritetest.cpp test/eventmanagertest.cpp test/ethernetpacketizertest.cpp + test/interprocessmailboxtest.cpp + test/sockettest.cpp test/i2cencoderdecodertest.cpp test/a2bencoderdecodertest.cpp ) diff --git a/api/icsneocpp/event.cpp b/api/icsneocpp/event.cpp index 9a17694..f9de85f 100644 --- a/api/icsneocpp/event.cpp +++ b/api/icsneocpp/event.cpp @@ -4,7 +4,7 @@ using namespace icsneo; -APIEvent::APIEvent(Type type, APIEvent::Severity severity, const Device* device) : eventStruct({}) { +APIEvent::APIEvent(Type type, Severity severity, const Device* device) : eventStruct({}) { this->device = device; if(device) { serial = device->getSerial(); @@ -14,6 +14,17 @@ APIEvent::APIEvent(Type type, APIEvent::Severity severity, const Device* device) init(type, severity); } +APIEvent::APIEvent(neosocketevent_t evStruct, const Device* device) +{ + this->device = device; + timepoint = EventClock::from_time_t(evStruct.timestamp); + serial = std::string(evStruct.serial); + eventStruct.eventNumber = evStruct.eventNumber; + eventStruct.severity = evStruct.severity; + eventStruct.description = DescriptionForType(APIEvent::getType()); + eventStruct.timestamp = evStruct.timestamp; +} + void APIEvent::init(Type event, APIEvent::Severity severity) { timepoint = EventClock::now(); eventStruct.description = DescriptionForType(event); @@ -45,6 +56,15 @@ std::string APIEvent::describe() const noexcept { return ss.str(); } +neosocketevent_t APIEvent::getNeoSocketEvent() const noexcept { + neosocketevent_t neoSocketEvent; + neoSocketEvent.eventNumber = eventStruct.eventNumber; + neoSocketEvent.severity = eventStruct.severity; + std::memcpy(neoSocketEvent.serial, eventStruct.serial, sizeof(eventStruct.serial)); + neoSocketEvent.timestamp = eventStruct.timestamp; + return neoSocketEvent; +} + void APIEvent::downgradeFromError() noexcept { eventStruct.severity = (uint8_t) APIEvent::Severity::EventWarning; } @@ -126,8 +146,31 @@ static constexpr const char* PCAP_COULD_NOT_START = "The PCAP driver could not b 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* SHARED_MEMORY_DATA_IS_NULL = "data() was called on invalid object."; +static constexpr const char* SHARED_MEMORY_FAILED_TO_CLOSE = "The server failed to close a shared memory location."; +static constexpr const char* SHARED_MEMORY_FAILED_TO_OPEN = "The server failed to open a shared memory location."; +static constexpr const char* SHARED_MEMORY_FAILED_TO_UNLINK = "The server failed to unlink a shared memory location."; +static constexpr const char* SHARED_MEMORY_FILE_TRUNCATE_ERROR = "The server failed to truncate shared memory file."; +static constexpr const char* SHARED_MEMORY_MAPPING_ERROR = "The server failed to map a shared memory file."; +static constexpr const char* SHARED_MEMORY_UNMAP_ERROR = "The server failed to unmap a shared memory file."; +static constexpr const char* SHARED_SEMAPHORE_FAILED_TO_CLOSE = "The server failed to close a shared semaphore."; +static constexpr const char* SHARED_SEMAPHORE_FAILED_TO_OPEN = "The server failed to open a shared semaphore."; +static constexpr const char* SHARED_SEMAPHORE_FAILED_TO_POST = "A post() call failed on a shared semaphore."; +static constexpr const char* SHARED_SEMAPHORE_FAILED_TO_UNLINK = "The server failed to unlink a shared semaphore."; +static constexpr const char* SHARED_SEMAPHORE_FAILED_TO_WAIT = "A wait() call failed on a shared semaphore."; +static constexpr const char* SHARED_SEMAPHORE_NOT_OPEN_FOR_POST = "post() was called on a shared semaphore that is not open."; +static constexpr const char* SHARED_SEMAPHORE_NOT_OPEN_FOR_WAIT = "wait() was called on a shared semaphore that is not open."; +static constexpr const char* SOCKET_FAILED_CONNECT = "A socket connection was attempted but failed."; +static constexpr const char* SOCKET_FAILED_OPEN = "A socket failed to open."; +static constexpr const char* SOCKET_FAILED_CLOSE = "A socket failed to close."; +static constexpr const char* SOCKET_FAILED_READ = "A socket read operation failed."; +static constexpr const char* SOCKET_FAILED_WRITE = "A socket write operation failed."; +static constexpr const char* ACCEPTOR_FAILED_BIND = "A socket acceptor failed to bind."; +static constexpr const char* ACCEPTOR_FAILED_LISTEN = "A socket acceptor failed to listen."; + 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."; +static constexpr const char* NO_ERROR_FOUND = "No errors found."; static constexpr const char* INVALID = "An invalid internal error occurred."; const char* APIEvent::DescriptionForType(Type type) { switch(type) { @@ -266,12 +309,58 @@ const char* APIEvent::DescriptionForType(Type type) { return PCAP_COULD_NOT_FIND_DEVICES; case Type::PacketDecodingError: return PACKET_DECODING; - + + // Device Sharing Server Events + case Type::SharedMemoryDataIsNull: + return SHARED_MEMORY_DATA_IS_NULL; + case Type::SharedMemoryFailedToClose: + return SHARED_MEMORY_FAILED_TO_CLOSE; + case Type::SharedMemoryFailedToOpen: + return SHARED_MEMORY_FAILED_TO_OPEN; + case Type::SharedMemoryFailedToUnlink: + return SHARED_MEMORY_FAILED_TO_UNLINK; + case Type::SharedMemoryFileTruncateError: + return SHARED_MEMORY_FILE_TRUNCATE_ERROR; + case Type::SharedMemoryMappingError: + return SHARED_MEMORY_MAPPING_ERROR; + case Type::SharedMemoryUnmapError: + return SHARED_MEMORY_UNMAP_ERROR; + case Type::SharedSemaphoreFailedToClose: + return SHARED_SEMAPHORE_FAILED_TO_CLOSE; + case Type::SharedSemaphoreFailedToOpen: + return SHARED_SEMAPHORE_FAILED_TO_OPEN; + case Type::SharedSemaphoreFailedToPost: + return SHARED_SEMAPHORE_FAILED_TO_POST; + case Type::SharedSemaphoreFailedToUnlink: + return SHARED_SEMAPHORE_FAILED_TO_UNLINK; + case Type::SharedSemaphoreFailedToWait: + return SHARED_SEMAPHORE_FAILED_TO_WAIT; + case Type::SharedSemaphoreNotOpenForPost: + return SHARED_SEMAPHORE_NOT_OPEN_FOR_POST; + case Type::SharedSemaphoreNotOpenForWait: + return SHARED_SEMAPHORE_NOT_OPEN_FOR_WAIT; + case Type::SocketFailedToOpen: + return SOCKET_FAILED_OPEN; + case Type::SocketFailedToClose: + return SOCKET_FAILED_CLOSE; + case Type::SocketFailedToConnect: + return SOCKET_FAILED_CONNECT; + case Type::SocketFailedToRead: + return SOCKET_FAILED_READ; + case Type::SocketFailedToWrite: + return SOCKET_FAILED_WRITE; + case Type::SocketAcceptorFailedToBind: + return ACCEPTOR_FAILED_BIND; + case Type::SocketAcceptorFailedToListen: + return ACCEPTOR_FAILED_LISTEN; + // Other Errors case Type::TooManyEvents: return TOO_MANY_EVENTS; case Type::Unknown: return UNKNOWN; + case Type::NoErrorFound: + return NO_ERROR_FOUND; default: return INVALID; } @@ -291,4 +380,14 @@ bool EventFilter::match(const APIEvent& event) const noexcept { return false; return true; +} + +neosocketeventfilter_t EventFilter::getNeoSocketEventFilter() const noexcept { + neosocketeventfilter_t filterStruct; + filterStruct.eventNumber = static_cast(type); + filterStruct.severity = static_cast(severity); + if((serial.length() + 1) == sizeof(filterStruct.serial)) { + std::memcpy(&filterStruct.serial[0], serial.c_str(), sizeof(filterStruct.serial)); + } + return filterStruct; } \ No newline at end of file diff --git a/api/icsneocpp/eventmanager.cpp b/api/icsneocpp/eventmanager.cpp index 2871c44..d33d831 100644 --- a/api/icsneocpp/eventmanager.cpp +++ b/api/icsneocpp/eventmanager.cpp @@ -1,5 +1,11 @@ #include "icsneo/api/eventmanager.h" +#include "icsneo/api/event.h" #include +#include + +#ifdef ICSNEO_ENABLE_DEVICE_SHARING +#include "icsneo/communication/socket.h" +#endif using namespace icsneo; @@ -40,7 +46,7 @@ void EventManager::add(APIEvent event) { if(i != downgradedThreads.end() && i->second) { event.downgradeFromError(); { - std::lock_guard eventsLock(eventsMutex); + std::lock_guard eventsLock{eventsMutex}; addEventInternal(event); } // free the lock so that callbacks may modify events runCallbacks(event); @@ -146,23 +152,60 @@ bool EventManager::isDowngradingErrorsOnCurrentThread() const { return false; } +#ifdef ICSNEO_ENABLE_DEVICE_SHARING +std::optional> EventManager::getServerEvents(const size_t& max) +{ + auto socket = lockSocket(); + if(!(socket.writeTyped(RPC::GET_EVENTS) && socket.writeTyped(max))) + return std::nullopt; + + size_t count; + if(!socket.readTyped(count) || count < 0 || count > eventLimit) + return std::nullopt; + + std::optional> ret; + if(count == size_t(0)) + return ret; + + auto& eventStructs = ret.emplace(count); + if(!socket.read(eventStructs.data(), (eventStructs.size() * sizeof(neosocketevent_t)))) + return std::nullopt; + + return ret; +} +#endif // ICSNEO_ENABLE_DEVICE_SHARING + void EventManager::get(std::vector& eventOutput, size_t max, EventFilter filter) { - std::lock_guard lk(eventsMutex); - - if(max == 0) // A limit of 0 indicates no limit - max = (size_t)-1; - - size_t count = 0; eventOutput.clear(); - auto it = events.begin(); - while(it != events.end()) { - if(filter.match(*it)) { - eventOutput.push_back(*it); - it = events.erase(it); - if(++count >= max) - break; // We now have as many written to output as we can - } else { - it++; + #ifdef ICSNEO_ENABLE_DEVICE_SHARING + { + auto serverEvents = getServerEvents(max); + if(serverEvents) { + std::scoped_lock eventsLock{eventsMutex}; + for(neosocketevent_t& event : *serverEvents) { + eventOutput.emplace_back(event); + } + if(max != 0u) + max -= eventOutput.size(); + } + } + #endif + std::scoped_lock eventsLock{eventsMutex}; + { + if(max == 0) // A limit of 0 indicates no limit + max = (size_t)-1; + + size_t count = 0; + auto it = events.begin(); + while(it != events.end()) { + if(filter.match(*it)) { + eventOutput.push_back(*it); + it = events.erase(it); + if(++count >= max) + break; // We now have as many written to output as we can + } else { + it++; + } } } } diff --git a/communication/communication.cpp b/communication/communication.cpp index fb33744..f558861 100644 --- a/communication/communication.cpp +++ b/communication/communication.cpp @@ -14,6 +14,10 @@ #include "icsneo/communication/message/readsettingsmessage.h" #include "icsneo/communication/message/versionmessage.h" +#ifdef ICSNEO_ENABLE_DEVICE_SHARING +#include "icsneo/communication/socket.h" +#endif + using namespace icsneo; int Communication::messageCallbackIDCounter = 1; @@ -67,6 +71,11 @@ bool Communication::isDisconnected() { return driver->isDisconnected(); } +void Communication::modifyRawCallbacks(std::function&)>&& cb) { + std::scoped_lock lk(rawCallbacksMutex); + cb(rawCallbacks); +} + bool Communication::sendPacket(std::vector& bytes) { // This is here so that other communication types (like multichannel) can override it return rawWrite(bytes); @@ -217,6 +226,15 @@ std::shared_ptr Communication::waitForMessageSync(std::function returnedMessage; + #ifdef ICSNEO_ENABLE_DEVICE_SHARING + auto socket = lockSocket(); + int64_t ms = timeout.count(); + if(!(socket.writeTyped(RPC::DEVICE_LOCK) && socket.writeString(driver->device.serial) && socket.writeTyped(ms))) + return nullptr; + if(bool ret; !(socket.readTyped(ret) && ret)) + return nullptr; + #endif + std::unique_lock fnLk(syncMessageMutex); // Only allow for one sync message at a time std::unique_lock cvLk(cvMutex); // Don't let the callback fire until we're waiting for it int cb = addMessageCallback(std::make_shared([&cvMutex, &returnedMessage, &cv](std::shared_ptr message) { @@ -239,6 +257,13 @@ std::shared_ptr Communication::waitForMessageSync(std::functiondevice.serial))) + return nullptr; + if(bool ret; !(socket.readTyped(ret) && ret)) + return nullptr; + #endif // Then we either will return the message we got or we will return the empty shared_ptr, caller responsible for checking return returnedMessage; @@ -274,6 +299,11 @@ void Communication::readTask() { } void Communication::handleInput(Packetizer& p, std::vector& readBytes) { + { + std::lock_guard lk(rawCallbacksMutex); + for(auto& cb : rawCallbacks) + cb(readBytes); + } if(redirectingRead) { // redirectingRead is an atomic so it can be set without acquiring a mutex // However, we do not clear it without the mutex. The idea is that if another diff --git a/communication/interprocessmailbox.cpp b/communication/interprocessmailbox.cpp new file mode 100644 index 0000000..bc6977d --- /dev/null +++ b/communication/interprocessmailbox.cpp @@ -0,0 +1,60 @@ +#include +#include "icsneo/communication/interprocessmailbox.h" + +using namespace icsneo; + +bool InterprocessMailbox::open(const std::string& name, bool create) +{ + if(!queuedSem.open(name + "-qs", create)) + return false; + + if(!emptySem.open(name + "-es", create, MESSAGE_COUNT)) + return false; + + if(!sharedMem.open(name + "-sm", BLOCK_SIZE * MESSAGE_COUNT, create)) + return false; + + valid = true; + return true; +} + +InterprocessMailbox::operator bool() const +{ + return valid; +} + +bool InterprocessMailbox::close() +{ + valid = false; + return queuedSem.close() && emptySem.close() && sharedMem.close(); +} + +bool InterprocessMailbox::read(void* data, LengthFieldType& messageLength, const std::chrono::milliseconds& timeout) +{ + if(!queuedSem.wait(timeout)) + return false; + auto it = sharedMem.data() + (index * BLOCK_SIZE); + messageLength = *(LengthFieldType*)it; + it += LENGTH_FIELD_SIZE; + std::memcpy(data, it, std::min(messageLength, MAX_DATA_SIZE)); + if(!emptySem.post()) + return false; + ++index; + index %= MESSAGE_COUNT; + return true; +} + +bool InterprocessMailbox::write(const void* data, LengthFieldType messageLength, const std::chrono::milliseconds& timeout) +{ + if(!emptySem.wait(timeout)) + return false; // the buffer is full and we timed out + auto it = sharedMem.data() + (index * BLOCK_SIZE); + *(LengthFieldType*)it = messageLength; + it += LENGTH_FIELD_SIZE; + std::memcpy(it, data, messageLength); + if(!queuedSem.post()) + return false; + ++index; + index %= MESSAGE_COUNT; + return true; +} diff --git a/communication/sdio.cpp b/communication/sdio.cpp new file mode 100644 index 0000000..3f0dbc7 --- /dev/null +++ b/communication/sdio.cpp @@ -0,0 +1,179 @@ +#include + +#include "icsneo/communication/sdio.h" +#include "icsneo/platform/sharedsemaphore.h" +#include "icsneo/platform/sharedmemory.h" +#include "icsneo/device/device.h" +#include "icsneo/communication/socket.h" + +using namespace icsneo; + +void SDIO::Find(std::vector& found) { + auto socket = lockSocket(); + if(!socket.writeTyped(RPC::DEVICE_FINDER_FIND_ALL)) + return; + uint16_t count; + if(!socket.readTyped(count)) + return; + + static constexpr auto serialSize = sizeof(FoundDevice::serial); + std::vector> serials(count); + if(!socket.read(serials.data(), serials.size() * serialSize)) + return; + + for(const auto& serial : serials) { + auto& foundDevice = found.emplace_back(); + for(std::size_t i = 0; i < serialSize - 1 /* omit '\0' */; i++) + foundDevice.serial[i] = static_cast(std::toupper(serial[i])); + foundDevice.makeDriver = [](const device_eventhandler_t& r, neodevice_t& d) { + return std::unique_ptr(new SDIO(r, d)); + }; + } +} + +bool SDIO::open() { + { + auto socket = lockSocket(); + if(!(socket.writeTyped(RPC::SDIO_OPEN) && socket.writeString(device.device->getSerial()))) + return false; + if(bool ret; !(socket.readTyped(ret) && ret)) + return false; + + { + std::string mailboxName; + if(!socket.readString(mailboxName)) + return false; + if(!inboundIO.open(mailboxName)) + return false; + } + + { + std::string mailboxName; + if(!socket.readString(mailboxName)) + return false; + if(!outboundIO.open(mailboxName)) + return false; + } + } + + readThread = std::thread(&SDIO::readTask, this); + writeThread = std::thread(&SDIO::writeTask, this); + + deviceOpen = true; + + return deviceOpen; +} + +bool SDIO::close() { + if(!isOpen() && !isDisconnected()) { + report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); + return false; + } + + closing = true; + + // wait for the reader/writer threads to close + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + // unblocks the reader/writer threads + if(!inboundIO.close()) + return false; + if(!outboundIO.close()) + return false; + + if(readThread.joinable()) + readThread.join(); + + if(writeThread.joinable()) + writeThread.join(); + + { + auto socket = lockSocket(); + if(!socket.writeTyped(RPC::SDIO_CLOSE)) + return false; + if(!socket.writeString(device.device->getSerial())) + return false; + if(bool ret; !(socket.readTyped(ret) && ret)) + return false; + } + + uint8_t flush; + WriteOperation flushop; + while(readQueue.try_dequeue(flush)) {} + while(writeQueue.try_dequeue(flushop)) {} + + closing = false; + disconnected = false; + deviceOpen = false; + return true; +} + +bool SDIO::isOpen() { + return deviceOpen; +} + +void SDIO::readTask() { + uint8_t data[MAX_DATA_SIZE]; + uint16_t messageLength; + while(!closing) { + if(!inboundIO.read(data, messageLength, std::chrono::milliseconds(100))) { + if(!inboundIO) + break; + continue; + } + + if(messageLength > 0) { + if(messageLength > MAX_DATA_SIZE) { // split message + std::vector reassembled(messageLength); + std::memcpy(reassembled.data(), data, MAX_DATA_SIZE); + auto offset = reassembled.data() + MAX_DATA_SIZE; + for(auto remaining = messageLength - MAX_DATA_SIZE; remaining > 0; remaining -= messageLength) { + if(!inboundIO.read(offset, messageLength, std::chrono::milliseconds(10))) { + report(APIEvent::Type::FailedToRead, APIEvent::Severity::Error); + break; + } + offset += messageLength; + } + readQueue.enqueue_bulk(reassembled.data(), reassembled.size()); + } else { + readQueue.enqueue_bulk(data, messageLength); + } + } + } +} + +void SDIO::writeTask() { + WriteOperation writeOp; + while(!closing && !isDisconnected()) { + if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100))) + continue; + + const auto dataSize = static_cast(writeOp.bytes.size()); + + const auto tryWrite = [&](const void* input, LengthFieldType length) -> bool { + for(int i = 0; i < 50; ++i) { // try to write for 5s, making sure we can close if need be + if(outboundIO.write(input, length, std::chrono::milliseconds(100))) + return true; + if(!outboundIO) + return false; + } + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + return false; + }; + + if(!tryWrite(writeOp.bytes.data(), dataSize)) + continue; + + if(writeOp.bytes.size() > MAX_DATA_SIZE) { + auto offset = writeOp.bytes.data() + MAX_DATA_SIZE; + for(LengthFieldType remaining = dataSize - MAX_DATA_SIZE; remaining > 0; ) { + const auto toWrite = std::min(MAX_DATA_SIZE, remaining); + if(!tryWrite(offset, toWrite)) + break; + remaining -= toWrite; + offset += toWrite; + } + } + } +} diff --git a/communication/socket.cpp b/communication/socket.cpp new file mode 100644 index 0000000..4868d1a --- /dev/null +++ b/communication/socket.cpp @@ -0,0 +1,257 @@ +#include "icsneo/communication/socket.h" +#include "icsneo/api/event.h" +#include "icsneo/api/eventmanager.h" + +namespace icsneo { + +bool SocketBase::open() { + #ifdef _WIN32 + WSADATA wsaData; + if(::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + EventManager::GetInstance().add(APIEvent::Type::SocketFailedToOpen, APIEvent::Severity::Error); + return false; + } + #endif + + if((sockFileDescriptor = ::socket(AF_INET, SOCK_STREAM, 0)) < 0) { + #ifdef _WIN32 + ::WSACleanup(); + #endif + EventManager::GetInstance().add(APIEvent::Type::SocketFailedToOpen, APIEvent::Severity::Error); + return false; + } + + sockIsOpen = true; + return true; +} + +bool SocketBase::close() { + #ifdef _WIN32 + if(::closesocket(sockFileDescriptor) < 0) { + // should probably check for WSAEWOULDBLOCK as ::closesocket must be repeated to close in that case + #ifdef ICSNEO_ENABLE_DEVICE_SHARING + EventManager::GetInstance().add(APIEvent::Type::SocketFailedToClose, APIEvent::Severity::Error); + #endif + return false; + } + ::WSACleanup(); + #else + // ignore ENOTCONN from ::shutdown as the peer may have already forcibly closed its socket (e.g. a crash) + if( ((::shutdown(sockFileDescriptor, SHUT_RDWR) < 0) && (ENOTCONN != errno)) || + ((::close(sockFileDescriptor) < 0) && (EBADF == errno)) ) + { + #ifdef ICSNEO_ENABLE_DEVICE_SHARING + EventManager::GetInstance().add(APIEvent::Type::SocketFailedToClose, APIEvent::Severity::Error); + #endif + return false; + } + #endif + + sockIsOpen = false; + sockIsConnected = false; + return true; +} + +bool SocketBase::connect() { + sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + if( (!isOpen() && !open()) || + (::inet_pton(addr.sin_family, "127.0.0.1", &addr.sin_addr) <= 0) || + (::connect(sockFileDescriptor, (sockaddr*)&addr, sizeof(addr)) < 0) ) + { + EventManager::GetInstance().add(APIEvent::Type::SocketFailedToConnect, APIEvent::Severity::Error); + return false; + } + + #ifdef _WIN32 + DWORD tv = 5000u; // 5 second receive timeout but in windows + #else + struct timeval tv; + tv.tv_sec = 5u; // 5 second receive timeout + tv.tv_usec = 0; + setIgnoreSIGPIPE(); + #endif + + // Set the 5 second timeout from above in the socket options + ::setsockopt(sockFileDescriptor, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); + ::setsockopt(sockFileDescriptor, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv)); + + sockIsConnected = true; + return true; +} + +bool SocketBase::isOpen() { + return sockIsOpen; +} + +bool SocketBase::isConnected() { + return sockIsConnected; +} + +bool SocketBase::read(void* output, std::size_t length) { + if(!(isOpen() && isConnected())) + return false; + + #ifdef _WIN32 + return ::recv(sockFileDescriptor, (char*)output, (int)length, 0) > 0; + #else + if(::read(sockFileDescriptor, output, length) <= 0) { + EventManager::GetInstance().add(APIEvent::Type::SocketFailedToRead, APIEvent::Severity::Error); + return false; + } + return true; + #endif +} + +bool SocketBase::write(const void* input, std::size_t length) { + if(!(isOpen() && isConnected())) + return false; + + #ifdef _WIN32 + if(::send(sockFileDescriptor, (char*)input, (int)length, 0) < 0) { + switch(WSAGetLastError()) { + case WSAETIMEDOUT: + case WSAENOTCONN: + case WSAESHUTDOWN: + case WSAECONNRESET: + case WSAECONNABORTED: + { + sockIsOpen = false; + break; + } + default: + { + EventManager::GetInstance().add(APIEvent::Type::SocketFailedToWrite, APIEvent::Severity::Error); + break; + } + } + return false; + } + #else + if(::write(sockFileDescriptor, input, length) < 0) { + switch(errno) { + case EPIPE: + case ETIMEDOUT: + { + sockIsOpen = false; + break; + } + default: + { + EventManager::GetInstance().add(APIEvent::Type::SocketFailedToWrite, APIEvent::Severity::Error); + break; + } + } + return false; + } + #endif + return true; +} + +bool SocketBase::readString(std::string& str) { + size_t length; + if(!read(&length, sizeof(length))) + return false; + str.resize(length); + if(!read(str.data(), length)) + return false; + return true; +} + +bool SocketBase::writeString(const std::string& str) { + size_t length = str.size(); + if(!write(&length, sizeof(length))) + return false; + if(!write(str.data(), length)) + return false; + return true; +} + +ActiveSocket::ActiveSocket(SocketFileDescriptor sockFD) { + sockFileDescriptor = sockFD; + sockIsOpen = true; + sockIsConnected = true; +} + +ActiveSocket::ActiveSocket(Protocol protocol, uint16_t port) { + this->protocol = protocol; + this->port = port; +} + +ActiveSocket::~ActiveSocket() { + if(isOpen()) + close(); +} + +LockedSocket::LockedSocket(SocketBase& base, std::unique_lock&& l) : + SocketBase(base), lock(std::move(l)) { +} + +LockedSocket lockSocket() { + static ActiveSocket socket(SocketBase::Protocol::TCP, RPC_PORT); + if(!socket.isOpen()) + socket.open(); + if(!socket.isConnected()) + socket.connect(); + static std::mutex lock; + return LockedSocket(socket, std::unique_lock(lock)); +} + +void SocketBase::setIgnoreSIGPIPE() { + #ifndef _WIN32 + struct sigaction sa{}; + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + ::sigaction(SIGPIPE, &sa, NULL); + #endif +} + +Acceptor::Acceptor(Protocol protocol, uint16_t port) + : ActiveSocket(protocol, port) { +} + +bool Acceptor::initialize() { + if(open() && bind() && listen()) { + isValid = true; + return true; + } + return false; +} + +std::shared_ptr Acceptor::accept() +{ + if(!isValid) + return nullptr; + const SocketFileDescriptor acceptFd = ::accept(sockFileDescriptor, (sockaddr*)NULL, NULL); + if(acceptFd < 0) + return nullptr; + return std::make_shared(acceptFd); +} + +bool Acceptor::bind() +{ + sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + if( (::inet_pton(addr.sin_family, "127.0.0.1", &addr.sin_addr) <= 0) || + (::bind(sockFileDescriptor, (sockaddr*)&addr, sizeof(addr)) < 0) ) + { + EventManager::GetInstance().add(APIEvent::Type::SocketAcceptorFailedToBind, APIEvent::Severity::Error); + return false; + } + return true; +} + +bool Acceptor::listen() +{ + if(::listen(sockFileDescriptor, UINT8_MAX) < 0) { + EventManager::GetInstance().add(APIEvent::Type::SocketAcceptorFailedToListen, APIEvent::Severity::Error); + return false; + } + return true; +} + +} //namespace icsneo diff --git a/device/device.cpp b/device/device.cpp index 771b062..ce528a1 100644 --- a/device/device.cpp +++ b/device/device.cpp @@ -13,6 +13,10 @@ #include #include +#ifdef ICSNEO_ENABLE_DEVICE_SHARING +#include "icsneo/communication/socket.h" +#endif + using namespace icsneo; static const uint8_t fromBase36Table[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -178,6 +182,15 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) { return false; } + #ifdef ICSNEO_ENABLE_DEVICE_SHARING + { + auto socket = lockSocket(); + if(!(socket.writeTyped(RPC::DEVICE_OPEN) && socket.writeString(getSerial()))) + return false; + if(bool ret; !(socket.readTyped(ret) && ret)) + return false; + } + #else APIEvent::Type attemptErr = attemptToBeginCommunication(); if(attemptErr != APIEvent::Type::NoErrorFound) { // We could not communicate with the device, let's see if an extension can @@ -199,6 +212,7 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) { return false; } } + #endif bool block = false; forEachExtension([&block, &flags, &handler](const std::shared_ptr& ext) { @@ -228,59 +242,61 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) { handleInternalMessage(message); })); - heartbeatThread = std::thread([this]() { - EventManager::GetInstance().downgradeErrorsOnCurrentThread(); + if(com->driver->enableHeartbeat()) { + heartbeatThread = std::thread([this]() { + EventManager::GetInstance().downgradeErrorsOnCurrentThread(); - MessageFilter filter; - filter.includeInternalInAny = true; - std::atomic receivedMessage{false}; - auto messageReceivedCallbackID = com->addMessageCallback(std::make_shared(filter, [&receivedMessage](std::shared_ptr message) { - receivedMessage = true; - })); + MessageFilter filter; + filter.includeInternalInAny = true; + std::atomic receivedMessage{false}; + auto messageReceivedCallbackID = com->addMessageCallback(std::make_shared(filter, [&receivedMessage](std::shared_ptr message) { + receivedMessage = true; + })); - // Give the device time to get situated - auto i = 150; - while(!stopHeartbeatThread && i != 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - i--; - } - - while(!stopHeartbeatThread) { - // Wait for 110ms for a possible heartbeat - std::this_thread::sleep_for(std::chrono::milliseconds(110)); - if(receivedMessage) { - receivedMessage = false; - } else { - // Some communication, such as the bootloader and extractor interfaces, must - // redirect the input stream from the device as it will no longer be in the - // packet format we expect here. As a result, status updates will not reach - // us here and suppressDisconnects() must be used. We don't want to request - // a status and then redirect the stream, as we'll then be polluting an - // otherwise quiet stream. This lock makes sure suppressDisconnects() will - // block until we've either gotten our status update or disconnected from - // the device. - std::lock_guard lk(heartbeatMutex); - if(heartbeatSuppressed()) - continue; - - // No heartbeat received, request a status - com->sendCommand(Command::RequestStatusUpdate); - // The response should come back quickly if the com is quiet + // Give the device time to get situated + auto i = 150; + while(!stopHeartbeatThread && i != 0) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); - // Check if we got a message, and if not, if settings are being applied + i--; + } + + while(!stopHeartbeatThread) { + // Wait for 110ms for a possible heartbeat + std::this_thread::sleep_for(std::chrono::milliseconds(110)); if(receivedMessage) { receivedMessage = false; } else { - if(!stopHeartbeatThread && !isDisconnected()) - report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); - break; + // Some communication, such as the bootloader and extractor interfaces, must + // redirect the input stream from the device as it will no longer be in the + // packet format we expect here. As a result, status updates will not reach + // us here and suppressDisconnects() must be used. We don't want to request + // a status and then redirect the stream, as we'll then be polluting an + // otherwise quiet stream. This lock makes sure suppressDisconnects() will + // block until we've either gotten our status update or disconnected from + // the device. + std::lock_guard lk(heartbeatMutex); + if(heartbeatSuppressed()) + continue; + + // No heartbeat received, request a status + com->sendCommand(Command::RequestStatusUpdate); + // The response should come back quickly if the com is quiet + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + // Check if we got a message, and if not, if settings are being applied + if(receivedMessage) { + receivedMessage = false; + } else { + if(!stopHeartbeatThread && !isDisconnected()) + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + break; + } } } - } - - com->removeMessageCallback(messageReceivedCallbackID); - }); + com->removeMessageCallback(messageReceivedCallbackID); + }); + } + return true; } @@ -338,19 +354,36 @@ bool Device::close() { stopHeartbeatThread = false; forEachExtension([](const std::shared_ptr& ext) { ext->onDeviceClose(); return true; }); + +#ifdef ICSNEO_ENABLE_DEVICE_SHARING + { + auto socket = lockSocket(); + if(!(socket.writeTyped(RPC::DEVICE_CLOSE) && socket.writeString(getSerial()))) + return false; + if(bool ret; !(socket.readTyped(ret) && ret)) + return false; + } +#endif + return com->close(); } bool Device::goOnline() { +#ifdef ICSNEO_ENABLE_DEVICE_SHARING + { + auto socket = lockSocket(); + if(!(socket.writeTyped(RPC::DEVICE_GO_ONLINE) && socket.writeString(getSerial()))) + return false; + if(bool ret; !(socket.readTyped(ret) && ret)) + return false; + } +#else + if(!com->sendCommand(Command::EnableNetworkCommunication, true)) return false; auto startTime = std::chrono::system_clock::now(); - ledState = LEDState::Online; - - updateLEDState(); - std::shared_ptr filter = std::make_shared(Network::NetID::Reset_Status); filter->includeInternalInAny = true; @@ -370,9 +403,13 @@ bool Device::goOnline() { if(failOut) return false; } - +#endif online = true; + ledState = LEDState::Online; + + updateLEDState(); + forEachExtension([](const std::shared_ptr& ext) { ext->onGoOnline(); return true; }); return true; } @@ -385,15 +422,21 @@ bool Device::goOffline() { return true; } + #ifdef ICSNEO_ENABLE_DEVICE_SHARING + { + auto socket = lockSocket(); + if(!(socket.writeTyped(RPC::DEVICE_GO_OFFLINE) && socket.writeString(getSerial()))) + return false; + if(bool ret; !(socket.readTyped(ret) && ret)) + return false; + } + #else + if(!com->sendCommand(Command::EnableNetworkCommunication, false)) return false; auto startTime = std::chrono::system_clock::now(); - ledState = (latestResetStatus && latestResetStatus->cmRunning) ? LEDState::CoreMiniRunning : LEDState::Offline; - - updateLEDState(); - std::shared_ptr filter = std::make_shared(Network::NetID::Reset_Status); filter->includeInternalInAny = true; @@ -407,6 +450,11 @@ bool Device::goOffline() { com->waitForMessageSync(filter, std::chrono::milliseconds(100)); } +#endif + + ledState = (latestResetStatus && latestResetStatus->cmRunning) ? LEDState::CoreMiniRunning : LEDState::Offline; + + updateLEDState(); online = false; diff --git a/device/devicefinder.cpp b/device/devicefinder.cpp index 213c76e..759d5d1 100644 --- a/device/devicefinder.cpp +++ b/device/devicefinder.cpp @@ -1,8 +1,13 @@ +#include #include "icsneo/device/devicefinder.h" #include "icsneo/platform/devices.h" #include "icsneo/device/founddevice.h" +#include "icsneo/communication/sdio.h" #include "generated/extensions/builtin.h" +#ifdef ICSNEO_ENABLE_DEVICE_SHARING +#include "icsneo/communication/socket.h" +#else #ifdef ICSNEO_ENABLE_FIRMIO #include "icsneo/platform/firmio.h" #endif @@ -18,6 +23,7 @@ #ifdef ICSNEO_ENABLE_FTDI #include "icsneo/platform/ftdi.h" #endif +#endif using namespace icsneo; @@ -43,6 +49,9 @@ std::vector> DeviceFinder::FindAll() { static std::vector newDriverFoundDevices; newDriverFoundDevices.clear(); + #ifdef ICSNEO_ENABLE_DEVICE_SHARING + SDIO::Find(newDriverFoundDevices); + #else #ifdef ICSNEO_ENABLE_FIRMIO FirmIO::Find(newDriverFoundDevices); #endif @@ -58,6 +67,7 @@ std::vector> DeviceFinder::FindAll() { #ifdef ICSNEO_ENABLE_FTDI FTDI::Find(newDriverFoundDevices); #endif + #endif // Weak because we don't want to keep devices open if they go out of scope elsewhere static std::vector> foundDevices; @@ -218,7 +228,27 @@ std::vector> DeviceFinder::FindAll() { } const std::vector& DeviceFinder::GetSupportedDevices() { - static std::vector supportedDevices = { + static std::vector supportedDevices; + + if (!supportedDevices.empty()) + return supportedDevices; + + #ifdef ICSNEO_ENABLE_DEVICE_SHARING + { + auto socket = lockSocket(); + if(!socket.writeTyped(RPC::DEVICE_FINDER_GET_SUPORTED_DEVICES)) + return supportedDevices; + uint16_t count; + if(!socket.readTyped(count)) + return supportedDevices; + std::vector devices(count); + socket.read(devices.data(), devices.size() * sizeof(devicetype_t)); + supportedDevices.reserve(count); + for(auto& dev : devices) + supportedDevices.emplace_back(DeviceType(dev)); + } + #else + supportedDevices = { #ifdef __ETHERBADGE_H_ EtherBADGE::DEVICE_TYPE, @@ -321,6 +351,7 @@ const std::vector& DeviceFinder::GetSupportedDevices() { #endif }; + #endif return supportedDevices; } diff --git a/examples/cpp/simple/CMakeLists.txt b/examples/cpp/simple/CMakeLists.txt index 4a5f5ec..f25e70b 100644 --- a/examples/cpp/simple/CMakeLists.txt +++ b/examples/cpp/simple/CMakeLists.txt @@ -24,4 +24,10 @@ endif() #add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../third-party/libicsneo ${CMAKE_CURRENT_BINARY_DIR}/third-party/libicsneo) add_executable(libicsneocpp-simple-example src/SimpleExample.cpp) -target_link_libraries(libicsneocpp-simple-example icsneocpp) \ No newline at end of file +target_link_libraries(libicsneocpp-simple-example icsneocpp) +add_executable(libicsneocpp-simple-rx src/SimpleRx.cpp) +add_executable(libicsneocpp-simple-tx src/SimpleTx.cpp) +target_link_libraries(libicsneocpp-simple-rx icsneocpp) +target_link_libraries(libicsneocpp-simple-tx icsneocpp) +add_executable(libicsneocpp-simple-events src/SimpleEvents.cpp) +target_link_libraries(libicsneocpp-simple-events icsneocpp) \ No newline at end of file diff --git a/examples/cpp/simple/src/SimpleEvents.cpp b/examples/cpp/simple/src/SimpleEvents.cpp new file mode 100644 index 0000000..6fa9d6d --- /dev/null +++ b/examples/cpp/simple/src/SimpleEvents.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +#include "icsneo/icsneocpp.h" + +int main(int argc, char** argv) { + std::vector args(argv, argv + argc); + if(args.size() != 2) { + std::cerr << "usage: " << args.front() << " " << std::endl; + return -1; + } + + const auto& deviceSerial = args[1]; + + const auto findDevice = [](const auto& serial) -> std::shared_ptr { + for(const auto& dev : icsneo::FindAllDevices()) { + if(serial == dev->getSerial()) + return dev; + } + return nullptr; + }; + + std::cout << "Finding device... " << std::flush; + const auto device = findDevice(deviceSerial); + if(!device) { + std::cerr << "FAIL" << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + std::cout << "Opening device... " << std::flush; + if(!device->open()) { + std::cerr << "FAIL" << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + std::cout << "Going online... " << std::flush; + if(!device->goOnline()) { + std::cerr << "FAIL" << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + std::cout << "Getting events from server..." << std::endl; + icsneo::EventFilter ef = {}; + auto events = icsneo::GetEvents(ef, 0UL); + if(events.size() == 0) + std::cout << "Event return is empty :(" << std::endl; + + for(auto& event: events) + std::cout << event.describe() << std::endl; + + return 0; +} diff --git a/examples/cpp/simple/src/SimpleExample.cpp b/examples/cpp/simple/src/SimpleExample.cpp index 960cf50..e4f3844 100644 --- a/examples/cpp/simple/src/SimpleExample.cpp +++ b/examples/cpp/simple/src/SimpleExample.cpp @@ -9,6 +9,12 @@ int main() { // Print version std::cout << "Running libicsneo " << icsneo::GetVersion() << std::endl; + // Register an event callback so we can see any errors that come in + icsneo::EventManager::GetInstance().downgradeErrorsOnCurrentThread(); + icsneo::EventManager::GetInstance().addEventCallback(icsneo::EventCallback([](std::shared_ptr evt) { + std::cerr << evt->describe() << std::endl; + })); + std::cout<< "Supported devices:" << std::endl; for(auto& dev : icsneo::GetSupportedDevices()) std::cout << '\t' << dev.getGenericProductName() << std::endl; diff --git a/examples/cpp/simple/src/SimpleRx.cpp b/examples/cpp/simple/src/SimpleRx.cpp new file mode 100644 index 0000000..4e6eaa1 --- /dev/null +++ b/examples/cpp/simple/src/SimpleRx.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +#include "icsneo/icsneocpp.h" + +int main(int argc, char** argv) { + std::vector args(argv, argv + argc); + if(args.size() != 2) { + std::cerr << "usage: " << args.front() << " " << std::endl; + return -1; + } + + const auto& deviceSerial = args[1]; + + const auto findDevice = [](const auto& serial) -> std::shared_ptr { + for(const auto& dev : icsneo::FindAllDevices()) { + if(serial == dev->getSerial()) + return dev; + } + return nullptr; + }; + + std::cout << "Finding device... " << std::flush; + const auto device = findDevice(deviceSerial); + if(!device) { + std::cerr << "FAIL" << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + std::cout << "Opening device... " << std::flush; + if(!device->open()) { + std::cerr << "FAIL" << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + std::cout << "Going online... " << std::flush; + if(!device->goOnline()) { + std::cerr << "FAIL" << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + std::cout << "Streaming CAN messages in, enter anything to stop..." << std::endl; + auto handler = device->addMessageCallback(std::make_shared([&](std::shared_ptr message) { + if(message->type == icsneo::Message::Type::Frame) { + auto frame = std::static_pointer_cast(message); + if(frame->network.getType() == icsneo::Network::Type::CAN) { + auto canMessage = std::static_pointer_cast(message); + std::cout << '\r'; + for(auto& databyte : canMessage->data) + std::cout << std::hex << std::setw(2) << (uint32_t)databyte << ' '; + std::cout << std::flush; + } + } + })); + + std::cin.ignore(); + + device->removeMessageCallback(handler); + + return 0; +} diff --git a/examples/cpp/simple/src/SimpleTx.cpp b/examples/cpp/simple/src/SimpleTx.cpp new file mode 100644 index 0000000..a614466 --- /dev/null +++ b/examples/cpp/simple/src/SimpleTx.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include + +#include "icsneo/icsneocpp.h" + +int main(int argc, char** argv) { + std::vector args(argv, argv + argc); + if (args.size() != 2) { + std::cerr << "usage: " << args.front() << " " << std::endl; + return -1; + } + + const auto& deviceSerial = args[1]; + + const auto findDevice = [](const auto& serial) -> std::shared_ptr { + for (const auto& dev : icsneo::FindAllDevices()) { + if (serial == dev->getSerial()) + return dev; + } + return nullptr; + }; + + std::cout << "Finding device... " << std::flush; + const auto device = findDevice(deviceSerial); + if (!device) { + std::cerr << "FAIL" << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + std::cout << "Opening device... " << std::flush; + if (!device->open()) { + std::cerr << "FAIL" << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + std::cout << "Going online... " << std::flush; + if (!device->goOnline()) { + std::cerr << "FAIL" << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + std::atomic stop = false; + + std::thread thread([&] { + std::cin.ignore(); + std::cout << "Stopping..." << std::endl; + stop = true; + }); + + auto txMessage = std::make_shared(); + txMessage->network = icsneo::Network::NetID::HSCAN; + txMessage->arbid = 0x1C5001C5; + txMessage->data.insert(txMessage->data.begin(), sizeof(size_t), 0); + txMessage->isExtended = true; + txMessage->isCANFD = true; + + std::cout << "Streaming CAN messages out, enter anything to stop..." << std::endl; + + size_t& value = *(size_t*)txMessage->data.data(); + + while (!stop) { + device->transmit(txMessage); + value++; + } + + thread.join(); + + return 0; +} diff --git a/include/icsneo/api/event.h b/include/icsneo/api/event.h index 84a2752..61d425b 100644 --- a/include/icsneo/api/event.h +++ b/include/icsneo/api/event.h @@ -4,15 +4,49 @@ #include #include +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4201) // nameless struct/union +#endif + typedef struct { - const char* description; - time_t timestamp; uint32_t eventNumber; uint8_t severity; char serial[7]; +} neoeventcontext_t; + +typedef struct { + time_t timestamp; + union { + struct { + uint32_t eventNumber; + int8_t severity; + char serial[7]; + }; + neoeventcontext_t eventContext; + }; +} neosocketevent_t; + +typedef neoeventcontext_t neosocketeventfilter_t; + +typedef struct { + const char* description; + union { + struct { + time_t timestamp; + uint32_t eventNumber; + uint8_t severity; + char serial[7]; + }; + neosocketevent_t socketEvent; + }; uint8_t reserved[16]; } neoevent_t; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #ifdef __cplusplus #include @@ -49,6 +83,7 @@ public: ValueNotYetPresent = 0x1013, Timeout = 0x1014, WiVINotSupported = 0x1015, + TestEvent = 0x1016, // Device Events PollingMessageOverflow = 0x2000, @@ -103,7 +138,31 @@ public: PCAPCouldNotStart = 0x3102, PCAPCouldNotFindDevices = 0x3103, PacketDecodingError = 0x3104, - + + // Device Sharing Server Events + SharedMemoryDataIsNull = 0x4001, + SharedMemoryFailedToClose = 0x4002, + SharedMemoryFailedToOpen = 0x4003, + SharedMemoryFailedToUnlink = 0x4004, + SharedMemoryFileTruncateError = 0x4005, + SharedMemoryMappingError = 0x4006, + SharedMemoryUnmapError = 0x4007, + SharedSemaphoreFailedToClose = 0x4008, + SharedSemaphoreFailedToOpen = 0x4009, + SharedSemaphoreFailedToPost = 0x4010, + SharedSemaphoreFailedToUnlink = 0x4011, + SharedSemaphoreFailedToWait = 0x4012, + SharedSemaphoreNotOpenForPost = 0x4013, + SharedSemaphoreNotOpenForWait = 0x4014, + SocketFailedToOpen = 0x4015, + SocketFailedToClose = 0x4016, + SocketFailedToConnect = 0x4017, + SocketFailedToRead = 0x4018, + SocketFailedToWrite = 0x4019, + SocketAcceptorFailedToBind = 0x4020, + SocketAcceptorFailedToListen = 0x4021, + + // Other Errors NoErrorFound = 0xFFFFFFFD, TooManyEvents = 0xFFFFFFFE, Unknown = 0xFFFFFFFF @@ -117,8 +176,10 @@ public: APIEvent() : eventStruct({}), serial(), timepoint(), device(nullptr) {} APIEvent(APIEvent::Type event, APIEvent::Severity severity, const Device* device = nullptr); + APIEvent(neosocketevent_t evStruct, const Device* device = nullptr); const neoevent_t* getNeoEvent() const noexcept { return &eventStruct; } + neosocketevent_t getNeoSocketEvent() const noexcept; Type getType() const noexcept { return Type(eventStruct.eventNumber); } Severity getSeverity() const noexcept { return Severity(eventStruct.severity); } std::string getDescription() const noexcept { return std::string(eventStruct.description); } @@ -157,8 +218,12 @@ public: EventFilter(const Device* device, APIEvent::Severity severity) : severity(severity), matchOnDevicePtr(true), device(device) {} EventFilter(std::string serial, APIEvent::Type type = APIEvent::Type::Any, APIEvent::Severity severity = APIEvent::Severity::Any) : type(type), severity(severity), serial(serial) {} EventFilter(std::string serial, APIEvent::Severity severity) : severity(severity), serial(serial) {} + EventFilter(neosocketeventfilter_t evFilterSt) : type(static_cast(evFilterSt.eventNumber)), + severity(static_cast(evFilterSt.severity)), + serial(std::string(evFilterSt.serial)) {} bool match(const APIEvent& event) const noexcept; + neosocketeventfilter_t getNeoSocketEventFilter() const noexcept; APIEvent::Type type = APIEvent::Type::Any; APIEvent::Severity severity = APIEvent::Severity::Any; diff --git a/include/icsneo/api/eventmanager.h b/include/icsneo/api/eventmanager.h index 5d5725d..3157f8f 100644 --- a/include/icsneo/api/eventmanager.h +++ b/include/icsneo/api/eventmanager.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "icsneo/api/event.h" #include "icsneo/api/eventcallback.h" @@ -34,6 +35,8 @@ public: // If this thread exists in the map, turn off downgrading void cancelErrorDowngradingOnCurrentThread(); + void removeEventMirror(const std::thread::id& id); + bool isDowngradingErrorsOnCurrentThread() const; int addEventCallback(const EventCallback &cb); @@ -108,6 +111,10 @@ private: bool enforceLimit(); // Returns whether the limit enforcement resulted in an overflow void discardOldest(size_t count = 1); + + #ifdef ICSNEO_ENABLE_DEVICE_SHARING + std::optional> getServerEvents(const size_t& max); + #endif }; } diff --git a/include/icsneo/communication/communication.h b/include/icsneo/communication/communication.h index 87ab9b6..5f859c7 100644 --- a/include/icsneo/communication/communication.h +++ b/include/icsneo/communication/communication.h @@ -21,11 +21,14 @@ #include #include #include +#include namespace icsneo { class Communication { public: + typedef std::function&)> RawCallback; + // Note that the Packetizer is not created by the constructor, // and should be done once the Communication module is in place. Communication( @@ -45,6 +48,7 @@ public: void modeChangeIncoming() { driver->modeChangeIncoming(); } void awaitModeChangeComplete() { driver->awaitModeChangeComplete(); } bool rawWrite(const std::vector& bytes) { return driver->write(bytes); } + void modifyRawCallbacks(std::function&)>&& cb); virtual bool sendPacket(std::vector& bytes); bool redirectRead(std::function&&)> redirectTo); void clearRedirectRead(); @@ -88,6 +92,8 @@ protected: std::atomic redirectingRead{false}; std::function&&)> redirectionFn; std::mutex redirectingReadMutex; // Don't allow read to be disabled while in the redirectionFn + std::mutex rawCallbacksMutex; + std::list&)>> rawCallbacks; std::mutex syncMessageMutex; void dispatchMessage(const std::shared_ptr& msg); diff --git a/include/icsneo/communication/driver.h b/include/icsneo/communication/driver.h index ccdf6b9..36c6168 100644 --- a/include/icsneo/communication/driver.h +++ b/include/icsneo/communication/driver.h @@ -9,6 +9,7 @@ #include #include #include +#include "icsneo/device/neodevice.h" #include "icsneo/api/eventmanager.h" #include "icsneo/third-party/concurrentqueue/blockingconcurrentqueue.h" @@ -16,7 +17,7 @@ namespace icsneo { class Driver { public: - Driver(const device_eventhandler_t& handler) : report(handler) {} + Driver(const device_eventhandler_t& handler, neodevice_t& forDevice) : report(handler), device(forDevice) {} virtual ~Driver() {} virtual bool open() = 0; virtual bool isOpen() = 0; @@ -24,12 +25,14 @@ public: virtual void awaitModeChangeComplete() {} virtual bool isDisconnected() { return disconnected; }; virtual bool close() = 0; + virtual bool enableHeartbeat() const { return false; } bool read(std::vector& bytes, size_t limit = 0); bool readWait(std::vector& bytes, std::chrono::milliseconds timeout = std::chrono::milliseconds(100), size_t limit = 0); bool write(const std::vector& bytes); virtual bool isEthernet() const { return false; } device_eventhandler_t report; + neodevice_t& device; size_t writeQueueSize = 50; bool writeBlocks = true; // Otherwise it just fails when the queue is full diff --git a/include/icsneo/communication/interprocessmailbox.h b/include/icsneo/communication/interprocessmailbox.h new file mode 100644 index 0000000..63120ee --- /dev/null +++ b/include/icsneo/communication/interprocessmailbox.h @@ -0,0 +1,42 @@ +#ifndef __INTERPROCESSMAILBOX_H_ +#define __INTERPROCESSMAILBOX_H_ + +#ifdef __cplusplus + +#include +#include "icsneo/platform/sharedmemory.h" +#include "icsneo/platform/sharedsemaphore.h" + +static constexpr uint16_t MESSAGE_COUNT = 1024; +static constexpr uint16_t BLOCK_SIZE = 2048; +using LengthFieldType = uint16_t; +static constexpr uint8_t LENGTH_FIELD_SIZE = sizeof(LengthFieldType); +static constexpr uint16_t MAX_DATA_SIZE = BLOCK_SIZE - LENGTH_FIELD_SIZE; + +namespace icsneo { + +class InterprocessMailbox { +public: + bool open(const std::string& name, bool create = false /* create the shared resources or not */); + bool close(); + operator bool() const; + + // data must be large enough to hold at least MAX_DATA_SIZE + // messageLength can be larger than MAX_DATA_SIZE if the message spans multiple blocks, only MAX_DATA_SIZE will be read + bool read(void* data, LengthFieldType& messageLength, const std::chrono::milliseconds& timeout); + + // if messageLength is larger than MAX_DATA_SIZE it's expected that future write() calls will send the remaining data + bool write(const void* data, LengthFieldType messageLength, const std::chrono::milliseconds& timeout); +private: + icsneo::SharedSemaphore queuedSem; + icsneo::SharedSemaphore emptySem; + icsneo::SharedMemory sharedMem; + unsigned index = 0; // index into messages; + bool valid = false; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/communication/sdio.h b/include/icsneo/communication/sdio.h new file mode 100644 index 0000000..10a2b73 --- /dev/null +++ b/include/icsneo/communication/sdio.h @@ -0,0 +1,34 @@ +#ifndef __SDIO_H_ +#define __SDIO_H_ + +#ifdef __cplusplus + +#include "icsneo/communication/driver.h" +#include "icsneo/communication/interprocessmailbox.h" + +namespace icsneo { + +class SDIO : public Driver { +public: + static void Find(std::vector& found); + + SDIO(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err, forDevice) {} + ~SDIO() { if(isOpen()) close(); } + bool open() override; + bool close() override; + bool isOpen() override; + bool enableHeartbeat() const override { return true; } + +private: + void readTask() override; + void writeTask() override; + bool deviceOpen = false; + InterprocessMailbox outboundIO; + InterprocessMailbox inboundIO; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/communication/socket.h b/include/icsneo/communication/socket.h new file mode 100644 index 0000000..b5465f5 --- /dev/null +++ b/include/icsneo/communication/socket.h @@ -0,0 +1,124 @@ +#ifndef __SOCKET_H_ +#define __SOCKET_H_ + +#ifdef __cplusplus + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define NOMINMAX +#include +#include +typedef SOCKET SocketFileDescriptor; +#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +#include +#include +#include +#include +#include +#include +#include +#include +typedef int SocketFileDescriptor; +#endif + +namespace icsneo { + +enum class RPC { + DEVICE_FINDER_FIND_ALL, + DEVICE_FINDER_GET_SUPORTED_DEVICES, + DEVICE_OPEN, + DEVICE_GO_ONLINE, + DEVICE_GO_OFFLINE, + DEVICE_CLOSE, + DEVICE_LOCK, + DEVICE_UNLOCK, + SDIO_OPEN, + SDIO_CLOSE, + GET_EVENTS, + GET_LAST_ERROR, + GET_EVENT_COUNT, + DISCARD_EVENTS, + SET_EVENT_LIMIT, + GET_EVENT_LIMIT +}; + +static constexpr uint16_t RPC_PORT = 54949; + +class SocketBase { +public: + enum class Protocol { + TCP = SOCK_STREAM, + }; + + bool open(); + bool close(); + bool connect(); + bool isOpen(); + bool isConnected(); + bool read(void* output, std::size_t length); + bool write(const void* input, std::size_t length); + bool writeString(const std::string& str); + bool readString(std::string& str); + + template + bool writeTyped(Ts... input) { + return (... && write(&input, sizeof(input))); + } + + template + bool readTyped(Ts&... output) { + return (... && read(&output, sizeof(output))); + } + +protected: + Protocol protocol; + uint16_t port; + SocketFileDescriptor sockFileDescriptor; + bool sockIsOpen = false; + bool sockIsConnected = false; + + void setIgnoreSIGPIPE(); +}; + +// RAII Socket +class ActiveSocket : public SocketBase { +public: + ActiveSocket(SocketFileDescriptor sockFD); + ActiveSocket(Protocol protocol, uint16_t port); + ~ActiveSocket(); +}; + +// RAII Socket IO +class LockedSocket : public SocketBase { +public: + LockedSocket(SocketBase& socket, std::unique_lock&& lock); +private: + std::unique_lock lock; +}; + +class Acceptor : public ActiveSocket { +public: + Acceptor(Protocol protocol, uint16_t port); + bool initialize(); + std::shared_ptr accept(); + +private: + bool isValid = false; + bool bind(); + bool listen(); +}; + +LockedSocket lockSocket(); + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/device/devicetype.h b/include/icsneo/device/devicetype.h index 0ac6ade..309672d 100644 --- a/include/icsneo/device/devicetype.h +++ b/include/icsneo/device/devicetype.h @@ -3,7 +3,7 @@ // Hold the length of the longest name, so that C applications can allocate memory accordingly // Currently the longest is "Intrepid Ethernet Evaluation Board" -#define ICSNEO_DEVICETYPE_LONGEST_NAME (35 + 1) // Add 1 so that if someone forgets, they still have space for null terminator +#define ICSNEO_DEVICETYPE_LONGEST_NAME (145 + 1) // Add 1 so that if someone forgets, they still have space for null terminator #define ICSNEO_DEVICETYPE_LONGEST_DESCRIPTION (ICSNEO_DEVICETYPE_LONGEST_NAME + 7) // 6 character serial, plus space #ifndef __cplusplus diff --git a/include/icsneo/device/founddevice.h b/include/icsneo/device/founddevice.h index 0f05eb6..a7552b2 100644 --- a/include/icsneo/device/founddevice.h +++ b/include/icsneo/device/founddevice.h @@ -11,7 +11,7 @@ typedef std::function< std::unique_ptr(device_eventhandler_t err, neodev class FoundDevice { public: neodevice_handle_t handle = 0; - char serial[7] = {}; + deviceserial_t serial = {}; uint16_t productId = 0; driver_factory_t makeDriver; }; diff --git a/include/icsneo/device/neodevice.h b/include/icsneo/device/neodevice.h index 94395fb..a2de491 100644 --- a/include/icsneo/device/neodevice.h +++ b/include/icsneo/device/neodevice.h @@ -18,6 +18,7 @@ typedef void* devicehandle_t; #endif typedef int32_t neodevice_handle_t; +typedef char deviceserial_t[7]; #pragma pack(push, 1) @@ -31,7 +32,7 @@ typedef struct { devicehandle_t device; // Pointer back to the C++ device object neodevice_handle_t handle; // Handle for use by the underlying driver devicetype_t type; - char serial[7]; + deviceserial_t serial; } neodevice_t; #pragma pack(pop) diff --git a/include/icsneo/platform/posix/cdcacm.h b/include/icsneo/platform/posix/cdcacm.h index ff14917..ea2ecca 100644 --- a/include/icsneo/platform/posix/cdcacm.h +++ b/include/icsneo/platform/posix/cdcacm.h @@ -25,7 +25,7 @@ public: * in cdcacmlinux.cpp and cdcacmdarwin.cpp respectively * Other POSIX systems (BSDs, QNX, etc) will need bespoke code written in the future */ - CDCACM(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err), device(forDevice) {} + CDCACM(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err, forDevice) {} ~CDCACM(); static void Find(std::vector& found); @@ -37,7 +37,6 @@ public: void awaitModeChangeComplete() override; private: - neodevice_t& device; int fd = -1; std::optional disallowedInode; std::atomic modeChanging{false}; diff --git a/include/icsneo/platform/posix/ftdi.h b/include/icsneo/platform/posix/ftdi.h index 1e1eb54..a53ecd8 100644 --- a/include/icsneo/platform/posix/ftdi.h +++ b/include/icsneo/platform/posix/ftdi.h @@ -60,8 +60,6 @@ private: void readTask(); void writeTask(); bool openable; // Set to false in the constructor if the object has not been found in searchResultDevices - - neodevice_t& device; }; } diff --git a/include/icsneo/platform/posix/pcap.h b/include/icsneo/platform/posix/pcap.h index 3167578..2d0b755 100644 --- a/include/icsneo/platform/posix/pcap.h +++ b/include/icsneo/platform/posix/pcap.h @@ -24,9 +24,9 @@ public: bool isOpen() override; bool close() override; bool isEthernet() const override { return true; } + bool enableHeartbeat() const override { return true; } private: char errbuf[PCAP_ERRBUF_SIZE] = { 0 }; - neodevice_t& device; uint8_t deviceMAC[6]; bool openable = true; EthernetPacketizer ethPacketizer; diff --git a/include/icsneo/platform/posix/sharedmemory.h b/include/icsneo/platform/posix/sharedmemory.h new file mode 100644 index 0000000..65a9b81 --- /dev/null +++ b/include/icsneo/platform/posix/sharedmemory.h @@ -0,0 +1,36 @@ +#ifndef __SHAREDMEMORY_POSIX_H_ +#define __SHAREDMEMORY_POSIX_H_ + +#ifdef __cplusplus + +#include +#include +#include "icsneo/api/eventmanager.h" + +namespace icsneo { + +class SharedMemory { +public: + SharedMemory() : report(makeEventHandler()) {}; + ~SharedMemory(); + bool open(const std::string& name, uint32_t size, bool create = false); + bool close(); + uint8_t* data(); + +private: + virtual device_eventhandler_t makeEventHandler() { + return [](APIEvent::Type type, APIEvent::Severity severity) + { EventManager::GetInstance().add(type, severity); }; + } + + device_eventhandler_t report; + std::optional mName; + std::optional> mData; + std::optional mCreated; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/platform/posix/sharedsemaphore.h b/include/icsneo/platform/posix/sharedsemaphore.h new file mode 100644 index 0000000..2c2123b --- /dev/null +++ b/include/icsneo/platform/posix/sharedsemaphore.h @@ -0,0 +1,40 @@ +#ifndef __SHAREDSEMAPHORE_POSIX_H_ +#define __SHAREDSEMAPHORE_POSIX_H_ + +#ifdef __cplusplus + +#include +#include +#include +#include +#include "icsneo/api/eventmanager.h" + +namespace icsneo { + +class SharedSemaphore { +public: + ~SharedSemaphore(); + bool open(const std::string& name, bool create = false, unsigned initialCount = 0); + bool close(); + bool wait(const std::chrono::milliseconds& timeout); + bool post(); + +private: + virtual device_eventhandler_t makeEventHandler() { + return [](APIEvent::Type type, APIEvent::Severity severity) + { EventManager::GetInstance().add(type, severity); }; + } + + std::atomic closing = false; + std::atomic waiting = false; + device_eventhandler_t report = makeEventHandler(); + std::optional mName; + std::optional semaphore; + std::optional created; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/platform/sharedmemory.h b/include/icsneo/platform/sharedmemory.h new file mode 100644 index 0000000..538d547 --- /dev/null +++ b/include/icsneo/platform/sharedmemory.h @@ -0,0 +1,12 @@ +#ifndef __SHAREDMEMORY_H_ +#define __SHAREDMEMORY_H_ + +#ifdef _WIN32 +#include "icsneo/platform/windows/sharedmemory.h" +#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +#include "icsneo/platform/posix/sharedmemory.h" +#else +#warning "Shared memory are not supported on this platform" +#endif + +#endif \ No newline at end of file diff --git a/include/icsneo/platform/sharedsemaphore.h b/include/icsneo/platform/sharedsemaphore.h new file mode 100644 index 0000000..b711685 --- /dev/null +++ b/include/icsneo/platform/sharedsemaphore.h @@ -0,0 +1,12 @@ +#ifndef __SHAREDSEMAPHORE_H_ +#define __SHAREDSEMAPHORE_H_ + +#ifdef _WIN32 +#include "icsneo/platform/windows/sharedsemaphore.h" +#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +#include "icsneo/platform/posix/sharedsemaphore.h" +#else +#warning "Shared semaphores are not supported on this platform" +#endif + +#endif \ No newline at end of file diff --git a/include/icsneo/platform/windows/pcap.h b/include/icsneo/platform/windows/pcap.h index f9a3cc8..0dff3ac 100644 --- a/include/icsneo/platform/windows/pcap.h +++ b/include/icsneo/platform/windows/pcap.h @@ -24,10 +24,10 @@ public: bool isOpen() override; bool close() override; bool isEthernet() const override { return true; } + bool enableHeartbeat() const override { return true; } private: const PCAPDLL& pcap; char errbuf[PCAP_ERRBUF_SIZE] = { 0 }; - neodevice_t& device; uint8_t deviceMAC[6]; bool openable = true; EthernetPacketizer ethPacketizer; diff --git a/include/icsneo/platform/windows/sharedmemory.h b/include/icsneo/platform/windows/sharedmemory.h new file mode 100644 index 0000000..770e406 --- /dev/null +++ b/include/icsneo/platform/windows/sharedmemory.h @@ -0,0 +1,37 @@ +#ifndef __SHAREDMEMORY_WINDOWS_H_ +#define __SHAREDMEMORY_WINDOWS_H_ + +#ifdef __cplusplus + +#include +#include +#include "icsneo/platform/windows.h" +#include "icsneo/api/eventmanager.h" + +namespace icsneo { + +class SharedMemory { +public: + SharedMemory() : report(makeEventHandler()) {}; + ~SharedMemory(); + bool open(const std::string& name, uint32_t size, bool create = false); + bool close(); + uint8_t* data(); + +private: + virtual device_eventhandler_t makeEventHandler() { + return [](APIEvent::Type type, APIEvent::Severity severity) + { EventManager::GetInstance().add(type, severity); }; + } + + device_eventhandler_t report; + std::optional mHandle; + std::optional> mData; + std::optional mCreated; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/platform/windows/sharedsemaphore.h b/include/icsneo/platform/windows/sharedsemaphore.h new file mode 100644 index 0000000..39fd62f --- /dev/null +++ b/include/icsneo/platform/windows/sharedsemaphore.h @@ -0,0 +1,38 @@ +#ifndef __SHAREDSEMAPHORE_WINDOWS_H_ +#define __SHAREDSEMAPHORE_WINDOWS_H_ + +#ifdef __cplusplus + +#include +#include +#include +#include "icsneo/platform/windows.h" +#include "icsneo/api/eventmanager.h" + +namespace icsneo { + +class SharedSemaphore { +public: + ~SharedSemaphore(); + bool open(const std::string& name, bool create = false, unsigned initialCount = 0); + bool close(); + bool wait(const std::chrono::milliseconds& timeout); + bool post(); + +private: + virtual device_eventhandler_t makeEventHandler() { + return [](APIEvent::Type type, APIEvent::Severity severity) + { EventManager::GetInstance().add(type, severity); }; + } + + bool closing = false; + device_eventhandler_t report = makeEventHandler(); + std::optional semaphore; + std::optional created; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/platform/windows/vcp.h b/include/icsneo/platform/windows/vcp.h index 612afb6..a19477d 100644 --- a/include/icsneo/platform/windows/vcp.h +++ b/include/icsneo/platform/windows/vcp.h @@ -31,7 +31,6 @@ public: private: bool open(bool fromAsync); bool opening = false; - neodevice_t& device; struct Detail; std::shared_ptr detail; diff --git a/platform/posix/firmio.cpp b/platform/posix/firmio.cpp index 0f62ee3..e17e8ff 100644 --- a/platform/posix/firmio.cpp +++ b/platform/posix/firmio.cpp @@ -65,8 +65,8 @@ void FirmIO::Find(std::vector& found) { memcpy(foundDevice.serial, serial->deviceSerial.c_str(), sizeof(foundDevice.serial) - 1); foundDevice.serial[sizeof(foundDevice.serial) - 1] = '\0'; - foundDevice.makeDriver = [](const device_eventhandler_t& report, neodevice_t&) { - return std::unique_ptr(new FirmIO(report)); + foundDevice.makeDriver = [](const device_eventhandler_t& report, neodevice_t& forDevice) { + return std::unique_ptr(new FirmIO(report, forDevice)); }; found.push_back(foundDevice); diff --git a/platform/posix/ftdi.cpp b/platform/posix/ftdi.cpp index a96328a..619eec1 100644 --- a/platform/posix/ftdi.cpp +++ b/platform/posix/ftdi.cpp @@ -47,7 +47,7 @@ void FTDI::Find(std::vector& found) { } } -FTDI::FTDI(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err), device(forDevice) { +FTDI::FTDI(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err, forDevice) { openable = strlen(forDevice.serial) > 0 && device.handle >= 0 && device.handle < (neodevice_handle_t)handles.size(); } diff --git a/platform/posix/pcap.cpp b/platform/posix/pcap.cpp index 341d503..20cdda6 100644 --- a/platform/posix/pcap.cpp +++ b/platform/posix/pcap.cpp @@ -200,7 +200,7 @@ bool PCAP::IsHandleValid(neodevice_handle_t handle) { return (netifIndex < knownInterfaces.size()); } -PCAP::PCAP(device_eventhandler_t err, neodevice_t& forDevice) : Driver(err), device(forDevice), ethPacketizer(err) { +PCAP::PCAP(device_eventhandler_t err, neodevice_t& forDevice) : Driver(err, forDevice), ethPacketizer(err) { if(IsHandleValid(device.handle)) { iface = knownInterfaces[(device.handle >> 24) & 0xFF]; iface.fp = nullptr; // We're going to open our own connection to the interface. This should already be nullptr but just in case. diff --git a/platform/posix/sharedmemory.cpp b/platform/posix/sharedmemory.cpp new file mode 100644 index 0000000..fe6d4bb --- /dev/null +++ b/platform/posix/sharedmemory.cpp @@ -0,0 +1,65 @@ +#include // TODO: Remove later +#include +#include +#include +#include "icsneo/platform/posix/sharedmemory.h" + +using namespace icsneo; + +SharedMemory::~SharedMemory() { + close(); +} + +bool SharedMemory::open(const std::string& name, uint32_t size, bool create) { + if(create) + shm_unlink(name.c_str()); + const auto fd = create ? shm_open(name.c_str(), O_CREAT | O_RDWR | O_EXCL, 0600) : shm_open(name.c_str(), O_RDWR, 0); + if(fd == -1) { + report(APIEvent::Type::SharedMemoryFailedToOpen, APIEvent::Severity::Error); + return false; + } + mName.emplace(name); + if(create && ftruncate(fd, size) == -1) { + report(APIEvent::Type::SharedMemoryFileTruncateError, APIEvent::Severity::Error); + close(); + return false; + } + const auto shm = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if(shm == MAP_FAILED) { + report(APIEvent::Type::SharedMemoryMappingError, APIEvent::Severity::Error); + close(); + return false; + } + mData.emplace(std::make_pair((uint8_t*)shm, size)); + mCreated.emplace(create); + return true; +} + +bool SharedMemory::close() { + bool failed = false; + if(mData) { + if(munmap((void*)mData->first, mData->second) == -1) { + report(APIEvent::Type::SharedMemoryUnmapError, APIEvent::Severity::EventWarning); + failed = true; + } + mData.reset(); + } + if(mName && mCreated && *mCreated) { + if(shm_unlink(mName->c_str()) == -1) { + report(APIEvent::Type::SharedMemoryFailedToUnlink, APIEvent::Severity::EventWarning); + failed = true; + } + mName.reset(); + } + if(failed) + report(APIEvent::Type::SharedMemoryFailedToClose, APIEvent::Severity::Error); + return !failed; +} + +uint8_t* SharedMemory::data() { + if(!mData) { + report(APIEvent::Type::SharedMemoryDataIsNull, APIEvent::Severity::Error); + return nullptr; + } + return mData->first; +} diff --git a/platform/posix/sharedsemaphore.cpp b/platform/posix/sharedsemaphore.cpp new file mode 100644 index 0000000..15937e9 --- /dev/null +++ b/platform/posix/sharedsemaphore.cpp @@ -0,0 +1,99 @@ +#include // TODO: Remove later +#include +#include "icsneo/platform/posix/sharedsemaphore.h" + +using namespace icsneo; + +SharedSemaphore::~SharedSemaphore() { + close(); +} + +bool SharedSemaphore::open(const std::string& name, bool create, unsigned initialCount) { + const auto slashPrefixed = "/" + name; + if(create) + sem_unlink(slashPrefixed.c_str()); // try clean-up, it's fine if it errors + const auto sem = create ? sem_open(slashPrefixed.c_str(), O_CREAT | O_EXCL, 0600, initialCount) : sem_open(slashPrefixed.c_str(), 0); + if(sem == SEM_FAILED) { + report(APIEvent::Type::SharedSemaphoreFailedToOpen, APIEvent::Severity::Error); + return false; + } + mName.emplace(slashPrefixed); + semaphore.emplace(sem); + created.emplace(create); + return true; +} + +bool SharedSemaphore::close() { + closing = true; + + post(); // wake any waiting + + bool failed = false; + if(semaphore) { + if(sem_close(semaphore.value()) == -1) { + report(APIEvent::Type::SharedSemaphoreFailedToClose, APIEvent::Severity::Error); + failed = true; + } + semaphore.reset(); + } + if(mName && created && *created) { + if(sem_unlink(mName->c_str()) == -1) { + report(APIEvent::Type::SharedSemaphoreFailedToUnlink, APIEvent::Severity::Error); + failed = true; + } + mName.reset(); + } + return !failed; +} + +bool SharedSemaphore::wait(const std::chrono::milliseconds& timeout) { + if(!semaphore) { + report(APIEvent::Type::SharedSemaphoreNotOpenForWait, APIEvent::Severity::Error); + return false; + } + const auto timedwait = [&]() -> bool { + #if defined(__MACH__) + // TODO: Quite inefficient due to Darwin's lack of sem_timedwait() + const auto tryTill = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() <= tryTill) { + if(sem_trywait(*semaphore) == 0) + return true; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + return false; + #else // UNIX + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += static_cast(timeout.count() / 1000); + ts.tv_nsec += static_cast((timeout.count() % 1000) * 1000000); + // potentially promote another second + if(ts.tv_nsec >= 1000000000) { + ts.tv_nsec -= 1000000000; + ++ts.tv_sec; + } + return sem_timedwait(*semaphore, &ts) != -1; + #endif + }; + if(!timedwait()) { + if(errno == ETIMEDOUT) + return false; // unable to lock within timeout + if(errno != EINTR) // we don't need a warning for this + report(APIEvent::Type::SharedSemaphoreFailedToWait, APIEvent::Severity::Error); + return false; + } + if(closing) + return false; // we were woken by close() + return true; +} + +bool SharedSemaphore::post() { + if(!semaphore) { + report(APIEvent::Type::SharedSemaphoreNotOpenForPost, APIEvent::Severity::Error); + return false; + } + if(sem_post(*semaphore) == -1) { + report(APIEvent::Type::SharedSemaphoreFailedToPost, APIEvent::Severity::Error); + return false; + } + return true; +} diff --git a/platform/windows/pcap.cpp b/platform/windows/pcap.cpp index fb1a513..1080b77 100644 --- a/platform/windows/pcap.cpp +++ b/platform/windows/pcap.cpp @@ -166,8 +166,8 @@ void PCAP::Find(std::vector& found) { memcpy(foundDevice.serial, serial->deviceSerial.c_str(), sizeof(foundDevice.serial) - 1); foundDevice.serial[sizeof(foundDevice.serial) - 1] = '\0'; - foundDevice.makeDriver = [](const device_eventhandler_t& reportFn, neodevice_t& device) { - return std::unique_ptr(new PCAP(reportFn, device)); + foundDevice.makeDriver = [](const device_eventhandler_t& reportFn, neodevice_t& forDevice) { + return std::unique_ptr(new PCAP(reportFn, forDevice)); }; found.push_back(foundDevice); @@ -184,7 +184,7 @@ bool PCAP::IsHandleValid(neodevice_handle_t handle) { return (netifIndex < knownInterfaces.size()); } -PCAP::PCAP(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err), device(forDevice), pcap(PCAPDLL::getInstance()), ethPacketizer(err) { +PCAP::PCAP(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err, forDevice), pcap(PCAPDLL::getInstance()), ethPacketizer(err) { if(IsHandleValid(device.handle)) { iface = knownInterfaces[(device.handle >> 24) & 0xFF]; iface.fp = nullptr; // We're going to open our own connection to the interface. This should already be nullptr but just in case. diff --git a/platform/windows/sharedmemory.cpp b/platform/windows/sharedmemory.cpp new file mode 100644 index 0000000..ac74224 --- /dev/null +++ b/platform/windows/sharedmemory.cpp @@ -0,0 +1,47 @@ +#include "icsneo/api/event.h" +#include "icsneo/platform/windows/sharedmemory.h" + +using namespace icsneo; + +SharedMemory::~SharedMemory() { + close(); +} + +bool SharedMemory::open(const std::string& name, uint32_t size, bool create) { + HANDLE shm; + if(create) + shm = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, name.c_str()); + else + shm = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, name.c_str()); + + if(shm == NULL) { + report(APIEvent::Type::SharedMemoryFailedToOpen, APIEvent::Severity::Error); + return false; + } + mHandle.emplace(shm); + const auto dataStart = MapViewOfFile(shm, FILE_MAP_ALL_ACCESS, 0, 0, size); + if(dataStart == NULL) { + report(APIEvent::Type::SharedMemoryMappingError, APIEvent::Severity::Error); + close(); + return false; + } + mData.emplace(std::make_pair((uint8_t*)dataStart, size)); + mCreated.emplace(create); + return true; +} + +bool SharedMemory::close() { + bool failed = false; // TODO: need to close properly + if(failed) { + report(APIEvent::Type::SharedMemoryFailedToClose, APIEvent::Severity::Error); + } + return !failed; +} + +uint8_t* SharedMemory::data() { + if(!mData) { + report(APIEvent::Type::SharedMemoryDataIsNull, APIEvent::Severity::Error); + return nullptr; + } + return mData->first; +} diff --git a/platform/windows/sharedsemaphore.cpp b/platform/windows/sharedsemaphore.cpp new file mode 100644 index 0000000..48767d6 --- /dev/null +++ b/platform/windows/sharedsemaphore.cpp @@ -0,0 +1,65 @@ +#include "icsneo/platform/windows/sharedsemaphore.h" + +using namespace icsneo; + +SharedSemaphore::~SharedSemaphore() { + close(); +} + +bool SharedSemaphore::open(const std::string& name, bool create, unsigned initialCount) { + HANDLE sem; + if(create) + sem = CreateSemaphore(NULL, initialCount, LONG_MAX, name.c_str()); + else + sem = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, name.c_str()); + + if(sem == NULL) { + report(APIEvent::Type::SharedSemaphoreFailedToOpen, APIEvent::Severity::Error); + return false; + } + semaphore.emplace(sem); + created.emplace(create); + return true; +} + +bool SharedSemaphore::close() { + if(!semaphore) + return false; + + closing = true; + + post(); // wake any waiting + + if(CloseHandle(*semaphore) == 0) { + report(APIEvent::Type::SharedSemaphoreFailedToClose, APIEvent::Severity::Error); + return false; + } + semaphore.reset(); + return true; +} + +bool SharedSemaphore::wait(const std::chrono::milliseconds& timeout) { + if(!semaphore) { + report(APIEvent::Type::SharedSemaphoreNotOpenForWait, APIEvent::Severity::Error); + return false; + } + if(WaitForSingleObject(*semaphore, static_cast(timeout.count())) != 0) { + report(APIEvent::Type::SharedSemaphoreFailedToWait, APIEvent::Severity::Error); + return false; + } + if(closing) + return false; // we were woken by close() + return true; +} + +bool SharedSemaphore::post() { + if(!semaphore) { + report(APIEvent::Type::SharedSemaphoreNotOpenForPost, APIEvent::Severity::Error); + return false; + } + if(ReleaseSemaphore(*semaphore, 1, NULL) == 0) { + report(APIEvent::Type::SharedSemaphoreFailedToPost, APIEvent::Severity::Error); + return false; + } + return true; +} diff --git a/platform/windows/vcp.cpp b/platform/windows/vcp.cpp index cc4650d..e35d0e4 100644 --- a/platform/windows/vcp.cpp +++ b/platform/windows/vcp.cpp @@ -192,7 +192,7 @@ void VCP::Find(std::vector& found, std::vector driver } } -VCP::VCP(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err), device(forDevice) { +VCP::VCP(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err, forDevice) { detail = std::make_shared(); } diff --git a/test/interprocessmailboxtest.cpp b/test/interprocessmailboxtest.cpp new file mode 100644 index 0000000..ea4f2dc --- /dev/null +++ b/test/interprocessmailboxtest.cpp @@ -0,0 +1,87 @@ +#include + +#include "gtest/gtest.h" +#include "icsneo/communication/interprocessmailbox.h" +#include "icsneo/icsneocpp.h" + +using namespace icsneo; + +static std::chrono::milliseconds TIMEOUT(10'000); + +// ensures that the shared-memory and shared-semaphores get cleared +TEST(InterprocessMailboxTest, CreateDestroy) { + static constexpr auto name = "icsneo-test"; + { + InterprocessMailbox mb; + EXPECT_TRUE(mb.open(name, true)); + EXPECT_TRUE(mb.close()); + } + { + InterprocessMailbox mb; + EXPECT_FALSE(mb.open(name)); // create == false + auto err = icsneo::GetLastError(); + EXPECT_EQ(err.getType(), APIEvent::Type::SharedSemaphoreFailedToOpen); + EXPECT_EQ(err.getSeverity(), APIEvent::Severity::Error); + EXPECT_FALSE(mb.close()); // never opened successfully + } +} + +// these test should really be done in separate processes +TEST(InterprocessMailboxTest, Looping) { + size_t data = SIZE_MAX; // just dummy data + InterprocessMailbox in; + InterprocessMailbox out; + EXPECT_TRUE(in.open("icsneo-test", true)); + EXPECT_TRUE(out.open("icsneo-test")); + for(unsigned i = 0; i < MESSAGE_COUNT * 64; ++i) { + // "send" the message + EXPECT_TRUE(out.write(&data, sizeof(data), TIMEOUT)); + uint8_t buff[MAX_DATA_SIZE]; + LengthFieldType read; + EXPECT_TRUE(in.read(buff, read, TIMEOUT)); + EXPECT_EQ(*(decltype(data)*)buff, data); + --data; + } + EXPECT_TRUE(in.close()); + EXPECT_TRUE(out.close()); +} + +TEST(InterprocessMailboxTest, FasterSender) { + std::thread sender([] { + InterprocessMailbox out; + EXPECT_TRUE(out.open("icsneo-test", true)); + + // nested here to spawn after the sender is set up + std::thread receiver([] { + InterprocessMailbox in; + EXPECT_TRUE(in.open("icsneo-test")); + + // wait for the sender to fill up + std::this_thread::sleep_for(std::chrono::seconds(2)); + uint8_t buff[MAX_DATA_SIZE]; + LengthFieldType read; + + // the messages should be 1, 2, 3, ..., MESSAGE_COUNT, this proves the sender waited + for(unsigned i = 0; i < MESSAGE_COUNT; ++i) { + EXPECT_TRUE(in.read(buff, read, TIMEOUT)); + EXPECT_EQ(*(decltype(i)*)buff, i); + } + + // make sure we can do it again for the rest + for(unsigned i = MESSAGE_COUNT; i < MESSAGE_COUNT * 2; ++i) { + EXPECT_TRUE(in.read(buff, read, TIMEOUT)); + EXPECT_EQ(*(decltype(i)*)buff, i); + } + EXPECT_TRUE(in.close()); + }); + + // try to send two times the count, we'll end up blocked at the end till the receiver unblocks + for(unsigned i = 0; i < MESSAGE_COUNT * 2; ++i) { + EXPECT_TRUE(out.write(&i, sizeof(i), TIMEOUT)); + } + + receiver.join(); + EXPECT_TRUE(out.close()); + }); + sender.join(); +} diff --git a/test/sockettest.cpp b/test/sockettest.cpp new file mode 100644 index 0000000..ba65d0a --- /dev/null +++ b/test/sockettest.cpp @@ -0,0 +1,43 @@ +#include +#include +#include "gtest/gtest.h" +#include "icsneo/communication/socket.h" + +using namespace icsneo; + +static constexpr uint16_t TEST_PORT = 55555; + +TEST(DeviceSharingSocketTest, SocketOpen) { + ActiveSocket socket(SocketBase::Protocol::TCP, TEST_PORT); + EXPECT_TRUE(socket.open()); + EXPECT_TRUE(socket.close()); +} + +TEST(DeviceSharingSocketTest, SocketOpenAndConnect) { + Acceptor acceptor(SocketBase::Protocol::TCP, TEST_PORT); + ActiveSocket socket(SocketBase::Protocol::TCP, TEST_PORT); + acceptor.initialize(); + + EXPECT_TRUE(socket.open()); + EXPECT_TRUE(socket.connect()); + EXPECT_TRUE(socket.close()); +} + +TEST(DeviceSharingSocketTest, SocketReadWrite) { + std::array testData = {0xDE, 0xAD, 0xBE, 0xEF}; + std::array readData; + Acceptor acceptor(SocketBase::Protocol::TCP, TEST_PORT); + acceptor.initialize(); + + ActiveSocket socket(SocketBase::Protocol::TCP, TEST_PORT); + EXPECT_TRUE(socket.open()); + EXPECT_TRUE(socket.connect()); + + auto acceptSocket = acceptor.accept(); + + EXPECT_TRUE(socket.write(testData.data(), testData.size())); + EXPECT_TRUE(acceptSocket->read(readData.data(), readData.size())); + EXPECT_EQ(testData, readData); + EXPECT_TRUE(socket.close()); + EXPECT_TRUE(acceptSocket->close()); +}