Device: Add network mutex support

pull/76/merge
Jonathan Schwartz 2025-10-24 16:12:36 +00:00 committed by Kyle Schwarz
parent bf311ebe30
commit c2f1022858
29 changed files with 1146 additions and 47 deletions

View File

@ -1,5 +1,6 @@
variables:
DEBIAN_FRONTEND: noninteractive
LIBICSNEO_ICSPB_REPO: https://gitlab-ci-token:${CI_JOB_TOKEN}@${LIBICSNEO_ICSPB_GIT}
stages:
- build
@ -13,7 +14,7 @@ stages:
build windows/x64:
stage: build
script:
- CMD.EXE /C ci\build-windows64.bat
- cmd /C ci\build-windows64.bat
artifacts:
when: always
paths:
@ -37,7 +38,7 @@ unit_test windows/x64:
build windows/x86:
stage: build
script:
- CMD.EXE /C ci\build-windows32.bat
- cmd /C ci\build-windows32.bat
artifacts:
when: always
paths:
@ -67,7 +68,9 @@ unit_test windows/x86:
script:
- apt update -y
- apt upgrade -y
- apt install -y g++ ninja-build cmake libpcap-dev git
- apt install -y g++ ninja-build cmake libpcap-dev git ca-certificates
- echo "$ICS_IPA_CA_CRT" >/usr/local/share/ca-certificates/ica-ipa-ca.crt
- update-ca-certificates --fresh
- sh ci/build-posix.sh
artifacts:
when: always
@ -93,7 +96,9 @@ unit_test windows/x86:
script:
- apt update -y
- apt upgrade -y
- apt install -y clang lld ninja-build cmake libpcap-dev git
- apt install -y clang lld ninja-build cmake libpcap-dev git ca-certificates
- echo "$ICS_IPA_CA_CRT" >/usr/local/share/ca-certificates/ica-ipa-ca.crt
- update-ca-certificates --fresh
- CC=clang CXX=clang++ LDFLAGS=-fuse-ld=lld sh ci/build-posix.sh
artifacts:
when: always
@ -175,7 +180,9 @@ unit_test linux/ubuntu/2404/amd64/clang:
- echo max_parallel_downloads=10 >>/etc/dnf/dnf.conf
- echo fastestmirror=True >>/etc/dnf/dnf.conf
- dnf upgrade -y
- dnf install -y g++ libpcap-devel cmake ninja-build git
- dnf install -y g++ libpcap-devel cmake ninja-build git ca-certificates
- echo "$ICS_IPA_CA_CRT" >/etc/pki/ca-trust/source/anchors/ica-ipa-ca.crt
- update-ca-trust
- sh ci/build-posix.sh
artifacts:
when: always
@ -209,7 +216,9 @@ unit_test linux/ubuntu/2404/amd64/clang:
- echo max_parallel_downloads=10 >>/etc/dnf/dnf.conf
- echo fastestmirror=True >>/etc/dnf/dnf.conf
- dnf upgrade -y
- dnf install -y clang lld libpcap-devel cmake ninja-build git
- dnf install -y clang lld libpcap-devel cmake ninja-build git ca-certificates
- echo "$ICS_IPA_CA_CRT" >/etc/pki/ca-trust/source/anchors/ica-ipa-ca.crt
- update-ca-trust
- CC=clang CXX=clang++ LDFLAGS=-fuse-ld=lld sh ci/build-posix.sh
artifacts:
when: always
@ -289,22 +298,13 @@ unit_test linux/fedora/42/amd64/clang:
build python/linux/amd64:
stage: build
tags:
- linux-build
image: python:3.13
services:
- name: docker:dind
entrypoint: ["env", "-u", "DOCKER_HOST"]
command: ["dockerd-entrypoint.sh"]
- linux-native-amd64
variables:
CIBW_BEFORE_ALL: yum install -y flex && sh ci/bootstrap-libpcap.sh
CIBW_BEFORE_ALL: sh ci/bootstrap-cibuildwheel.sh && sh ci/bootstrap-libpcap.sh
CIBW_BUILD: "*manylinux*" # no musl
CIBW_ARCHS: x86_64
DOCKER_HOST: unix:///var/run/docker.sock
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
CIBW_ENVIRONMENT: CMAKE_PREFIX_PATH=/project/libpcap/install
script:
- curl -sSL https://get.docker.com/ | sh
- sh ci/build-wheel-posix.sh
artifacts:
paths:
@ -315,7 +315,7 @@ build python/linux/arm64:
tags:
- arm64-linux-build
variables:
CIBW_BEFORE_ALL: yum install -y flex && sh ci/bootstrap-libpcap.sh
CIBW_BEFORE_ALL: sh ci/bootstrap-cibuildwheel.sh && sh ci/bootstrap-libpcap.sh
CIBW_BUILD: "*manylinux*" # no musl
CIBW_ARCHS: aarch64
CIBW_ENVIRONMENT: CMAKE_PREFIX_PATH=/project/libpcap/install

View File

@ -27,9 +27,8 @@ option(LIBICSNEO_ENABLE_DXX "Enable devices which communicate over D2XX/D3XX via
option(LIBICSNEO_ENABLE_BINDINGS_PYTHON "Enable Python library" OFF)
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded")
include(GNUInstallDirs)
@ -37,17 +36,13 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# Enable Warnings
if(MSVC)
# Force to always compile with W4
if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
endif()
set(LIBICSNEO_COMPILER_WARNINGS /W4)
# http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0618r0.html
# Still supported until a suitable replacement is standardized
add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
add_definitions(-D_ITERATOR_DEBUG_LEVEL=0)
else() #if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-switch -Wno-unknown-pragmas")
set(LIBICSNEO_COMPILER_WARNINGS -Wall -Wno-switch -Wno-unknown-pragmas)
endif()
find_package(Threads REQUIRED)
@ -201,6 +196,9 @@ set(SRC_FILES
communication/message/gptpstatusmessage.cpp
communication/message/ethernetstatusmessage.cpp
communication/message/macsecmessage.cpp
communication/message/networkmutexmessage.cpp
communication/message/clientidmessage.cpp
communication/message/transmitmessage.cpp
communication/packet/flexraypacket.cpp
communication/packet/canpacket.cpp
communication/packet/a2bpacket.cpp
@ -324,6 +322,7 @@ target_include_directories(icsneocpp
target_link_libraries(icsneocpp PUBLIC Threads::Threads $<$<BOOL:${WIN32}>:ws2_32 iphlpapi>)
set_property(TARGET icsneocpp PROPERTY POSITION_INDEPENDENT_CODE ON)
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_options(icsneocpp PRIVATE ${LIBICSNEO_COMPILER_WARNINGS})
message("Loaded extensions: " ${LIBICSNEO_EXTENSION_TARGETS})
target_link_libraries(icsneocpp PUBLIC ${LIBICSNEO_EXTENSION_TARGETS})
if(LIBICSNEO_ENABLE_FIRMIO)
@ -383,6 +382,21 @@ if(LIBICSNEO_ENABLE_RAW_ETHERNET)
endif(WIN32)
endif(LIBICSNEO_ENABLE_RAW_ETHERNET)
# protobuf
if(DEFINED ENV{LIBICSNEO_ICSPB_REPO})
set(LIBICSNEO_ICSPB_REPO "$ENV{LIBICSNEO_ICSPB_REPO}")
endif()
if(NOT LIBICSNEO_ICSPB_REPO)
set(LIBICSNEO_ICSPB_REPO https://github.com/intrepidcs/icspb.git)
endif()
include(FetchContent)
FetchContent_Declare(icspb
GIT_REPOSITORY ${LIBICSNEO_ICSPB_REPO}
GIT_TAG 2881dc4239319214dbf72fd3aa0427a4aefddd42
)
FetchContent_MakeAvailable(icspb)
target_link_libraries(icsneocpp PRIVATE icspb::icspb)
if(LIBICSNEO_BUILD_ICSNEOC)
add_library(icsneoc SHARED api/icsneoc/icsneoc.cpp ${CMAKE_CURRENT_BINARY_DIR}/generated/icsneoc/version.rc)
target_include_directories(icsneoc
@ -394,6 +408,7 @@ if(LIBICSNEO_BUILD_ICSNEOC)
)
target_link_libraries(icsneoc PRIVATE icsneocpp)
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_options(icsneoc PRIVATE ${LIBICSNEO_COMPILER_WARNINGS})
endif()
if(LIBICSNEO_BUILD_ICSNEOC_STATIC)
@ -408,6 +423,7 @@ if(LIBICSNEO_BUILD_ICSNEOC_STATIC)
target_link_libraries(icsneoc-static PUBLIC icsneocpp)
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_definitions(icsneoc-static PUBLIC ICSNEOC_BUILD_STATIC)
target_compile_options(icsneoc-static PRIVATE ${LIBICSNEO_COMPILER_WARNINGS})
endif()
if(LIBICSNEO_BUILD_ICSNEOLEGACY)
@ -426,6 +442,7 @@ if(LIBICSNEO_BUILD_ICSNEOLEGACY)
)
target_link_libraries(icsneolegacy PRIVATE icsneocpp)
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)
target_compile_options(icsneolegacy PRIVATE ${LIBICSNEO_COMPILER_WARNINGS})
endif()
if(LIBICSNEO_BUILD_ICSNEOLEGACY_STATIC)
@ -445,6 +462,7 @@ if(LIBICSNEO_BUILD_ICSNEOLEGACY_STATIC)
target_link_libraries(icsneolegacy-static PUBLIC icsneocpp)
target_compile_features(icsneolegacy-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_definitions(icsneolegacy-static PUBLIC ICSNEOC_BUILD_STATIC)
target_compile_options(icsneolegacy-static PRIVATE ${LIBICSNEO_COMPILER_WARNINGS})
endif()
add_subdirectory(bindings)
@ -475,6 +493,8 @@ if(LIBICSNEO_BUILD_UNIT_TESTS)
test/unit/periodictest.cpp
)
target_compile_options(libicsneo-unit-tests PRIVATE ${LIBICSNEO_COMPILER_WARNINGS})
target_link_libraries(libicsneo-unit-tests gtest gtest_main)
target_link_libraries(libicsneo-unit-tests icsneocpp)
@ -487,4 +507,4 @@ endif()
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
include(CPack)

View File

@ -0,0 +1,7 @@
#!/bin/sh
yum install -y flex ca-certificates || exit 1
echo "$ICS_IPA_CA_CRT" >/etc/pki/ca-trust/source/anchors/ica-ipa-ca.crt
update-ca-trust || exit 1

View File

@ -1,6 +1,5 @@
#!/bin/sh
export CFLAGS="-Wall -Werror"
export CXXFLAGS="-Wall -Werror"
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Release -DLIBICSNEO_BUILD_EXAMPLES=ON \
-DLIBICSNEO_BUILD_UNIT_TESTS=ON -DLIBICSNEO_ENABLE_TCP=OFF || exit 1

View File

@ -1,12 +1,9 @@
REM clean intermediate directories
rmdir /s /q build
mkdir build
@setlocal
@echo off
REM build
cd build
set CFLAGS=/WX /W4 /wd4127
set CXXFLAGS=/WX /W4 /wd4127
cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DLIBICSNEO_BUILD_UNIT_TESTS=ON -DLIBICSNEO_ENABLE_TCP=ON ..
if %errorlevel% neq 0 exit /b %errorlevel%
cmake --build .
if %errorlevel% neq 0 exit /b %errorlevel%
mkdir build >nul 2>&1
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Release -DLIBICSNEO_BUILD_UNIT_TESTS=ON ^
-DLIBICSNEO_ENABLE_TCP=ON || exit /b 1
cmake --build build || exit /b 1

View File

@ -14,6 +14,12 @@
#include "icsneo/communication/message/readsettingsmessage.h"
#include "icsneo/communication/message/versionmessage.h"
#include "icsneo/communication/message/componentversionsmessage.h"
#include "icsneo/communication/message/filter/extendedresponsefilter.h"
#include "icsneo/communication/message/clientidmessage.h"
#include "icsneo/communication/icspb.h"
#include <commands/generic/v1/client_id.pb.h>
#include <commands/network/v1/mutex.pb.h>
using namespace icsneo;
@ -313,3 +319,32 @@ std::optional< std::vector<ComponentVersion> > Communication::getComponentVersio
return std::make_optional< std::vector<ComponentVersion> >(std::move(ver->versions));
}
std::optional<uint32_t> Communication::getClientIDSync() {
constexpr auto timeout = std::chrono::milliseconds(250);
commands::generic::v1::ClientId msg;
msg.Clear();
std::vector<uint8_t> payload = protoapi::getPayload(protoapi::Command::GET, msg);
std::shared_ptr<Message> response = waitForMessageSync(
[this, payload](){
return sendCommand(ExtendedCommand::ProtobufAPI, payload);
},
std::make_shared<MessageFilter>(Message::Type::ClientId),
timeout
);
if(!response) {
report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
return std::nullopt;
}
auto clientIdMessage = std::dynamic_pointer_cast<ClientIdMessage>(response);
if(!clientIdMessage) {
report(APIEvent::Type::UnexpectedResponse, APIEvent::Severity::Error);
return std::nullopt;
}
return clientIdMessage->clientId;
}

View File

@ -23,6 +23,8 @@
#include "icsneo/communication/message/gptpstatusmessage.h"
#include "icsneo/communication/message/apperrormessage.h"
#include "icsneo/communication/message/ethernetstatusmessage.h"
#include "icsneo/communication/message/networkmutexmessage.h"
#include "icsneo/communication/message/clientidmessage.h"
#include "icsneo/communication/command.h"
#include "icsneo/device/device.h"
#include "icsneo/communication/packet/canpacket.h"
@ -44,6 +46,8 @@
#include "icsneo/communication/packet/livedatapacket.h"
#include "icsneo/communication/packet/hardwareinfopacket.h"
#include "icsneo/communication/packet/spipacket.h"
#include "icsneo/communication/icspb.h"
#include <iostream>
@ -338,6 +342,25 @@ bool Decoder::decode(std::shared_ptr<Message>& result, const std::shared_ptr<Pac
result = GPTPStatus::DecodeToMessage(packet->data, report);
return true;
}
case ExtendedCommand::ProtobufAPI: {
// get the proto id
std::vector<uint8_t> responseBody(
packet->data.begin() + sizeof(ExtendedResponseMessage::ResponseHeader),
packet->data.end()
);
protoapi::Id protoId = protoapi::getProtoId(responseBody.data(), responseBody.size());
switch(protoId) {
case protoapi::Id::NetworkMutex:
result = NetworkMutexMessage::DecodeToMessage(responseBody);
return true;
case protoapi::Id::ClientId:
result = ClientIdMessage::DecodeToMessage(responseBody);
return true;
default:
report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::Error);
return false;
}
}
case ExtendedCommand::GetDiskDetails:
case ExtendedCommand::DiskFormatProgress: {
std::vector<uint8_t> responseBody(

View File

@ -0,0 +1,21 @@
#include "icsneo/communication/message/clientidmessage.h"
#include "icsneo/communication/icspb.h"
#include "icsneo/communication/command.h"
#include "icsneo/communication/message/extendedresponsemessage.h"
using namespace icsneo;
std::shared_ptr<ClientIdMessage> ClientIdMessage::DecodeToMessage(const std::vector<uint8_t>& bytestream) {
ClientIdMessage decoded;
commands::generic::v1::ClientId msg;
if(!protoapi::processResponse(bytestream.data(), bytestream.size(), msg)) {
return nullptr;
}
if(msg.has_client_id()) {
decoded.clientId.emplace(msg.client_id());
}
return std::make_shared<ClientIdMessage>(decoded);
}

View File

@ -0,0 +1,89 @@
#include "icsneo/communication/message/networkmutexmessage.h"
#include "icsneo/communication/icspb.h"
#include "icsneo/communication/command.h"
#include "icsneo/communication/message/extendedresponsemessage.h"
using namespace icsneo;
std::shared_ptr<NetworkMutexMessage> NetworkMutexMessage::DecodeToMessage(const std::vector<uint8_t>& bytestream) {
NetworkMutexMessage decoded;
commands::network::v1::NetworkMutex msg;
if(!protoapi::processResponse(bytestream.data(), bytestream.size(), msg)) {
return nullptr;
}
if(msg.has_client_id()) {
decoded.owner_id.emplace(msg.client_id());
}
if(msg.has_type()) {
decoded.type.emplace(static_cast<NetworkMutexType>(msg.type()));
}
if(msg.has_priority()) {
decoded.priority.emplace(msg.priority());
}
if(msg.has_ttl()){
decoded.ttlMs.emplace(msg.ttl());
}
if(msg.has_event()){
decoded.event.emplace(static_cast<NetworkMutexEvent>(msg.event()));
}
for(int i = 0 ; i < msg.network_ids_size(); ++i){
decoded.networks.emplace(static_cast<Network::NetID>(msg.network_ids(i)));
}
return std::make_shared<NetworkMutexMessage>(decoded);
}
std::vector<uint8_t> NetworkMutexMessage::EncodeArgumentsForLock(uint32_t client_id, NetworkMutexType type, uint32_t priority, uint32_t ttlMs, const std::set<Network::NetID>& networks, const device_eventhandler_t& /* report */) {
commands::network::v1::NetworkMutex msg;
for(auto&& network_id : networks) {
msg.add_network_ids(static_cast<commands::network::v1::NetworkId>(network_id));
}
msg.set_client_id(client_id);
msg.set_priority(priority);
msg.set_ttl(ttlMs);
msg.set_type(static_cast<commands::network::v1::MutexType>(type));
return protoapi::getPayload(protoapi::Command::PUT, msg);
}
std::vector<uint8_t> NetworkMutexMessage::EncodeArgumentsForLockAll(uint32_t client_id, NetworkMutexType type, uint32_t priority, uint32_t ttlMs, const device_eventhandler_t& /* report */) {
commands::network::v1::NetworkMutex msg;
msg.set_client_id(client_id);
msg.set_priority(priority);
msg.set_ttl(ttlMs);
msg.set_type(static_cast<commands::network::v1::MutexType>(type));
msg.set_global(true);
return protoapi::getPayload(protoapi::Command::PUT, msg);
}
std::vector<uint8_t> NetworkMutexMessage::EncodeArgumentsForUnlock(uint32_t client_id, const std::set<Network::NetID>& networks, const device_eventhandler_t& /* report */) {
commands::network::v1::NetworkMutex msg;
msg.Clear();
for(auto&& network_id : networks)
{
msg.add_network_ids(static_cast<commands::network::v1::NetworkId>(network_id));
}
msg.set_client_id(client_id);
msg.set_release(true);
return protoapi::getPayload(protoapi::Command::PUT, msg);
}
std::vector<uint8_t> NetworkMutexMessage::EncodeArgumentsForUnlockAll(uint32_t client_id, const device_eventhandler_t& /* report */) {
commands::network::v1::NetworkMutex msg;
msg.Clear();
msg.set_client_id(client_id);
msg.set_release(true);
msg.set_global(true);
return protoapi::getPayload(protoapi::Command::PUT, msg);
}
std::vector<uint8_t> NetworkMutexMessage::EncodeArgumentsForStatus(Network::NetID network, const device_eventhandler_t& /* report */) {
commands::network::v1::NetworkMutex msg;
msg.add_network_ids(static_cast<commands::network::v1::NetworkId>(network));
return protoapi::getPayload(protoapi::Command::GET, msg);
}

View File

@ -0,0 +1,160 @@
#include "icsneo/communication/message/transmitmessage.h"
// packet defs
#include "icsneo/communication/packet/ethernetpacket.h"
#include "icsneo/communication/packet/canpacket.h"
#include "icsneo/communication/packet/linpacket.h"
using namespace icsneo;
// copied.. TODO
static std::optional<uint8_t> CAN_LengthToDLC(size_t dataLength, bool fd) {
if(dataLength <= 8)
return uint8_t(dataLength);
if(fd) {
if(dataLength <= 12)
return uint8_t(0x9);
if(dataLength <= 16)
return uint8_t(0xA);
if(dataLength <= 20)
return uint8_t(0xB);
if(dataLength <= 24)
return uint8_t(0xC);
if(dataLength <= 32)
return uint8_t(0xD);
if(dataLength <= 48)
return uint8_t(0xE);
if(dataLength <= 64)
return uint8_t(0xF);
}
return std::nullopt;
}
static std::vector<uint8_t> EncodeFromMessageEthernet(std::shared_ptr<Frame> frame, const device_eventhandler_t& report) {
auto ethmsg = std::dynamic_pointer_cast<EthernetMessage>(frame);
if(!ethmsg) {
report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error);
return {};
}
std::vector<uint8_t> encoded;
size_t messageLen = ethmsg->data.size();
encoded.resize(sizeof(TransmitMessage) + messageLen);
TransmitMessage* const msg = (TransmitMessage*)encoded.data();
HardwareEthernetPacket* const ethpacket = (HardwareEthernetPacket*)(msg->commonHeader);
uint8_t* const payload = encoded.data() + sizeof(TransmitMessage);
ethpacket->header.ENABLE_PADDING = ethmsg->noPadding ? 0 : 1;
ethpacket->header.FCS_OVERRIDE = ethmsg->fcs ? 1 : 0;
ethpacket->eid.txlen = static_cast<uint16_t>(messageLen);
ethpacket->Length = static_cast<uint16_t>(messageLen);
ethpacket->stats = ethmsg->description;
ethpacket->NetworkID = static_cast<uint16_t>(ethmsg->network.getNetID());
std::copy(ethmsg->data.begin(), ethmsg->data.end(), payload);
return encoded;
}
static std::vector<uint8_t> EncodeFromMessageCAN(std::shared_ptr<Frame> frame, const device_eventhandler_t& report) {
auto canmsg = std::dynamic_pointer_cast<CANMessage>(frame);
if(!canmsg) {
report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error);
return {};
}
if(canmsg->isCANFD && canmsg->isRemote) {
report(APIEvent::Type::RTRNotSupported, APIEvent::Severity::Error);
return {}; // RTR frames can not be used with CAN FD
}
std::vector<uint8_t> encoded;
size_t messageLen = canmsg->data.size();
size_t extraLen = 0;
if(messageLen > 8) {
extraLen = messageLen - 8;
}
encoded.resize(sizeof(TransmitMessage) + extraLen);
TransmitMessage* const msg = (TransmitMessage*)encoded.data();
HardwareCANPacket* const canpacket = (HardwareCANPacket*)(msg->commonHeader);
uint8_t* const extra_payload = encoded.data() + sizeof(TransmitMessage);
const size_t dataSize = canmsg->data.size();
std::optional<uint8_t> dlc = CAN_LengthToDLC(dataSize, canmsg->isCANFD);
if(!dlc.has_value()) {
report(APIEvent::Type::MessageMaxLengthExceeded, APIEvent::Severity::Error);
return {}; // Too much data for the protocol
}
// arb id
if(canmsg->isExtended) {
canpacket->header.IDE = 1;
canpacket->header.SID = (canmsg->arbid >> 18) & 0x7FF;
canpacket->eid.EID = (canmsg->arbid >> 6) & 0xfff;
canpacket->dlc.EID2 = canmsg->arbid & 0x3f;
} else {
canpacket->header.IDE = 0;
canpacket->header.SID = canmsg->arbid & 0x7FF;
}
// DLC
canpacket->dlc.DLC = dlc.value();
// FDF/BRS or remote frames
if(canmsg->isCANFD) {
canpacket->header.EDL = 1;
canpacket->header.BRS = canmsg->baudrateSwitch ? 1 : 0;
canpacket->header.ESI = canmsg->errorStateIndicator ? 1 : 0;
canpacket->dlc.RTR = 0;
} else {
canpacket->header.EDL = 0;
canpacket->header.BRS = 0;
canpacket->header.ESI = 0;
canpacket->dlc.RTR = canmsg->isRemote ? 1 : 0;
}
// network
canpacket->NetworkID = static_cast<uint16_t>(canmsg->network.getNetID());
canpacket->Length = static_cast<uint16_t>(extraLen);
// description id
canpacket->stats = canmsg->description;
// first 8 bytes
std::copy(canmsg->data.begin(), canmsg->data.begin() + (messageLen > 8 ? 8 : messageLen), canpacket->data);
// extra bytes
if(extraLen > 0) {
// copy extra data after the can packet
std::copy(canmsg->data.begin() + 8, canmsg->data.end(), extra_payload);
}
return encoded;
}
static std::vector<uint8_t> EncodeFromMessageLIN(std::shared_ptr<Frame> /* frame */, const device_eventhandler_t& report) {
// TODO
report(APIEvent::Type::UnsupportedTXNetwork, APIEvent::Severity::Error);
return {};
}
std::vector<uint8_t> TransmitMessage::EncodeFromMessage(std::shared_ptr<Frame> frame, uint32_t client_id, const device_eventhandler_t& report) {
std::vector<uint8_t> result;
switch(frame->network.getType()) {
case Network::Type::Ethernet:
result = EncodeFromMessageEthernet(frame, report);
break;
case Network::Type::CAN:
result = EncodeFromMessageCAN(frame, report);
break;
case Network::Type::LIN:
result = EncodeFromMessageLIN(frame, report);
break;
default:
report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
return result;
}
// common fields
TransmitMessage* const msg = (TransmitMessage*)result.data();
msg->options.clientId = client_id;
msg->options.networkId = static_cast<uint32_t>(frame->network.getNetID());
msg->options.reserved[0] = 0;
msg->options.reserved[1] = 0;
msg->options.reserved[2] = 0;
return result;
}

View File

@ -114,7 +114,7 @@ std::shared_ptr<Message> HardwareCANPacket::DecodeToMessage(const std::vector<ui
msg->data.insert(msg->data.end(), data->data, data->data + (length > 8 ? 8 : length));
if(length > 8) { // If there are more than 8 bytes, they come at the end of the message
// Messages with extra data are formatted as message, then uint16_t netid, then uint16_t length, then extra data
const auto extraDataStart = bytestream.begin() + sizeof(HardwareCANPacket) + 2 + 2;
const auto extraDataStart = bytestream.begin() + sizeof(HardwareCANPacket);
msg->data.insert(msg->data.end(), extraDataStart, extraDataStart + (length - 8));
}
}

View File

@ -6,6 +6,8 @@
#include "icsneo/device/extensions/deviceextension.h"
#include "icsneo/disk/fat.h"
#include "icsneo/communication/message/filter/extendedresponsefilter.h"
#include "icsneo/communication/message/networkmutexmessage.h"
#include "icsneo/communication/message/transmitmessage.h"
#ifdef _MSC_VER
#pragma warning(disable : 4996) // STL time functions
@ -457,6 +459,29 @@ APIEvent::Type Device::attemptToBeginCommunication() {
return getCommunicationNotEstablishedError();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
assignedClientId = com->getClientIDSync();
networkMutexCallbackHandle = addMessageCallback(
std::make_shared<MessageCallback>(
[this](std::shared_ptr<Message> message) {
auto netMutexMsg = std::static_pointer_cast<NetworkMutexMessage>(message);
if(netMutexMsg->networks.size() && netMutexMsg->event.has_value()) {
switch(*netMutexMsg->event) {
case NetworkMutexEvent::Acquired:
lockedNetworks.emplace(*netMutexMsg->networks.begin());
break;
case NetworkMutexEvent::Released: {
auto it = lockedNetworks.find(*netMutexMsg->networks.begin());
if (it != lockedNetworks.end())
lockedNetworks.erase(it);
break;
}
}
}
},
std::make_shared<MessageFilter>(Message::Type::NetworkMutex)
)
);
auto serial = com->getSerialNumberSync();
int i = 0;
while(!serial) {
@ -895,9 +920,12 @@ bool Device::transmit(std::shared_ptr<Frame> frame) {
return transmitStatusFromExtension;
std::vector<uint8_t> packet;
if(assignedClientId.has_value()) {
packet = TransmitMessage::EncodeFromMessage(frame, *assignedClientId, report);
return packet.size() && com->sendCommand(ExtendedCommand::TransmitMessage, packet);
}
if(!com->encoder->encode(*com->packetizer, packet, frame))
return false;
return com->sendPacket(packet);
}
@ -3857,3 +3885,209 @@ std::shared_ptr<DiskDetails> Device::getDiskDetails(std::chrono::milliseconds ti
return DiskDetails::Decode(extResponse->data, getDiskCount(), report);
}
[[nodiscard]] std::optional<int> Device::lockNetworks(const std::set<Network::NetID>& networks, uint32_t priority, uint32_t ttlMs, NetworkMutexType type, std::function<void(std::shared_ptr<Message>)>&& on_event)
{
if(!supportsNetworkMutex()) {
report(APIEvent::Type::NotSupported, APIEvent::Severity::Error);
return std::nullopt;
}
if(!isOnline()) {
report(APIEvent::Type::DeviceCurrentlyOffline, APIEvent::Severity::Error);
return std::nullopt;
}
if(!assignedClientId.has_value()) {
report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error);
return std::nullopt;
}
constexpr auto timeout = std::chrono::milliseconds(250);
std::vector<uint8_t> payload = NetworkMutexMessage::EncodeArgumentsForLock(*assignedClientId, type, priority, ttlMs, networks, report);
std::optional<int> handle = std::nullopt;
if(on_event) {
handle.emplace(addMessageCallback(
std::make_shared<MessageCallback>(
on_event,
std::make_shared<MessageFilter>(Message::Type::NetworkMutex)
)
));
}
std::shared_ptr<Message> response = com->waitForMessageSync(
[this, payload](){
return com->sendCommand(ExtendedCommand::ProtobufAPI, payload);
},
std::make_shared<ExtendedResponseFilter>(ExtendedCommand::ProtobufAPI),
timeout
);
if(!response) {
report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
return std::nullopt;
}
auto extResponse = std::dynamic_pointer_cast<ExtendedResponseMessage>(response);
if(!extResponse) {
report(APIEvent::Type::UnexpectedResponse, APIEvent::Severity::Error);
return std::nullopt;
}
if(extResponse->response != ExtendedResponse::OK && extResponse->response != ExtendedResponse::OperationPending) {
return std::nullopt;
}
return handle;
}
bool Device::unlockNetworks(const std::set<Network::NetID>& networks)
{
if(!supportsNetworkMutex()) {
report(APIEvent::Type::NotSupported, APIEvent::Severity::Error);
return false;
}
if(!assignedClientId.has_value()) {
report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error);
return false;
}
constexpr auto timeout = std::chrono::milliseconds(250);
std::vector<uint8_t> payload = NetworkMutexMessage::EncodeArgumentsForUnlock(*assignedClientId, networks, report);
std::shared_ptr<Message> response = com->waitForMessageSync(
[this, payload](){
return com->sendCommand(ExtendedCommand::ProtobufAPI, payload);
},
std::make_shared<ExtendedResponseFilter>(ExtendedCommand::ProtobufAPI),
timeout
);
if(!response) {
report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
return false;
}
auto extResponse = std::dynamic_pointer_cast<ExtendedResponseMessage>(response);
if(!extResponse) {
report(APIEvent::Type::UnexpectedResponse, APIEvent::Severity::Error);
return false;
}
if(extResponse->response != ExtendedResponse::OK) {
return false;
}
return true;
}
std::shared_ptr<NetworkMutexMessage> Device::getNetworkMutexStatus(Network::NetID network)
{
if(!supportsNetworkMutex()) {
report(APIEvent::Type::NotSupported, APIEvent::Severity::Error);
return nullptr;
}
constexpr auto timeout = std::chrono::milliseconds(1000);
std::vector<uint8_t> payload = NetworkMutexMessage::EncodeArgumentsForStatus(network, report);
std::shared_ptr<Message> response = com->waitForMessageSync(
[this, payload](){
return com->sendCommand(ExtendedCommand::ProtobufAPI, payload);
},
std::make_shared<MessageFilter>(Message::Type::NetworkMutex),
timeout
);
return std::dynamic_pointer_cast<NetworkMutexMessage>(response);
}
[[nodiscard]] std::optional<int> Device::lockAllNetworks(uint32_t priority, uint32_t ttlMs, NetworkMutexType type, std::function<void(std::shared_ptr<Message>)>&& on_event)
{
if(!supportsNetworkMutex()) {
report(APIEvent::Type::NotSupported, APIEvent::Severity::Error);
return std::nullopt;
}
if(!isOnline()) {
report(APIEvent::Type::DeviceCurrentlyOffline, APIEvent::Severity::Error);
return std::nullopt;
}
if(!assignedClientId.has_value()) {
report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error);
return std::nullopt;
}
constexpr auto timeout = std::chrono::milliseconds(1000);
std::vector<uint8_t> payload = NetworkMutexMessage::EncodeArgumentsForLockAll(*assignedClientId, type, priority, ttlMs, report);
int handle = addMessageCallback(
std::make_shared<MessageCallback>(
on_event,
std::make_shared<MessageFilter>(Message::Type::NetworkMutex)
)
);
std::shared_ptr<Message> response = com->waitForMessageSync(
[this, payload](){
return com->sendCommand(ExtendedCommand::ProtobufAPI, payload);
},
std::make_shared<ExtendedResponseFilter>(ExtendedCommand::ProtobufAPI),
timeout
);
if(!response) {
report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
return std::nullopt;
}
auto extResponse = std::dynamic_pointer_cast<ExtendedResponseMessage>(response);
if(!extResponse) {
report(APIEvent::Type::UnexpectedResponse, APIEvent::Severity::Error);
return std::nullopt;
}
if(extResponse->response != ExtendedResponse::OK && extResponse->response != ExtendedResponse::OperationPending) {
return std::nullopt;
}
return std::make_optional(handle);
}
bool Device::unlockAllNetworks()
{
if(!supportsNetworkMutex()) {
report(APIEvent::Type::NotSupported, APIEvent::Severity::Error);
return false;
}
if(!assignedClientId.has_value()) {
report(APIEvent::Type::RequiredParameterNull, APIEvent::Severity::Error);
return false;
}
constexpr auto timeout = std::chrono::milliseconds(1000);
std::vector<uint8_t> payload = NetworkMutexMessage::EncodeArgumentsForUnlockAll(*assignedClientId, report);
std::shared_ptr<Message> response = com->waitForMessageSync(
[this, payload](){
return com->sendCommand(ExtendedCommand::ProtobufAPI, payload);
},
std::make_shared<ExtendedResponseFilter>(ExtendedCommand::ProtobufAPI),
timeout
);
if(!response) {
report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
return false;
}
auto extResponse = std::dynamic_pointer_cast<ExtendedResponseMessage>(response);
if(!extResponse) {
report(APIEvent::Type::UnexpectedResponse, APIEvent::Severity::Error);
return false;
}
if(extResponse->response != ExtendedResponse::OK) {
return false;
}
return true;
}

View File

@ -12,6 +12,9 @@ option(LIBICSNEO_BUILD_CPP_VSA_EXAMPLE "Build the VSA example." ON)
option(LIBICSNEO_BUILD_CPP_APP_ERROR_EXAMPLE "Build the app error example." ON)
option(LIBICSNEO_BUILD_CPP_FLEXRAY_EXAMPLE "Build the FlexRay example." ON)
option(LIBICSNEO_BUILD_CPP_SPI_EXAMPLE "Build the SPI example." ON)
option(LIBICSNEO_BUILD_CPP_MUTEX_EXAMPLE "Build the NetworkMutex example." ON)
add_compile_options(${LIBICSNEO_COMPILER_WARNINGS})
if(LIBICSNEO_BUILD_C_INTERACTIVE_EXAMPLE)
add_subdirectory(c/interactive)
@ -67,4 +70,8 @@ endif()
if(LIBICSNEO_BUILD_CPP_SPI_EXAMPLE)
add_subdirectory(cpp/spi)
endif()
endif()
if(LIBICSNEO_BUILD_CPP_MUTEX_EXAMPLE)
add_subdirectory(cpp/mutex)
endif()

View File

@ -0,0 +1,2 @@
add_executable(libicsneocpp-mutex-example src/MutexExample.cpp)
target_link_libraries(libicsneocpp-mutex-example icsneocpp)

View File

@ -0,0 +1,215 @@
#include <iostream>
#include <iomanip>
#include <thread>
#include <chrono>
#include <numeric>
#include <unordered_set>
#include "icsneo/icsneocpp.h"
#ifdef _MSC_VER
#pragma warning(disable : 4996) // STL time functions
#endif
static std::vector<std::shared_ptr<icsneo::Device>> findDevices(icsneo::DeviceType type)
{
std::vector<std::shared_ptr<icsneo::Device>> ret;
for(auto&& dev : icsneo::FindAllDevices()) {
if(dev->getType() == type) {
ret.push_back(dev);
}
}
return ret;
}
static std::vector<icsneo::Network> supported_nets;
static std::unordered_map<icsneo::Network::NetID, icsneo::NetworkMutexEvent> network_mutex_states;
constexpr uint32_t MUTEX_PRIORITY = 12345678;
constexpr uint32_t MUTEX_TTL_MS = 2500;
static void on_mutex_event(std::shared_ptr<icsneo::Message> msg) {
if(msg->type == icsneo::Message::Type::NetworkMutex) {
auto nmm = std::static_pointer_cast<icsneo::NetworkMutexMessage>(msg);
if(nmm->event.has_value() && nmm->networks.size())
{
auto network = *nmm->networks.begin();
if(std::find_if(supported_nets.begin(), supported_nets.end(), [network](const icsneo::Network& net){ return net.getNetID() == network; }) != supported_nets.end())
{
std::cout << icsneo::Network::GetNetIDString(static_cast<icsneo::Network::NetID>(network)) << ": Mutex " << icsneo::NetworkMutexMessage::GetNetworkMutexEventString(nmm->event.value()) << std::endl;
network_mutex_states[network] = *nmm->event;
}
}
}
}
static void do_mutexed_operation(std::shared_ptr<icsneo::Device> device, icsneo::Network::NetID network, std::function<void()>&& operation)
{
// Demonstrate taking a mutex on a single / group of networks
auto lt = device->lockNetworks({network}, MUTEX_PRIORITY, MUTEX_TTL_MS, icsneo::NetworkMutexType::TxExclusive, on_mutex_event);
if(lt.has_value()) {
while(network_mutex_states.find(network) == network_mutex_states.end() ||
network_mutex_states[network] != icsneo::NetworkMutexEvent::Acquired)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
operation();
device->unlockNetworks({network});
while(network_mutex_states.find(network) != network_mutex_states.end() &&
network_mutex_states[network] == icsneo::NetworkMutexEvent::Acquired)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
device->removeMessageCallback(*lt);
} else {
std::cout << "Failed to acquire mutex for " << icsneo::Network::GetNetIDString(network) << std::endl;
}
}
int main() {
// Print version
std::cout << "libicsneo version " << icsneo::GetVersion() << std::endl;
std::cout << "\nFinding devices... " << std::flush;
auto devices = findDevices(icsneo::DeviceType::FIRE3);
// You now hold the shared_ptrs for these devices, you are considered to "own" these devices from a memory perspective
std::cout << "OK, " << devices.size() << " device" << (devices.size() == 1 ? "" : "s") << " found" << std::endl;
// List off the devices
for(auto& device : devices)
std::cout << '\t' << device->describe() << " @ Handle " << device->getNeoDevice().handle << std::endl;
std::cout << std::endl;
for(auto& device : devices) {
std::cout << "Connecting to " << device->describe() << "... ";
bool ret = device->open();
if(!ret) { // Failed to open
std::cout << "FAIL" << std::endl;
std::cout << icsneo::GetLastError() << std::endl << std::endl;
continue;
}
std::cout << "OK" << std::endl;
supported_nets = device->getSupportedTXNetworks();
device->goOnline();
// Demonstrate lock and unlock all networks
std::cout << "Requesting global exclusive network mutex" << std::endl;
auto lt = device->lockAllNetworks(MUTEX_PRIORITY, MUTEX_TTL_MS, icsneo::NetworkMutexType::TxExclusive, on_mutex_event);
if(lt.has_value()) {
// wait until we have the mutex on all networks
auto all_nets = device->getSupportedTXNetworks();
for(auto net = all_nets.begin(); net != all_nets.end();) {
auto state = network_mutex_states.find(net->getNetID());
if(state != network_mutex_states.end() && state->second == icsneo::NetworkMutexEvent::Acquired) {
net = all_nets.erase(net);
} else {
++net;
}
}
std::cout << "Acquired mutex for all supported networks on " << device->describe() << std::endl;
// request release
if(device->unlockAllNetworks()) {
std::cout << "Requested release of all networks" << std::endl;
} else {
std::cout << "Failed to request release of all networks" << std::endl;
}
// wait until we have released the mutex on all networks
all_nets = device->getSupportedTXNetworks();
for(auto net = all_nets.begin(); net != all_nets.end();) {
auto state = network_mutex_states.find(net->getNetID());
if(state == network_mutex_states.end() || state->second == icsneo::NetworkMutexEvent::Released) {
net = all_nets.erase(net);
} else {
++net;
}
}
std::cout << "released mutex on all supported networks" << std::endl;
device->removeMessageCallback(*lt);
} else {
std::cout << "Failed to acquire global mutex" << std::endl;
}
std::cout << "Doing mutexed operation on DWCAN 01" << std::endl;
do_mutexed_operation(device, icsneo::Network::NetID::DWCAN_01, [&](){
auto tx_message = std::make_shared<icsneo::CANMessage>();
tx_message->arbid = 0x12;
tx_message->isCANFD = true;
tx_message->baudrateSwitch = true;
tx_message->data.resize(64);
tx_message->network = icsneo::Network::NetID::DWCAN_01;
constexpr auto txInterval = std::chrono::microseconds(500);
auto nextRefresh = std::chrono::steady_clock::now() + std::chrono::milliseconds(MUTEX_TTL_MS / 2);
auto nextTx = std::chrono::steady_clock::now() + txInterval;
size_t numTx = 0;
while(numTx < 5000) {
if(std::chrono::steady_clock::now() < nextTx) {
continue;
}
nextTx = std::chrono::steady_clock::now() + txInterval;
std::iota(tx_message->data.begin(), tx_message->data.end(), (uint8_t)(numTx & 0xFF));
if(device->transmit(tx_message)) {
++numTx;
}
// check the mutex periodically - every 200 messages for demonstrative purposes
if(std::chrono::steady_clock::now() > nextRefresh) {
auto status = device->getNetworkMutexStatus(icsneo::Network::NetID::DWCAN_01);
if(!status) {
std::cout << "failed to poll status of network mutex for DWCAN 01" << std::endl;
} else {
std::cout << "DWCAN 01 mutex TTL: " << *status->ttlMs << " ms" << std::endl;
}
std::cout << "refreshing mutex on DWCAN 01, current tx count: " << numTx << std::endl;
(void)device->lockNetworks({icsneo::Network::NetID::DWCAN_01}, MUTEX_PRIORITY, MUTEX_TTL_MS, icsneo::NetworkMutexType::TxExclusive, nullptr);
nextRefresh = std::chrono::steady_clock::now() + std::chrono::milliseconds(MUTEX_TTL_MS / 2);
}
}
});
std::cout << "Completed mutexed operation on DWCAN 01" << std::endl;
std::cout << "Doing mutexed operation on Ethernet 03" << std::endl;
do_mutexed_operation(device, icsneo::Network::NetID::ETHERNET_03, [&](){
auto tx_message = std::make_shared<icsneo::EthernetMessage>();
tx_message->data.resize(512);
// set mac addresses and ethertype
std::iota(tx_message->data.begin(), tx_message->data.begin() + 14, static_cast<uint8_t>(0x01));
tx_message->network = icsneo::Network::NetID::ETHERNET_03;
constexpr auto txInterval = std::chrono::microseconds(250);
auto nextRefresh = std::chrono::steady_clock::now() + std::chrono::milliseconds(MUTEX_TTL_MS / 2);
auto nextTx = std::chrono::steady_clock::now() + txInterval;
size_t numTx = 0;
while(numTx < 50000) {
if(std::chrono::steady_clock::now() < nextTx) {
continue;
}
nextTx = std::chrono::steady_clock::now() + txInterval;
// keep the mac addresses and ethertype constant
std::iota(tx_message->data.begin() + 14, tx_message->data.end(), (uint8_t)(numTx & 0xFF));
if(device->transmit(tx_message)) {
++numTx;
}
if(std::chrono::steady_clock::now() > nextRefresh) {
auto status = device->getNetworkMutexStatus(icsneo::Network::NetID::ETHERNET_03);
if(!status) {
std::cout << "failed to poll status of network mutex for Ethernet 03" << std::endl;
} else {
std::cout << "Ethernet 03 mutex TTL: " << *status->ttlMs << " ms" << std::endl;
}
std::cout << "refreshing mutex on Ethernet 03, current tx count: " << numTx << std::endl;
(void)device->lockNetworks({icsneo::Network::NetID::ETHERNET_03}, MUTEX_PRIORITY, MUTEX_TTL_MS, icsneo::NetworkMutexType::TxExclusive, nullptr);
nextRefresh = std::chrono::steady_clock::now() + std::chrono::milliseconds(MUTEX_TTL_MS / 2);
}
}
});
std::cout << "Completed mutexed operation on Ethernet 03" << std::endl;
device->close();
}
return 0;
}

View File

@ -62,6 +62,8 @@ enum class ExtendedCommand : uint16_t {
RequestTC10Wake = 0x003D,
RequestTC10Sleep = 0x003E,
GetTC10Status = 0x003F,
ProtobufAPI = 0x0041,
TransmitMessage = 0x0042,
};
enum class ExtendedResponse : int32_t {

View File

@ -64,6 +64,8 @@ public:
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));
std::optional<uint32_t> getClientIDSync();
int addMessageCallback(const std::shared_ptr<MessageCallback>& cb);
bool removeMessageCallback(int id);

View File

@ -0,0 +1,127 @@
#ifndef COMMUNICATION_PROTO_API_IMPL_H
#define COMMUNICATION_PROTO_API_IMPL_H
#include <cstdint>
#ifdef _WIN32
#pragma warning(push, 0)
#endif
#include <common/v1/proto_header.pb.h>
#include <commands/generic/v1/client_id.pb.h>
#include <commands/network/v1/mutex.pb.h>
#ifdef _WIN32
#pragma warning(pop)
#endif
namespace icsneo {
namespace protoapi {
enum class Command : uint8_t {
Unspecified = 0,
GET = 1,
PUT = 2,
PATCH = 3,
};
enum class Id : uint32_t {
Unspecified = 0,
MfgConfig = 1,
CpuId = 2,
NetworkMutex = 3,
ClientId = 4,
};
template <typename T>
struct IDLookup {
constexpr static common::v1::ProtoId value = common::v1::ProtoId::PROTO_ID_UNSPECIFIED;
};
template <>
struct IDLookup<commands::network::v1::NetworkMutex> {
constexpr static common::v1::ProtoId value = common::v1::ProtoId::PROTO_ID_NETWORK_MUTEX;
};
template <>
struct IDLookup<commands::generic::v1::ClientId> {
constexpr static common::v1::ProtoId value = common::v1::ProtoId::PROTO_ID_GET_CLIENT_ID;
};
inline uint8_t* writeVarint32(uint32_t value, uint8_t* target) {
return google::protobuf::io::CodedOutputStream::WriteVarint32ToArray(value, target);
}
inline const uint8_t* readVarint32(uint32_t& value, const uint8_t* source) {
value = 0;
do {
value <<= 7;
value += *source & 0x7F;
} while(*source++ & 0x80);
return source;
}
template <typename T>
std::vector<uint8_t> getPayload(Command command, const T& msg) {
std::vector<uint8_t> payload;
constexpr uint32_t proto_version = 1;
common::v1::ProtoHeader hdr;
hdr.set_version(proto_version);
hdr.set_api_command(static_cast<common::v1::ApiCommand>(command));
hdr.set_auth_method(common::v1::AuthMethod::PROTO_API_AUTH_NONE); // TODO
hdr.set_proto_id(IDLookup<T>::value);
static constexpr size_t varint_size_max = 5;
size_t header_size = hdr.ByteSizeLong();
size_t msg_size = msg.ByteSizeLong();
size_t payload_size_max = header_size + msg_size + (varint_size_max * 2); // TODO: auth
payload.resize(payload_size_max);
uint8_t* target = payload.data();
target = writeVarint32(static_cast<uint32_t>(header_size), target);
hdr.SerializeToArray(target, static_cast<int>(header_size));
target += header_size;
if(msg_size) {
target = writeVarint32(static_cast<uint32_t>(msg_size), target);
msg.SerializeToArray(target, static_cast<int>(msg_size));
target += msg_size;
}
payload.resize(target - payload.data());
return payload;
}
template <typename T>
bool processResponse(const uint8_t* payload, size_t /* payload_length */, T& msg) {
// TODO: we should probably check payload_length throughout to not go past the end of the supplied buffer
// header
common::v1::ProtoHeader hdr;
uint32_t length;
const uint8_t* serialized = readVarint32(length, payload);
if(!hdr.ParseFromArray(serialized, length)) {
return false;
}
// message payload
serialized = readVarint32(length, serialized + length);
if(!msg.ParseFromArray(serialized, length)) {
return false;
}
return true;
}
inline Id getProtoId(const uint8_t* payload, size_t /* payload_length */) {
// TODO: we should probably check this throughout to not go past the end of the supplied buffer
// header
common::v1::ProtoHeader hdr;
uint32_t length;
const uint8_t* serialized = readVarint32(length, payload);
if(!hdr.ParseFromArray(serialized, length)) {
return Id::Unspecified;
}
return static_cast<Id>(hdr.proto_id());
}
} // namespace protoapi
} // namespace icsneo
#endif

View File

@ -0,0 +1,21 @@
#ifndef CLIENT_ID_MESSAGE_H_
#define CLIENT_ID_MESSAGE_H_
#include <cstdint>
#include "icsneo/communication/network.h"
#include "icsneo/communication/message/message.h"
#include "icsneo/api/eventmanager.h"
namespace icsneo {
class ClientIdMessage : public Message {
public:
ClientIdMessage() : Message(Message::Type::ClientId) {}
static std::shared_ptr<ClientIdMessage> DecodeToMessage(const std::vector<uint8_t>& bytestream);
std::optional<uint32_t> clientId;
};
} // namespace icsneo
#endif

View File

@ -45,6 +45,8 @@ public:
GPTPStatus = 0x8013,
EthernetStatus = 0x8014,
LogData = 0x8015,
NetworkMutex = 0x8016,
ClientId = 0x8017,
};
Message(Type t) : type(t) {}

View File

@ -0,0 +1,82 @@
#ifndef __NETWORKMUTEXMESSAGE_H_
#define __NETWORKMUTEXMESSAGE_H_
#include <cstdint>
#include <set>
#include "icsneo/communication/network.h"
#include "icsneo/communication/message/message.h"
#include "icsneo/communication/message/extendedresponsemessage.h"
#include "icsneo/api/eventmanager.h"
namespace icsneo {
enum class NetworkMutexType : uint8_t {
Shared = 0,
TxExclusive = 1,
ExternalExclusive = 2,
FullyExclusive = 3,
};
enum class NetworkMutexEvent : uint8_t {
Released = 0,
Expired = 1,
Preempted = 2,
Queued = 3,
Acquired = 4,
};
class NetworkMutexMessage : public Message
{
public:
NetworkMutexMessage() : Message(Message::Type::NetworkMutex) {}
static std::shared_ptr<NetworkMutexMessage> DecodeToMessage(const std::vector<uint8_t>& bytestream);
static std::vector<uint8_t> EncodeArgumentsForLock(uint32_t client_id, NetworkMutexType type, uint32_t priority, uint32_t ttlMs, const std::set<Network::NetID>& networks, const device_eventhandler_t& report);
static std::vector<uint8_t> EncodeArgumentsForLockAll(uint32_t client_id, NetworkMutexType type, uint32_t priority, uint32_t ttlMs, const device_eventhandler_t& report);
static std::vector<uint8_t> EncodeArgumentsForUnlock(uint32_t client_id, const std::set<Network::NetID>& networks, const device_eventhandler_t& report);
static std::vector<uint8_t> EncodeArgumentsForUnlockAll(uint32_t client_id, const device_eventhandler_t& report);
static std::vector<uint8_t> EncodeArgumentsForStatus(Network::NetID network, const device_eventhandler_t& report);
static const char* GetNetworkMutexTypeString(NetworkMutexType type) {
switch(type) {
case icsneo::NetworkMutexType::Shared:
return "Shared";
case icsneo::NetworkMutexType::TxExclusive:
return "TxExclusive";
case icsneo::NetworkMutexType::ExternalExclusive:
return "ExternalExclusive";
case icsneo::NetworkMutexType::FullyExclusive:
return "FullyExclusive";
default:
return "Unknown";
}
}
static const char* GetNetworkMutexEventString(NetworkMutexEvent event) {
switch(event) {
case icsneo::NetworkMutexEvent::Acquired:
return "Acquired";
case icsneo::NetworkMutexEvent::Released:
return "Released";
case icsneo::NetworkMutexEvent::Preempted:
return "Preempted";
case icsneo::NetworkMutexEvent::Expired:
return "Expired";
case icsneo::NetworkMutexEvent::Queued:
return "Queued";
default:
return "Unknown";
}
}
std::optional<uint32_t> owner_id;
std::optional<NetworkMutexType> type;
std::optional<uint32_t> priority;
std::optional<uint32_t> ttlMs;
std::set<Network::NetID> networks;
std::optional<NetworkMutexEvent> event;
};
} // namespace icsneo
#endif

View File

@ -0,0 +1,30 @@
#ifndef TRANSMIT_MESSAGE_H_
#define TRANSMIT_MESSAGE_H_
#include <cstdint>
#include "icsneo/communication/message/message.h"
#include "icsneo/api/eventmanager.h"
namespace icsneo {
struct TransmitMessage {
static std::vector<uint8_t> EncodeFromMessage(std::shared_ptr<Frame> message, uint32_t client_id, const device_eventhandler_t& report);
constexpr static size_t messageOptionsOffset = 0;
constexpr static size_t messageOptionsSize = 20; // todo determine max
constexpr static size_t messageCommonHeaderOffset = messageOptionsOffset + messageOptionsSize;
constexpr static size_t messageCommonHeaderSize = 28; // CoreminiMsgExtendedHdr
#pragma pack(push,1)
struct
{
uint32_t clientId;
uint32_t networkId;
uint32_t reserved[3]; // set to 0
} options;
uint8_t commonHeader[messageCommonHeaderSize];
#pragma pack(pop)
};
} // namespace icsneo
#endif // TRANSMIT_MESSAGE_H_

View File

@ -50,6 +50,9 @@ struct HardwareCANPacket {
uint64_t : 3; // Reserved for future status bits
uint64_t IsExtended : 1;
} timestamp;
uint16_t NetworkID;
uint16_t Length;
};
struct HardwareCANErrorPacket {
uint8_t error_code;

View File

@ -57,7 +57,7 @@
#include "icsneo/disk/vsa/vsaparser.h"
#include "icsneo/communication/message/versionmessage.h"
#include "icsneo/communication/message/gptpstatusmessage.h"
#include "icsneo/communication/message/networkmutexmessage.h"
#define ICSNEO_FINDABLE_DEVICE_BASE(className, type) \
static constexpr DeviceType::Enum DEVICE_TYPE = type; \
@ -856,6 +856,14 @@ public:
virtual bool writeMACsecConfig(const MACsecMessage& message, uint16_t binaryIndex);
std::shared_ptr<DeviceExtension> getExtension(const std::string& name) const;
[[nodiscard]] std::optional<int> lockNetworks(const std::set<Network::NetID>& networks, uint32_t priority, uint32_t ttlMs, NetworkMutexType type, std::function<void(std::shared_ptr<Message>)>&& on_event);
[[nodiscard]] std::optional<int> lockAllNetworks(uint32_t priority, uint32_t ttlMs, NetworkMutexType type, std::function<void(std::shared_ptr<Message>)>&& on_event);
bool unlockNetworks(const std::set<Network::NetID>& networks);
bool unlockAllNetworks();
std::shared_ptr<NetworkMutexMessage> getNetworkMutexStatus(Network::NetID network);
virtual bool supportsNetworkMutex() const { return false; }
protected:
bool online = false;
@ -969,6 +977,8 @@ protected:
};
LEDState ledState;
void updateLEDState();
private:
neodevice_t data;
std::shared_ptr<ResetStatusMessage> latestResetStatus;
@ -1127,6 +1137,11 @@ private:
// Keeponline (keepalive for online)
std::unique_ptr<Periodic> keeponline;
std::optional<uint32_t> assignedClientId;
std::set<icsneo::Network::NetID> lockedNetworks;
std::optional<int> networkMutexCallbackHandle;
};
}

View File

@ -123,6 +123,7 @@ protected:
return 2;
}
bool supportsNetworkMutex() const override { return true; }
};
}

View File

@ -145,6 +145,8 @@ protected:
return ret;
}
bool supportsNetworkMutex() const override { return true; }
};
}

View File

@ -103,6 +103,8 @@ protected:
size_t getDiskCount() const override {
return 2;
}
bool supportsNetworkMutex() const override { return true; }
};
}

View File

@ -50,3 +50,4 @@ CMAKE_MSVC_RUNTIME_LIBRARY = "MultiThreaded"
[tool.cibuildwheel]
skip = "pp*"
environment-pass = ["ICS_IPA_CA_CRT", "LIBICSNEO_ICSPB_REPO"]

View File

@ -39,7 +39,7 @@ public:
return readAmount;
}
std::optional<uint64_t> writeLogicalDiskAligned(Communication&, device_eventhandler_t report, uint64_t pos,
std::optional<uint64_t> writeLogicalDiskAligned(Communication&, device_eventhandler_t /* report */, uint64_t pos,
const uint8_t* from, uint64_t amount, std::chrono::milliseconds, Disk::MemoryType) override {
writeCalls++;