All: Remove deprecated Drivers

This commit is contained in:
Kyle Schwarz
2026-10-29 12:33:38 -04:00
parent 320fec74bd
commit 5c61a3b3c1
62 changed files with 27 additions and 6583 deletions
-262
View File
@@ -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;
}
-192
View File
@@ -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];
}
-212
View File
@@ -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();
}
-314
View File
@@ -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
}
}