mirror of
https://github.com/intrepidcs/libicsneo.git
synced 2026-11-04 12:59:07 +01:00
All: Remove deprecated Drivers
This commit is contained in:
@@ -1,201 +0,0 @@
|
||||
#include "icsneo/platform/dxx.h"
|
||||
|
||||
#define ICS_USB_VID 0x093C
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
static APIEvent::Type eventError(libredxx_status status) {
|
||||
switch (status) {
|
||||
case LIBREDXX_STATUS_ERROR_SYS: return APIEvent::Type::DXXErrorSys;
|
||||
case LIBREDXX_STATUS_ERROR_INTERRUPTED: return APIEvent::Type::DXXErrorSys;
|
||||
case LIBREDXX_STATUS_ERROR_OVERFLOW: return APIEvent::Type::DXXErrorSys;
|
||||
case LIBREDXX_STATUS_ERROR_IO: return APIEvent::Type::DXXErrorSys;
|
||||
case LIBREDXX_STATUS_ERROR_INVALID_ARGUMENT: return APIEvent::Type::DXXErrorSys;
|
||||
default: return APIEvent::Type::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
void DXX::Find(std::vector<FoundDevice>& found) {
|
||||
libredxx_status status;
|
||||
static libredxx_find_filter filters[] = {
|
||||
{ LIBREDXX_DEVICE_TYPE_D2XX, { ICS_USB_VID, 0x0005 } }, // RAD-Star 2
|
||||
{ LIBREDXX_DEVICE_TYPE_D2XX, { ICS_USB_VID, 0x0006 } }, // RAD-A2B Rev A
|
||||
{ LIBREDXX_DEVICE_TYPE_D2XX, { ICS_USB_VID, 0x0601 } }, // ValueCAN 3
|
||||
{ LIBREDXX_DEVICE_TYPE_D2XX, { ICS_USB_VID, 0x0701 } }, // FIRE
|
||||
{ LIBREDXX_DEVICE_TYPE_D2XX, { ICS_USB_VID, 0x0801 } }, // neoVI PLASMA
|
||||
{ LIBREDXX_DEVICE_TYPE_D2XX, { ICS_USB_VID, 0x0901 } }, // neoVI ION
|
||||
{ LIBREDXX_DEVICE_TYPE_D2XX, { ICS_USB_VID, 0x1000 } }, // neoVI FIRE2
|
||||
{ LIBREDXX_DEVICE_TYPE_D3XX, { ICS_USB_VID, 0x1201 } }, // RAD-SuperMoon
|
||||
{ LIBREDXX_DEVICE_TYPE_D3XX, { ICS_USB_VID, 0x1202 } }, // RAD-Moon2
|
||||
{ LIBREDXX_DEVICE_TYPE_D3XX, { ICS_USB_VID, 0x1205 } }, // RAD-Moon2
|
||||
{ LIBREDXX_DEVICE_TYPE_D3XX, { ICS_USB_VID, 0x1203 } }, // RAD-Gigalog
|
||||
{ LIBREDXX_DEVICE_TYPE_D3XX, { ICS_USB_VID, 0x1204 } }, // RAD-Gigastar
|
||||
{ LIBREDXX_DEVICE_TYPE_D3XX, { ICS_USB_VID, 0x1206 } }, // RAD-A2B Rev B
|
||||
{ LIBREDXX_DEVICE_TYPE_D3XX, { ICS_USB_VID, 0x1207 } }, // RAD-Comet2
|
||||
{ LIBREDXX_DEVICE_TYPE_D3XX, { ICS_USB_VID, 0x1208 } }, // RAD-Comet3
|
||||
{ LIBREDXX_DEVICE_TYPE_D3XX, { ICS_USB_VID, 0x1209 } }, // RAD-MoonT1S
|
||||
{ LIBREDXX_DEVICE_TYPE_D3XX, { ICS_USB_VID, 0x1210 } }, // RAD-Gigastar 2
|
||||
};
|
||||
static size_t filterCount = sizeof(filters) / sizeof(filters[0]);
|
||||
|
||||
libredxx_found_device** foundDevices = nullptr;
|
||||
size_t foundDevicesCount;
|
||||
status = libredxx_find_devices(filters, filterCount, &foundDevices, &foundDevicesCount);
|
||||
if(status != LIBREDXX_STATUS_SUCCESS) {
|
||||
EventManager::GetInstance().add(eventError(status), APIEvent::Severity::Error);
|
||||
return;
|
||||
}
|
||||
if(foundDevicesCount == 0) {
|
||||
return;
|
||||
}
|
||||
for(size_t i = 0; i < foundDevicesCount; ++i) {
|
||||
libredxx_found_device* foundDevice = foundDevices[i];
|
||||
libredxx_serial serial = {};
|
||||
status = libredxx_get_serial(foundDevice, &serial);
|
||||
if(status != LIBREDXX_STATUS_SUCCESS) {
|
||||
EventManager::GetInstance().add(eventError(status), APIEvent::Severity::Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
libredxx_device_id id;
|
||||
status = libredxx_get_device_id(foundDevice, &id);
|
||||
if(status != LIBREDXX_STATUS_SUCCESS) {
|
||||
EventManager::GetInstance().add(eventError(status), APIEvent::Severity::Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
libredxx_device_type type;
|
||||
status = libredxx_get_device_type(foundDevice, &type);
|
||||
if(status != LIBREDXX_STATUS_SUCCESS) {
|
||||
EventManager::GetInstance().add(eventError(status), APIEvent::Severity::Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& device = found.emplace_back();
|
||||
// -1 to make sure serial has a trailing '\0'
|
||||
std::copy(serial.serial, serial.serial + (sizeof(device.serial) - 1), device.serial);
|
||||
device.makeDriver = [id, type](device_eventhandler_t err, neodevice_t& forDevice) {
|
||||
return std::make_unique<DXX>(err, forDevice, id.pid, type);
|
||||
};
|
||||
}
|
||||
libredxx_free_found(foundDevices);
|
||||
}
|
||||
|
||||
DXX::DXX(const device_eventhandler_t& err, neodevice_t& forDevice, uint16_t pid, libredxx_device_type type) :
|
||||
Driver(err), neodevice(forDevice), pid(pid), type(type) {
|
||||
}
|
||||
|
||||
bool DXX::open() {
|
||||
libredxx_status status;
|
||||
libredxx_find_filter filters[] = {
|
||||
{ (libredxx_device_type)type, { ICS_USB_VID, pid } }
|
||||
};
|
||||
libredxx_found_device** foundDevices = nullptr;
|
||||
size_t foundDevicesCount;
|
||||
status = libredxx_find_devices(filters, 1, &foundDevices, &foundDevicesCount);
|
||||
if(status != LIBREDXX_STATUS_SUCCESS) {
|
||||
EventManager::GetInstance().add(eventError(status), APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
if(foundDevicesCount == 0) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
libredxx_found_device* foundDevice = nullptr;
|
||||
for(size_t i = 0; i < foundDevicesCount; ++i) {
|
||||
libredxx_serial serial = {};
|
||||
status = libredxx_get_serial(foundDevices[i], &serial);
|
||||
if(status != LIBREDXX_STATUS_SUCCESS) {
|
||||
EventManager::GetInstance().add(eventError(status), APIEvent::Severity::EventWarning);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(strncmp(serial.serial, neodevice.serial, sizeof(neodevice.serial) - 1) == 0) {
|
||||
foundDevice = foundDevices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(foundDevice == nullptr) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error);
|
||||
libredxx_free_found(foundDevices);
|
||||
return false;
|
||||
}
|
||||
status = libredxx_open_device(foundDevice, &device);
|
||||
if(status != LIBREDXX_STATUS_SUCCESS) {
|
||||
EventManager::GetInstance().add(eventError(status), APIEvent::Severity::Error);
|
||||
libredxx_free_found(foundDevices);
|
||||
return false;
|
||||
}
|
||||
libredxx_free_found(foundDevices);
|
||||
setIsDisconnected(false);
|
||||
readThread = std::thread(&DXX::read, this);
|
||||
writeThread = std::thread(&DXX::write, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DXX::isOpen() {
|
||||
return device != nullptr;
|
||||
}
|
||||
|
||||
bool DXX::close() {
|
||||
setIsClosing(true);
|
||||
libredxx_close_device(device); // unblock read thread & close
|
||||
writeQueue.enqueue(WriteOperation{}); // unblock write thread
|
||||
readThread.join();
|
||||
writeThread.join();
|
||||
device = nullptr;
|
||||
setIsClosing(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DXX::read() {
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
|
||||
std::vector<uint8_t> buffer(ICSNEO_DRIVER_RINGBUFFER_SIZE);
|
||||
|
||||
while(!isDisconnected() && !isClosing()) {
|
||||
size_t received = buffer.size();
|
||||
const auto status = libredxx_read(device, buffer.data(), &received, LIBREDXX_ENDPOINT_A);
|
||||
if(isDisconnected() || isClosing()) {
|
||||
return;
|
||||
}
|
||||
if(status != LIBREDXX_STATUS_SUCCESS) {
|
||||
EventManager::GetInstance().add(eventError(status), APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
while(!isDisconnected() && !isClosing()) {
|
||||
if(pushRx(buffer.data(), received))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DXX::write() {
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
|
||||
WriteOperation writeOp;
|
||||
|
||||
while(!isDisconnected() && !isClosing()) {
|
||||
writeQueue.wait_dequeue(writeOp);
|
||||
|
||||
if(isDisconnected() || isClosing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(size_t totalWritten = 0; totalWritten < writeOp.bytes.size();) {
|
||||
size_t size = writeOp.bytes.size() - totalWritten;
|
||||
const auto status = libredxx_write(device, &writeOp.bytes[totalWritten], &size, LIBREDXX_ENDPOINT_A);
|
||||
if(isDisconnected() || isClosing()) {
|
||||
return;
|
||||
}
|
||||
if(status != LIBREDXX_STATUS_SUCCESS) {
|
||||
EventManager::GetInstance().add(eventError(status), APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
totalWritten += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
#include "icsneo/platform/cdcacm.h"
|
||||
#include <dirent.h>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <termios.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
CDCACM::~CDCACM() {
|
||||
awaitModeChangeComplete();
|
||||
if(isOpen())
|
||||
close();
|
||||
}
|
||||
|
||||
bool CDCACM::open() {
|
||||
if(isOpen()) {
|
||||
report(APIEvent::Type::DeviceCurrentlyOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& ttyPath = HandleToTTY(device.handle);
|
||||
if(ttyPath.empty()) {
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some devices can take a while to boot
|
||||
for(int i = 0; i != 50; ++i) {
|
||||
fd = ::open(ttyPath.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||
|
||||
if(fd != -1) {
|
||||
// Opened successfully, check if it's the same inode as the disallowed one
|
||||
// The inode is disallowed if we're expecting the device to re-enumerate (modeChanging)
|
||||
if(disallowedInode.has_value()) {
|
||||
struct stat fileStat = {};
|
||||
if(fstat(fd, &fileStat) >= 0) {
|
||||
if(fileStat.st_ino != *disallowedInode)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
// This is still the old device, wait and try again
|
||||
::close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
disallowedInode.reset();
|
||||
if(!isOpen()) {
|
||||
//std::cout << "Open of " << ttyPath.c_str() << " failed with " << strerror(errno) << ' ';
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct termios tty = {};
|
||||
struct termios compare = {};
|
||||
|
||||
if(tcgetattr(fd, &tty) != 0) {
|
||||
close();
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
tty.c_cflag |= (CLOCAL | CREAD); // Ignore modem controls
|
||||
tty.c_cflag &= ~CSIZE;
|
||||
tty.c_cflag |= CS8; // 8-bit characters
|
||||
tty.c_cflag &= ~PARENB; // No parity bit
|
||||
tty.c_cflag &= ~CSTOPB; // One stop bit
|
||||
tty.c_cflag &= ~CRTSCTS; // No hardware flow control
|
||||
|
||||
// Non-canonical mode
|
||||
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
||||
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||
tty.c_oflag &= ~OPOST;
|
||||
|
||||
// Fetch bytes as they become available
|
||||
// See http://man7.org/linux/man-pages/man3/termios.3.html
|
||||
tty.c_cc[VMIN] = 0;
|
||||
tty.c_cc[VTIME] = 1; // 100ms timeout (1 decisecond, what?)
|
||||
|
||||
if(tcsetattr(fd, TCSAFLUSH, &tty) != 0) { // Flushes input and output buffers as well as setting settings
|
||||
close();
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(tcgetattr(fd, &compare) != 0 || memcmp(&tty, &compare, sizeof(struct termios)) != 0) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create threads
|
||||
readThread = std::thread(&CDCACM::readTask, this);
|
||||
writeThread = std::thread(&CDCACM::writeTask, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDCACM::isOpen() {
|
||||
return fd >= 0; // Negative fd indicates error or not opened yet
|
||||
}
|
||||
|
||||
bool CDCACM::close() {
|
||||
if(!isOpen() && !isDisconnected()) {
|
||||
report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsClosing(true);
|
||||
|
||||
if(readThread.joinable())
|
||||
readThread.join();
|
||||
|
||||
if(writeThread.joinable())
|
||||
writeThread.join();
|
||||
|
||||
setIsClosing(false);
|
||||
setIsDisconnected(false);
|
||||
|
||||
if(modeChanging) {
|
||||
// We're expecting this inode to go away after we close the device
|
||||
// In order to block waiting for this to happen, we first need to
|
||||
// get the inode.
|
||||
struct stat fileStat = {};
|
||||
if(fstat(fd, &fileStat) >= 0)
|
||||
disallowedInode = fileStat.st_ino;
|
||||
}
|
||||
|
||||
int ret = ::close(fd);
|
||||
fd = -1;
|
||||
|
||||
clearBuffers();
|
||||
|
||||
if(modeChanging) {
|
||||
modeChanging = false;
|
||||
return open(); // Reopen the reenumerated device
|
||||
}
|
||||
|
||||
if(ret == 0) {
|
||||
return true;
|
||||
} else {
|
||||
report(APIEvent::Type::DriverFailedToClose, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CDCACM::modeChangeIncoming() {
|
||||
modeChanging = true;
|
||||
}
|
||||
|
||||
void CDCACM::awaitModeChangeComplete() {
|
||||
std::unique_lock<std::mutex> lk(modeChangeMutex);
|
||||
if(modeChanging && !modeChangeThread.joinable()) // Waiting for the thread to start
|
||||
modeChangeCV.wait_for(lk, std::chrono::seconds(1), [this] { return modeChangeThread.joinable(); });
|
||||
if(modeChangeThread.joinable())
|
||||
modeChangeThread.join();
|
||||
}
|
||||
|
||||
void CDCACM::readTask() {
|
||||
constexpr size_t READ_BUFFER_SIZE = 2048;
|
||||
uint8_t readbuf[READ_BUFFER_SIZE];
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
while(!isClosing() && !isDisconnected()) {
|
||||
fd_set rfds = {0};
|
||||
struct timeval tv = {};
|
||||
FD_SET(fd, &rfds);
|
||||
tv.tv_usec = 50000; // 50ms
|
||||
::select(fd + 1, &rfds, NULL, NULL, &tv);
|
||||
auto bytesRead = ::read(fd, readbuf, READ_BUFFER_SIZE);
|
||||
if(bytesRead > 0) {
|
||||
#if 0 // Perhaps helpful for debugging :)
|
||||
std::cout << "Read data: (" << bytesRead << ')' << std::hex << std::endl;
|
||||
for(int i = 0; i < bytesRead; i += 16) {
|
||||
for(int j = 0; j < std::min<int>(bytesRead - i, 16); j++)
|
||||
std::cout << std::setw(2) << std::setfill('0') << uint32_t(readbuf[i+j]) << ' ';
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << std::dec << std::endl;
|
||||
#endif
|
||||
pushRx(readbuf, bytesRead);
|
||||
} else {
|
||||
if(modeChanging) {
|
||||
// We were expecting a disconnect for reenumeration
|
||||
modeChangeThread = std::thread([this] {
|
||||
modeChangeCV.notify_all();
|
||||
// Requesting thread is responsible for calling close. This allows for more flexibility
|
||||
});
|
||||
break;
|
||||
} else if(!isClosing() && !fdIsValid() && !isDisconnected()) {
|
||||
setIsDisconnected(true);
|
||||
report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CDCACM::writeTask() {
|
||||
WriteOperation writeOp;
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
while(!isClosing() && !isDisconnected()) {
|
||||
if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100)))
|
||||
continue;
|
||||
|
||||
const ssize_t totalWriteSize = (ssize_t)writeOp.bytes.size();
|
||||
ssize_t totalWritten = 0;
|
||||
while(totalWritten < totalWriteSize) {
|
||||
const ssize_t writeSize = totalWriteSize - totalWritten;
|
||||
ssize_t actualWritten = ::write(fd, writeOp.bytes.data() + totalWritten, writeSize);
|
||||
if(actualWritten != writeSize) {
|
||||
// If we partially wrote, it's probably EAGAIN but it won't have been set
|
||||
// so don't signal an error unless it's < 0, we'll come back around and
|
||||
// get a -1 to see the real error.
|
||||
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
|
||||
// We filled the TX FIFO, use select to wait for it to become available again
|
||||
fd_set wfds = {0};
|
||||
struct timeval tv = {};
|
||||
FD_SET(fd, &wfds);
|
||||
tv.tv_usec = 50000; // 50ms
|
||||
::select(fd + 1, nullptr, &wfds, nullptr, &tv);
|
||||
} else if (actualWritten < 0) {
|
||||
if(!fdIsValid()) {
|
||||
if(!isDisconnected()) {
|
||||
setIsDisconnected(true);
|
||||
report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error);
|
||||
}
|
||||
} else
|
||||
report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(actualWritten > 0) {
|
||||
#if 0 // Perhaps helpful for debugging :)
|
||||
std::cout << "Wrote data: (" << actualWritten << ')' << std::hex << std::endl;
|
||||
for(int i = 0; i < actualWritten; i += 16) {
|
||||
for(int j = 0; j < std::min<int>(actualWritten - i, 16); j++)
|
||||
std::cout << std::setw(2) << std::setfill('0') << uint32_t(writeOp.bytes[totalWritten+i+j]) << ' ';
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << std::dec << std::endl;
|
||||
#endif
|
||||
|
||||
totalWritten += actualWritten;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CDCACM::fdIsValid() {
|
||||
struct termios tty = {};
|
||||
return tcgetattr(fd, &tty) == 0 ? true : false;
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
#include "icsneo/platform/cdcacm.h"
|
||||
#include "icsneo/device/founddevice.h"
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <cctype>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/usb/IOUSBLib.h>
|
||||
#include <IOKit/serial/IOSerialKeys.h>
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
/* The index into the TTY table starts at zero, but we want to keep zero
|
||||
* for an undefined handle, so add a constant.
|
||||
*/
|
||||
static constexpr const neodevice_handle_t HANDLE_OFFSET = 10;
|
||||
|
||||
static std::mutex ttyTableMutex;
|
||||
static std::vector<std::string> ttyTable;
|
||||
|
||||
static neodevice_handle_t TTYToHandle(const std::string& tty) {
|
||||
std::lock_guard<std::mutex> lk(ttyTableMutex);
|
||||
for(size_t i = 0; i < ttyTable.size(); i++) {
|
||||
if(ttyTable[i] == tty)
|
||||
return neodevice_handle_t(i + HANDLE_OFFSET);
|
||||
}
|
||||
ttyTable.push_back(tty);
|
||||
return neodevice_handle_t(ttyTable.size() - 1 + HANDLE_OFFSET);
|
||||
}
|
||||
|
||||
static std::string CFStringToString(CFStringRef cfString) {
|
||||
if(cfString == nullptr)
|
||||
return std::string();
|
||||
// As an optimization, we can try to directly read the CFString's CStringPtr
|
||||
// CoreFoundation will seemingly not lift a finger if the pointer is not readily available
|
||||
// so it will often return nullptr and we'll have to get the string "the hard way"
|
||||
const char* cstr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8);
|
||||
if(cstr != nullptr)
|
||||
return std::string(cstr);
|
||||
// "The hard way"
|
||||
// CFStringGetLength returns the length in UTF-16 Code Points
|
||||
// CFStringGetCString will convert to UTF-8 for us so long as we give it enough space
|
||||
const int len = CFStringGetLength(cfString);
|
||||
if(len <= 0)
|
||||
return std::string();
|
||||
std::vector<char> utf8data;
|
||||
utf8data.resize(len * 4 + 1); // UTF-16 => UTF-8, 1 code point can become up to 4 bytes, plus NUL
|
||||
if(!CFStringGetCString(cfString, utf8data.data(), utf8data.size(), kCFStringEncodingUTF8))
|
||||
return std::string();
|
||||
return std::string(utf8data.data());
|
||||
}
|
||||
|
||||
class CFReleaser {
|
||||
public:
|
||||
CFReleaser(CFTypeRef obj) : toRelease(obj) {}
|
||||
~CFReleaser() {
|
||||
if(toRelease != nullptr)
|
||||
CFRelease(toRelease);
|
||||
}
|
||||
CFReleaser(CFReleaser const&) = delete;
|
||||
CFReleaser& operator=(CFReleaser const&) = delete;
|
||||
|
||||
private:
|
||||
CFTypeRef toRelease;
|
||||
};
|
||||
|
||||
class IOReleaser {
|
||||
public:
|
||||
IOReleaser(io_object_t obj) : toRelease(obj) {}
|
||||
~IOReleaser() {
|
||||
if(toRelease != 0)
|
||||
IOObjectRelease(toRelease);
|
||||
}
|
||||
IOReleaser(IOReleaser&& moved) : toRelease(moved.toRelease) {
|
||||
moved.toRelease = 0;
|
||||
}
|
||||
IOReleaser(IOReleaser const&) = delete;
|
||||
IOReleaser& operator=(IOReleaser const&) = delete;
|
||||
|
||||
private:
|
||||
io_object_t toRelease;
|
||||
};
|
||||
|
||||
void CDCACM::Find(std::vector<FoundDevice>& found) {
|
||||
CFMutableDictionaryRef ref = IOServiceMatching(kIOSerialBSDServiceValue);
|
||||
if(ref == nullptr)
|
||||
return;
|
||||
io_iterator_t matchingServices = 0;
|
||||
kern_return_t kernResult = IOServiceGetMatchingServices(kIOMainPortDefault, ref, &matchingServices);
|
||||
if(KERN_SUCCESS != kernResult || matchingServices == 0)
|
||||
return;
|
||||
IOReleaser matchingServicesReleaser(matchingServices);
|
||||
|
||||
io_object_t serialPort;
|
||||
while((serialPort = IOIteratorNext(matchingServices))) {
|
||||
IOReleaser serialPortReleaser(serialPort);
|
||||
|
||||
// First get the parent device
|
||||
// We want to check that it has the right VID/PID
|
||||
|
||||
// Find the parent structure that describes the USB device providing this port
|
||||
io_object_t parent = 0;
|
||||
io_object_t current = serialPort;
|
||||
io_object_t usb = 0;
|
||||
// Need to release every parent we find in the chain, but we should defer that until later
|
||||
std::vector<IOReleaser> releasers;
|
||||
while(IORegistryEntryGetParentEntry(current, kIOServicePlane, &parent) == KERN_SUCCESS) {
|
||||
releasers.emplace_back(parent);
|
||||
current = parent;
|
||||
// On old macOSes, IOUSBDevice is the type of the class we want
|
||||
// On newer macOSes, IOUSBDevice may further be subclassed as IOUSBHostDevice
|
||||
if(IOObjectConformsTo(parent, kIOUSBDeviceClassName)) {
|
||||
usb = parent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!usb) // Did not find a USB parent (maybe this is a Bluetooth modem or something)
|
||||
continue;
|
||||
|
||||
// Get the VID
|
||||
CFTypeRef vendorProp = IORegistryEntryCreateCFProperty(usb, CFSTR("idVendor"), kCFAllocatorDefault, 0);
|
||||
if(vendorProp == nullptr)
|
||||
continue;
|
||||
CFReleaser vendorPropReleaser(vendorProp);
|
||||
if(CFGetTypeID(vendorProp) != CFNumberGetTypeID())
|
||||
continue;
|
||||
uint16_t vid = 0;
|
||||
if(!CFNumberGetValue(static_cast<CFNumberRef>(vendorProp), kCFNumberSInt16Type, &vid))
|
||||
continue;
|
||||
if(vid != INTREPID_USB_VENDOR_ID)
|
||||
continue;
|
||||
|
||||
// Get the PID
|
||||
CFTypeRef productProp = IORegistryEntryCreateCFProperty(usb, CFSTR("idProduct"), kCFAllocatorDefault, 0);
|
||||
if(productProp == nullptr)
|
||||
continue;
|
||||
CFReleaser productPropReleaser(productProp);
|
||||
if(CFGetTypeID(productProp) != CFNumberGetTypeID())
|
||||
continue;
|
||||
|
||||
// Read the PID directly into the FoundDevice structure
|
||||
FoundDevice device;
|
||||
if(!CFNumberGetValue(static_cast<CFNumberRef>(productProp), kCFNumberSInt16Type, &device.productId))
|
||||
continue;
|
||||
|
||||
// Now, let's get the "call-out" device (/dev/cu.*)
|
||||
// We get the /dev/cu.* variant instead of the /dev/tty.* variant because the device will not assert DCD
|
||||
// Therefore, the open call on /dev/tty.* will hang, whereas /dev/cu.* will not
|
||||
// This `propertyValue` will need to be freed, that will happen in CFStringToString
|
||||
CFTypeRef calloutProp = IORegistryEntryCreateCFProperty(serialPort, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
|
||||
if(calloutProp == nullptr)
|
||||
continue;
|
||||
CFReleaser calloutPropReleaser(calloutProp);
|
||||
if(CFGetTypeID(calloutProp) != CFStringGetTypeID())
|
||||
continue;
|
||||
// We can static cast here because we have verified the type to be a CFString
|
||||
const std::string tty = CFStringToString(static_cast<CFStringRef>(calloutProp));
|
||||
if(tty.empty())
|
||||
continue;
|
||||
device.handle = TTYToHandle(tty);
|
||||
|
||||
// Last but not least, get the serial number
|
||||
CFTypeRef serialProp = IORegistryEntryCreateCFProperty(usb, CFSTR("kUSBSerialNumberString"), kCFAllocatorDefault, 0);
|
||||
if(serialProp == nullptr)
|
||||
continue;
|
||||
CFReleaser serialPropReleaser(serialProp);
|
||||
if(CFGetTypeID(serialProp) != CFStringGetTypeID())
|
||||
continue;
|
||||
// We can static cast here because we have verified the type to be a CFString
|
||||
std::string serial = CFStringToString(static_cast<CFStringRef>(serialProp));
|
||||
if(serial.empty())
|
||||
continue;
|
||||
for(char& c : serial)
|
||||
c = static_cast<char>(toupper(c));
|
||||
device.serial[serial.copy(device.serial, sizeof(device.serial)-1)] = '\0';
|
||||
|
||||
// Add a factory to make the driver
|
||||
device.makeDriver = [](const device_eventhandler_t& report, neodevice_t& device) {
|
||||
return std::unique_ptr<Driver>(new CDCACM(report, device));
|
||||
};
|
||||
|
||||
found.push_back(device);
|
||||
}
|
||||
}
|
||||
|
||||
std::string CDCACM::HandleToTTY(neodevice_handle_t handle) {
|
||||
std::lock_guard<std::mutex> lk(ttyTableMutex);
|
||||
const size_t index = size_t(handle - HANDLE_OFFSET);
|
||||
if(index >= ttyTable.size())
|
||||
return ""; // Not found, generic driver (cdcacm.cpp) will throw an error
|
||||
return ttyTable[index];
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
#include "icsneo/platform/cdcacm.h"
|
||||
#include "icsneo/device/founddevice.h"
|
||||
#include <dirent.h>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <termios.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
/* The TTY numbering starts at zero, but we want to keep zero for an undefined
|
||||
* handle, so add a constant.
|
||||
*/
|
||||
static constexpr const neodevice_handle_t HANDLE_OFFSET = 10;
|
||||
|
||||
class Directory {
|
||||
public:
|
||||
class Listing {
|
||||
public:
|
||||
Listing(std::string newName, uint8_t newType) : name(newName), type(newType) {}
|
||||
const std::string& getName() const { return name; }
|
||||
uint8_t getType() const { return type; }
|
||||
private:
|
||||
std::string name;
|
||||
uint8_t type;
|
||||
};
|
||||
Directory(std::string directory) {
|
||||
dir = opendir(directory.c_str());
|
||||
}
|
||||
~Directory() {
|
||||
if(openedSuccessfully())
|
||||
closedir(dir);
|
||||
dir = nullptr;
|
||||
}
|
||||
bool openedSuccessfully() { return dir != nullptr; }
|
||||
std::vector<Listing> ls() {
|
||||
std::vector<Listing> results;
|
||||
struct dirent* entry;
|
||||
while((entry = readdir(dir)) != nullptr) {
|
||||
std::string name = entry->d_name;
|
||||
if(name != "." && name != "..") // Ignore parent and self
|
||||
results.emplace_back(name, entry->d_type);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
private:
|
||||
DIR* dir;
|
||||
};
|
||||
|
||||
class USBSerialGetter {
|
||||
public:
|
||||
USBSerialGetter(std::string usbid) {
|
||||
std::stringstream ss;
|
||||
auto colonpos = usbid.find(":");
|
||||
if(colonpos == std::string::npos) {
|
||||
succeeded = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ss << "/sys/bus/usb/devices/" << usbid.substr(0, colonpos) << "/serial";
|
||||
try {
|
||||
std::ifstream reader(ss.str());
|
||||
std::getline(reader, serial);
|
||||
for (auto& c : serial)
|
||||
c = toupper(c);
|
||||
} catch(...) {
|
||||
succeeded = false;
|
||||
return;
|
||||
}
|
||||
|
||||
succeeded = true;
|
||||
}
|
||||
bool success() const { return succeeded; }
|
||||
const std::string& getSerial() const { return serial; }
|
||||
private:
|
||||
bool succeeded;
|
||||
std::string serial;
|
||||
};
|
||||
|
||||
void CDCACM::Find(std::vector<FoundDevice>& found) {
|
||||
Directory directory("/sys/bus/usb/drivers/cdc_acm"); // Query the CDCACM driver
|
||||
if(!directory.openedSuccessfully())
|
||||
return;
|
||||
|
||||
std::vector<std::string> foundusbs;
|
||||
for(auto& entry : directory.ls()) {
|
||||
/* This directory will have directories (links) for all devices using the cdc_acm driver (as CDCACM devices do)
|
||||
* There will also be other files and directories providing information about the driver in here. We want to ignore them.
|
||||
* Devices will be named like "7-2:1.0" where 7 is the enumeration for the USB controller, 2 is the device enumeration on
|
||||
* that specific controller (will change if the device is unplugged and replugged), 1 is the device itself and 0 is
|
||||
* enumeration for different services provided by the device. We're looking for the service that provides TTY.
|
||||
* For now we find the directories with a digit for the first character, these are likely to be our USB devices.
|
||||
*/
|
||||
if(isdigit(entry.getName()[0]) && entry.getType() == DT_LNK)
|
||||
foundusbs.emplace_back(entry.getName());
|
||||
}
|
||||
|
||||
// Map the USB directory to the TTY and PID if found
|
||||
// The PID will be filled later
|
||||
std::map< std::string, std::pair<std::string, uint16_t> > foundttys;
|
||||
for(auto& usb : foundusbs) {
|
||||
std::stringstream ss;
|
||||
ss << "/sys/bus/usb/drivers/cdc_acm/" << usb << "/tty";
|
||||
Directory devicedir(ss.str());
|
||||
if(!devicedir.openedSuccessfully()) // The tty directory doesn't exist, because this is not the tty service we want
|
||||
continue;
|
||||
|
||||
auto listing = devicedir.ls();
|
||||
if(listing.size() != 1) // We either got no serial ports or multiple, either way no good
|
||||
continue;
|
||||
|
||||
foundttys.insert(std::make_pair(usb, std::make_pair(listing[0].getName(), 0)));
|
||||
}
|
||||
|
||||
// We're going to remove from the map if this is not the product we're looking for
|
||||
for(auto iter = foundttys.begin(); iter != foundttys.end(); ) {
|
||||
auto& [_, pair] = *iter;
|
||||
auto& [tty, ttyPid] = pair;
|
||||
const std::string matchString = "PRODUCT=";
|
||||
std::stringstream ss;
|
||||
ss << "/sys/class/tty/" << tty << "/device/uevent"; // Read the uevent file, which contains should have a line like "PRODUCT=93c/1101/100"
|
||||
std::ifstream fs(ss.str());
|
||||
std::string productLine;
|
||||
size_t pos = std::string::npos;
|
||||
do {
|
||||
std::getline(fs, productLine, '\n');
|
||||
} while(((pos = productLine.find(matchString)) == std::string::npos) && !fs.eof());
|
||||
|
||||
if(pos != 0) { // We did not find a product line... weird
|
||||
iter = foundttys.erase(iter); // Remove the element, this also moves iter forward for us
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t firstSlashPos = productLine.find('/', matchString.length());
|
||||
if(firstSlashPos == std::string::npos) {
|
||||
iter = foundttys.erase(iter);
|
||||
continue;
|
||||
}
|
||||
size_t pidpos = firstSlashPos + 1;
|
||||
|
||||
std::string vidstr = productLine.substr(matchString.length(), firstSlashPos - matchString.length());
|
||||
std::string pidstr = productLine.substr(pidpos, productLine.find('/', pidpos) - pidpos); // In hex like "1101" or "93c"
|
||||
|
||||
uint16_t vid, pid;
|
||||
try {
|
||||
vid = (uint16_t)std::stoul(vidstr, nullptr, 16);
|
||||
pid = (uint16_t)std::stoul(pidstr, nullptr, 16);
|
||||
} catch(...) {
|
||||
iter = foundttys.erase(iter); // We could not parse the numbers
|
||||
continue;
|
||||
}
|
||||
|
||||
if(vid != INTREPID_USB_VENDOR_ID) {
|
||||
iter = foundttys.erase(iter); // Not the right VID, remove
|
||||
continue;
|
||||
}
|
||||
ttyPid = pid; // Set the PID for this TTY
|
||||
iter++; // If the loop ends without erasing the iter from the map, the item is good
|
||||
}
|
||||
|
||||
// At this point, foundttys contains the the devices we want
|
||||
|
||||
// Get the serial number, create the neodevice_t
|
||||
for(auto& [usb, pair] : foundttys) {
|
||||
auto& [tty, ttyPid] = pair;
|
||||
FoundDevice device;
|
||||
|
||||
USBSerialGetter getter(usb);
|
||||
if(!getter.success())
|
||||
continue; // Failure, could not get serial number
|
||||
|
||||
// In ttyACM0, we want the i to be the first character of the number
|
||||
size_t i;
|
||||
for(i = 0; i < tty.length(); i++) {
|
||||
if(isdigit(tty[i]))
|
||||
break;
|
||||
}
|
||||
// Now we try to parse the number so we have a handle for later
|
||||
try {
|
||||
device.handle = (neodevice_handle_t)std::stoul(tty.substr(i));
|
||||
/* The TTY numbering starts at zero, but we want to keep zero for an undefined
|
||||
* handle, so add a constant, and we'll subtract that constant in the open function.
|
||||
*/
|
||||
device.handle += HANDLE_OFFSET;
|
||||
} catch(...) {
|
||||
continue; // Somehow this failed, have to toss the device
|
||||
}
|
||||
|
||||
device.productId = ttyPid;
|
||||
device.serial[getter.getSerial().copy(device.serial, sizeof(device.serial)-1)] = '\0';
|
||||
|
||||
// Add a factory to make the driver
|
||||
device.makeDriver = [](const device_eventhandler_t& report, neodevice_t& device) {
|
||||
return std::unique_ptr<Driver>(new CDCACM(report, device));
|
||||
};
|
||||
|
||||
found.push_back(device); // Finally, add device to search results
|
||||
}
|
||||
}
|
||||
|
||||
std::string CDCACM::HandleToTTY(neodevice_handle_t handle) {
|
||||
std::stringstream ss;
|
||||
ss << "/dev/ttyACM" << (int)(handle - HANDLE_OFFSET);
|
||||
return ss.str();
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
#include "icsneo/platform/posix/pcap.h"
|
||||
#include "icsneo/communication/network.h"
|
||||
#include "icsneo/communication/communication.h"
|
||||
#include "icsneo/communication/ethernetpacketizer.h"
|
||||
#include "icsneo/communication/packetizer.h"
|
||||
#include "icsneo/communication/decoder.h"
|
||||
#include <codecvt>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#ifdef __linux__
|
||||
#include <netpacket/packet.h>
|
||||
#else
|
||||
#include <net/if_dl.h>
|
||||
#endif
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
std::vector<PCAP::NetworkInterface> PCAP::knownInterfaces;
|
||||
|
||||
void PCAP::Find(std::vector<FoundDevice>& found) {
|
||||
static bool warned = false; // Only warn once for failure to open devices
|
||||
|
||||
// First we ask PCAP to give us all of the devices
|
||||
pcap_if_t* alldevs;
|
||||
char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
|
||||
bool success = false;
|
||||
// Calling pcap_findalldevs too quickly can cause various errors. Retry a few times in this case.
|
||||
for(auto retry = 0; retry < 10; retry++) {
|
||||
auto ret = pcap_findalldevs(&alldevs, errbuf);
|
||||
if(ret == 0) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!success) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::PCAPCouldNotFindDevices, APIEvent::Severity::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<NetworkInterface> interfaces;
|
||||
for(pcap_if_t* dev = alldevs; dev != nullptr; dev = dev->next) {
|
||||
if(dev->name == nullptr)
|
||||
continue;
|
||||
if(dev->addresses == nullptr) {
|
||||
//std::cout << dev->name << " has no addresses" << std::endl;
|
||||
continue;
|
||||
}
|
||||
NetworkInterface netif;
|
||||
netif.nameFromPCAP = dev->name;
|
||||
if(dev->description)
|
||||
netif.descriptionFromPCAP = dev->description;
|
||||
pcap_addr* currentAddress = dev->addresses;
|
||||
bool hasAddress = false;
|
||||
while(!hasAddress && currentAddress != nullptr) {
|
||||
#ifdef __linux__
|
||||
if(currentAddress->addr && currentAddress->addr->sa_family == AF_PACKET) {
|
||||
struct sockaddr_ll* s = (struct sockaddr_ll*)currentAddress->addr;
|
||||
memcpy(netif.macAddress, s->sll_addr, sizeof(netif.macAddress));
|
||||
hasAddress = true;
|
||||
break;
|
||||
}
|
||||
#else // macOS and likely other BSDs
|
||||
if(currentAddress->addr && currentAddress->addr->sa_family == AF_LINK) {
|
||||
struct sockaddr_dl* s = (struct sockaddr_dl*)currentAddress->addr;
|
||||
if(s->sdl_alen == 6 && s->sdl_alen + s->sdl_nlen < sizeof(s->sdl_data)) {
|
||||
const uint8_t* mac = (uint8_t*)(s->sdl_data) + s->sdl_nlen;
|
||||
memcpy(netif.macAddress, mac, sizeof(netif.macAddress));
|
||||
hasAddress = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
currentAddress = currentAddress->next;
|
||||
}
|
||||
|
||||
if(!hasAddress)
|
||||
continue;
|
||||
|
||||
interfaces.push_back(netif);
|
||||
}
|
||||
|
||||
pcap_freealldevs(alldevs);
|
||||
|
||||
for(auto& iface : interfaces) {
|
||||
bool exists = false;
|
||||
for(auto& known : knownInterfaces)
|
||||
if(memcmp(iface.macAddress, known.macAddress, sizeof(iface.macAddress)) == 0)
|
||||
exists = true;
|
||||
if(!exists)
|
||||
knownInterfaces.emplace_back(iface);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < knownInterfaces.size(); i++) {
|
||||
auto& iface = knownInterfaces[i];
|
||||
// if(iface.fullName.length() == 0)
|
||||
// continue; // Win32 did not find this interface in the previous step
|
||||
|
||||
errbuf[0] = '\0';
|
||||
iface.fp = pcap_open_live(iface.nameFromPCAP.c_str(), 65536, 1,
|
||||
#ifdef __linux__ // -1 is required for instant reporting of new packets
|
||||
-1, // to_ms
|
||||
#else // macOS gives BIOCSRTIMEOUT for -1 and no packets for 0
|
||||
1,
|
||||
#endif
|
||||
errbuf);
|
||||
// TODO Handle warnings
|
||||
// if(strlen(errbuf) != 0) { // This means a warning
|
||||
// std::cout << "Warning for " << iface.nameFromPCAP << " " << errbuf << std::endl;
|
||||
// }
|
||||
|
||||
if(iface.fp == nullptr) {
|
||||
if (!warned) {
|
||||
warned = true;
|
||||
EventManager::GetInstance().add(APIEvent::Type::PCAPCouldNotFindDevices, APIEvent::Severity::EventWarning);
|
||||
// std::cout << "pcap_open_live failed for " << iface.nameFromPCAP << " with " << errbuf << std::endl;
|
||||
}
|
||||
continue; // Could not open the interface
|
||||
}
|
||||
|
||||
pcap_setnonblock(iface.fp, 1, errbuf);
|
||||
|
||||
EthernetPacketizer::EthernetPacket requestPacket;
|
||||
memcpy(requestPacket.srcMAC, iface.macAddress, sizeof(requestPacket.srcMAC));
|
||||
requestPacket.payload.reserve(4);
|
||||
requestPacket.payload = {
|
||||
((1 << 4) | (uint8_t)Network::NetID::Main51), // Packet size of 1 on NETID_MAIN51
|
||||
(uint8_t)Command::RequestSerialNumber
|
||||
};
|
||||
requestPacket.payload.push_back(Packetizer::ICSChecksum(requestPacket.payload));
|
||||
requestPacket.payload.insert(requestPacket.payload.begin(), 0xAA);
|
||||
|
||||
auto bs = requestPacket.getBytestream();
|
||||
pcap_sendpacket(iface.fp, bs.data(), (int)bs.size());
|
||||
|
||||
auto timeout = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(50);
|
||||
constexpr const size_t TempBufferSize = 4096;
|
||||
RingBuffer tempBuffer(TempBufferSize);
|
||||
while(std::chrono::high_resolution_clock::now() <= timeout) { // Wait up to 50ms for the response
|
||||
struct pcap_pkthdr* header;
|
||||
const uint8_t* data;
|
||||
auto res = pcap_next_ex(iface.fp, &header, &data);
|
||||
if(res == 0)
|
||||
continue;
|
||||
if(res < 0 || !header || !data) {
|
||||
if (!warned) {
|
||||
warned = true;
|
||||
EventManager::GetInstance().add(APIEvent::Type::PCAPCouldNotFindDevices, APIEvent::Severity::EventWarning);
|
||||
// std::cout << "pcapnextex failed with " << res << std::endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
EthernetPacketizer ethPacketizer([](APIEvent::Type, APIEvent::Severity) {});
|
||||
memcpy(ethPacketizer.hostMAC, iface.macAddress, sizeof(ethPacketizer.hostMAC));
|
||||
ethPacketizer.allowInPacketsFromAnyMAC = true;
|
||||
if(!ethPacketizer.inputUp({ data, data + header->caplen }))
|
||||
continue; // This packet is not for us
|
||||
|
||||
Packetizer packetizer([](APIEvent::Type, APIEvent::Severity) {});
|
||||
tempBuffer.write(ethPacketizer.outputUp());
|
||||
if(!packetizer.input(tempBuffer))
|
||||
continue; // This packet was not well formed
|
||||
|
||||
EthernetPacketizer::EthernetPacket decoded(data, header->caplen);
|
||||
Decoder decoder([](APIEvent::Type, APIEvent::Severity) {});
|
||||
for(const auto& packet : packetizer.output()) {
|
||||
std::shared_ptr<Message> message;
|
||||
if(!decoder.decode(message, packet))
|
||||
continue;
|
||||
|
||||
const auto serial = std::dynamic_pointer_cast<SerialNumberMessage>(message);
|
||||
if(!serial || serial->deviceSerial.size() != 6)
|
||||
continue;
|
||||
|
||||
FoundDevice foundDevice;
|
||||
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<Driver>(new PCAP(report, device));
|
||||
};
|
||||
|
||||
found.push_back(foundDevice);
|
||||
}
|
||||
}
|
||||
|
||||
pcap_close(iface.fp);
|
||||
iface.fp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool PCAP::IsHandleValid(neodevice_handle_t handle) {
|
||||
uint8_t netifIndex = (uint8_t)(handle >> 24);
|
||||
return (netifIndex < knownInterfaces.size());
|
||||
}
|
||||
|
||||
PCAP::PCAP(device_eventhandler_t err, neodevice_t& forDevice) : Driver(err), device(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.
|
||||
|
||||
deviceMAC[0] = 0x00;
|
||||
deviceMAC[1] = 0xFC;
|
||||
deviceMAC[2] = 0x70;
|
||||
deviceMAC[3] = (device.handle >> 16) & 0xFF;
|
||||
deviceMAC[4] = (device.handle >> 8) & 0xFF;
|
||||
deviceMAC[5] = device.handle & 0xFF;
|
||||
memcpy(ethPacketizer.deviceMAC, deviceMAC, 6);
|
||||
memcpy(ethPacketizer.hostMAC, iface.macAddress, 6);
|
||||
} else {
|
||||
openable = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PCAP::open() {
|
||||
if(!openable)
|
||||
return false;
|
||||
|
||||
if(isOpen())
|
||||
return false;
|
||||
|
||||
// Open the interface
|
||||
iface.fp = pcap_open_live(iface.nameFromPCAP.c_str(), 65536, 1,
|
||||
#ifdef __linux__ // -1 is required for instant reporting of new packets
|
||||
-1, // to_ms
|
||||
#else // macOS gives BIOCSRTIMEOUT for -1 and no packets for 0
|
||||
1,
|
||||
#endif
|
||||
errbuf);
|
||||
if(iface.fp == nullptr) {
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
pcap_setnonblock(iface.fp, 0, errbuf);
|
||||
pcap_set_immediate_mode(iface.fp, 1);
|
||||
|
||||
// Create threads
|
||||
readThread = std::thread(&PCAP::readTask, this);
|
||||
writeThread = std::thread(&PCAP::writeTask, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PCAP::isOpen() {
|
||||
return iface.fp != nullptr;
|
||||
}
|
||||
|
||||
bool PCAP::close() {
|
||||
if(!isOpen())
|
||||
return false;
|
||||
|
||||
setIsClosing(true); // Signal the threads that we are closing
|
||||
pcap_breakloop(iface.fp);
|
||||
#ifndef __linux__
|
||||
pthread_cancel(readThread.native_handle());
|
||||
#endif
|
||||
readThread.join();
|
||||
writeThread.join();
|
||||
setIsClosing(false);
|
||||
|
||||
pcap_close(iface.fp);
|
||||
iface.fp = nullptr;
|
||||
|
||||
clearBuffers();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PCAP::readTask() {
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
while (!isClosing()) {
|
||||
pcap_dispatch(iface.fp, -1, [](uint8_t* obj, const struct pcap_pkthdr* header, const uint8_t* data) {
|
||||
PCAP* driver = reinterpret_cast<PCAP*>(obj);
|
||||
if(driver->ethPacketizer.inputUp({data, data + header->caplen})) {
|
||||
const auto bytes = driver->ethPacketizer.outputUp();
|
||||
driver->pushRx(bytes.data(), bytes.size());
|
||||
}
|
||||
}, (uint8_t*)this);
|
||||
}
|
||||
}
|
||||
|
||||
void PCAP::writeTask() {
|
||||
WriteOperation writeOp;
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
|
||||
while(!isClosing()) {
|
||||
if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100)))
|
||||
continue;
|
||||
|
||||
// If we have a bunch of small packets to send, try to pack them into a packet
|
||||
// We use the average packet size to determine if we're likely to have enough room
|
||||
size_t bytesPushed = 0;
|
||||
size_t packetsPushed = 0;
|
||||
do {
|
||||
packetsPushed++;
|
||||
bytesPushed += writeOp.bytes.size();
|
||||
ethPacketizer.inputDown(std::move(writeOp.bytes));
|
||||
} while(bytesPushed < (EthernetPacketizer::MaxPacketLength - (bytesPushed / packetsPushed * 2)) && writeQueue.try_dequeue(writeOp));
|
||||
|
||||
for(const auto& packet : ethPacketizer.outputDown()) {
|
||||
pcap_sendpacket(iface.fp, packet.data(), (int)packet.size());
|
||||
}
|
||||
// TODO Handle packet send errors
|
||||
}
|
||||
}
|
||||
@@ -1,573 +0,0 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <vector>
|
||||
#include <fcntl.h>
|
||||
#include <cstring>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#include <optional>
|
||||
#include "icsneo/platform/tcp.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN_INT(a) static_cast<int>(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::Socket::poll(uint16_t event, uint32_t msTimeout) {
|
||||
#ifdef _WIN32
|
||||
WSAPOLLFD pfd;
|
||||
pfd.fd = fd;
|
||||
pfd.events = event;
|
||||
::WSAPoll(&pfd, 1, msTimeout);
|
||||
#else
|
||||
struct pollfd pfd;
|
||||
pfd.fd = fd;
|
||||
pfd.events = event;
|
||||
::poll(&pfd, 1, msTimeout);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TCP::Find(std::vector<FoundDevice>& found) {
|
||||
static const auto tcpDisabled = []() -> bool {
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
const auto disabled = std::getenv("LIBICSNEO_DISABLE_TCP");
|
||||
return disabled ? std::stoi(disabled) : false;
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
};
|
||||
if(tcpDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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<uint8_t> storage;
|
||||
#endif
|
||||
InterfaceHandle front;
|
||||
};
|
||||
|
||||
IFAddresses interfaces;
|
||||
if(!interfaces) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::GetIfAddrsError, APIEvent::Severity::EventWarning);
|
||||
return;
|
||||
}
|
||||
|
||||
for(auto intf = interfaces.begin(); intf; intf = intf.next()) {
|
||||
if(!intf.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, intf.name().data(), intf.name().size()) < 0) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::ErrorSettingSocketOption, APIEvent::Severity::EventWarning);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
auto ifAddrIn = (sockaddr_in*)intf.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<uint8_t, 35> 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;
|
||||
}
|
||||
|
||||
const auto rxTill = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while(std::chrono::steady_clock::now() < rxTill) {
|
||||
static constexpr size_t bufferLen = 2048;
|
||||
uint8_t buffer[bufferLen];
|
||||
// keep trying till the timeout
|
||||
const auto msWait = std::chrono::duration_cast<std::chrono::milliseconds>(rxTill - std::chrono::steady_clock::now()).count();
|
||||
if(msWait < 0) {
|
||||
break;
|
||||
}
|
||||
socket.poll(POLLIN, static_cast<uint32_t>(msWait));
|
||||
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<std::optional<size_t>(uint8_t*,std::vector<std::string_view>&)> parseStrings = [&](uint8_t* start, std::vector<std::string_view>& strings) -> std::optional<size_t> {
|
||||
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<std::string_view> name;
|
||||
Type type;
|
||||
uint8_t* data;
|
||||
uint16_t dataLength;
|
||||
};
|
||||
const auto parseRecord = [&](uint8_t* start, Record& parsedRecord) -> std::optional<size_t> {
|
||||
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<Record>& parsed) -> std::optional<size_t> {
|
||||
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<Record> answers(answerCount);
|
||||
const auto answersLength = parseRecords(buffer + headerLength, answers);
|
||||
if(!answersLength)
|
||||
continue;
|
||||
|
||||
std::vector<Record> 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<uint32_t> devIP;
|
||||
std::optional<uint16_t> 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(intf.name()),
|
||||
ntohl(ifAddrIn->sin_addr.s_addr)
|
||||
};
|
||||
|
||||
foundDevice.makeDriver = [=](const device_eventhandler_t& eh, neodevice_t&) {
|
||||
return std::unique_ptr<Driver>(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), interfaceDescription(on), dstIP(dstIP), dstPort(dstPort) {
|
||||
}
|
||||
|
||||
bool TCP::open() {
|
||||
if(socket) {
|
||||
report(APIEvent::Type::DeviceCurrentlyOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto partiallyOpenSocket = std::make_unique<Socket>(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
if(::setsockopt(*partiallyOpenSocket, SOL_SOCKET, SO_BINDTODEVICE, interfaceDescription.name.c_str(), interfaceDescription.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(interfaceDescription.ip);
|
||||
APPLE_SIN_LEN(addr);
|
||||
if(::bind(*partiallyOpenSocket, (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(*partiallyOpenSocket, (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
|
||||
|
||||
partiallyOpenSocket->poll(POLLOUT, 1000);
|
||||
|
||||
if(::connect(*partiallyOpenSocket, (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
|
||||
}
|
||||
}
|
||||
|
||||
socket = std::move(partiallyOpenSocket);
|
||||
readThread = std::thread(&TCP::readTask, this);
|
||||
writeThread = std::thread(&TCP::writeTask, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TCP::isOpen() {
|
||||
return socket ? true : false;
|
||||
}
|
||||
|
||||
bool TCP::close() {
|
||||
if(!isOpen() && !isDisconnected()) {
|
||||
report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsClosing(true);
|
||||
setIsDisconnected(false);
|
||||
|
||||
if(readThread.joinable())
|
||||
readThread.join();
|
||||
if(writeThread.joinable())
|
||||
writeThread.join();
|
||||
|
||||
clearBuffers();
|
||||
|
||||
socket.reset();
|
||||
setIsClosing(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TCP::readTask() {
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
|
||||
constexpr size_t READ_BUFFER_SIZE = 2048;
|
||||
uint8_t readbuf[READ_BUFFER_SIZE];
|
||||
while(!isClosing()) {
|
||||
if(const auto received = ::recv(*socket, (char*)readbuf, READ_BUFFER_SIZE, 0); received > 0) {
|
||||
pushRx(readbuf, received);
|
||||
} else {
|
||||
socket->poll(POLLIN, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TCP::writeTask() {
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
|
||||
WriteOperation writeOp;
|
||||
while(!isClosing()) {
|
||||
if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100)))
|
||||
continue;
|
||||
|
||||
while(!isClosing()) {
|
||||
if(::send(*socket, (char*)writeOp.bytes.data(), WIN_INT(writeOp.bytes.size()), 0) > 0)
|
||||
break;
|
||||
socket->poll(POLLOUT, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
#include "icsneo/platform/windows/cdcacm.h"
|
||||
|
||||
#include <setupapi.h>
|
||||
#include <initguid.h>
|
||||
#include <usbiodef.h>
|
||||
#include <devpkey.h>
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
#define SERIAL_SIZE 6
|
||||
|
||||
CDCACM::CDCACM(const device_eventhandler_t& err, const std::wstring& path) : Driver(err), path(path) {
|
||||
}
|
||||
|
||||
bool CDCACM::open() {
|
||||
handle = CreateFileW(path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
|
||||
if(handle == INVALID_HANDLE_VALUE) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
COMMTIMEOUTS timeouts;
|
||||
timeouts.ReadIntervalTimeout = MAXDWORD;
|
||||
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
|
||||
timeouts.ReadTotalTimeoutConstant = MAXDWORD - 1;
|
||||
timeouts.WriteTotalTimeoutMultiplier = 0;
|
||||
timeouts.WriteTotalTimeoutConstant = 0;
|
||||
|
||||
if(!SetCommTimeouts(handle, &timeouts)) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
DCB comstate;
|
||||
if(!GetCommState(handle, &comstate)) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
comstate.BaudRate = 115200;
|
||||
comstate.ByteSize = 8;
|
||||
comstate.fRtsControl = RTS_CONTROL_DISABLE;
|
||||
if(!SetCommState(handle, &comstate)) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
PurgeComm(handle, PURGE_RXCLEAR);
|
||||
|
||||
readOverlapped.hEvent = CreateEventA(nullptr, false, false, nullptr);
|
||||
writeOverlapped.hEvent = CreateEventA(nullptr, false, false, nullptr);
|
||||
|
||||
setIsDisconnected(false);
|
||||
readThread = std::thread(&CDCACM::read, this);
|
||||
writeThread = std::thread(&CDCACM::write, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDCACM::isOpen() {
|
||||
return handle != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
bool CDCACM::close() {
|
||||
setIsClosing(true);
|
||||
SetEvent(readOverlapped.hEvent); // unblock read thread
|
||||
SetEvent(writeOverlapped.hEvent); // unblock write thread if waiting on COM write
|
||||
writeQueue.enqueue(WriteOperation{}); // unblock write thread if waiting on write queue pop
|
||||
readThread.join();
|
||||
writeThread.join();
|
||||
CloseHandle(readOverlapped.hEvent);
|
||||
CloseHandle(writeOverlapped.hEvent);
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
setIsClosing(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CDCACM::read() {
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
|
||||
std::vector<uint8_t> buffer(ICSNEO_DRIVER_RINGBUFFER_SIZE);
|
||||
|
||||
while(!isDisconnected() && !isClosing()) {
|
||||
if(!ReadFile(handle, buffer.data(), (DWORD)buffer.size(), nullptr, &readOverlapped)) {
|
||||
if(GetLastError() != ERROR_IO_PENDING) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
DWORD read = 0;
|
||||
if(!GetOverlappedResult(handle, &readOverlapped, &read, true)) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
if(read == 0) {
|
||||
continue;
|
||||
}
|
||||
while(!isDisconnected() && !isClosing()) {
|
||||
if(pushRx(buffer.data(), read))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CDCACM::write() {
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
|
||||
WriteOperation writeOp;
|
||||
|
||||
while(!isDisconnected() && !isClosing()) {
|
||||
writeQueue.wait_dequeue(writeOp);
|
||||
|
||||
if(isDisconnected() || isClosing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!WriteFile(handle, writeOp.bytes.data(), (DWORD)writeOp.bytes.size(), nullptr, &writeOverlapped)) {
|
||||
if(GetLastError() != ERROR_IO_PENDING) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
DWORD written;
|
||||
if(!GetOverlappedResult(handle, &writeOverlapped, &written, true)) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(written != writeOp.bytes.size()) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error);
|
||||
setIsDisconnected(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DeviceInfo {
|
||||
public:
|
||||
DeviceInfo() {
|
||||
mDeviceInfo = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
}
|
||||
~DeviceInfo() {
|
||||
SetupDiDestroyDeviceInfoList(mDeviceInfo);
|
||||
}
|
||||
operator HDEVINFO() const {
|
||||
return mDeviceInfo;
|
||||
}
|
||||
operator bool() const {
|
||||
return mDeviceInfo != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
private:
|
||||
HDEVINFO mDeviceInfo;
|
||||
};
|
||||
|
||||
class DeviceInfoData {
|
||||
public:
|
||||
DeviceInfoData() {
|
||||
mDeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||
}
|
||||
operator SP_DEVINFO_DATA*() {
|
||||
return &mDeviceInfoData;
|
||||
}
|
||||
private:
|
||||
SP_DEVINFO_DATA mDeviceInfoData;
|
||||
};
|
||||
|
||||
static constexpr size_t WSTRING_ELEMENT_SIZE = sizeof(std::wstring::value_type);
|
||||
|
||||
void CDCACM::Find(std::vector<FoundDevice>& found) {
|
||||
DeviceInfoData deviceInfoData;
|
||||
const std::wstring intrepidUSB(L"USB\\VID_093C");
|
||||
DeviceInfo deviceInfoSet;
|
||||
if(!deviceInfoSet) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
for(DWORD i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, deviceInfoData); ++i) {
|
||||
DWORD DataT;
|
||||
DWORD buffersize = 0;
|
||||
|
||||
std::wstring wclass;
|
||||
while(!SetupDiGetDevicePropertyW(deviceInfoSet, deviceInfoData, &DEVPKEY_Device_Class, &DataT, reinterpret_cast<PBYTE>(wclass.data()), static_cast<DWORD>((wclass.size() + 1) * WSTRING_ELEMENT_SIZE), &buffersize, 0)) {
|
||||
wclass.resize((buffersize - 1) / WSTRING_ELEMENT_SIZE);
|
||||
}
|
||||
|
||||
if(wclass != L"Ports") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: is this a bug in Windows? why is this returned size different/wrong? It's like it's not a wstring at all
|
||||
std::wstring deviceInstanceId;
|
||||
while(!SetupDiGetDeviceInstanceIdW(deviceInfoSet, deviceInfoData, deviceInstanceId.data(), static_cast<DWORD>(deviceInstanceId.size() + 1), &buffersize)) {
|
||||
deviceInstanceId.resize(buffersize - 1);
|
||||
}
|
||||
|
||||
if(deviceInstanceId.find(intrepidUSB) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto serialOffset = deviceInstanceId.find_last_of('\\');
|
||||
|
||||
if(serialOffset == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
++serialOffset; // move past '\'
|
||||
|
||||
if(deviceInstanceId.size() < serialOffset + SERIAL_SIZE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::wstring wserial(deviceInstanceId.c_str() + serialOffset, SERIAL_SIZE);
|
||||
|
||||
FoundDevice device = {0};
|
||||
|
||||
if(WideCharToMultiByte(CP_ACP, 0, wserial.c_str(), (int)wserial.size(), device.serial, SERIAL_SIZE, NULL, NULL) == 0) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::SyscallError, APIEvent::Severity::Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::wstring wport;
|
||||
while(!SetupDiGetCustomDevicePropertyW(deviceInfoSet, deviceInfoData, L"PortName", 0, &DataT, reinterpret_cast<PBYTE>(wport.data()), static_cast<DWORD>((wport.size() + 1) * WSTRING_ELEMENT_SIZE), &buffersize)) {
|
||||
wport.resize((buffersize - 1) / WSTRING_ELEMENT_SIZE);
|
||||
}
|
||||
|
||||
const std::wstring path(L"\\\\.\\" + wport);
|
||||
|
||||
device.makeDriver = [path](device_eventhandler_t err, neodevice_t&) {
|
||||
return std::make_unique<CDCACM>(err, path);
|
||||
};
|
||||
|
||||
found.emplace_back(std::move(device));
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
#include "icsneo/platform/windows/internal/pcapdll.h"
|
||||
#include <tchar.h>
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
const PCAPDLL& PCAPDLL::getInstance()
|
||||
{
|
||||
static PCAPDLL instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
PCAPDLL::~PCAPDLL()
|
||||
{
|
||||
closeDLL();
|
||||
}
|
||||
|
||||
void PCAPDLL::closeDLL()
|
||||
{
|
||||
if (dll)
|
||||
FreeLibrary(dll);
|
||||
dll = nullptr;
|
||||
}
|
||||
|
||||
bool PCAPDLL::ok() const
|
||||
{
|
||||
return dll != nullptr;
|
||||
}
|
||||
|
||||
PCAPDLL::PCAPDLL()
|
||||
{
|
||||
TCHAR dir[512] = {0};
|
||||
if(GetSystemDirectory(dir, 480)) {
|
||||
_tcscat_s(dir, 512, TEXT("\\Npcap"));
|
||||
if(SetDllDirectory(dir)) {
|
||||
// will search for Npcap first, then fall back to WinPcap
|
||||
dll = LoadLibrary(TEXT("wpcap.dll"));
|
||||
SetDllDirectory(nullptr); // reset
|
||||
}
|
||||
}
|
||||
|
||||
if(dll == NULL) {
|
||||
closeDLL();
|
||||
} else {
|
||||
findalldevs_ex = (PCAPFINDDEVICE)GetProcAddress(dll, "pcap_findalldevs_ex");
|
||||
open = (PCAPOPEN)GetProcAddress(dll, "pcap_open");
|
||||
freealldevs = (PCAPFREEDEVS)GetProcAddress(dll, "pcap_freealldevs");
|
||||
close = (PCAPCLOSE)GetProcAddress(dll, "pcap_close");
|
||||
stats = (PCAPSTATS)GetProcAddress(dll, "pcap_stats");
|
||||
next_ex = (PCAPNEXTEX)GetProcAddress(dll, "pcap_next_ex");
|
||||
sendpacket = (PCAPSENDPACKET)GetProcAddress(dll, "pcap_sendpacket");
|
||||
sendqueue_alloc = (PCAPSENDQUEUEALLOC)GetProcAddress(dll, "pcap_sendqueue_alloc");
|
||||
sendqueue_queue = (PCAPSENDQUEUEQUEUE)GetProcAddress(dll, "pcap_sendqueue_queue");
|
||||
sendqueue_destroy = (PCAPSENDQUEUEDESTROY)GetProcAddress(dll, "pcap_sendqueue_destroy");
|
||||
sendqueue_transmit = (PCAPSENDQUEUETRANSMIT)GetProcAddress(dll, "pcap_sendqueue_transmit");
|
||||
datalink = (PCAPDATALINK)GetProcAddress(dll, "pcap_datalink");
|
||||
createsrcstr = (PCAPCREATESRCSTR)GetProcAddress(dll, "pcap_createsrcstr");
|
||||
setbuff = (PCAPSETBUFF)GetProcAddress(dll, "pcap_setbuff");
|
||||
if(findalldevs_ex == NULL || open == NULL ||
|
||||
freealldevs == NULL || close == NULL ||
|
||||
stats == NULL || next_ex == NULL ||
|
||||
sendpacket == NULL ||
|
||||
sendqueue_alloc == NULL ||
|
||||
sendqueue_queue == NULL || sendqueue_destroy == NULL ||
|
||||
sendqueue_transmit == NULL ||
|
||||
datalink == NULL ||
|
||||
createsrcstr == NULL || setbuff == NULL) {
|
||||
closeDLL();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
|
||||
#include "icsneo/platform/windows/pcap.h"
|
||||
#include "icsneo/platform/windows/strings.h"
|
||||
#include "icsneo/communication/network.h"
|
||||
#include "icsneo/communication/communication.h"
|
||||
#include "icsneo/communication/ethernetpacketizer.h"
|
||||
#include "icsneo/communication/packetizer.h"
|
||||
#include "icsneo/communication/decoder.h"
|
||||
#include "icsneo/core/ringbuffer.h"
|
||||
#include <pcap.h>
|
||||
#include <iphlpapi.h>
|
||||
#pragma comment(lib, "IPHLPAPI.lib")
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
|
||||
std::vector<PCAP::NetworkInterface> PCAP::knownInterfaces;
|
||||
|
||||
void PCAP::Find(std::vector<FoundDevice>& found) {
|
||||
const PCAPDLL& pcap = PCAPDLL::getInstance();
|
||||
if(!pcap.ok()) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::PCAPCouldNotStart, APIEvent::Severity::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// First we ask WinPCAP to give us all of the devices
|
||||
pcap_if_t* alldevs;
|
||||
char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
|
||||
bool success = false;
|
||||
// Calling pcap.findalldevs_ex too quickly can cause various errors. Retry a few times in this case.
|
||||
for(auto retry = 0; retry < 10; retry++) {
|
||||
auto ret = pcap.findalldevs_ex((char*)PCAP_SRC_IF_STRING, nullptr, &alldevs, errbuf);
|
||||
if(ret == 0) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!success) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::PCAPCouldNotFindDevices, APIEvent::Severity::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<NetworkInterface> interfaces;
|
||||
for(pcap_if_t* dev = alldevs; dev != nullptr; dev = dev->next) {
|
||||
NetworkInterface netif;
|
||||
netif.nameFromWinPCAP = dev->name;
|
||||
netif.descriptionFromWinPCAP = dev->description;
|
||||
interfaces.push_back(netif);
|
||||
}
|
||||
|
||||
pcap.freealldevs(alldevs);
|
||||
|
||||
// Now we're going to ask Win32 for the information as well
|
||||
ULONG size = 0;
|
||||
if(GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr, &size) != ERROR_BUFFER_OVERFLOW) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::PCAPCouldNotFindDevices, APIEvent::Severity::Error);
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> adapterAddressBuffer;
|
||||
adapterAddressBuffer.resize(size);
|
||||
if(GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, (IP_ADAPTER_ADDRESSES*)adapterAddressBuffer.data(), &size) != ERROR_SUCCESS) {
|
||||
EventManager::GetInstance().add(APIEvent::Type::PCAPCouldNotFindDevices, APIEvent::Severity::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// aa->AdapterName constains a unique name of the interface like "{3B1D2791-435A-456F-8A7B-9CB0EEE5DAB3}"
|
||||
// iface.nameFromWinPCAP has "rpcap://\Device\NPF_{3B1D2791-435A-456F-8A7B-9CB0EEE5DAB3}"
|
||||
// We're comparing strings to match the Win32 info with the WinPCAP info
|
||||
for(IP_ADAPTER_ADDRESSES* aa = (IP_ADAPTER_ADDRESSES*)adapterAddressBuffer.data(); aa != nullptr; aa = aa->Next) {
|
||||
for(auto& iface : interfaces) {
|
||||
if(iface.nameFromWinPCAP.find(aa->AdapterName) == std::string::npos)
|
||||
continue; // This is not the interface that corresponds
|
||||
|
||||
memcpy(iface.macAddress, aa->PhysicalAddress, sizeof(iface.macAddress));
|
||||
iface.nameFromWin32API = aa->AdapterName;
|
||||
iface.descriptionFromWin32API = convertWideString(aa->Description);
|
||||
iface.friendlyNameFromWin32API = convertWideString(aa->FriendlyName);
|
||||
if(iface.descriptionFromWin32API.find("LAN9512/LAN9514") != std::string::npos) {
|
||||
// This is an Ethernet EVB device
|
||||
iface.fullName = "Intrepid Ethernet EVB ( " + iface.friendlyNameFromWin32API + " : " + iface.descriptionFromWin32API + " )";
|
||||
} else {
|
||||
iface.fullName = iface.friendlyNameFromWin32API + " : " + iface.descriptionFromWin32API;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& iface : interfaces) {
|
||||
bool exists = false;
|
||||
for(auto& known : knownInterfaces)
|
||||
if(memcmp(iface.macAddress, known.macAddress, sizeof(iface.macAddress)) == 0)
|
||||
exists = true;
|
||||
if(!exists)
|
||||
knownInterfaces.emplace_back(iface);
|
||||
}
|
||||
|
||||
constexpr auto openflags = (PCAP_OPENFLAG_MAX_RESPONSIVENESS | PCAP_OPENFLAG_NOCAPTURE_LOCAL);
|
||||
for(size_t i = 0; i < knownInterfaces.size(); i++) {
|
||||
auto& iface = knownInterfaces[i];
|
||||
if(iface.fullName.length() == 0)
|
||||
continue; // Win32 did not find this interface in the previous step
|
||||
|
||||
iface.fp = pcap.open(iface.nameFromWinPCAP.c_str(), 1518, openflags, 1, nullptr, errbuf);
|
||||
|
||||
if(iface.fp == nullptr)
|
||||
continue; // Could not open the interface
|
||||
|
||||
EthernetPacketizer::EthernetPacket requestPacket;
|
||||
memcpy(requestPacket.srcMAC, iface.macAddress, sizeof(requestPacket.srcMAC));
|
||||
requestPacket.payload.reserve(4);
|
||||
requestPacket.payload = {
|
||||
((1 << 4) | (uint8_t)Network::NetID::Main51), // Packet size of 1 on NETID_MAIN51
|
||||
(uint8_t)Command::RequestSerialNumber
|
||||
};
|
||||
requestPacket.payload.push_back(Packetizer::ICSChecksum(requestPacket.payload));
|
||||
requestPacket.payload.insert(requestPacket.payload.begin(), 0xAA);
|
||||
|
||||
auto bs = requestPacket.getBytestream();
|
||||
pcap.sendpacket(iface.fp, bs.data(), (int)bs.size());
|
||||
|
||||
auto timeout = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(250);
|
||||
constexpr const size_t TempBufferSize = 4096;
|
||||
RingBuffer tempBuffer(TempBufferSize);
|
||||
while(std::chrono::high_resolution_clock::now() <= timeout) { // Wait up to 5ms for the response
|
||||
struct pcap_pkthdr* header;
|
||||
const uint8_t* data;
|
||||
auto res = pcap.next_ex(iface.fp, &header, &data);
|
||||
if(res < 0) {
|
||||
//std::cout << "pcapnextex failed with " << res << std::endl;
|
||||
break;
|
||||
}
|
||||
if(res == 0)
|
||||
continue; // Keep waiting for that packet
|
||||
|
||||
EthernetPacketizer ethPacketizer([](APIEvent::Type, APIEvent::Severity) {});
|
||||
memcpy(ethPacketizer.hostMAC, iface.macAddress, sizeof(ethPacketizer.hostMAC));
|
||||
ethPacketizer.allowInPacketsFromAnyMAC = true;
|
||||
if(!ethPacketizer.inputUp({ data, data + header->caplen }))
|
||||
continue; // This packet is not for us
|
||||
|
||||
Packetizer packetizer([](APIEvent::Type, APIEvent::Severity) {});
|
||||
tempBuffer.write(ethPacketizer.outputUp());
|
||||
if(!packetizer.input(tempBuffer))
|
||||
continue; // This packet was not well formed
|
||||
|
||||
EthernetPacketizer::EthernetPacket decoded(data, header->caplen);
|
||||
for(const auto& packet : packetizer.output()) {
|
||||
Decoder decoder([](APIEvent::Type, APIEvent::Severity) {});
|
||||
std::shared_ptr<Message> message;
|
||||
if(!decoder.decode(message, packet))
|
||||
continue;
|
||||
|
||||
const auto serial = std::dynamic_pointer_cast<SerialNumberMessage>(message);
|
||||
if(!serial || serial->deviceSerial.size() != 6)
|
||||
continue;
|
||||
|
||||
FoundDevice foundDevice;
|
||||
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<Driver>(new PCAP(reportFn, device));
|
||||
};
|
||||
|
||||
found.push_back(foundDevice);
|
||||
}
|
||||
}
|
||||
|
||||
pcap.close(iface.fp);
|
||||
iface.fp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool PCAP::IsHandleValid(neodevice_handle_t handle) {
|
||||
uint8_t netifIndex = (uint8_t)(handle >> 24);
|
||||
return (netifIndex < knownInterfaces.size());
|
||||
}
|
||||
|
||||
PCAP::PCAP(const device_eventhandler_t& eventHandler, neodevice_t& forDevice) : Driver(eventHandler), pcap(PCAPDLL::getInstance()), device(forDevice), ethPacketizer(eventHandler) {
|
||||
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.
|
||||
|
||||
deviceMAC[0] = 0x00;
|
||||
deviceMAC[1] = 0xFC;
|
||||
deviceMAC[2] = 0x70;
|
||||
deviceMAC[3] = (device.handle >> 16) & 0xFF;
|
||||
deviceMAC[4] = (device.handle >> 8) & 0xFF;
|
||||
deviceMAC[5] = device.handle & 0xFF;
|
||||
memcpy(ethPacketizer.deviceMAC, deviceMAC, 6);
|
||||
memcpy(ethPacketizer.hostMAC, iface.macAddress, 6);
|
||||
} else {
|
||||
openable = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PCAP::open() {
|
||||
if(!openable) {
|
||||
report(APIEvent::Type::InvalidNeoDevice, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!pcap.ok()) {
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(isOpen()) {
|
||||
report(APIEvent::Type::DeviceCurrentlyOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the interface
|
||||
iface.fp = pcap.open(iface.nameFromWinPCAP.c_str(), 65536, PCAP_OPENFLAG_MAX_RESPONSIVENESS | PCAP_OPENFLAG_NOCAPTURE_LOCAL, 50, nullptr, errbuf);
|
||||
if(iface.fp == nullptr) {
|
||||
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create threads
|
||||
readThread = std::thread(&PCAP::readTask, this);
|
||||
writeThread = std::thread(&PCAP::writeTask, this);
|
||||
transmitThread = std::thread(&PCAP::transmitTask, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PCAP::isOpen() {
|
||||
return iface.fp != nullptr;
|
||||
}
|
||||
|
||||
bool PCAP::close() {
|
||||
if(!isOpen()) {
|
||||
report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsClosing(true); // Signal the threads that we are closing
|
||||
readThread.join();
|
||||
writeThread.join();
|
||||
transmitThread.join();
|
||||
setIsClosing(false);
|
||||
|
||||
pcap.close(iface.fp);
|
||||
iface.fp = nullptr;
|
||||
|
||||
clearBuffers();
|
||||
|
||||
transmitQueue = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PCAP::readTask() {
|
||||
struct pcap_pkthdr* header;
|
||||
const uint8_t* data;
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
while(!isClosing()) {
|
||||
auto readBytes = pcap.next_ex(iface.fp, &header, &data);
|
||||
if(readBytes < 0) {
|
||||
report(APIEvent::Type::FailedToRead, APIEvent::Severity::Error);
|
||||
break;
|
||||
}
|
||||
if(readBytes == 0)
|
||||
continue; // Keep waiting for that packet
|
||||
|
||||
if(ethPacketizer.inputUp({data, data + header->caplen})) {
|
||||
const auto bytes = ethPacketizer.outputUp();
|
||||
pushRx(bytes.data(), bytes.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PCAP::writeTask() {
|
||||
WriteOperation writeOp;
|
||||
EventManager::GetInstance().downgradeErrorsOnCurrentThread();
|
||||
|
||||
pcap_send_queue* queue1 = pcap.sendqueue_alloc(128000);
|
||||
pcap_send_queue* queue2 = pcap.sendqueue_alloc(128000);
|
||||
pcap_send_queue* queue = queue1;
|
||||
|
||||
while(!isClosing()) {
|
||||
// Potentially, we added frames to a second queue faster than the other thread was able to hand the first
|
||||
// off to the kernel. In that case, wait for a minimal amount of time before checking whether we can
|
||||
// transmit it again.
|
||||
if(writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(queue->len ? 1 : 100))) {
|
||||
unsigned int i = 0;
|
||||
do {
|
||||
ethPacketizer.inputDown(std::move(writeOp.bytes));
|
||||
if(i++ >= (queue->maxlen - queue->len) / 1518 / 3)
|
||||
break; // Not safe to try to fit any more packets in this queue, let it transmit and come around again
|
||||
} while(writeQueue.try_dequeue(writeOp));
|
||||
|
||||
for(const auto& data : ethPacketizer.outputDown()) {
|
||||
pcap_pkthdr header = {};
|
||||
header.caplen = header.len = bpf_u_int32(data.size());
|
||||
if(pcap.sendqueue_queue(queue, &header, data.data()) != 0)
|
||||
report(APIEvent::Type::FailedToWrite, APIEvent::Severity::EventWarning);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lk(transmitQueueMutex);
|
||||
// Check if we want to transmit our current queue
|
||||
// If we're not currently transmitting a queue, let this one transmit immediately for good latency
|
||||
// If our queue is full and we're transmitting the other, we can't accept any more packets out of the writeQueue
|
||||
// In that case we're putting as many packets into the driver as possible, so wait for it to be free
|
||||
// This puts the backpressure on the writeQueue
|
||||
if(queue->len && (!transmitQueue || queue->len + (1518*2) >= queue->maxlen)) {
|
||||
if(transmitQueue) // Need to wait for the queue to become available
|
||||
transmitQueueCV.wait(lk, [this] { return !transmitQueue; });
|
||||
|
||||
// Time to swap
|
||||
transmitQueue = queue;
|
||||
lk.unlock();
|
||||
transmitQueueCV.notify_one();
|
||||
|
||||
// Set up our next queue
|
||||
if(queue == queue1) {
|
||||
pcap.sendqueue_destroy(queue2);
|
||||
queue = queue2 = pcap.sendqueue_alloc(128000);
|
||||
} else {
|
||||
pcap.sendqueue_destroy(queue1);
|
||||
queue = queue1 = pcap.sendqueue_alloc(128000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pcap.sendqueue_destroy(queue1);
|
||||
pcap.sendqueue_destroy(queue2);
|
||||
}
|
||||
|
||||
void PCAP::transmitTask() {
|
||||
while(!isClosing()) {
|
||||
std::unique_lock<std::mutex> lk(transmitQueueMutex);
|
||||
if(transmitQueueCV.wait_for(lk, std::chrono::milliseconds(100), [this] { return !!transmitQueue; }) && !isClosing() && transmitQueue) {
|
||||
pcap_send_queue* current = transmitQueue;
|
||||
lk.unlock();
|
||||
pcap.sendqueue_transmit(iface.fp, current, 0);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk2(transmitQueueMutex);
|
||||
transmitQueue = nullptr;
|
||||
}
|
||||
transmitQueueCV.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
#include "icsneo/platform/windows/registry.h"
|
||||
#include "icsneo/platform/windows/strings.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <codecvt>
|
||||
#include <vector>
|
||||
#include <locale>
|
||||
|
||||
using namespace icsneo;
|
||||
|
||||
class Key {
|
||||
public:
|
||||
Key(std::wstring path, bool readwrite = false);
|
||||
~Key();
|
||||
HKEY GetKey() { return key; }
|
||||
bool IsOpen() { return key != nullptr; }
|
||||
private:
|
||||
HKEY key;
|
||||
};
|
||||
|
||||
Key::Key(std::wstring path, bool readwrite) {
|
||||
DWORD dwDisposition;
|
||||
if(readwrite)
|
||||
RegCreateKeyExW(HKEY_LOCAL_MACHINE, path.c_str(), 0, nullptr, 0, KEY_QUERY_VALUE | KEY_WRITE, nullptr, &key, &dwDisposition);
|
||||
else
|
||||
RegOpenKeyExW(HKEY_LOCAL_MACHINE, path.c_str(), 0, KEY_READ, &key);
|
||||
}
|
||||
|
||||
Key::~Key() {
|
||||
if(IsOpen())
|
||||
RegCloseKey(key);
|
||||
}
|
||||
|
||||
bool Registry::EnumerateSubkeys(std::wstring path, std::vector<std::wstring>& subkeys) {
|
||||
Key regKey(path);
|
||||
if(!regKey.IsOpen())
|
||||
return false;
|
||||
|
||||
wchar_t className[MAX_PATH];
|
||||
memset(className, 0, sizeof(className));
|
||||
DWORD classNameLen = MAX_PATH;
|
||||
DWORD subKeyCount = 0;
|
||||
DWORD maxSubKeyLen, maxClassStringLen, valueCount, maxValueNameLen, maxValueDataLen, securityDescriptorLen;
|
||||
FILETIME lastWriteTime;
|
||||
auto ret = RegQueryInfoKeyW(
|
||||
regKey.GetKey(),
|
||||
className,
|
||||
&classNameLen,
|
||||
nullptr,
|
||||
&subKeyCount,
|
||||
&maxSubKeyLen,
|
||||
&maxClassStringLen,
|
||||
&valueCount,
|
||||
&maxValueNameLen,
|
||||
&maxValueDataLen,
|
||||
&securityDescriptorLen,
|
||||
&lastWriteTime);
|
||||
|
||||
if(ret != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
subkeys.clear();
|
||||
for(DWORD i = 0; i < subKeyCount; i++) {
|
||||
DWORD nameLen = MAX_PATH;
|
||||
wchar_t name[MAX_PATH];
|
||||
memset(name, 0, sizeof(name));
|
||||
ret = RegEnumKeyExW(regKey.GetKey(), i, name, &nameLen, nullptr, nullptr, nullptr, &lastWriteTime);
|
||||
if(ret == ERROR_SUCCESS)
|
||||
subkeys.push_back(name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Registry::Get(std::wstring path, std::wstring key, std::wstring& value) {
|
||||
Key regKey(path);
|
||||
if(!regKey.IsOpen())
|
||||
return false;
|
||||
|
||||
// Query for the type and size of the data
|
||||
DWORD type, size;
|
||||
auto ret = RegQueryValueExW(regKey.GetKey(), key.c_str(), nullptr, &type, (LPBYTE)nullptr, &size);
|
||||
if(ret != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
// Query for the data itself
|
||||
std::vector<wchar_t> data(size / 2 + 1);
|
||||
DWORD bytesRead = size; // We want to read up to the size we got earlier
|
||||
ret = RegQueryValueExW(regKey.GetKey(), key.c_str(), nullptr, &type, (LPBYTE)data.data(), &bytesRead);
|
||||
if(ret != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
value = data.data();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Registry::Get(std::string path, std::string key, std::string& value) {
|
||||
std::wstring wvalue;
|
||||
bool ret = Get(convertStringToWide(path), convertStringToWide(key), wvalue);
|
||||
value = convertWideString(wvalue);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Registry::Get(std::wstring path, std::wstring key, uint32_t& value) {
|
||||
Key regKey(path);
|
||||
if(!regKey.IsOpen())
|
||||
return false;
|
||||
|
||||
// Query for the data
|
||||
DWORD type, size = sizeof(DWORD), kvalue;
|
||||
auto ret = RegQueryValueExW(regKey.GetKey(), key.c_str(), nullptr, &type, (LPBYTE)&kvalue, &size);
|
||||
if(ret != ERROR_SUCCESS || type != REG_DWORD)
|
||||
return false;
|
||||
|
||||
value = kvalue;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Registry::Get(std::string path, std::string key, uint32_t& value) {
|
||||
return Get(convertStringToWide(path), convertStringToWide(key), value);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#include <string>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
|
||||
#include <icsneo/platform/windows/strings.h>
|
||||
|
||||
// Helper function to convert UTF-16 to UTF-8 strings (wide to standard)
|
||||
std::string icsneo::convertWideString(const std::wstring& value) {
|
||||
// Get the width of the string (character count)
|
||||
int width = WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)value.c_str(), -1, 0, 0, NULL, NULL);
|
||||
// Create the new string
|
||||
std::string new_string(width+1, '\0');
|
||||
// fill the new string with the converted characters
|
||||
WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)value.c_str(), -1, new_string.data(), width, NULL, NULL);
|
||||
return new_string;
|
||||
};
|
||||
|
||||
// Helper function to convert UTF-8 to UTF-16 strings (standard to wide)
|
||||
std::wstring icsneo::convertStringToWide(const std::string& value) {
|
||||
// Get the width of the string (character count)
|
||||
int width = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, NULL, 0);
|
||||
// Create the new string
|
||||
std::wstring new_string(width+1, '\0');
|
||||
// fill the new string with the converted characters
|
||||
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, (LPWSTR)new_string.data(), width);
|
||||
return new_string;
|
||||
}
|
||||
Reference in New Issue
Block a user