Drivers: Decouple from devices

This allows us to better implement alternative drivers
for devices, such as for device sharing servers or
talking to CoreMini processors within the same device.
This commit is contained in:
Paul Hollinsky
2022-03-27 14:30:31 -04:00
parent 0ff12300f3
commit 781fc2c034
66 changed files with 780 additions and 1566 deletions
+13 -12
View File
@@ -1,4 +1,5 @@
#include "icsneo/platform/cdcacm.h"
#include "icsneo/device/founddevice.h"
#include <mutex>
#include <vector>
#include <CoreFoundation/CoreFoundation.h>
@@ -79,22 +80,19 @@ private:
io_object_t toRelease;
};
std::vector<neodevice_t> CDCACM::FindByProduct(int product) {
std::vector<neodevice_t> found;
void CDCACM::Find(std::vector<FoundDevice>& found) {
CFMutableDictionaryRef ref = IOServiceMatching(kIOSerialBSDServiceValue);
if(ref == nullptr)
return found;
return;
io_iterator_t matchingServices = 0;
kern_return_t kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, ref, &matchingServices);
if(KERN_SUCCESS != kernResult || matchingServices == 0)
return found;
return;
IOReleaser matchingServicesReleaser(matchingServices);
io_object_t serialPort;
while((serialPort = IOIteratorNext(matchingServices))) {
IOReleaser serialPortReleaser(serialPort);
neodevice_t device;
// First get the parent device
// We want to check that it has the right VID/PID
@@ -138,10 +136,10 @@ std::vector<neodevice_t> CDCACM::FindByProduct(int product) {
CFReleaser productPropReleaser(productProp);
if(CFGetTypeID(productProp) != CFNumberGetTypeID())
continue;
uint16_t pid = 0;
if(!CFNumberGetValue(static_cast<CFNumberRef>(productProp), kCFNumberSInt16Type, &pid))
continue;
if(pid != product)
// 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.*)
@@ -173,10 +171,13 @@ std::vector<neodevice_t> CDCACM::FindByProduct(int product) {
continue;
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);
}
return found;
}
std::string CDCACM::HandleToTTY(neodevice_handle_t handle) {
+34 -29
View File
@@ -1,4 +1,5 @@
#include "icsneo/platform/ftdi.h"
#include "icsneo/device/founddevice.h"
#include <iostream>
#include <stdio.h>
#include <cstring>
@@ -9,22 +10,21 @@
using namespace icsneo;
std::vector<std::tuple<int, std::string>> FTDI::handles;
std::vector<std::string> FTDI::handles;
std::vector<neodevice_t> FTDI::FindByProduct(int product) {
void FTDI::Find(std::vector<FoundDevice>& found) {
constexpr size_t deviceSerialBufferLength = sizeof(device.serial);
std::vector<neodevice_t> found;
static FTDIContext context;
std::pair<int, std::vector<std::string>> result = context.findDevices(product);
const auto result = context.findDevices();
if(result.first < 0)
return found; // TODO Flag an error for the client application, there was an issue with FTDI
return; // TODO Flag an error for the client application, there was an issue with FTDI
for(auto& serial : result.second) {
neodevice_t d;
for(const auto& [serial, pid] : result.second) {
FoundDevice d;
strncpy(d.serial, serial.c_str(), deviceSerialBufferLength - 1);
d.serial[deviceSerialBufferLength - 1] = '\0'; // strncpy does not write a null terminator if serial is too long
std::tuple<int, std::string> devHandle = std::make_tuple(product, serial);
std::string devHandle = serial;
auto it = std::find(handles.begin(), handles.end(), devHandle);
size_t foundHandle = SIZE_MAX;
if(it != handles.end()) {
@@ -34,10 +34,14 @@ std::vector<neodevice_t> FTDI::FindByProduct(int product) {
handles.push_back(devHandle);
}
d.handle = foundHandle;
d.productId = pid;
d.makeDriver = [](const device_eventhandler_t& report, neodevice_t& device) {
return std::unique_ptr<Driver>(new FTDI(report, device));
};
found.push_back(d);
}
return found;
}
FTDI::FTDI(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err), device(forDevice) {
@@ -56,8 +60,8 @@ bool FTDI::open() {
}
// At this point the handle has been checked to be within the bounds of the handles array
std::tuple<int, std::string>& handle = handles[device.handle];
const int openError = ftdi.openDevice(std::get<0>(handle), std::get<1>(handle).c_str());
auto& handle = handles[device.handle];
const int openError = ftdi.openDevice(0, handle.c_str());
if(openError == -5) { // Unable to claim device
report(APIEvent::Type::DeviceInUse, APIEvent::Severity::Error);
return false;
@@ -112,17 +116,13 @@ bool FTDI::close() {
return ret;
}
std::pair<int, std::vector<std::string>> FTDI::FTDIContext::findDevices(int pid) {
std::pair<int, std::vector<std::string>> ret;
std::pair<int, std::vector< std::pair<std::string, uint16_t> > > FTDI::FTDIContext::findDevices(int pid) {
std::pair<int, std::vector< std::pair<std::string, uint16_t> > > ret;
if(context == nullptr) {
ret.first = -1;
return ret;
}
if(pid == 0) {
ret.first = -2;
return ret;
}
struct ftdi_device_list* devlist = nullptr;
ret.first = ftdi_usb_find_all(context, &devlist, INTREPID_USB_VENDOR_ID, pid);
@@ -138,18 +138,23 @@ std::pair<int, std::vector<std::string>> FTDI::FTDIContext::findDevices(int pid)
return ret;
}
for (struct ftdi_device_list* curdev = devlist; curdev != NULL;) {
char serial[32];
memset(serial, 0, sizeof(serial));
int result = ftdi_usb_get_strings(context, curdev->dev, nullptr, 0, nullptr, 0, serial, 32);
size_t len = strlen(serial);
if(result >= 0 && len > 0)
ret.second.emplace_back(serial);
else if(ret.first > 0)
ret.first--; // We're discarding this device
curdev = curdev->next;
for(struct ftdi_device_list* curdev = devlist; curdev != nullptr; curdev = curdev->next) {
struct libusb_device_descriptor descriptor = {};
// Check against bDeviceClass here as it will be 0 for FTDI devices
// It will be 2 for CDC ACM devices, which we don't want to handle here
if(libusb_get_device_descriptor(curdev->dev, &descriptor) != 0 || descriptor.bDeviceClass != 0)
continue;
char serial[16] = {};
if(ftdi_usb_get_strings(context, curdev->dev, nullptr, 0, nullptr, 0, serial, sizeof(serial)) < 0)
continue;
const auto len = strnlen(serial, sizeof(serial));
if(len > 4 && len < 10)
ret.second.emplace_back(serial, descriptor.idProduct);
}
ret.first = static_cast<int>(ret.second.size());
ftdi_list_free(&devlist);
return ret;
}
@@ -157,7 +162,7 @@ std::pair<int, std::vector<std::string>> FTDI::FTDIContext::findDevices(int pid)
int FTDI::FTDIContext::openDevice(int pid, const char* serial) {
if(context == nullptr)
return 1;
if(pid == 0 || serial == nullptr)
if(serial == nullptr)
return 2;
if(serial[0] == '\0')
return 3;
+27 -20
View File
@@ -1,4 +1,5 @@
#include "icsneo/platform/cdcacm.h"
#include "icsneo/device/founddevice.h"
#include <dirent.h>
#include <cstring>
#include <iostream>
@@ -80,12 +81,10 @@ private:
std::string serial;
};
std::vector<neodevice_t> CDCACM::FindByProduct(int product) {
std::vector<neodevice_t> found;
void CDCACM::Find(std::vector<FoundDevice>& found) {
Directory directory("/sys/bus/usb/drivers/cdc_acm"); // Query the CDCACM driver
if(!directory.openedSuccessfully())
return found;
return;
std::vector<std::string> foundusbs;
for(auto& entry : directory.ls()) {
@@ -100,8 +99,9 @@ std::vector<neodevice_t> CDCACM::FindByProduct(int product) {
foundusbs.emplace_back(entry.getName());
}
// Pair the USB and TTY if found
std::map<std::string, std::string> foundttys;
// 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";
@@ -113,15 +113,16 @@ std::vector<neodevice_t> CDCACM::FindByProduct(int product) {
if(listing.size() != 1) // We either got no serial ports or multiple, either way no good
continue;
foundttys.insert(std::make_pair(usb, listing[0].getName()));
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(); ) {
const auto& dev = *iter;
auto& [_, pair] = *iter;
auto& [tty, ttyPid] = pair;
const std::string matchString = "PRODUCT=";
std::stringstream ss;
ss << "/sys/class/tty/" << dev.second << "/device/uevent"; // Read the uevent file, which contains should have a line like "PRODUCT=93c/1101/100"
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;
@@ -153,32 +154,34 @@ std::vector<neodevice_t> CDCACM::FindByProduct(int product) {
continue;
}
if(vid != INTREPID_USB_VENDOR_ID || pid != product) {
iter = foundttys.erase(iter); // Not the right VID or PID, remove
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& dev : foundttys) {
neodevice_t device;
for(auto& [usb, pair] : foundttys) {
auto& [tty, ttyPid] = pair;
FoundDevice device;
USBSerialGetter getter(dev.first);
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 < dev.second.length(); i++) {
if(isdigit(dev.second[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(dev.second.substr(i));
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.
*/
@@ -186,13 +189,17 @@ std::vector<neodevice_t> CDCACM::FindByProduct(int product) {
} 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
}
return found;
}
std::string CDCACM::HandleToTTY(neodevice_handle_t handle) {
+41 -42
View File
@@ -1,7 +1,9 @@
#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>
@@ -15,14 +17,10 @@
using namespace icsneo;
static const uint8_t BROADCAST_MAC[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
static const uint8_t ICS_UNSET_MAC[6] = { 0x00, 0xFC, 0x70, 0xFF, 0xFF, 0xFF };
std::vector<PCAP::NetworkInterface> PCAP::knownInterfaces;
std::vector<PCAP::PCAPFoundDevice> PCAP::FindAll() {
void PCAP::Find(std::vector<FoundDevice>& found) {
static bool warned = false; // Only warn once for failure to open devices
std::vector<PCAPFoundDevice> foundDevices;
// First we ask PCAP to give us all of the devices
pcap_if_t* alldevs;
@@ -39,7 +37,7 @@ std::vector<PCAP::PCAPFoundDevice> PCAP::FindAll() {
if(!success) {
EventManager::GetInstance().add(APIEvent::Type::PCAPCouldNotFindDevices, APIEvent::Severity::Error);
return std::vector<PCAPFoundDevice>();
return;
}
std::vector<NetworkInterface> interfaces;
@@ -138,7 +136,7 @@ std::vector<PCAP::PCAPFoundDevice> PCAP::FindAll() {
pcap_sendpacket(iface.fp, bs.data(), (int)bs.size());
auto timeout = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(50);
while(std::chrono::high_resolution_clock::now() <= timeout) { // Wait up to 5ms for the response
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);
@@ -152,48 +150,49 @@ std::vector<PCAP::PCAPFoundDevice> PCAP::FindAll() {
}
if(res == 0)
continue; // Keep waiting for that packet
EthernetPacketizer::EthernetPacket packet(data, header->caplen);
// Is this an ICS response packet (0xCAB2) from an ICS MAC, either to broadcast or directly to us?
if(packet.etherType == 0xCAB2 && packet.srcMAC[0] == 0x00 && packet.srcMAC[1] == 0xFC && packet.srcMAC[2] == 0x70 && (
memcmp(packet.destMAC, iface.macAddress, sizeof(packet.destMAC)) == 0 ||
memcmp(packet.destMAC, BROADCAST_MAC, sizeof(packet.destMAC)) == 0 ||
memcmp(packet.destMAC, ICS_UNSET_MAC, sizeof(packet.destMAC)) == 0
)) {
/* We have received a packet from a device. We don't know if this is the device we're
* looking for, we don't know if it's actually a response to our RequestSerialNumber
* or not, we just know we got something.
*
* Unlike most transport layers, we can't get the serial number here as we actually
* need to parse this message that has been returned. Some devices parse messages
* differently, so we need to use their communication layer. We could technically
* create a communication layer to parse the packet we have in `payload` here, but
* we'd need to be given a packetizer and decoder for the device. I'm intentionally
* avoiding passing that information down here for code quality's sake. Instead, pass
* the packet we received back up so the device can handle it.
*/
neodevice_handle_t handle = (neodevice_handle_t)((i << 24) | (packet.srcMAC[3] << 16) | (packet.srcMAC[4] << 8) | (packet.srcMAC[5]));
PCAPFoundDevice* alreadyExists = nullptr;
for(auto& dev : foundDevices)
if(dev.device.handle == handle)
alreadyExists = &dev;
if(alreadyExists == nullptr) {
PCAPFoundDevice foundDevice;
foundDevice.device.handle = handle;
foundDevice.discoveryPackets.push_back(std::move(packet.payload));
foundDevices.push_back(foundDevice);
} else {
alreadyExists->discoveryPackets.push_back(std::move(packet.payload));
}
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) {});
if(!packetizer.input(ethPacketizer.outputUp()))
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 neodevice_handle_t handle = (neodevice_handle_t)((i << 24) | (decoded.srcMAC[3] << 16) | (decoded.srcMAC[4] << 8) | (decoded.srcMAC[5]));
if(std::any_of(found.begin(), found.end(), [&handle](const auto& found) { return handle == found.handle; }))
continue; // We already have this device on this interface
const auto serial = std::dynamic_pointer_cast<SerialNumberMessage>(message);
if(!serial || serial->deviceSerial.size() != 6)
continue;
FoundDevice foundDevice;
foundDevice.handle = handle;
foundDevice.productId = decoded.srcMAC[2];
memcpy(foundDevice.serial, serial->deviceSerial.c_str(), sizeof(foundDevice.serial) - 1);
foundDevice.serial[sizeof(foundDevice.serial) - 1] = '\0';
foundDevice.makeDriver = [](const device_eventhandler_t& report, neodevice_t& device) {
return std::unique_ptr<Driver>(new PCAP(report, device));
};
found.push_back(foundDevice);
}
}
pcap_close(iface.fp);
iface.fp = nullptr;
}
return foundDevices;
}
bool PCAP::IsHandleValid(neodevice_handle_t handle) {