Device: Add GetComponentVersions and GetSupportedFeatures commands

Driver: Fix re-open and failed open cases for TCP

Also enforces even length packets for the RED2, FIRE3, and FIRE3 FlexRay devices.
pull/56/head
Jonathan Schwartz 2023-05-08 21:07:43 +00:00
parent b6d9ef4c7e
commit 32900ae263
22 changed files with 303 additions and 23 deletions

View File

@ -240,6 +240,8 @@ set(SRC_FILES
communication/packet/i2cpacket.cpp
communication/packet/linpacket.cpp
communication/packet/scriptstatuspacket.cpp
communication/packet/componentversionpacket.cpp
communication/packet/supportedfeaturespacket.cpp
communication/decoder.cpp
communication/encoder.cpp
communication/ethernetpacketizer.cpp

View File

@ -115,6 +115,7 @@ static constexpr const char* WIVI_UPLOAD_STACK_OVERFLOW = "The Wireless neoVI up
static constexpr const char* A2B_MESSAGE_INCOMPLETE_FRAME = "At least one of the frames of the A2B message does not contain samples for each channel and stream.";
static constexpr const char* COREMINI_UPLOAD_VERSION_MISMATCH = "The version of the coremini engine on the device and the script uploaded are not the same.";
static constexpr const char* DISK_NOT_CONNECTED = "The program tried to access a disk that is not connected.";
static constexpr const char* UNEXPECTED_RESPONSE = "Received an unexpected or invalid response from the device.";
// Transport Errors
static constexpr const char* FAILED_TO_READ = "A read operation failed.";
@ -290,6 +291,8 @@ const char* APIEvent::DescriptionForType(Type type) {
return COREMINI_UPLOAD_VERSION_MISMATCH;
case Type::DiskNotConnected:
return DISK_NOT_CONNECTED;
case Type::UnexpectedResponse:
return UNEXPECTED_RESPONSE;
// Transport Errors
case Type::FailedToRead:
return FAILED_TO_READ;

View File

@ -13,6 +13,7 @@
#include "icsneo/communication/message/filter/main51messagefilter.h"
#include "icsneo/communication/message/readsettingsmessage.h"
#include "icsneo/communication/message/versionmessage.h"
#include "icsneo/communication/message/componentversionsmessage.h"
using namespace icsneo;
@ -301,3 +302,18 @@ void Communication::handleInput(Packetizer& p, std::vector<uint8_t>& readBytes)
}
}
}
std::optional< std::vector<ComponentVersion> > Communication::getComponentVersionsSync(std::chrono::milliseconds timeout) {
static const std::shared_ptr<MessageFilter> filter = std::make_shared<MessageFilter>(Message::Type::ComponentVersions);
std::shared_ptr<Message> msg = waitForMessageSync([this]() {
return sendCommand(ExtendedCommand::GetComponentVersions, {});
}, filter, timeout);
if(!msg) // Did not receive a message
return std::nullopt;
auto ver = std::dynamic_pointer_cast<ComponentVersionsMessage>(msg);
if(!ver) // Could not upcast for some reason
return std::nullopt;
return std::make_optional< std::vector<ComponentVersion> >(std::move(ver->versions));
}

View File

@ -26,6 +26,8 @@
#include "icsneo/communication/packet/i2cpacket.h"
#include "icsneo/communication/packet/scriptstatuspacket.h"
#include "icsneo/communication/packet/linpacket.h"
#include "icsneo/communication/packet/componentversionpacket.h"
#include "icsneo/communication/packet/supportedfeaturespacket.h"
#include <iostream>
using namespace icsneo;
@ -247,13 +249,21 @@ bool Decoder::decode(std::shared_ptr<Message>& result, const std::shared_ptr<Pac
break; // Handle as a raw message, might not be a generic response
const auto& resp = *reinterpret_cast<ExtendedResponseMessage::PackedGenericResponse*>(packet->data.data());
if(resp.header.command != ExtendedCommand::GenericReturn)
break; // Handle as a raw message
const auto msg = std::make_shared<ExtendedResponseMessage>(resp.command, resp.returnCode);
result = msg;
switch(resp.header.command) {
case ExtendedCommand::GetComponentVersions:
result = ComponentVersionPacket::DecodeToMessage(packet->data);
return true;
case ExtendedCommand::GetSupportedFeatures:
result = SupportedFeaturesPacket::DecodeToMessage(packet->data);
return true;
case ExtendedCommand::GenericReturn:
result = std::make_shared<ExtendedResponseMessage>(resp.command, resp.returnCode);
return true;
default:
// No defined handler, treat this as a RawMessage
break;
}
} break;
case Network::NetID::FlexRayControl: {
auto frResult = std::make_shared<FlexRayControlMessage>(*packet);
if(!frResult->decoded) {

View File

@ -0,0 +1,45 @@
#include "icsneo/communication/packet/componentversionpacket.h"
#include "icsneo/communication/message/componentversionsmessage.h"
using namespace icsneo;
#pragma pack(push, 2)
struct PackedComponentVersion {
uint8_t valid;
uint8_t expansionSlot;
uint8_t componentInfo; // Component specific data (e.g. Linux: boot device)
uint8_t reserved;
uint32_t identifier;
uint32_t dotVersion; // Represents a.b.c.d, a.b.c, or a.b, depending on leading zeros.
uint32_t commitHash;
};
static constexpr size_t MaxReportedVersions = 16;
struct ComponentVersionsResponse {
ExtendedResponseMessage::ResponseHeader header;
uint16_t numVersions;
PackedComponentVersion versions[MaxReportedVersions];
};
#pragma pack(pop)
std::shared_ptr<ComponentVersionsMessage> ComponentVersionPacket::DecodeToMessage(const std::vector<uint8_t>& bytes) {
auto msg = std::make_shared<ComponentVersionsMessage>();
// Length checks: At least a header and numVersions field.
if(bytes.size() < sizeof(ExtendedResponseMessage::ResponseHeader) + 2) {
return msg; // Empty
}
// Get a reference to the payload to fully validate the length
const auto& response = *reinterpret_cast<const ComponentVersionsResponse*>(bytes.data());
// Expected size is the header, numVersions field, and numVersions ComponentVersion objects.
auto expectedSize = sizeof(ExtendedResponseMessage::ResponseHeader) + 2 + (response.numVersions * sizeof(ComponentVersion));
// If the response is malformed (too small), return an empty message.
if(bytes.size() < expectedSize) {
return msg; // Empty
}
// Unpack into the portable class
for(unsigned int i = 0; i < response.numVersions; ++i) {
const auto& packedVersion = response.versions[i];
msg->versions.emplace_back(packedVersion.valid, packedVersion.componentInfo, packedVersion.identifier, packedVersion.dotVersion, packedVersion.commitHash);
}
return msg;
}

View File

@ -0,0 +1,41 @@
#include "icsneo/communication/packet/supportedfeaturespacket.h"
#include "icsneo/communication/message/supportedfeaturesmessage.h"
using namespace icsneo;
static constexpr uint16_t SupportedFeaturesCommandVersion = 1;
static constexpr size_t NumSupportedFeaturesFields =
(static_cast<size_t>(SupportedFeature::numSupportedFeatures) + 31) / 32;
#pragma pack(push, 2)
struct SupportedFeaturesResponse {
ExtendedResponseMessage::ResponseHeader header;
uint16_t cmdVersion;
uint16_t numValidBits;
uint32_t featuresFields[NumSupportedFeaturesFields];
};
#pragma pack(pop)
std::shared_ptr<SupportedFeaturesMessage> SupportedFeaturesPacket::DecodeToMessage(const std::vector<uint8_t>& bytes) {
auto msg = std::make_shared<SupportedFeaturesMessage>();
// Length check: At least a header, a 2-byte cmdVersion field, and a 2-byte numValidBits field.
if(bytes.size() < sizeof(ExtendedResponseMessage::ResponseHeader) + 4) {
return msg; // Empty
}
// Get a reference to the payload to fully validate the length
const auto& response = *reinterpret_cast<const SupportedFeaturesResponse*>(bytes.data());
// Expected size is the header, cmdVersion and numValidBits fields, plus the number of 32-bit bitfields in the response based on numValidBits
auto expectedSize = sizeof(ExtendedResponseMessage::ResponseHeader) + 4 + ((response.numValidBits + 31) / 32) * 4;
// If the response is malformed (too small), return an empty message
if(bytes.size() < expectedSize) {
return msg; // Empty
}
unsigned int loopLimit = std::min<unsigned int>(response.numValidBits, NumSupportedFeaturesFields);
for(unsigned int i = 0; i < loopLimit; ++i) {
uint32_t wordOffset = i / 32;
uint32_t bitOffset = i % 32;
if((response.featuresFields[wordOffset] >> bitOffset) & 1) {
msg->features.insert(static_cast<SupportedFeature>(i));
}
}
return msg;
}

View File

@ -217,6 +217,13 @@ bool Device::open(OpenFlags flags, OpenStatusHandler handler) {
if(block) // Extensions say no
return false;
// Get component versions *after* the extension "onDeviceOpen" hooks (e.g. device reflashes)
if (auto compVersions = com->getComponentVersionsSync())
componentVersions = std::move(*compVersions);
else
report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::EventWarning);
if(!settings->disabled) {
// Since we will not fail the open if a settings read fails,
// downgrade any errors to warnings. Otherwise the error will
@ -303,6 +310,10 @@ APIEvent::Type Device::attemptToBeginCommunication() {
return getCommunicationNotEstablishedError();
}
if(!com->sendCommand(Command::EnableNetworkCommunication, false))
return getCommunicationNotEstablishedError();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
auto serial = com->getSerialNumberSync();
int i = 0;
while(!serial) {
@ -1752,3 +1763,20 @@ bool Device::setRTC(const std::chrono::time_point<std::chrono::system_clock>& ti
com->sendCommand(Command::SetRTC, bytestream);
return true;
}
std::optional<std::set<SupportedFeature>> Device::getSupportedFeatures() {
auto timeout = std::chrono::milliseconds(100);
std::shared_ptr<Message> msg = com->waitForMessageSync(
[this](){ return com->sendCommand(ExtendedCommand::GetSupportedFeatures, {}); },
std::make_shared<MessageFilter>(Message::Type::SupportedFeatures), timeout);
if(!msg) {
report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
return std::nullopt;
}
const auto& typedResponse = std::dynamic_pointer_cast<SupportedFeaturesMessage>(msg);
if(!typedResponse) {
report(APIEvent::Type::UnexpectedResponse, APIEvent::Severity::Error);
return std::nullopt;
}
return std::move(typedResponse->features);
}

View File

@ -93,6 +93,7 @@ public:
A2BMessageIncompleteFrame = 0x2039,
CoreminiUploadVersionMismatch = 0x2040,
DiskNotConnected = 0x2041,
UnexpectedResponse = 0x2042,
// Transport Events
FailedToRead = 0x3000,

View File

@ -47,6 +47,8 @@ enum class ExtendedCommand : uint16_t {
Extract = 0x0015,
StartDHCPServer = 0x0016,
StopDHCPServer = 0x0017,
GetSupportedFeatures = 0x0018,
GetComponentVersions = 0x001A,
Reboot = 0x001C,
SetUploadedFlag = 0x0027,
};

View File

@ -10,6 +10,8 @@
#include "icsneo/communication/message/callback/messagecallback.h"
#include "icsneo/communication/message/serialnumbermessage.h"
#include "icsneo/communication/message/logicaldiskinfomessage.h"
#include "icsneo/communication/message/componentversionsmessage.h"
#include "icsneo/communication/message/extendedresponsemessage.h"
#include "icsneo/device/deviceversion.h"
#include "icsneo/api/eventmanager.h"
#include "icsneo/communication/packetizer.h"
@ -58,6 +60,7 @@ public:
std::shared_ptr<SerialNumberMessage> getSerialNumberSync(std::chrono::milliseconds timeout = std::chrono::milliseconds(50));
std::optional< std::vector< std::optional<DeviceAppVersion> > > getVersionsSync(std::chrono::milliseconds timeout = std::chrono::milliseconds(50));
std::shared_ptr<LogicalDiskInfoMessage> getLogicalDiskInfoSync(std::chrono::milliseconds timeout = std::chrono::milliseconds(50));
std::optional< std::vector<ComponentVersion> > getComponentVersionsSync(std::chrono::milliseconds timeout = std::chrono::milliseconds(50));
int addMessageCallback(const std::shared_ptr<MessageCallback>& cb);
bool removeMessageCallback(int id);

View File

@ -0,0 +1,29 @@
#ifndef _EXTENDED_COMPONENT_VERSIONS_RESPONSE_MESSAGE_H_
#define _EXTENDED_COMPONENT_VERSIONS_RESPONSE_MESSAGE_H_
#ifdef __cplusplus
#include "icsneo/communication/message/extendedresponsemessage.h"
namespace icsneo {
class ComponentVersion {
public:
ComponentVersion(uint8_t valid, uint8_t componentInfo, uint32_t identifier, uint32_t dotVersion, uint32_t commitHash) :
valid(valid), componentInfo(componentInfo), identifier(identifier), dotVersion(dotVersion), commitHash(commitHash) {}
const bool valid;
const uint8_t componentInfo;
const uint32_t identifier;
const uint32_t dotVersion;
const uint32_t commitHash;
};
class ComponentVersionsMessage : public Message {
public:
ComponentVersionsMessage() : Message(Message::Type::ComponentVersions) {}
std::vector<ComponentVersion> versions;
};
}
#endif // __cplusplus
#endif // _EXTENDED_COMPONENT_VERSIONS_RESPONSE_MESSAGE_H_

View File

@ -34,6 +34,8 @@ public:
ExtendedResponse = 0x8009,
WiVICommandResponse = 0x800a,
ScriptStatus = 0x800b,
ComponentVersions = 0x800c,
SupportedFeatures = 0x800d,
};
Message(Type t) : type(t) {}

View File

@ -0,0 +1,44 @@
#ifndef _SUPPORTED_FEATURES_RESPONSE_MESSAGE_H_
#define _SUPPORTED_FEATURES_RESPONSE_MESSAGE_H_
#ifdef __cplusplus
#include "icsneo/communication/message/extendedresponsemessage.h"
#include <cstdint>
#include <set>
namespace icsneo {
enum class SupportedFeature : uint16_t {
networkDWCAN01 = 0,
networkDWCAN02 = 1,
networkDWCAN03 = 2,
networkDWCAN04 = 3,
networkDWCAN05 = 4,
networkDWCAN06 = 5,
networkDWCAN07 = 6,
networkDWCAN08 = 7,
networkTerminationDWCAN01 = 8,
networkTerminationDWCAN02 = 9,
networkTerminationDWCAN03 = 10,
networkTerminationDWCAN04 = 11,
networkTerminationDWCAN05 = 12,
networkTerminationDWCAN06 = 13,
networkTerminationDWCAN07 = 14,
networkTerminationDWCAN08 = 15,
enhancedFlashDriver = 16,
rtcCalibration = 17,
rtcClosedLoopCalibration = 18,
numSupportedFeatures,
};
class SupportedFeaturesMessage : public Message {
public:
SupportedFeaturesMessage() : Message(Message::Type::SupportedFeatures) {}
std::set<SupportedFeature> features;
};
}
#endif // __cplusplus
#endif // _SUPPORTED_FEATURES_RESPONSE_MESSAGE_H_

View File

@ -0,0 +1,22 @@
#ifndef __COMPONENTVERSIONPACKET_H__
#define __COMPONENTVERSIONPACKET_H__
#ifdef __cplusplus
#include <cstdint>
#include <memory>
#include <vector>
namespace icsneo {
class ComponentVersionsMessage;
struct ComponentVersionPacket {
static std::shared_ptr<ComponentVersionsMessage> DecodeToMessage(const std::vector<uint8_t>& bytes);
};
}
#endif // __cplusplus
#endif // __DEVICECOMPONENTVERSIONPACKET_H__

View File

@ -0,0 +1,22 @@
#ifndef __SUPPORTEDFEATURESPACKET_H_
#define __SUPPORTEDFEATURESPACKET_H_
#ifdef __cplusplus
#include <cstdint>
#include <memory>
#include <vector>
namespace icsneo {
class SupportedFeaturesMessage;
struct SupportedFeaturesPacket {
static std::shared_ptr<SupportedFeaturesMessage> DecodeToMessage(const std::vector<uint8_t>& bytes);
};
}
#endif // __cplusplus
#endif // __SUPPORTEDFEATURESPACKET_H_

View File

@ -12,6 +12,7 @@
#include <type_traits>
#include <optional>
#include <unordered_map>
#include <set>
#include "icsneo/api/eventmanager.h"
#include "icsneo/api/lifetime.h"
#include "icsneo/device/neodevice.h"
@ -31,6 +32,7 @@
#include "icsneo/communication/message/resetstatusmessage.h"
#include "icsneo/communication/message/wiviresponsemessage.h"
#include "icsneo/communication/message/scriptstatusmessage.h"
#include "icsneo/communication/message/supportedfeaturesmessage.h"
#include "icsneo/device/extensions/flexray/controller.h"
#include "icsneo/communication/message/flexray/control/flexraycontrolmessage.h"
#include "icsneo/communication/message/ethphymessage.h"
@ -530,7 +532,7 @@ public:
* For use by extensions only. A more stable API will be provided in the future.
*/
const std::vector<std::optional<DeviceAppVersion>>& getVersions() const { return versions; }
const std::vector<ComponentVersion>& getComponentVersions() const { return componentVersions; }
/**
* Some alternate communication protocols do not support DFU
*/
@ -548,6 +550,9 @@ public:
std::optional<std::chrono::time_point<std::chrono::system_clock>> getRTC();
bool setRTC(const std::chrono::time_point<std::chrono::system_clock>& time);
// Get a bitfield from the device representing supported networks and features.
std::optional<std::set<SupportedFeature>> getSupportedFeatures();
/**
* Returns true if this device supports the Wireless neoVI featureset
*/
@ -669,6 +674,7 @@ private:
neodevice_t data;
std::shared_ptr<ResetStatusMessage> latestResetStatus;
std::vector<std::optional<DeviceAppVersion>> versions;
std::vector<ComponentVersion> componentVersions;
mutable std::mutex diskLock;
std::unique_ptr<Disk::ReadDriver> diskReadDriver;

View File

@ -62,7 +62,7 @@ protected:
void setupPacketizer(Packetizer& packetizer) override {
Device::setupPacketizer(packetizer);
packetizer.align16bit = false;
packetizer.align16bit = true;
}
void setupSupportedRXNetworks(std::vector<Network>& rxNetworks) override {

View File

@ -64,7 +64,7 @@ protected:
void setupPacketizer(Packetizer& packetizer) override {
Device::setupPacketizer(packetizer);
packetizer.align16bit = false;
packetizer.align16bit = true;
}
void setupSupportedRXNetworks(std::vector<Network>& rxNetworks) override {

View File

@ -47,7 +47,7 @@ protected:
void setupPacketizer(Packetizer& packetizer) override {
Device::setupPacketizer(packetizer);
packetizer.align16bit = false;
packetizer.align16bit = true;
}
void setupSupportedRXNetworks(std::vector<Network>& rxNetworks) override {

View File

@ -3,7 +3,7 @@
#ifdef __cplusplus
#include <optional>
#include <memory>
#include "icsneo/communication/driver.h"
#include "icsneo/device/founddevice.h"
@ -45,7 +45,7 @@ private:
NetworkInterface interface;
uint32_t dstIP;
uint16_t dstPort;
std::optional<Socket> socket;
std::unique_ptr<Socket> socket;
void readTask() override;
void writeTask() override;
};

View File

@ -198,7 +198,7 @@ void CDCACM::readTask() {
// We were expecting a disconnect for reenumeration
modeChangeThread = std::thread([this] {
modeChangeCV.notify_all();
close(); // Which will trigger an open() due to modeChanging
// Requesting thread is responsible for calling close. This allows for more flexibility
});
break;
} else if(!closing && !fdIsValid() && !isDisconnected()) {

View File

@ -16,6 +16,7 @@
#include <arpa/inet.h>
#endif
#include <optional>
#include "icsneo/platform/tcp.h"
#ifdef _WIN32
@ -415,9 +416,10 @@ bool TCP::open() {
return false;
}
socket.emplace(AF_INET, SOCK_STREAM, IPPROTO_TCP);
auto partiallyOpenSocket = std::make_unique<Socket>(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#if !defined(_WIN32) && !defined(__APPLE__)
if(::setsockopt(*socket, SOL_SOCKET, SO_BINDTODEVICE, interface.name.c_str(), interface.name.size()) < 0) {
if(::setsockopt(*partiallyOpenSocket, SOL_SOCKET, SO_BINDTODEVICE, interface.name.c_str(), interface.name.size()) < 0) {
report(APIEvent::Type::ErrorSettingSocketOption, APIEvent::Severity::Error);
return false;
}
@ -428,7 +430,7 @@ bool TCP::open() {
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(interface.ip);
APPLE_SIN_LEN(addr);
if(::bind(*socket, (sockaddr*)&addr, sizeof(addr)) < 0) {
if(::bind(*partiallyOpenSocket, (sockaddr*)&addr, sizeof(addr)) < 0) {
report(APIEvent::Type::FailedToBind, APIEvent::Severity::Error);
return false;
}
@ -442,7 +444,7 @@ bool TCP::open() {
APPLE_SIN_LEN(addr);
// the socket is non-blocking so it's expected that the first connect will fail
if(::connect(*socket, (sockaddr*)&addr, sizeof(addr)) == 0) {
if(::connect(*partiallyOpenSocket, (sockaddr*)&addr, sizeof(addr)) == 0) {
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
return false;
}
@ -462,11 +464,11 @@ bool TCP::open() {
timeout.tv_sec = 1;
fd_set writefs;
FD_ZERO(&writefs);
int nfds = WIN_INT(*socket) + 1;
FD_SET(*socket, &writefs);
int nfds = WIN_INT(*partiallyOpenSocket) + 1;
FD_SET(*partiallyOpenSocket, &writefs);
::select(nfds, 0, &writefs, 0, &timeout);
if(::connect(*socket, (sockaddr*)&addr, sizeof(addr)) < 0) {
if(::connect(*partiallyOpenSocket, (sockaddr*)&addr, sizeof(addr)) < 0) {
#ifdef _WIN32
if(::WSAGetLastError() != WSAEISCONN) {
report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error);
@ -484,13 +486,14 @@ bool TCP::open() {
}
}
socket = std::move(partiallyOpenSocket);
readThread = std::thread(&TCP::readTask, this);
writeThread = std::thread(&TCP::writeTask, this);
return true;
}
bool TCP::isOpen() {
return socket.has_value();
return socket ? true : false;
}
bool TCP::close() {
@ -513,6 +516,7 @@ bool TCP::close() {
while(writeQueue.try_dequeue(flushop)) {}
socket.reset();
closing = false;
return true;
}