Merge remote-tracking branch 'origin/master' into pcap

pcap
Paul Hollinsky 2019-05-06 13:16:29 -04:00
commit 3aaccaad74
25 changed files with 2453 additions and 989 deletions

View File

@ -0,0 +1,26 @@
# Intrepid Control System, Inc. neoVI Devices
SUBSYSTEM=="usb", ATTRS{idVendor}=="093c", GROUP="users", MODE="0666"
KERNEL=="ttyUSB?", ATTRS{idVendor}=="093c", GROUP="users", MODE="0666"
KERNEL=="ttyACM?", ATTRS{idVendor}=="093c", GROUP="users", MODE="0666"
# neoVI ION/PLASMA PIDs are not in the latest ftdi_sio driver so lets make a
# rule to add it when we see a new unclaimed device.
# PLASMA = 0x0801, ION = 0x0901
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_interface", \
ATTRS{idVendor}=="093c", ATTRS{idProduct}=="0801", \
DRIVER=="", \
RUN+="/sbin/modprobe -b ftdi_sio"
ACTION=="add", SUBSYSTEM=="drivers", \
ENV{DEVPATH}=="/bus/usb-serial/drivers/ftdi_sio", \
ATTR{new_id}="093c 0801"
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_interface", \
ATTRS{idVendor}=="093c", ATTRS{idProduct}=="0901", \
DRIVER=="", \
RUN+="/sbin/modprobe -b ftdi_sio"
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_interface", \
ATTRS{idVendor}=="093c", ATTRS{idProduct}=="1000", \
DRIVER=="", \
RUN+="/sbin/modprobe -b ftdi_sio"
ACTION=="add", SUBSYSTEM=="drivers", \
ENV{DEVPATH}=="/bus/usb-serial/drivers/ftdi_sio", \
ATTR{new_id}="093c 0901"

View File

@ -164,6 +164,16 @@ target_include_directories(icsneoc
)
target_link_libraries(icsneoc PRIVATE icsneocpp)
add_library(icsneoc-static STATIC api/icsneoc/icsneoc.cpp)
target_include_directories(icsneoc-static
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(icsneoc-static PUBLIC icsneocpp)
add_library(icsneolegacy SHARED
api/icsneolegacy/icsneolegacy.cpp
api/icsneolegacy/icsneolegacyextra.cpp
@ -180,6 +190,7 @@ target_link_libraries(icsneolegacy PRIVATE icsneocpp)
target_compile_features(icsneocpp PUBLIC cxx_auto_type cxx_constexpr cxx_lambdas cxx_nullptr cxx_range_for cxx_rvalue_references cxx_sizeof_member cxx_strong_enums)
target_compile_features(icsneoc PRIVATE cxx_auto_type cxx_constexpr cxx_lambdas cxx_nullptr cxx_range_for cxx_rvalue_references cxx_sizeof_member cxx_strong_enums)
target_compile_features(icsneoc-static PUBLIC cxx_auto_type cxx_constexpr cxx_lambdas cxx_nullptr cxx_range_for cxx_rvalue_references cxx_sizeof_member cxx_strong_enums)
target_compile_features(icsneolegacy PRIVATE cxx_auto_type cxx_constexpr cxx_lambdas cxx_nullptr cxx_range_for cxx_rvalue_references cxx_sizeof_member cxx_strong_enums)
# libftdi

5
CONTRIBUTING.md 100644
View File

@ -0,0 +1,5 @@
# Contribution Guidelines
To get started, <a href="https://www.clahub.com/agreements/intrepidcs/libicsneo">sign the Contributor License Agreement</a>. It protects us as well as yourself and your contribution from legal issues.
While strict code style rules are not currently enforced, please take care to follow the conventions throughout the code as closely as possible to keep the code clean. Tabs are used rather than spaces. Classes and static functions follow UpperCamelCase while member functions and variables follow standard camelCase. Structures and types which are intended to be used in the C API should be in snake_case and end with `_t`.

View File

@ -1,29 +1,38 @@
# Hardware Support
- Ethernet devices
- Connecting over Ethernet
- neoVI FIRE 2
- CAN works
- CAN FD works
- RADGalaxy
- CAN works
- STM32 devices
- ValueCAN 4
- ValueCAN 4-2EL
- CAN works
- CAN FD works
- Ethernet works
- RADGalaxy
- CAN works
- Ethernet works
- RADStar 2
- CAN works
- Ethernet works
- Connecting over USB
- ValueCAN 4 series
- CAN works
- CAN FD works
- Ethernet works (on 4-2EL)
- neoOBD2 PRO
- CAN works
- FTDI devices
- neoVI FIRE
- CAN works
- neoVI FIRE 2
- CAN works
- CAN FD works
- Ethernet works
- ValueCAN 3
- CAN works
- RADStar 2
- CAN works
- Ethernet works
- neoVI PLASMA
- CAN works
- neoVI ION

View File

@ -1,4 +1,4 @@
Copyright 2018 Intrepid Control Systems, Inc.
Copyright 2018-2019 Intrepid Control Systems, Inc.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View File

@ -121,4 +121,10 @@ The dependencies are as follows
- CMake 3.2 or above
- GCC 4.7 or above, 4.8+ recommended
- `libusb-1.0-0-dev`
- `build-essential` is recommended
- `build-essential` is recommended
If you'd like to be able to run programs that use this library without being root, consider using the included udev rules
```
$ sudo cp 99-intrepidcs.rules /etc/udev/rules.d/
```

View File

@ -63,6 +63,7 @@ static constexpr const char* ERROR_FAILED_TO_READ = "A read operation failed.";
static constexpr const char* ERROR_FAILED_TO_WRITE = "A write operation failed.";
static constexpr const char* ERROR_DRIVER_FAILED_TO_OPEN = "The device driver encountered a low-level error while opening the device.";
static constexpr const char* ERROR_PACKET_CHECKSUM_ERROR = "There was a checksum error while decoding a packet. The packet was dropped.";
static constexpr const char* ERROR_TRANSMIT_BUFFER_FULL = "The transmit buffer is full and the device is set to non-blocking.";
static constexpr const char* ERROR_PCAP_COULD_NOT_START = "The PCAP driver could not be started. Ethernet devices will not be found.";
static constexpr const char* ERROR_PCAP_COULD_NOT_FIND_DEVICES = "The PCAP driver failed to find devices. Ethernet devices will not be found.";
@ -110,6 +111,8 @@ const char* APIError::DescriptionForType(ErrorType type) {
return ERROR_DRIVER_FAILED_TO_OPEN;
case PacketChecksumError:
return ERROR_PACKET_CHECKSUM_ERROR;
case TransmitBufferFull:
return ERROR_TRANSMIT_BUFFER_FULL;
case PCAPCouldNotStart:
return ERROR_PCAP_COULD_NOT_START;
case PCAPCouldNotFindDevices:
@ -154,6 +157,7 @@ APIError::Severity APIError::SeverityForType(ErrorType type) {
case FailedToWrite:
case DriverFailedToOpen:
case PacketChecksumError:
case TransmitBufferFull:
// Other Errors
case TooManyErrors:
case Unknown:

View File

@ -14,6 +14,9 @@
#include <map>
#include <algorithm>
#include <cstring>
#include <climits>
#ifdef _MSC_VER
#pragma warning(disable : 4100) // unreferenced formal parameter
#endif
@ -41,7 +44,6 @@ static void NeoMessageToSpyMessage(const neomessage_t& newmsg, icsSpyMessage& ol
oldmsg.ExtraDataPtr = (void*)newmsg.data;
memcpy(oldmsg.Data, newmsg.data, std::min(newmsg.length, (size_t)8));
oldmsg.ArbIDOrHeader = *(uint32_t*)newmsg.header;
oldmsg.ExtraDataPtrEnabled = newmsg.length > 8;
oldmsg.NetworkID = (uint8_t)newmsg.netid; // Note: NetID remapping from the original API is not supported
oldmsg.StatusBitField = newmsg.status.statusBitfield[0];
oldmsg.StatusBitField2 = newmsg.status.statusBitfield[1];
@ -65,7 +67,7 @@ int icsneoFindDevices(NeoDeviceEx* devs, int* devCount, unsigned int* devTypes,
if(devCount == nullptr)
return 0;
unsigned int devTypesDefault[] = { NEODEVICE_ALL };
unsigned int devTypesDefault[] = { std::numeric_limits<unsigned int>::max() };
if(devTypes == nullptr || devTypeCount == 0) {
devTypes = devTypesDefault;
devTypeCount = 1;
@ -184,24 +186,7 @@ int icsneoGetMessages(void* hObject, icsSpyMessage* pMsg, int* pNumberOfMessages
}
int icsneoTxMessages(void* hObject, icsSpyMessage* pMsg, int lNetworkID, int lNumMessages) {
if(!icsneoValidateHObject(hObject))
return false;
neodevice_t* device = (neodevice_t*)hObject;
std::vector<uint8_t> data;
neomessage_t newmsg = {};
newmsg.netid = (uint16_t)lNetworkID;
memcpy(newmsg.header, &pMsg[0].ArbIDOrHeader, sizeof(newmsg.header));
for(int i = 0; i < lNumMessages; i++)
data.insert(data.end(), pMsg[i].Data, pMsg[i].Data + pMsg[i].NumberBytesData);
newmsg.data = data.data();
newmsg.length = data.size();
newmsg.status.statusBitfield[0] = pMsg[0].StatusBitField;
newmsg.status.statusBitfield[1] = pMsg[0].StatusBitField2;
newmsg.status.statusBitfield[2] = pMsg[0].StatusBitField3;
newmsg.status.statusBitfield[3] = pMsg[0].StatusBitField4;
if(pMsg[0].Protocol == SPY_PROTOCOL_CANFD)
newmsg.status.canfdFDF = true;
return icsneo_transmit(device, &newmsg);
return icsneoTxMessagesEx(hObject, pMsg, lNetworkID, lNumMessages, nullptr, 0);
}
int icsneoTxMessagesEx(void* hObject, icsSpyMessage* pMsg, unsigned int lNetworkID, unsigned int lNumMessages, unsigned int* NumTxed, unsigned int zero2) {
@ -209,6 +194,9 @@ int icsneoTxMessagesEx(void* hObject, icsSpyMessage* pMsg, unsigned int lNetwork
return false;
neodevice_t* device = (neodevice_t*)hObject;
neomessage_t newmsg;
unsigned int temp = 0;
if(NumTxed == nullptr)
NumTxed = &temp;
*NumTxed = 0;
for(unsigned int i = 0; i < lNumMessages; i++) {
const icsSpyMessage& oldmsg = pMsg[i];
@ -216,7 +204,7 @@ int icsneoTxMessagesEx(void* hObject, icsSpyMessage* pMsg, unsigned int lNetwork
newmsg.netid = (uint16_t)lNetworkID;
memcpy(newmsg.header, &oldmsg.ArbIDOrHeader, sizeof(newmsg.header));
newmsg.length = oldmsg.NumberBytesData | (oldmsg.NodeID << 8);
if (oldmsg.ExtraDataPtrEnabled)
if (oldmsg.ExtraDataPtr != nullptr)
newmsg.data = reinterpret_cast<const uint8_t*>(oldmsg.ExtraDataPtr);
else
newmsg.data = oldmsg.Data;
@ -543,4 +531,4 @@ int icsneoEnableNetworkCom(void* hObject, int Enable) {
int icsneoFindAllCOMDevices(int lDriverType, int lGetSerialNumbers, int lStopAtFirst, int lUSBCommOnly, int* p_lDeviceTypes, int* p_lComPorts, int* p_lSerialNumbers, int*lNumDevices) {
// TODO Implement
return false;
}
}

View File

@ -39,5 +39,16 @@ bool ICommunication::readWait(std::vector<uint8_t>& bytes, std::chrono::millisec
}
bool ICommunication::write(const std::vector<uint8_t>& bytes) {
if(writeBlocks) {
std::unique_lock<std::mutex> lk(writeMutex);
if(writeQueue.size_approx() > writeQueueSize) {
writeCV.wait(lk);
}
} else {
if(writeQueue.size_approx() > writeQueueSize) {
err(APIError::TransmitBufferFull);
return false;
}
}
return writeQueue.enqueue(WriteOperation(bytes));
}

View File

@ -30,6 +30,7 @@ neomessage_t icsneo::CreateNeoMessage(const std::shared_ptr<Message> message) {
can.status.canfdRTR = canmsg->isRemote;
can.status.canfdFDF = canmsg->isCANFD;
can.status.canfdBRS = canmsg->baudrateSwitch;
can.status.canfdESI = canmsg->errorStateIndicator;
break;
}
case Network::Type::Ethernet: {
@ -66,6 +67,7 @@ std::shared_ptr<Message> icsneo::CreateMessageFromNeoMessage(const neomessage_t*
canmsg->isRemote = can.status.remoteFrame | can.status.canfdRTR;
canmsg->isCANFD = can.status.canfdFDF;
canmsg->baudrateSwitch = can.status.canfdBRS;
canmsg->errorStateIndicator = can.status.canfdESI;
return canmsg;
}
default:

View File

@ -27,6 +27,7 @@ std::shared_ptr<CANMessage> HardwareCANPacket::DecodeToMessage(const std::vector
if(data->header.EDL && data->timestamp.IsExtended) { // CAN FD
msg->isCANFD = true;
msg->baudrateSwitch = data->header.BRS; // CAN FD Baudrate Switch
msg->errorStateIndicator = data->header.ESI;
if(length > 8) {
switch(length) { // CAN FD Length Decoding
case 0x9:
@ -87,26 +88,76 @@ bool HardwareCANPacket::EncodeFromMessage(const CANMessage& message, std::vector
return false; // Too much data for the protocol
uint8_t lengthNibble = uint8_t(message.data.size());
uint8_t paddingBytes = 0;
if(lengthNibble > 8) {
switch(lengthNibble) {
case 9: paddingBytes++;
case 10: paddingBytes++;
case 11: paddingBytes++;
case 12:
lengthNibble = 0x9;
break;
case 13: paddingBytes++;
case 14: paddingBytes++;
case 15: paddingBytes++;
case 16:
lengthNibble = 0xA;
break;
case 17: paddingBytes++;
case 18: paddingBytes++;
case 19: paddingBytes++;
case 20:
lengthNibble = 0xB;
break;
case 21: paddingBytes++;
case 22: paddingBytes++;
case 23: paddingBytes++;
case 24:
lengthNibble = 0xC;
break;
case 25: paddingBytes++;
case 26: paddingBytes++;
case 27: paddingBytes++;
case 28: paddingBytes++;
case 29: paddingBytes++;
case 30: paddingBytes++;
case 31: paddingBytes++;
case 32:
lengthNibble = 0xD;
break;
case 33: paddingBytes++;
case 34: paddingBytes++;
case 35: paddingBytes++;
case 36: paddingBytes++;
case 37: paddingBytes++;
case 38: paddingBytes++;
case 39: paddingBytes++;
case 40: paddingBytes++;
case 41: paddingBytes++;
case 42: paddingBytes++;
case 43: paddingBytes++;
case 44: paddingBytes++;
case 45: paddingBytes++;
case 46: paddingBytes++;
case 47: paddingBytes++;
case 48:
lengthNibble = 0xE;
break;
case 49: paddingBytes++;
case 50: paddingBytes++;
case 51: paddingBytes++;
case 52: paddingBytes++;
case 53: paddingBytes++;
case 54: paddingBytes++;
case 55: paddingBytes++;
case 56: paddingBytes++;
case 57: paddingBytes++;
case 58: paddingBytes++;
case 59: paddingBytes++;
case 60: paddingBytes++;
case 61: paddingBytes++;
case 62: paddingBytes++;
case 63: paddingBytes++;
case 64:
lengthNibble = 0xF;
break;
@ -116,7 +167,7 @@ bool HardwareCANPacket::EncodeFromMessage(const CANMessage& message, std::vector
}
// Pre-allocate as much memory as we will possibly need for speed
result.reserve(17 + dataSize);
result.reserve(17 + dataSize + paddingBytes);
result.push_back(0 /* byte count here later */ << 4 | (uint8_t(message.network.getNetID()) & 0xF));
@ -150,6 +201,7 @@ bool HardwareCANPacket::EncodeFromMessage(const CANMessage& message, std::vector
uint8_t fdStatusByte = lengthNibble;
if(message.baudrateSwitch)
fdStatusByte |= 0x80; // BRS status bit
// The firmware does not yet support transmitting ESI
result.push_back(fdStatusByte);
} else {
// TODO Support high voltage wakeup, bitwise-or in 0x8 here to enable
@ -159,6 +211,7 @@ bool HardwareCANPacket::EncodeFromMessage(const CANMessage& message, std::vector
// Now finally the payload
result.insert(result.end(), message.data.begin(), message.data.end());
result.resize(result.size() + paddingBytes);
result.push_back(0);
// Fill in the length byte from earlier

View File

@ -54,6 +54,7 @@ public:
FailedToWrite = 0x3001,
DriverFailedToOpen = 0x3002,
PacketChecksumError = 0x3003,
TransmitBufferFull = 0x3004,
PCAPCouldNotStart = 0x3102,
PCAPCouldNotFindDevices = 0x3103,

View File

@ -5,12 +5,16 @@
#include <chrono>
#include <atomic>
#include <thread>
#include <mutex>
#include <condition_variable>
#include "icsneo/api/errormanager.h"
#include "icsneo/third-party/concurrentqueue/blockingconcurrentqueue.h"
namespace icsneo {
class ICommunication {
public:
ICommunication(const device_errorhandler_t& handler) : err(handler) {}
virtual ~ICommunication() {}
virtual bool open() = 0;
virtual bool isOpen() = 0;
@ -18,12 +22,21 @@ public:
virtual bool read(std::vector<uint8_t>& bytes, size_t limit = 0);
virtual bool readWait(std::vector<uint8_t>& bytes, std::chrono::milliseconds timeout = std::chrono::milliseconds(100), size_t limit = 0);
virtual bool write(const std::vector<uint8_t>& bytes);
inline void onWrite() {
if(writeQueue.size_approx() < (writeQueueSize * 3/4))
writeCV.notify_one();
}
device_errorhandler_t err;
size_t writeQueueSize = 50;
bool writeBlocks = true; // Otherwise it just fails when the queue is full
protected:
class WriteOperation {
public:
WriteOperation() {}
WriteOperation(std::vector<uint8_t> b) { bytes = b; }
WriteOperation(const std::vector<uint8_t>& b) : bytes(b) {}
std::vector<uint8_t> bytes;
};
enum IOTaskState {
@ -34,6 +47,8 @@ protected:
virtual void writeTask() = 0;
moodycamel::BlockingConcurrentQueue<uint8_t> readQueue;
moodycamel::BlockingConcurrentQueue<WriteOperation> writeQueue;
std::mutex writeMutex;
std::condition_variable writeCV;
std::thread readThread, writeThread;
std::atomic<bool> closing{false};
};

View File

@ -9,10 +9,11 @@ class CANMessage : public Message {
public:
uint32_t arbid;
uint8_t dlcOnWire;
bool isRemote = false;
bool isRemote = false; // Not allowed if CAN FD
bool isExtended = false;
bool isCANFD = false;
bool baudrateSwitch = false; // CAN FD only
bool errorStateIndicator = false; // CAN FD only
};
}

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@ class FTDI : public ICommunication {
public:
static std::vector<neodevice_t> FindByProduct(int product);
FTDI(device_errorhandler_t err, neodevice_t& forDevice);
FTDI(const device_errorhandler_t& err, neodevice_t& forDevice);
~FTDI() { close(); }
bool open();
bool close();
@ -56,7 +56,6 @@ private:
bool openable; // Set to false in the constructor if the object has not been found in searchResultDevices
neodevice_t& device;
device_errorhandler_t err;
};
}

View File

@ -11,7 +11,7 @@ namespace icsneo {
class STM32 : public ICommunication {
public:
STM32(device_errorhandler_t err, neodevice_t& forDevice) : device(forDevice), err(err) {}
STM32(const device_errorhandler_t& err, neodevice_t& forDevice) : ICommunication(err), device(forDevice) {}
static std::vector<neodevice_t> FindByProduct(int product);
bool open();
@ -20,7 +20,6 @@ public:
private:
neodevice_t& device;
device_errorhandler_t err;
int fd = -1;
static constexpr neodevice_handle_t HANDLE_OFFSET = 10;

View File

@ -7,7 +7,7 @@ namespace icsneo {
class FTDI : public VCP {
public:
FTDI(device_errorhandler_t err, neodevice_t& forDevice) : VCP(err, forDevice) {}
FTDI(const device_errorhandler_t& err, neodevice_t& forDevice) : VCP(err, forDevice) {}
static std::vector<neodevice_t> FindByProduct(int product) { return VCP::FindByProduct(product, { L"serenum" /*, L"ftdibus" */ }); }
};

View File

@ -21,7 +21,7 @@ public:
static std::string GetEthDevSerialFromMacAddress(uint8_t product, uint16_t macSerial);
static bool IsHandleValid(neodevice_handle_t handle);
PCAP(device_errorhandler_t err, neodevice_t& forDevice);
PCAP(const device_errorhandler_t& err, neodevice_t& forDevice);
bool open();
bool isOpen();
bool close();

View File

@ -7,7 +7,7 @@ namespace icsneo {
class STM32 : public VCP {
public:
STM32(device_errorhandler_t err, neodevice_t& forDevice) : VCP(err, forDevice) {}
STM32(const device_errorhandler_t& err, neodevice_t& forDevice) : VCP(err, forDevice) {}
static std::vector<neodevice_t> FindByProduct(int product) { return VCP::FindByProduct(product, { L"usbser" }); }
};

View File

@ -20,7 +20,7 @@ public:
static bool IsHandleValid(neodevice_handle_t handle);
typedef void(*fn_boolCallback)(bool success);
VCP(device_errorhandler_t err, neodevice_t& forDevice) : device(forDevice), err(err) {
VCP(const device_errorhandler_t& err, neodevice_t& forDevice) : ICommunication(err), device(forDevice) {
overlappedRead.hEvent = INVALID_HANDLE_VALUE;
overlappedWrite.hEvent = INVALID_HANDLE_VALUE;
overlappedWait.hEvent = INVALID_HANDLE_VALUE;

View File

@ -13,7 +13,7 @@ std::vector<std::tuple<int, std::string>> FTDI::handles;
std::vector<neodevice_t> FTDI::FindByProduct(int product) {
constexpr size_t deviceSerialBufferLength = sizeof(device.serial);
std::vector<neodevice_t> found;
FTDIContext context;
static FTDIContext context;
std::pair<int, std::vector<std::string>> result = context.findDevices(product);
if(result.first < 0)
@ -39,7 +39,7 @@ std::vector<neodevice_t> FTDI::FindByProduct(int product) {
return found;
}
FTDI::FTDI(device_errorhandler_t err, neodevice_t& forDevice) : device(forDevice), err(err) {
FTDI::FTDI(const device_errorhandler_t& err, neodevice_t& forDevice) : ICommunication(err), device(forDevice) {
openable = strlen(forDevice.serial) > 0 && device.handle >= 0 && device.handle < (neodevice_handle_t)handles.size();
}
@ -183,5 +183,6 @@ void FTDI::writeTask() {
continue;
ftdi.write(writeOp.bytes.data(), (int)writeOp.bytes.size());
onWrite();
}
}

View File

@ -285,5 +285,6 @@ void STM32::writeTask() {
ssize_t actualWritten = ::write(fd, writeOp.bytes.data(), writeSize);
if(actualWritten != writeSize)
err(APIError::FailedToWrite);
onWrite();
}
}

View File

@ -177,7 +177,7 @@ bool PCAP::IsHandleValid(neodevice_handle_t handle) {
return (netifIndex < knownInterfaces.size());
}
PCAP::PCAP(device_errorhandler_t err, neodevice_t& forDevice) : device(forDevice), err(err) {
PCAP::PCAP(const device_errorhandler_t& err, neodevice_t& forDevice) : ICommunication(err), device(forDevice) {
if(IsHandleValid(device.handle)) {
interface = knownInterfaces[(device.handle >> 24) & 0xFF];
interface.fp = nullptr; // We're going to open our own connection to the interface. This should already be nullptr but just in case.
@ -287,6 +287,7 @@ void PCAP::writeTask() {
auto bs = sendPacket.getBytestream();
if(!closing)
pcap.sendpacket(interface.fp, bs.data(), (int)bs.size());
onWrite();
// TODO Handle packet send errors
}
}

View File

@ -389,6 +389,8 @@ void VCP::writeTask() {
bytesWritten = 0;
if(WriteFile(handle, writeOp.bytes.data(), (DWORD)writeOp.bytes.size(), nullptr, &overlappedWrite))
continue;
onWrite();
auto winerr = GetLastError();
if(winerr == ERROR_IO_PENDING) {