From 9ae3e115fc6882ee34c5b5451276e09e6054859b Mon Sep 17 00:00:00 2001 From: David Rebbe Date: Thu, 30 Apr 2026 19:28:40 +0000 Subject: [PATCH] C2: Add Ethernet message support --- api/icsneoc2/icsneoc2.cpp | 1 + api/icsneoc2/icsneoc2messages.cpp | 192 ++++++ api/icsneoc2/icsneoc2settings.cpp | 210 ++++++ .../communication/message/ethernetmessage.cpp | 31 +- communication/message/neomessage.cpp | 2 +- communication/packet/ethernetpacket.cpp | 37 +- docs/icsneoc2/examples.rst | 24 + docs/icsneopy/examples.rst | 8 + examples/CMakeLists.txt | 15 + examples/c2/ethernet_receive/CMakeLists.txt | 6 + examples/c2/ethernet_receive/src/main.c | 223 ++++++ examples/c2/ethernet_transmit/CMakeLists.txt | 6 + examples/c2/ethernet_transmit/src/main.c | 204 ++++++ examples/c2/t1s_loopback/CMakeLists.txt | 6 + examples/c2/t1s_loopback/src/main.c | 643 ++++++++++++++++++ .../interactive/src/InteractiveExample.cpp | 26 +- examples/cpp/simple/src/SimpleExample.cpp | 22 +- .../cpp/t1s/src/T1SSymbolDecodingExample.cpp | 22 +- .../ethernet/ethernet_complete_example.py | 17 +- examples/python/t1s/t1s_settings.py | 54 +- examples/python/t1s/t1s_symbol_decoding.py | 77 ++- .../communication/message/ethernetmessage.h | 94 ++- .../communication/packet/ethernetpacket.h | 1 + include/icsneo/icsneoc2.h | 1 + include/icsneo/icsneoc2messages.h | 120 ++++ include/icsneo/icsneoc2settings.h | 156 +++++ test/unit/icsneoc2.cpp | 249 +++++++ 27 files changed, 2295 insertions(+), 152 deletions(-) create mode 100644 examples/c2/ethernet_receive/CMakeLists.txt create mode 100644 examples/c2/ethernet_receive/src/main.c create mode 100644 examples/c2/ethernet_transmit/CMakeLists.txt create mode 100644 examples/c2/ethernet_transmit/src/main.c create mode 100644 examples/c2/t1s_loopback/CMakeLists.txt create mode 100644 examples/c2/t1s_loopback/src/main.c diff --git a/api/icsneoc2/icsneoc2.cpp b/api/icsneoc2/icsneoc2.cpp index 56e7e58..90642eb 100644 --- a/api/icsneoc2/icsneoc2.cpp +++ b/api/icsneoc2/icsneoc2.cpp @@ -68,6 +68,7 @@ icsneoc2_error_t icsneoc2_error_code_get(icsneoc2_error_t error_code, char* valu "Script load prepare failed", // icsneoc2_error_script_load_prepare_failed "Close failed", // icsneoc2_error_close_failed "Reconnect failed", // icsneoc2_error_reconnect_failed + "Invalid data", // icsneoc2_error_invalid_data }; static_assert(std::size(error_strings) == icsneoc2_error_maxsize, "error_strings is out of sync with _icsneoc2_error_t enum - update both together"); diff --git a/api/icsneoc2/icsneoc2messages.cpp b/api/icsneoc2/icsneoc2messages.cpp index f577ff5..fac9ce4 100644 --- a/api/icsneoc2/icsneoc2messages.cpp +++ b/api/icsneoc2/icsneoc2messages.cpp @@ -415,3 +415,195 @@ icsneoc2_error_t icsneoc2_message_lin_calc_checksum(icsneoc2_message_t* message) LINMessage::calcChecksum(*lin_msg); return icsneoc2_error_success; } + +icsneoc2_error_t icsneoc2_message_eth_create(icsneoc2_message_t** message) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + try { + *message = new icsneoc2_message_t; + (*message)->message = std::make_shared(); + } catch(const std::bad_alloc&) { + return icsneoc2_error_out_of_memory; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_eth_props_set(icsneoc2_message_t* message, const icsneoc2_message_eth_flags_t* flags, const bool* has_fcs, const uint32_t* fcs) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + auto eth_msg = std::dynamic_pointer_cast(message->message); + if(!eth_msg) { + return icsneoc2_error_invalid_type; + } + if(flags) { + eth_msg->frameTooShort = (*flags & ICSNEOC2_MESSAGE_ETH_FLAGS_FRAME_TOO_SHORT); + eth_msg->noPadding = (*flags & ICSNEOC2_MESSAGE_ETH_FLAGS_NO_PADDING); + eth_msg->fcsVerified = (*flags & ICSNEOC2_MESSAGE_ETH_FLAGS_FCS_VERIFIED); + eth_msg->txAborted = (*flags & ICSNEOC2_MESSAGE_ETH_FLAGS_TX_ABORTED); + eth_msg->crcError = (*flags & ICSNEOC2_MESSAGE_ETH_FLAGS_CRC_ERROR); + } + if(has_fcs && !*has_fcs) { + eth_msg->fcs = std::nullopt; + } else if(has_fcs && *has_fcs) { + // I'm pretty sure we can leave this alone, setting fcs below will take care of the behavior, + // otherwise we get into a weird state where we have to set fcs to zero if fcs is null but has_fcs is true. + } + if (fcs) { + eth_msg->fcs = *fcs; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_eth_props_get(icsneoc2_message_t* message, icsneoc2_message_eth_flags_t* flags, bool* has_fcs, uint32_t* fcs) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + auto eth_msg = std::dynamic_pointer_cast(message->message); + if(!eth_msg) { + return icsneoc2_error_invalid_type; + } + if(flags) { + *flags = 0; + if(eth_msg->noPadding) + *flags |= ICSNEOC2_MESSAGE_ETH_FLAGS_NO_PADDING; + if(eth_msg->fcsVerified) + *flags |= ICSNEOC2_MESSAGE_ETH_FLAGS_FCS_VERIFIED; + if(eth_msg->txAborted) + *flags |= ICSNEOC2_MESSAGE_ETH_FLAGS_TX_ABORTED; + if(eth_msg->crcError) + *flags |= ICSNEOC2_MESSAGE_ETH_FLAGS_CRC_ERROR; + if(eth_msg->frameTooShort) + *flags |= ICSNEOC2_MESSAGE_ETH_FLAGS_FRAME_TOO_SHORT; + } + if(has_fcs) { + *has_fcs = eth_msg->fcs.has_value(); + } + if(fcs) { + if(eth_msg->fcs) { + *fcs = eth_msg->fcs.value(); + } else { + *fcs = 0; + } + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_eth_mac_get(icsneoc2_message_t* message, uint8_t* dst_mac, uint8_t* src_mac) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + auto eth_msg = std::dynamic_pointer_cast(message->message); + if(!eth_msg) { + return icsneoc2_error_invalid_type; + } + if(dst_mac) { + if(auto mac = eth_msg->getDestinationMAC(); mac.has_value()) { + const auto& mac_value = mac.value(); + std::memcpy(dst_mac, mac_value.data(), mac_value.size()); + } else { + return icsneoc2_error_invalid_data; + } + } + if(src_mac) { + if(auto mac = eth_msg->getSourceMAC(); mac.has_value()) { + const auto& mac_value = mac.value(); + std::memcpy(src_mac, mac_value.data(), mac_value.size()); + } else { + return icsneoc2_error_invalid_data; + } + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_eth_ether_type_get(icsneoc2_message_t* message, uint16_t* ether_type) { + if(!message || !ether_type) { + return icsneoc2_error_invalid_parameters; + } + auto eth_msg = std::dynamic_pointer_cast(message->message); + if(!eth_msg) { + return icsneoc2_error_invalid_type; + } + if (auto et = eth_msg->getEtherType(); et.has_value()) { + *ether_type = et.value(); + } else { + return icsneoc2_error_invalid_data; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_eth_t1s_props_set(icsneoc2_message_t* message, const icsneoc2_message_eth_t1s_flags_t* flags, const uint8_t* node_id, const uint8_t* burst_count, const uint8_t* symbol_type) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + auto eth_msg = std::dynamic_pointer_cast(message->message); + if(!eth_msg) { + return icsneoc2_error_invalid_type; + } + // If all parameters are null/zero, clear the T1S state. Otherwise, if any parameters are set and we don't have a T1S state, create it. + if(!flags && !node_id && !burst_count && !symbol_type) { + eth_msg->t1s = std::nullopt; + return icsneoc2_error_success; + } + if((flags || node_id || burst_count || symbol_type) && !eth_msg->t1s.has_value()) { + eth_msg->t1s.emplace(); + } + if(flags) { + eth_msg->t1s->isSymbol = (*flags & ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_SYMBOL); + eth_msg->t1s->isBurst = (*flags & ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_BURST); + eth_msg->t1s->txCollision = (*flags & ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_TX_COLLISION); + eth_msg->t1s->isWake = (*flags & ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_WAKE); + } + if(node_id) { + eth_msg->t1s->nodeId = *node_id; + } + if(burst_count) { + eth_msg->t1s->burstCount = *burst_count; + } + if(symbol_type) { + eth_msg->t1s->symbolType = *symbol_type; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_eth_t1s_props_get(icsneoc2_message_t* message, icsneoc2_message_eth_t1s_flags_t* flags, uint8_t* node_id, uint8_t* burst_count, uint8_t* symbol_type) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + auto eth_msg = std::dynamic_pointer_cast(message->message); + if(!eth_msg) { + return icsneoc2_error_invalid_type; + } + if(flags) { + *flags = 0; + if(eth_msg->t1s.has_value()) { + if(eth_msg->t1s->isSymbol) + *flags |= ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_SYMBOL; + if(eth_msg->t1s->isBurst) + *flags |= ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_BURST; + if(eth_msg->t1s->txCollision) + *flags |= ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_TX_COLLISION; + if(eth_msg->t1s->isWake) + *flags |= ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_WAKE; + } + } + if(node_id) { + *node_id = eth_msg->t1s.has_value() ? eth_msg->t1s->nodeId : 0; + } + if(burst_count) { + *burst_count = eth_msg->t1s.has_value() ? eth_msg->t1s->burstCount : 0; + } + if(symbol_type) { + *symbol_type = eth_msg->t1s.has_value() ? eth_msg->t1s->symbolType : 0; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_message_is_ethernet(icsneoc2_message_t* message, bool* is_ethernet) { + if(!message) { + return icsneoc2_error_invalid_parameters; + } + *is_ethernet = std::dynamic_pointer_cast(message->message) != nullptr; + return icsneoc2_error_success; +} diff --git a/api/icsneoc2/icsneoc2settings.cpp b/api/icsneoc2/icsneoc2settings.cpp index 587a4dc..6e59a88 100644 --- a/api/icsneoc2/icsneoc2settings.cpp +++ b/api/icsneoc2/icsneoc2settings.cpp @@ -600,6 +600,36 @@ icsneoc2_error_t icsneoc2_settings_t1s_tx_opp_timer_set(icsneoc2_device_t* devic return icsneoc2_error_success; } +icsneoc2_error_t icsneoc2_settings_t1s_burst_timer_get(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + auto network = Network(static_cast(netid)); + if(auto result = device->device->settings->getT1SBurstTimerFor(network); result.has_value()) { + *value = result.value(); + return icsneoc2_error_success; + } else { + *value = 0; + return icsneoc2_error_get_settings_failure; + } +} + +icsneoc2_error_t icsneoc2_settings_t1s_burst_timer_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + auto network = Network(static_cast(netid)); + if(!device->device->settings->setT1SBurstTimerFor(network, value)) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} + icsneoc2_error_t icsneoc2_settings_t1s_max_burst_timer_for_get(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t* value) { // Make sure the device is valid auto res = icsneoc2_device_is_valid(device); @@ -632,6 +662,186 @@ icsneoc2_error_t icsneoc2_settings_t1s_max_burst_timer_for_set(icsneoc2_device_t return icsneoc2_error_success; } +icsneoc2_error_t icsneoc2_settings_t1s_local_id_alternate_get(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + auto network = Network(static_cast(netid)); + if(auto result = device->device->settings->getT1SLocalIDAlternateFor(network); result.has_value()) { + *value = result.value(); + return icsneoc2_error_success; + } else { + *value = 0; + return icsneoc2_error_get_settings_failure; + } +} + +icsneoc2_error_t icsneoc2_settings_t1s_local_id_alternate_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + auto network = Network(static_cast(netid)); + if(!device->device->settings->setT1SLocalIDAlternateFor(network, value)) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_settings_t1s_is_termination_enabled_for(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + auto network = Network(static_cast(netid)); + if(auto result = device->device->settings->isT1STerminationEnabledFor(network); result.has_value()) { + *value = result.value(); + return icsneoc2_error_success; + } else { + *value = false; + return icsneoc2_error_get_settings_failure; + } +} + +icsneoc2_error_t icsneoc2_settings_t1s_termination_for_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + auto network = Network(static_cast(netid)); + if(!device->device->settings->setT1STerminationFor(network, value)) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_settings_t1s_is_bus_decoding_beacons_enabled_for(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + auto network = Network(static_cast(netid)); + if(auto result = device->device->settings->isT1SBusDecodingBeaconsEnabledFor(network); result.has_value()) { + *value = result.value(); + return icsneoc2_error_success; + } else { + *value = false; + return icsneoc2_error_get_settings_failure; + } +} + +icsneoc2_error_t icsneoc2_settings_t1s_bus_decoding_beacons_for_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + auto network = Network(static_cast(netid)); + if(!device->device->settings->setT1SBusDecodingBeaconsFor(network, value)) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_settings_t1s_is_bus_decoding_all_enabled_for(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + auto network = Network(static_cast(netid)); + if(auto result = device->device->settings->isT1SBusDecodingAllEnabledFor(network); result.has_value()) { + *value = result.value(); + return icsneoc2_error_success; + } else { + *value = false; + return icsneoc2_error_get_settings_failure; + } +} + +icsneoc2_error_t icsneoc2_settings_t1s_bus_decoding_all_for_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + auto network = Network(static_cast(netid)); + if(!device->device->settings->setT1SBusDecodingAllFor(network, value)) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_settings_t1s_multi_id_enable_mask_get(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + auto network = Network(static_cast(netid)); + if(auto result = device->device->settings->getT1SMultiIDEnableMaskFor(network); result.has_value()) { + *value = result.value(); + return icsneoc2_error_success; + } else { + *value = 0; + return icsneoc2_error_get_settings_failure; + } +} + +icsneoc2_error_t icsneoc2_settings_t1s_multi_id_enable_mask_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + auto network = Network(static_cast(netid)); + if(!device->device->settings->setT1SMultiIDEnableMaskFor(network, value)) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} + +icsneoc2_error_t icsneoc2_settings_t1s_multi_id_get(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t index, uint8_t* value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + if(!value) { + return icsneoc2_error_invalid_parameters; + } + auto network = Network(static_cast(netid)); + if(auto result = device->device->settings->getT1SMultiIDFor(network, index); result.has_value()) { + *value = result.value(); + return icsneoc2_error_success; + } else { + *value = 0; + return icsneoc2_error_get_settings_failure; + } +} + +icsneoc2_error_t icsneoc2_settings_t1s_multi_id_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t index, uint8_t value) { + auto res = icsneoc2_device_is_valid(device); + if(res != icsneoc2_error_success) { + return res; + } + auto network = Network(static_cast(netid)); + if(!device->device->settings->setT1SMultiIDFor(network, index, value)) { + return icsneoc2_error_set_settings_failure; + } + return icsneoc2_error_success; +} + icsneoc2_error_t icsneoc2_settings_misc_io_analog_output_enabled_set(icsneoc2_device_t* device, uint8_t pin, uint8_t value) { // Make sure the device is valid auto res = icsneoc2_device_is_valid(device); diff --git a/bindings/python/icsneopy/communication/message/ethernetmessage.cpp b/bindings/python/icsneopy/communication/message/ethernetmessage.cpp index 525e149..e592dfe 100644 --- a/bindings/python/icsneopy/communication/message/ethernetmessage.cpp +++ b/bindings/python/icsneopy/communication/message/ethernetmessage.cpp @@ -7,32 +7,29 @@ namespace icsneo { void init_ethernetmessage(pybind11::module_& m) { - pybind11::classh(m, "MACAddress") - .def("to_string", &MACAddress::toString) - .def("__repr__", &MACAddress::toString); - + pybind11::classh(m, "EthernetMessageT1S") + .def(pybind11::init()) + .def_readwrite("isSymbol", &EthernetMessage::T1S::isSymbol) + .def_readwrite("isBurst", &EthernetMessage::T1S::isBurst) + .def_readwrite("txCollision", &EthernetMessage::T1S::txCollision) + .def_readwrite("isWake", &EthernetMessage::T1S::isWake) + .def_readwrite("nodeId", &EthernetMessage::T1S::nodeId) + .def_readwrite("burstCount", &EthernetMessage::T1S::burstCount) + .def_readwrite("symbolType", &EthernetMessage::T1S::symbolType); + pybind11::classh(m, "EthernetMessage") .def(pybind11::init()) - .def_readwrite("preemptionEnabled", &EthernetMessage::preemptionEnabled) - .def_readwrite("preemptionFlags", &EthernetMessage::preemptionFlags) .def_readwrite("fcs", &EthernetMessage::fcs) .def_readwrite("frameTooShort", &EthernetMessage::frameTooShort) .def_readwrite("noPadding", &EthernetMessage::noPadding) .def_readwrite("fcsVerified", &EthernetMessage::fcsVerified) .def_readwrite("txAborted", &EthernetMessage::txAborted) .def_readwrite("crcError", &EthernetMessage::crcError) - .def_readwrite("isT1S", &EthernetMessage::isT1S) - .def_readwrite("isT1SSymbol", &EthernetMessage::isT1SSymbol) - .def_readwrite("isT1SBurst", &EthernetMessage::isT1SBurst) - .def_readwrite("txCollision", &EthernetMessage::txCollision) - .def_readwrite("isT1SWake", &EthernetMessage::isT1SWake) - .def_readwrite("t1sNodeId", &EthernetMessage::t1sNodeId) - .def_readwrite("t1sBurstCount", &EthernetMessage::t1sBurstCount) - .def_readwrite("t1sSymbolType", &EthernetMessage::t1sSymbolType) - .def("get_destination_mac", &EthernetMessage::getDestinationMAC, pybind11::return_value_policy::reference) - .def("get_source_mac", &EthernetMessage::getSourceMAC, pybind11::return_value_policy::reference) + .def_readwrite("t1s", &EthernetMessage::t1s) + .def_readwrite("preemptionFlags", &EthernetMessage::preemptionFlags) + .def("get_destination_mac", &EthernetMessage::getDestinationMAC) + .def("get_source_mac", &EthernetMessage::getSourceMAC) .def("get_ether_type", &EthernetMessage::getEtherType); } } // namespace icsneo - diff --git a/communication/message/neomessage.cpp b/communication/message/neomessage.cpp index 30af252..e6cc90a 100644 --- a/communication/message/neomessage.cpp +++ b/communication/message/neomessage.cpp @@ -48,7 +48,7 @@ neomessage_t icsneo::CreateNeoMessage(const std::shared_ptr message) { case Network::Type::AutomotiveEthernet: { neomessage_eth_t& eth = *(neomessage_eth_t*)&neomsg; auto ethmsg = std::static_pointer_cast(message); - eth.preemptionFlags = ethmsg->preemptionFlags; + eth.preemptionFlags = ethmsg->preemptionFlags.value_or(0); eth.status.incompleteFrame = ethmsg->frameTooShort; // TODO Fill in extra status bits //eth.status.xyz = ethmsg->preemptionEnabled; diff --git a/communication/packet/ethernetpacket.cpp b/communication/packet/ethernetpacket.cpp index 0c65d4a..298f1d5 100644 --- a/communication/packet/ethernetpacket.cpp +++ b/communication/packet/ethernetpacket.cpp @@ -1,6 +1,7 @@ #include "icsneo/communication/packet/ethernetpacket.h" #include #include +#include using namespace icsneo; @@ -24,9 +25,6 @@ std::shared_ptr HardwareEthernetPacket::DecodeToMessage(const s message.transmitted = packet->eid.TXMSG; if(message.transmitted) message.description = packet->stats; - message.preemptionEnabled = packet->header.PREEMPTION_ENABLED; - if(message.preemptionEnabled) - message.preemptionFlags = (uint8_t)((rawWords[0] & 0x03F8) >> 4); message.frameTooShort = packet->header.RUNT_FRAME; message.noPadding = !packet->header.ENABLE_PADDING; message.fcsVerified = packet->header.FCS_VERIFIED; @@ -39,15 +37,21 @@ std::shared_ptr HardwareEthernetPacket::DecodeToMessage(const s // Decoder will fix as it has information about the timestampResolution increments message.timestamp = packet->timestamp.TS; + // Ethernet Frame Preemption for TSN + if(packet->header.PREEMPTION_ENABLED) { + message.preemptionFlags = static_cast((rawWords[0] & 0x03F8) >> 4); + } + // Check if this is a T1S packet and populate T1S-specific fields - message.isT1S = packet->header.T1S_ETHERNET; - if(message.isT1S) { - message.isT1SSymbol = packet->eid.T1S_SYMBOL; - message.isT1SBurst = packet->eid.T1S_BURST; - message.txCollision = packet->t1s_status.TXCollision; - message.isT1SWake = packet->t1s_status.T1SWake; - message.t1sNodeId = packet->t1s_node.T1S_NODE_ID; - message.t1sBurstCount = packet->t1s_node.T1S_BURST_COUNT; + if(packet->header.T1S_ETHERNET) { + auto& t1s = message.t1s.emplace(); + + t1s.isSymbol = packet->eid.T1S_SYMBOL; + t1s.isBurst = packet->eid.T1S_BURST; + t1s.txCollision = packet->t1s_status.TXCollision; + t1s.isWake = packet->t1s_status.T1SWake; + t1s.nodeId = packet->t1s_node.T1S_NODE_ID; + t1s.burstCount = packet->t1s_node.T1S_BURST_COUNT; } const std::vector::const_iterator databegin = bytestream.begin() + sizeof(HardwareEthernetPacket); @@ -72,7 +76,7 @@ bool HardwareEthernetPacket::EncodeFromMessage(const EthernetMessage& message, s if(description & 0x8000) return false; - const bool preempt = message.preemptionEnabled; + const bool preempt = message.preemptionFlags.has_value(); // full header including parent const size_t headerByteCount = preempt ? 15 : 14; // local header for netID, description, and flags @@ -121,12 +125,15 @@ bool HardwareEthernetPacket::EncodeFromMessage(const EthernetMessage& message, s uint8_t flags = 0x00; if(!message.noPadding) flags |= FLAG_PADDING; if(message.fcs) flags |= FLAG_FCS; - if(message.preemptionEnabled) flags |= FLAG_PREEMPTION; + if(message.preemptionFlags.has_value()) { + flags |= FLAG_PREEMPTION; + } bytestream.push_back(flags); - if(preempt) - bytestream.push_back(static_cast(message.preemptionFlags)); + if(preempt) { + bytestream.push_back(message.preemptionFlags.value()); + } bytestream.insert(bytestream.end(), message.data.begin(), message.data.end()); diff --git a/docs/icsneoc2/examples.rst b/docs/icsneoc2/examples.rst index 336e991..e547a3a 100644 --- a/docs/icsneoc2/examples.rst +++ b/docs/icsneoc2/examples.rst @@ -49,3 +49,27 @@ LIN Transmit .. literalinclude:: ../../examples/c2/lin_transmit/src/main.c :language: c + +Ethernet Transmit +================= + +:download:`Download example <../../examples/c2/ethernet_transmit/src/main.c>` + +.. literalinclude:: ../../examples/c2/ethernet_transmit/src/main.c + :language: c + +Ethernet Receive +================ + +:download:`Download example <../../examples/c2/ethernet_receive/src/main.c>` + +.. literalinclude:: ../../examples/c2/ethernet_receive/src/main.c + :language: c + +T1S Loopback +============ + +:download:`Download example <../../examples/c2/t1s_loopback/src/main.c>` + +.. literalinclude:: ../../examples/c2/t1s_loopback/src/main.c + :language: c diff --git a/docs/icsneopy/examples.rst b/docs/icsneopy/examples.rst index cfc5fa3..0c3034d 100644 --- a/docs/icsneopy/examples.rst +++ b/docs/icsneopy/examples.rst @@ -92,6 +92,14 @@ SPI Example for 10BASE-T1S .. literalinclude:: ../../examples/python/spi/spi_example.py :language: python +10BASE-T1S Settings Configuration +================================= + +:download:`Download example <../../examples/python/t1s/t1s_settings.py>` + +.. literalinclude:: ../../examples/python/t1s/t1s_settings.py + :language: python + Analog Output Control ===================== diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c4190ca..dcf0ed8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -8,6 +8,9 @@ option(LIBICSNEO_BUILD_C2_RECONNECT_EXAMPLE "Build the C2 reconnect example." ON option(LIBICSNEO_BUILD_C2_DEVICE_INFO_EXAMPLE "Build the C2 device info example." ON) option(LIBICSNEO_BUILD_C2_LIN_EXAMPLE "Build the C2 LIN example." ON) option(LIBICSNEO_BUILD_C2_LIN_TRANSMIT_EXAMPLE "Build the C2 LIN transmit example." ON) +option(LIBICSNEO_BUILD_C2_ETHERNET_TRANSMIT_EXAMPLE "Build the C2 ethernet transmit example." ON) +option(LIBICSNEO_BUILD_C2_ETHERNET_RECEIVE_EXAMPLE "Build the C2 ethernet receive example." ON) +option(LIBICSNEO_BUILD_C2_T1S_LOOPBACK_EXAMPLE "Build the C2 RAD-Comet3 T1S loopback example." ON) option(LIBICSNEO_BUILD_CPP_SIMPLE_EXAMPLE "Build the simple C++ example." ON) option(LIBICSNEO_BUILD_CPP_INTERACTIVE_EXAMPLE "Build the command-line interactive C++ example." ON) option(LIBICSNEO_BUILD_CPP_A2B_EXAMPLE "Build the A2B example." ON) @@ -67,6 +70,18 @@ if(LIBICSNEO_BUILD_C2_LIN_TRANSMIT_EXAMPLE) add_subdirectory(c2/lin_transmit) endif() +if(LIBICSNEO_BUILD_C2_ETHERNET_TRANSMIT_EXAMPLE) + add_subdirectory(c2/ethernet_transmit) +endif() + +if(LIBICSNEO_BUILD_C2_ETHERNET_RECEIVE_EXAMPLE) + add_subdirectory(c2/ethernet_receive) +endif() + +if(LIBICSNEO_BUILD_C2_T1S_LOOPBACK_EXAMPLE) + add_subdirectory(c2/t1s_loopback) +endif() + if(LIBICSNEO_BUILD_CPP_SIMPLE_EXAMPLE) add_subdirectory(cpp/simple) endif() diff --git a/examples/c2/ethernet_receive/CMakeLists.txt b/examples/c2/ethernet_receive/CMakeLists.txt new file mode 100644 index 0000000..9f23f11 --- /dev/null +++ b/examples/c2/ethernet_receive/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(libicsneoc2-ethernet-receive-example src/main.c) +target_link_libraries(libicsneoc2-ethernet-receive-example icsneoc2-static) + +if(WIN32) + target_compile_definitions(libicsneoc2-ethernet-receive-example PRIVATE _CRT_SECURE_NO_WARNINGS) +endif() diff --git a/examples/c2/ethernet_receive/src/main.c b/examples/c2/ethernet_receive/src/main.c new file mode 100644 index 0000000..ffb2f6e --- /dev/null +++ b/examples/c2/ethernet_receive/src/main.c @@ -0,0 +1,223 @@ +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +void sleep_ms(uint32_t ms) { +#ifdef _WIN32 + Sleep(ms); +#else + sleep(ms / 1000); +#endif +} + +int print_error_code(const char* message, icsneoc2_error_t error) { + char error_str[64]; + size_t error_str_len = sizeof(error_str); + icsneoc2_error_t res = icsneoc2_error_code_get(error, error_str, &error_str_len); + if(res != icsneoc2_error_success) { + printf("%s: Failed to get string for error code %d with error code %d\n", message, error, res); + return res; + } + printf("%s: \"%s\" (%u)\n", message, error_str, error); + return (int)error; +} + +void print_events(void) { + icsneoc2_event_t* events[256] = {0}; + size_t events_count = 256; + for(size_t i = 0; i < events_count; ++i) { + icsneoc2_error_t res = icsneoc2_event_get(&events[i], NULL); + if(res != icsneoc2_error_success) { + (void)print_error_code("\tFailed to get events", res); + return; + } + if(events[i] == NULL) { + events_count = i; + break; + } + } + for(size_t i = 0; i < events_count; i++) { + char description[255] = {0}; + size_t description_length = 255; + icsneoc2_error_t res = icsneoc2_event_description_get(events[i], description, &description_length); + if(res != icsneoc2_error_success) { + print_error_code("\tFailed to get event description", res); + continue; + } + printf("\tEvent %zu: %s\n", i, description); + } + for(size_t i = 0; i < events_count; i++) { + icsneoc2_event_free(events[i]); + } + if(events_count > 0) { + printf("\tReceived %zu events\n", events_count); + } +} + +void print_mac(const char* label, const uint8_t* mac) { + printf("%s: %02x:%02x:%02x:%02x:%02x:%02x", label, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +int process_ethernet_message(icsneoc2_message_t* message, size_t index) { + icsneoc2_netid_t netid = 0; + char netid_name[128] = {0}; + size_t netid_name_length = 128; + + icsneoc2_error_t res = icsneoc2_message_netid_get(message, &netid); + if(res != icsneoc2_error_success) { + return print_error_code("\tFailed to get netid", res); + } + res = icsneoc2_netid_name_get(netid, netid_name, &netid_name_length); + if(res != icsneoc2_error_success) { + return print_error_code("\tFailed to get netid name", res); + } + + /* Get data length first */ + size_t data_length = 0; + res = icsneoc2_message_data_get(message, NULL, &data_length); + if(res != icsneoc2_error_success) { + return print_error_code("\tFailed to get data length", res); + } + + printf("\t%zu) Ethernet Frame on %s (0x%x) - %zu bytes\n", index, netid_name, netid, data_length); + + /* Get MAC addresses and EtherType if we have enough data */ + uint8_t dst_mac[6] = {0}; + uint8_t src_mac[6] = {0}; + uint16_t ether_type = 0; + + res = icsneoc2_message_eth_mac_get(message, dst_mac, src_mac); + if(res == icsneoc2_error_success) { + printf("\t "); + print_mac("Dst", dst_mac); + printf(" "); + print_mac("Src", src_mac); + printf("\n"); + } + + res = icsneoc2_message_eth_ether_type_get(message, ðer_type); + if(res == icsneoc2_error_success) { + printf("\t EtherType: 0x%04x\n", ether_type); + } + + /* Get flags */ + icsneoc2_message_eth_flags_t flags = 0; + res = icsneoc2_message_eth_props_get(message, &flags, NULL, NULL); + if(res == icsneoc2_error_success && flags != 0) { + printf("\t Flags: 0x%" PRIx64, flags); + if(flags & ICSNEOC2_MESSAGE_ETH_FLAGS_CRC_ERROR) printf(" [CRC_ERROR]"); + if(flags & ICSNEOC2_MESSAGE_ETH_FLAGS_FRAME_TOO_SHORT) printf(" [FRAME_TOO_SHORT]"); + if(flags & ICSNEOC2_MESSAGE_ETH_FLAGS_TX_ABORTED) printf(" [TX_ABORTED]"); + if(flags & ICSNEOC2_MESSAGE_ETH_FLAGS_FCS_VERIFIED) printf(" [FCS_VERIFIED]"); + if(flags & ICSNEOC2_MESSAGE_ETH_FLAGS_PREEMPTION_ENABLED) printf(" [PREEMPTION]"); + if(flags & ICSNEOC2_MESSAGE_ETH_FLAGS_IS_T1S) printf(" [T1S]"); + printf("\n"); + } + + /* Print data bytes */ + uint8_t data[1600] = {0}; + res = icsneoc2_message_data_get(message, data, &data_length); + if(res == icsneoc2_error_success) { + printf("\t Data:\n\t "); + for(size_t x = 0; x < data_length; x++) { + printf("0x%02x ", data[x]); + if((x + 1) % 20 == 0 && x + 1 < data_length) { + printf("\n\t "); + } + } + printf("\n"); + } + + return icsneoc2_error_success; +} + +int main(void) { + /* Open the first available device */ + printf("Opening first available device...\n"); + icsneoc2_device_t* device = NULL; + icsneoc2_error_t res = icsneoc2_device_open_first(0, icsneoc2_open_options_default, &device); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to open first device", res); + } + + /* Get a description of the opened device */ + char description[255] = {0}; + size_t description_length = 255; + res = icsneoc2_device_description_get(device, description, &description_length); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to get device description", res); + } + printf("Opened device: %s\n", description); + + /* Wait for Ethernet frames to arrive */ + const int duration_seconds = 10; + printf("Listening for Ethernet frames for %d seconds...\n", duration_seconds); + sleep_ms(duration_seconds * 1000); + + /* Retrieve and process messages */ + icsneoc2_message_t* messages[20000] = {0}; + size_t message_count = 20000; + size_t eth_count = 0; + + for(size_t i = 0; i < message_count; ++i) { + res = icsneoc2_device_message_get(device, &messages[i], 0); + if(res != icsneoc2_error_success) { + print_events(); + return print_error_code("Failed to get messages", res); + } + if(messages[i] == NULL) { + message_count = i; + break; + } + } + + printf("Got %zu messages total, filtering for Ethernet...\n", message_count); + + for(size_t i = 0; i < message_count; i++) { + icsneoc2_message_t* message = messages[i]; + + /* Check if this is a TX echo (skip it) */ + bool is_tx = false; + res = icsneoc2_message_is_transmit(message, &is_tx); + if(res != icsneoc2_error_success || is_tx) { + continue; + } + + /* Check if this is an Ethernet message */ + bool is_ethernet = false; + res = icsneoc2_message_is_ethernet(message, &is_ethernet); + if(res != icsneoc2_error_success || !is_ethernet) { + continue; + } + + process_ethernet_message(message, eth_count); + eth_count++; + } + + printf("Received %zu Ethernet frames out of %zu total messages\n", eth_count, message_count); + + /* Free all messages */ + for(size_t i = 0; i < message_count; ++i) { + icsneoc2_message_free(messages[i]); + } + + /* Print any events */ + print_events(); + + /* Close the device */ + printf("Closing device...\n"); + res = icsneoc2_device_close(device); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to close device", res); + } + + return 0; +} diff --git a/examples/c2/ethernet_transmit/CMakeLists.txt b/examples/c2/ethernet_transmit/CMakeLists.txt new file mode 100644 index 0000000..8a3a6c1 --- /dev/null +++ b/examples/c2/ethernet_transmit/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(libicsneoc2-ethernet-transmit-example src/main.c) +target_link_libraries(libicsneoc2-ethernet-transmit-example icsneoc2-static) + +if(WIN32) + target_compile_definitions(libicsneoc2-ethernet-transmit-example PRIVATE _CRT_SECURE_NO_WARNINGS) +endif() diff --git a/examples/c2/ethernet_transmit/src/main.c b/examples/c2/ethernet_transmit/src/main.c new file mode 100644 index 0000000..cbf55a7 --- /dev/null +++ b/examples/c2/ethernet_transmit/src/main.c @@ -0,0 +1,204 @@ +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +int print_error_code(const char* message, icsneoc2_error_t error) { + char error_str[64]; + size_t error_str_len = sizeof(error_str); + icsneoc2_error_t res = icsneoc2_error_code_get(error, error_str, &error_str_len); + if(res != icsneoc2_error_success) { + printf("%s: Failed to get string for error code %d with error code %d\n", message, error, res); + return res; + } + printf("%s: \"%s\" (%u)\n", message, error_str, error); + return (int)error; +} + +void print_events(void) { + icsneoc2_event_t* events[256] = {0}; + size_t events_count = 256; + for(size_t i = 0; i < events_count; ++i) { + icsneoc2_error_t res = icsneoc2_event_get(&events[i], NULL); + if(res != icsneoc2_error_success) { + (void)print_error_code("\tFailed to get events", res); + return; + } + if(events[i] == NULL) { + events_count = i; + break; + } + } + for(size_t i = 0; i < events_count; i++) { + char description[255] = {0}; + size_t description_length = 255; + icsneoc2_error_t res = icsneoc2_event_description_get(events[i], description, &description_length); + if(res != icsneoc2_error_success) { + print_error_code("\tFailed to get event description", res); + continue; + } + printf("\tEvent %zu: %s\n", i, description); + } + for(size_t i = 0; i < events_count; i++) { + icsneoc2_event_free(events[i]); + } + if(events_count > 0) { + printf("\tReceived %zu events\n", events_count); + } +} + +int main(void) { + /* Open the first available device */ + printf("Opening first available device...\n"); + icsneoc2_device_t* device = NULL; + icsneoc2_error_t res = icsneoc2_device_open_first(0, icsneoc2_open_options_default, &device); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to open first device", res); + } + + /* Get a description of the opened device */ + char description[255] = {0}; + size_t description_length = 255; + res = icsneoc2_device_description_get(device, description, &description_length); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to get device description", res); + } + printf("Opened device: %s\n", description); + + icsneoc2_netid_t tx_networks[255] = {0}; + size_t tx_net_count = sizeof(tx_networks) / sizeof(tx_networks[0]); + res = icsneoc2_device_supported_tx_networks_get(device, tx_networks, &tx_net_count); + if(res != icsneoc2_error_success) { + print_events(); + return print_error_code("Failed to get TX networks", res); + } + + /* Filter for Ethernet/AutomotiveEthernet networks */ + icsneoc2_netid_t eth_networks[64] = {0}; + char eth_names[64][128] = {{0}}; + size_t eth_count = 0; + for(size_t i = 0; i < tx_net_count && eth_count < 64; i++) { + /* Create a temporary message to check network type */ + icsneoc2_message_t* tmp = NULL; + res = icsneoc2_message_eth_create(&tmp); + if(res != icsneoc2_error_success) continue; + res = icsneoc2_message_netid_set(tmp, tx_networks[i]); + if(res != icsneoc2_error_success) { icsneoc2_message_free(tmp); continue; } + icsneoc2_network_type_t ntype = 0; + res = icsneoc2_message_network_type_get(tmp, &ntype); + icsneoc2_message_free(tmp); + if(res != icsneoc2_error_success) continue; + if(ntype == icsneoc2_network_type_ethernet || ntype == icsneoc2_network_type_automotive_ethernet) { + eth_networks[eth_count] = tx_networks[i]; + size_t name_len = 128; + icsneoc2_netid_name_get(tx_networks[i], eth_names[eth_count], &name_len); + eth_count++; + } + } + + if(eth_count == 0) { + printf("No Ethernet TX networks available on this device.\n"); + icsneoc2_device_close(device); + return 0; + } + + /* Let the user pick */ + printf("Available Ethernet TX networks:\n"); + for(size_t i = 0; i < eth_count; i++) { + printf(" %zu) %s\n", i + 1, eth_names[i]); + } + printf("Select network [1-%zu]: ", eth_count); + int selection = 0; + if(scanf("%d", &selection) != 1 || selection < 1 || (size_t)selection > eth_count) { + printf("Invalid selection, using first available.\n"); + selection = 1; + } + icsneoc2_netid_t netid = eth_networks[selection - 1]; + printf("Selected: %s\n", eth_names[selection - 1]); + /* Transmit Ethernet frames */ + const size_t msg_count = 10; + printf("Transmitting %zu Ethernet frames on %s...\n", msg_count, eth_names[selection - 1]); + + for(size_t i = 0; i < msg_count; i++) { + /* Create an Ethernet message */ + icsneoc2_message_t* message = NULL; + res = icsneoc2_message_eth_create(&message); + if(res != icsneoc2_error_success) { + print_events(); + return print_error_code("Failed to create Ethernet message", res); + } + + /* Set the network ID */ + res = icsneoc2_message_netid_set(message, netid ); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(message); + print_events(); + return print_error_code("Failed to set netid", res); + } + + /* Build Ethernet frame data: + * Bytes 0-5: Destination MAC (00:FC:70:00:01:02) + * Bytes 6-11: Source MAC (00:FC:70:00:01:01) + * Bytes 12-13: EtherType (0x0800 = IPv4) + * Bytes 14+: Payload + */ + uint8_t frame_data[] = { + 0x00, 0xFC, 0x70, 0x00, 0x01, 0x02, /* Destination MAC */ + 0x00, 0xFC, 0x70, 0x00, 0x01, 0x01, /* Source MAC */ + 0x08, 0x00, /* EtherType (IPv4) */ + 0x45, 0x00, 0x00, 0x20, /* IPv4: ver/IHL, DSCP, total length (32) */ + 0x00, 0x00, 0x00, 0x00, /* Identification, flags/fragment offset */ + 0x40, 0x11, 0x00, 0x00, /* TTL (64), protocol (UDP), checksum (0) */ + 0xC0, 0xA8, 0x01, 0x01, /* Source IP (192.168.1.1) */ + 0xC0, 0xA8, 0x01, 0x02, /* Destination IP (192.168.1.2) */ + 0xC3, 0x50, 0xC3, 0x51, /* UDP: src port (50000), dst port (50001) */ + 0x00, 0x0C, 0x00, 0x00, /* UDP: length (12), checksum (0) */ + 0x00, 0x00, 0x00, 0x00 /* UDP payload (4 bytes, frame counter) */ + }; + + /* Put the frame counter in the UDP payload */ + frame_data[42] = (uint8_t)((i >> 24) & 0xFF); + frame_data[43] = (uint8_t)((i >> 16) & 0xFF); + frame_data[44] = (uint8_t)((i >> 8) & 0xFF); + frame_data[45] = (uint8_t)(i & 0xFF); + + res = icsneoc2_message_data_set(message, frame_data, sizeof(frame_data)); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(message); + print_events(); + return print_error_code("Failed to set frame data", res); + } + + /* Transmit the message */ + res = icsneoc2_device_message_transmit(device, message); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(message); + print_events(); + return print_error_code("Failed to transmit Ethernet frame", res); + } + + icsneoc2_message_free(message); + printf("\tTransmitted frame %zu\n", i + 1); + } + + printf("Successfully transmitted %zu Ethernet frames\n", msg_count); + + /* Print any events */ + print_events(); + + /* Close the device */ + printf("Closing device...\n"); + res = icsneoc2_device_close(device); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to close device", res); + } + + return 0; +} diff --git a/examples/c2/t1s_loopback/CMakeLists.txt b/examples/c2/t1s_loopback/CMakeLists.txt new file mode 100644 index 0000000..ea3e811 --- /dev/null +++ b/examples/c2/t1s_loopback/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(libicsneoc2-t1s-loopback-example src/main.c) +target_link_libraries(libicsneoc2-t1s-loopback-example icsneoc2-static) + +if(WIN32) + target_compile_definitions(libicsneoc2-t1s-loopback-example PRIVATE _CRT_SECURE_NO_WARNINGS) +endif() \ No newline at end of file diff --git a/examples/c2/t1s_loopback/src/main.c b/examples/c2/t1s_loopback/src/main.c new file mode 100644 index 0000000..39a0fc6 --- /dev/null +++ b/examples/c2/t1s_loopback/src/main.c @@ -0,0 +1,643 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#define TX_LOCAL_ID 0u +#define RX_LOCAL_ID 1u +#define T1S_MAX_NODES 8u +#define T1S_TX_OPP_TIMER 20u +#define T1S_BURST_TIMER 64u +#define T1S_MAX_BURST 128u +#define LOOPBACK_ETHER_TYPE 0x9000u +#define LOOPBACK_FRAME_SIZE 60u + +typedef struct selectable_network { + icsneoc2_netid_t netid; + char name[64]; +} selectable_network_t; + +/* Sleep for a short period while waiting for the device to apply settings. */ +static void sleep_ms(uint32_t ms); + +/* Print a readable error string and return the same failure code to the caller. */ +static int print_error_code(const char* message, icsneoc2_error_t error); + +/* Drain and print queued library events when the example encounters an error. */ +static void print_events(void); + +/* Convert a netid to a readable name such as "AE 02". */ +static int get_netid_name(icsneoc2_netid_t netid, char* buffer, size_t buffer_size); + +/* Gather the device's TX and RX networks, keeping only automotive Ethernet ports. */ +static int get_available_networks(const icsneoc2_device_t* device, + selectable_network_t* tx_networks, + size_t* tx_count, + selectable_network_t* rx_networks, + size_t* rx_count); + +/* Prompt the user to choose one TX or RX network from the filtered list. */ +static int prompt_for_network_selection(const char* label, const selectable_network_t* networks, size_t count, icsneoc2_netid_t* selected_netid, char* selected_name, size_t selected_name_size); + +/* Apply the small set of T1S settings needed for this two-port loopback example. */ +static int configure_t1s_port(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t local_id); + +/* Print a MAC address in a compact human-readable form. */ +static void print_mac(const char* label, const uint8_t* mac); + +/* Print payload bytes as hex for the TX echo and RX frame output. */ +static void print_payload_hex(const uint8_t* data, size_t length); + +/* Build one recognizable Ethernet frame that both transmit and receive paths share. */ +static void build_loopback_frame(uint8_t* frame_data, size_t frame_size, const char* tx_name, const char* rx_name); + +/* Check whether a received Ethernet frame matches the loopback frame this example sent. */ +static int message_matches_loopback_frame(icsneoc2_message_t* message, const uint8_t* expected, size_t expected_length, bool* matches); + +/* Print the key details of an Ethernet message found during the loopback test. */ +static int print_ethernet_message(icsneoc2_message_t* message, const char* direction_label); + +/* Transmit the loopback Ethernet frame on the selected TX port. */ +static int transmit_loopback_frame(icsneoc2_device_t* device, icsneoc2_netid_t tx_netid, const char* tx_name, const char* rx_name); + +/* Poll until the example sees both the TX echo and the matching RX frame. */ +static int poll_for_loopback_messages(icsneoc2_device_t* device, + icsneoc2_netid_t tx_netid, + icsneoc2_netid_t rx_netid, + const char* tx_name, + const char* rx_name, + const uint8_t* expected_frame, + size_t expected_frame_length); + +int main(void) { + icsneoc2_device_t* device = NULL; + icsneoc2_error_t res; + char description[255] = {0}; + char serial[64] = {0}; + size_t description_length = sizeof(description); + size_t serial_length = sizeof(serial); + selectable_network_t available_tx_networks[128] = {0}; + selectable_network_t available_rx_networks[128] = {0}; + size_t available_tx_count = 0; + size_t available_rx_count = 0; + icsneoc2_netid_t tx_netid = 0; + icsneoc2_netid_t rx_netid = 0; + uint8_t expected_frame[LOOPBACK_FRAME_SIZE] = {0}; + char tx_name[64] = {0}; + char rx_name[64] = {0}; + int status = 1; + + printf("RAD-Comet3 C2 T1S loopback example\n"); + printf("Opening first available RAD-Comet3...\n"); + + res = icsneoc2_device_open_first(icsneoc2_devicetype_rad_comet3, icsneoc2_open_options_default, &device); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to open a RAD-Comet3", res); + } + + res = icsneoc2_device_description_get(device, description, &description_length); + if(res != icsneoc2_error_success) { + print_error_code("Failed to get device description", res); + goto cleanup; + } + res = icsneoc2_device_serial_get(device, serial, &serial_length); + if(res != icsneoc2_error_success) { + print_error_code("Failed to get device serial", res); + goto cleanup; + } + printf("Opened device: %s [%s]\n", description, serial); + + if(get_available_networks(device, available_tx_networks, &available_tx_count, available_rx_networks, &available_rx_count) != 0) { + goto cleanup; + } + + if(prompt_for_network_selection("TX", available_tx_networks, available_tx_count, &tx_netid, tx_name, sizeof(tx_name)) != 0) { + goto cleanup; + } + if(prompt_for_network_selection("RX", available_rx_networks, available_rx_count, &rx_netid, rx_name, sizeof(rx_name)) != 0) { + goto cleanup; + } + + printf("Selected loopback wiring: %s connected to %s\n", tx_name, rx_name); + + if(tx_netid == rx_netid) { + printf("TX and RX networks are the same. This example is intended for a physical loopback between two ports.\n"); + goto cleanup; + } + + // Use the same expected frame bytes for transmit and for receive-side matching. + build_loopback_frame(expected_frame, sizeof(expected_frame), tx_name, rx_name); + + res = icsneoc2_settings_refresh(device); + if(res != icsneoc2_error_success) { + print_error_code("Failed to refresh device settings", res); + goto cleanup; + } + + if(configure_t1s_port(device, tx_netid, TX_LOCAL_ID) != 0) { + goto cleanup; + } + if(configure_t1s_port(device, rx_netid, RX_LOCAL_ID) != 0) { + goto cleanup; + } + + printf("Applying T1S settings to the device.\n"); + printf("Note: icsneoc2_settings_apply() persists these settings on the device.\n"); + res = icsneoc2_settings_apply(device); + if(res != icsneoc2_error_success) { + print_error_code("Failed to apply T1S settings", res); + goto cleanup; + } + + sleep_ms(500); + + if(transmit_loopback_frame(device, tx_netid, tx_name, rx_name) != 0) { + goto cleanup; + } + + if(poll_for_loopback_messages(device, tx_netid, rx_netid, tx_name, rx_name, expected_frame, sizeof(expected_frame)) != 0) { + print_events(); + goto cleanup; + } + + status = 0; + +cleanup: + if(device != NULL) { + res = icsneoc2_device_close(device); + if(res != icsneoc2_error_success) { + (void)print_error_code("Failed to close device", res); + } + res = icsneoc2_device_free(device); + if(res != icsneoc2_error_success) { + (void)print_error_code("Failed to free device", res); + } + } + + return status; +} + +static void sleep_ms(uint32_t ms) { +#ifdef _WIN32 + Sleep(ms); +#else + usleep(ms * 1000); +#endif +} + +static int print_error_code(const char* message, icsneoc2_error_t error) { + char error_str[64] = {0}; + size_t error_str_len = sizeof(error_str); + icsneoc2_error_t res = icsneoc2_error_code_get(error, error_str, &error_str_len); + if(res != icsneoc2_error_success) { + printf("%s: failed to get string for error code %u with error code %u\n", message, error, res); + return (int)res; + } + printf("%s: \"%s\" (%u)\n", message, error_str, error); + return (int)error; +} + +static void print_events(void) { + icsneoc2_event_t* events[64] = {0}; + size_t count = sizeof(events) / sizeof(events[0]); + + for(size_t i = 0; i < count; ++i) { + icsneoc2_error_t res = icsneoc2_event_get(&events[i], NULL); + if(res != icsneoc2_error_success) { + (void)print_error_code("Failed to get events", res); + return; + } + if(events[i] == NULL) { + count = i; + break; + } + } + + for(size_t i = 0; i < count; ++i) { + char description[255] = {0}; + size_t description_length = sizeof(description); + icsneoc2_error_t res = icsneoc2_event_description_get(events[i], description, &description_length); + if(res == icsneoc2_error_success) { + printf("Event %zu: %s\n", i, description); + } + icsneoc2_event_free(events[i]); + } +} + +static int get_netid_name(icsneoc2_netid_t netid, char* buffer, size_t buffer_size) { + size_t length = buffer_size; + icsneoc2_error_t res = icsneoc2_netid_name_get(netid, buffer, &length); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to get netid name", res); + } + return 0; +} + +static int get_available_networks(const icsneoc2_device_t* device, + selectable_network_t* tx_networks, + size_t* tx_count, + selectable_network_t* rx_networks, + size_t* rx_count) { + icsneoc2_netid_t supported_tx_networks[128] = {0}; + icsneoc2_netid_t supported_rx_networks[128] = {0}; + size_t tx_supported_count = sizeof(supported_tx_networks) / sizeof(supported_tx_networks[0]); + size_t rx_supported_count = sizeof(supported_rx_networks) / sizeof(supported_rx_networks[0]); + icsneoc2_message_t* probe = NULL; + icsneoc2_network_type_t network_type = 0; + icsneoc2_error_t res = icsneoc2_device_supported_tx_networks_get(device, supported_tx_networks, &tx_supported_count); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to get supported TX networks", res); + } + res = icsneoc2_device_supported_rx_networks_get(device, supported_rx_networks, &rx_supported_count); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to get supported RX networks", res); + } + + *tx_count = 0; + *rx_count = 0; + + // Reuse one temporary Ethernet message to classify each netid by network type. + res = icsneoc2_message_eth_create(&probe); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to create temporary Ethernet message", res); + } + + for(size_t i = 0; i < tx_supported_count; ++i) { + res = icsneoc2_message_netid_set(probe, supported_tx_networks[i]); + if(res != icsneoc2_error_success) { + continue; + } + res = icsneoc2_message_network_type_get(probe, &network_type); + if(res != icsneoc2_error_success || network_type != icsneoc2_network_type_automotive_ethernet) { + continue; + } + tx_networks[*tx_count].netid = supported_tx_networks[i]; + if(get_netid_name(supported_tx_networks[i], tx_networks[*tx_count].name, sizeof(tx_networks[*tx_count].name)) != 0) { + icsneoc2_message_free(probe); + return 1; + } + (*tx_count)++; + } + + for(size_t i = 0; i < rx_supported_count; ++i) { + res = icsneoc2_message_netid_set(probe, supported_rx_networks[i]); + if(res != icsneoc2_error_success) { + continue; + } + res = icsneoc2_message_network_type_get(probe, &network_type); + if(res != icsneoc2_error_success || network_type != icsneoc2_network_type_automotive_ethernet) { + continue; + } + rx_networks[*rx_count].netid = supported_rx_networks[i]; + if(get_netid_name(supported_rx_networks[i], rx_networks[*rx_count].name, sizeof(rx_networks[*rx_count].name)) != 0) { + icsneoc2_message_free(probe); + return 1; + } + (*rx_count)++; + } + icsneoc2_message_free(probe); + + if(*tx_count == 0) { + printf("No automotive Ethernet TX networks are available on this device.\n"); + return 1; + } + if(*rx_count == 0) { + printf("No automotive Ethernet RX networks are available on this device.\n"); + return 1; + } + return 0; +} + +static int prompt_for_network_selection(const char* label, const selectable_network_t* networks, size_t count, icsneoc2_netid_t* selected_netid, char* selected_name, size_t selected_name_size) { + char input[32] = {0}; + char* end_ptr = NULL; + long selected_index = 0; + + if(!label || !networks || count == 0 || !selected_netid || !selected_name || selected_name_size == 0) { + return print_error_code("Invalid network selection parameters", icsneoc2_error_invalid_parameters); + } + + printf("Available automotive Ethernet %s networks:\n", label); + for(size_t i = 0; i < count; ++i) { + printf(" %zu) %s\n", i + 1, networks[i].name); + } + printf("Select %s network [1-%zu, default 1]: ", label, count); + + if(fgets(input, sizeof(input), stdin) == NULL || input[0] == '\n') { + selected_index = 1; + } else { + selected_index = strtol(input, &end_ptr, 10); + if(end_ptr == input || selected_index < 1 || (size_t)selected_index > count) { + printf("Invalid selection, using %s.\n", networks[0].name); + selected_index = 1; + } + } + + *selected_netid = networks[selected_index - 1].netid; + strncpy(selected_name, networks[selected_index - 1].name, selected_name_size - 1); + selected_name[selected_name_size - 1] = '\0'; + return 0; +} + +static int configure_t1s_port(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t local_id) { + char netid_name[64] = {0}; + icsneoc2_error_t res; + bool termination = false; + + if(get_netid_name(netid, netid_name, sizeof(netid_name)) != 0) { + return 1; + } + + printf("Configuring %s: PLCA on, LocalID=%u, MaxNodes=%u, TxOpp=%u, BurstTimer=%u, MaxBurst=%u\n", + netid_name, + (unsigned)local_id, + (unsigned)T1S_MAX_NODES, + (unsigned)T1S_TX_OPP_TIMER, + (unsigned)T1S_BURST_TIMER, + (unsigned)T1S_MAX_BURST); + + // Keep the example explicit about the small set of T1S settings needed for loopback. + res = icsneoc2_settings_t1s_plca_enabled_for_set(device, netid, true); + if(res != icsneoc2_error_success) return print_error_code("Failed to enable T1S PLCA", res); + res = icsneoc2_settings_t1s_local_id_set(device, netid, local_id); + if(res != icsneoc2_error_success) return print_error_code("Failed to set T1S local ID", res); + res = icsneoc2_settings_t1s_max_nodes_set(device, netid, T1S_MAX_NODES); + if(res != icsneoc2_error_success) return print_error_code("Failed to set T1S max nodes", res); + res = icsneoc2_settings_t1s_tx_opp_timer_set(device, netid, T1S_TX_OPP_TIMER); + if(res != icsneoc2_error_success) return print_error_code("Failed to set T1S TX opportunity timer", res); + res = icsneoc2_settings_t1s_burst_timer_set(device, netid, T1S_BURST_TIMER); + if(res != icsneoc2_error_success) return print_error_code("Failed to set T1S burst timer", res); + res = icsneoc2_settings_t1s_max_burst_timer_for_set(device, netid, T1S_MAX_BURST); + if(res != icsneoc2_error_success) return print_error_code("Failed to set T1S max burst", res); + + res = icsneoc2_settings_t1s_is_termination_enabled_for(device, netid, &termination); + if(res == icsneoc2_error_success) { + res = icsneoc2_settings_t1s_termination_for_set(device, netid, true); + if(res != icsneoc2_error_success) return print_error_code("Failed to enable T1S termination", res); + } else if(res != icsneoc2_error_get_settings_failure) { + return print_error_code("Failed to query T1S termination support", res); + } + + return 0; +} + +static void print_mac(const char* label, const uint8_t* mac) { + printf("%s %02x:%02x:%02x:%02x:%02x:%02x", label, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +static void print_payload_hex(const uint8_t* data, size_t length) { + for(size_t i = 0; i < length; ++i) { + printf("%02x", data[i]); + if(i + 1 < length) { + printf(" "); + } + } + printf("\n"); +} + +static void build_loopback_frame(uint8_t* frame_data, size_t frame_size, const char* tx_name, const char* rx_name) { + const char* tx_label = tx_name ? tx_name : "TX"; + const char* rx_label = rx_name ? rx_name : "RX"; + + // Build one recognizable Ethernet frame so the receive side can match exactly what we sent. + memset(frame_data, 0, frame_size); + frame_data[0] = 0x00; + frame_data[1] = 0xFC; + frame_data[2] = 0x70; + frame_data[3] = 0x00; + frame_data[4] = 0x00; + frame_data[5] = 0x02; + frame_data[6] = 0x00; + frame_data[7] = 0xFC; + frame_data[8] = 0x70; + frame_data[9] = 0x00; + frame_data[10] = 0x00; + frame_data[11] = 0x01; + frame_data[12] = (uint8_t)((LOOPBACK_ETHER_TYPE >> 8) & 0xFF); + frame_data[13] = (uint8_t)(LOOPBACK_ETHER_TYPE & 0xFF); + snprintf((char*)&frame_data[14], frame_size - 14, "C2 T1S loopback %s->%s", tx_label, rx_label); +} + +static int message_matches_loopback_frame(icsneoc2_message_t* message, const uint8_t* expected, size_t expected_length, bool* matches) { + uint8_t data[1600] = {0}; + size_t data_length = sizeof(data); + uint16_t ether_type = 0; + icsneoc2_error_t res; + + if(!expected || !matches || expected_length < 14) { + return print_error_code("Invalid loopback match output", icsneoc2_error_invalid_parameters); + } + + *matches = false; + + res = icsneoc2_message_eth_ether_type_get(message, ðer_type); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to read loopback EtherType", res); + } + if(ether_type != LOOPBACK_ETHER_TYPE) { + return 0; + } + + res = icsneoc2_message_data_get(message, data, &data_length); + if(res != icsneoc2_error_success) { + return print_error_code("Failed to read loopback frame data", res); + } + + if(data_length < 14) { + return 0; + } + + if(data_length >= expected_length) { + *matches = memcmp(data, expected, expected_length) == 0; + } else { + *matches = memcmp(data, expected, data_length) == 0; + } + return 0; +} + +static int print_ethernet_message(icsneoc2_message_t* message, const char* direction_label) { + icsneoc2_netid_t netid = 0; + char netid_name[64] = {0}; + uint8_t dst_mac[6] = {0}; + uint8_t src_mac[6] = {0}; + uint16_t ether_type = 0; + icsneoc2_message_eth_t1s_flags_t t1s_flags = 0; + uint8_t node_id = 0; + uint8_t burst_count = 0; + uint8_t symbol_type = 0; + uint8_t data[1600] = {0}; + size_t data_length = sizeof(data); + icsneoc2_error_t res; + + res = icsneoc2_message_netid_get(message, &netid); + if(res != icsneoc2_error_success) return print_error_code("Failed to get message netid", res); + if(get_netid_name(netid, netid_name, sizeof(netid_name)) != 0) return 1; + + res = icsneoc2_message_data_get(message, data, &data_length); + if(res != icsneoc2_error_success) return print_error_code("Failed to get Ethernet data", res); + + printf("%s on %s: %zu bytes\n", direction_label, netid_name, data_length); + + res = icsneoc2_message_eth_mac_get(message, dst_mac, src_mac); + if(res == icsneoc2_error_success) { + print_mac(" Dst", dst_mac); + printf(" "); + print_mac("Src", src_mac); + printf("\n"); + } + + res = icsneoc2_message_eth_ether_type_get(message, ðer_type); + if(res == icsneoc2_error_success) { + printf(" EtherType: 0x%04x\n", ether_type); + } + + res = icsneoc2_message_eth_t1s_props_get(message, &t1s_flags, &node_id, &burst_count, &symbol_type); + if(res == icsneoc2_error_success && (t1s_flags != 0 || node_id != 0 || burst_count != 0 || symbol_type != 0)) { + printf(" T1S: node=%u burst=%u symbol=%u", (unsigned)node_id, (unsigned)burst_count, (unsigned)symbol_type); + if(t1s_flags & ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_SYMBOL) printf(" [SYMBOL]"); + if(t1s_flags & ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_BURST) printf(" [BURST]"); + if(t1s_flags & ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_TX_COLLISION) printf(" [TX_COLLISION]"); + if(t1s_flags & ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_WAKE) printf(" [WAKE]"); + printf("\n"); + } + + printf(" Payload bytes: "); + if(data_length > 14) { + print_payload_hex(&data[14], data_length - 14); + } else { + printf("\n"); + } + + return 0; +} + +static int transmit_loopback_frame(icsneoc2_device_t* device, icsneoc2_netid_t tx_netid, const char* tx_name, const char* rx_name) { + icsneoc2_message_t* message = NULL; + icsneoc2_error_t res; + uint8_t frame_data[LOOPBACK_FRAME_SIZE] = {0}; + + build_loopback_frame(frame_data, sizeof(frame_data), tx_name, rx_name); + + res = icsneoc2_message_eth_create(&message); + if(res != icsneoc2_error_success) return print_error_code("Failed to create Ethernet message", res); + + res = icsneoc2_message_netid_set(message, tx_netid); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(message); + return print_error_code("Failed to set Ethernet netid", res); + } + + res = icsneoc2_message_data_set(message, frame_data, sizeof(frame_data)); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(message); + return print_error_code("Failed to set Ethernet payload", res); + } + + printf("Transmitting one T1S loopback frame on %s...\n", tx_name ? tx_name : "selected TX network"); + res = icsneoc2_device_message_transmit(device, message); + icsneoc2_message_free(message); + if(res != icsneoc2_error_success) return print_error_code("Failed to transmit loopback frame", res); + + return 0; +} + +static int poll_for_loopback_messages(icsneoc2_device_t* device, + icsneoc2_netid_t tx_netid, + icsneoc2_netid_t rx_netid, + const char* tx_name, + const char* rx_name, + const uint8_t* expected_frame, + size_t expected_frame_length) { + bool saw_tx_echo = false; + bool saw_rx_frame = false; + + printf("Polling for TX echo on %s and RX frame on %s...\n", tx_name, rx_name); + for(size_t attempt = 0; attempt < 60 && !(saw_tx_echo && saw_rx_frame); ++attempt) { + icsneoc2_message_t* message = NULL; + icsneoc2_error_t res = icsneoc2_device_message_get(device, &message, 100); + if(res != icsneoc2_error_success) { + return print_error_code("Failed while polling for loopback messages", res); + } + if(message == NULL) { + continue; + } + + bool is_ethernet = false; + bool matches_loopback = false; + res = icsneoc2_message_is_ethernet(message, &is_ethernet); + if(res != icsneoc2_error_success || !is_ethernet) { + icsneoc2_message_free(message); + continue; + } + + icsneoc2_netid_t netid = 0; + res = icsneoc2_message_netid_get(message, &netid); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(message); + return print_error_code("Failed to get polled netid", res); + } + + if(netid != tx_netid && netid != rx_netid) { + icsneoc2_message_free(message); + continue; + } + + // Ignore unrelated traffic on the selected ports and only count the frame this example transmitted. + if(message_matches_loopback_frame(message, expected_frame, expected_frame_length, &matches_loopback) != 0) { + icsneoc2_message_free(message); + return 1; + } + if(!matches_loopback) { + icsneoc2_message_free(message); + continue; + } + + bool is_tx = false; + res = icsneoc2_message_is_transmit(message, &is_tx); + if(res != icsneoc2_error_success) { + icsneoc2_message_free(message); + return print_error_code("Failed to determine TX status", res); + } + + if(netid == tx_netid && is_tx && !saw_tx_echo) { + if(print_ethernet_message(message, "TX echo") != 0) { + icsneoc2_message_free(message); + return 1; + } + saw_tx_echo = true; + } else if(netid == rx_netid && !saw_rx_frame) { + if(print_ethernet_message(message, "RX frame") != 0) { + icsneoc2_message_free(message); + return 1; + } + if(is_tx) { + printf(" Note: RX port message was also marked as transmit.\n"); + } + saw_rx_frame = true; + } + + icsneoc2_message_free(message); + } + + if(!saw_tx_echo || !saw_rx_frame) { + printf("Loopback incomplete: saw_tx_echo=%s, saw_rx_frame=%s\n", + saw_tx_echo ? "true" : "false", + saw_rx_frame ? "true" : "false"); + printf("Confirm %s is physically connected to %s and both ports are configured for 10BASE-T1S.\n", tx_name, rx_name); + return 1; + } + + printf("Loopback complete: TX echo on %s and RX frame on %s were both observed.\n", tx_name, rx_name); + return 0; +} \ No newline at end of file diff --git a/examples/cpp/interactive/src/InteractiveExample.cpp b/examples/cpp/interactive/src/InteractiveExample.cpp index b34c770..12a48f4 100644 --- a/examples/cpp/interactive/src/InteractiveExample.cpp +++ b/examples/cpp/interactive/src/InteractiveExample.cpp @@ -223,8 +223,30 @@ void printMessage(const std::shared_ptr& message) { std::cout << "\t\t Timestamped:\t"<< ethMessage->timestamp << " ns since 1/1/2007\n"; // The MACAddress may be printed directly or accessed with the `data` member - std::cout << "\t\t Source:\t" << ethMessage->getSourceMAC() << "\n"; - std::cout << "\t\t Destination:\t" << ethMessage->getDestinationMAC(); + auto printMAC = [](const icsneo::MACAddress& mac) { + std::ostringstream oss; + for(size_t i = 0; i < mac.size(); i++) { + oss << std::hex << std::setw(2) << std::setfill('0') << (uint32_t)mac[i]; + if(i != mac.size() - 1) + oss << ':'; + } + return oss.str(); + }; + if (auto destMAC = ethMessage->getDestinationMAC(); destMAC.has_value()) { + std::cout << "\t\t Destination:\t" << printMAC(*destMAC) << "\n"; + } else { + std::cout << "\t\t Destination:\t N/A\n"; + } + if (auto srcMAC = ethMessage->getSourceMAC(); srcMAC.has_value()) { + std::cout << "\t\t Source:\t" << printMAC(*srcMAC) << "\n"; + } else { + std::cout << "\t\t Source:\t N/A\n"; + } + if (auto etherType = ethMessage->getEtherType(); etherType.has_value()) { + std::cout << "\t\t EtherType:\t" << std::hex << std::setw(4) << std::setfill('0') << *etherType << "\n"; + } else { + std::cout << "\t\t EtherType:\t N/A\n"; + } // Print the data for(size_t i = 0; i < ethMessage->data.size(); i++) { diff --git a/examples/cpp/simple/src/SimpleExample.cpp b/examples/cpp/simple/src/SimpleExample.cpp index cd025d1..e74893a 100644 --- a/examples/cpp/simple/src/SimpleExample.cpp +++ b/examples/cpp/simple/src/SimpleExample.cpp @@ -208,8 +208,26 @@ int main() { std::cout << "\t\t Timestamped:\t"<< ethMessage->timestamp << " ns since 1/1/2007\n"; // The MACAddress may be printed directly or accessed with the `data` member - std::cout << "\t\t Source:\t" << ethMessage->getSourceMAC() << "\n"; - std::cout << "\t\t Destination:\t" << ethMessage->getDestinationMAC(); + // The MACAddress may be printed directly or accessed with the `data` member + auto printMAC = [](const icsneo::MACAddress& mac) { + std::ostringstream oss; + for(size_t i = 0; i < mac.size(); i++) { + oss << std::hex << std::setw(2) << std::setfill('0') << (uint32_t)mac[i]; + if(i != mac.size() - 1) + oss << ':'; + } + return oss.str(); + }; + if (auto destMAC = ethMessage->getDestinationMAC(); destMAC.has_value()) { + std::cout << "\t\t Destination:\t" << printMAC(*destMAC) << "\n"; + } else { + std::cout << "\t\t Destination:\t N/A\n"; + } + if (auto srcMAC = ethMessage->getSourceMAC(); srcMAC.has_value()) { + std::cout << "\t\t Source:\t" << printMAC(*srcMAC) << "\n"; + } else { + std::cout << "\t\t Source:\t N/A\n"; + } // Print the data for(size_t i = 0; i < ethMessage->data.size(); i++) { diff --git a/examples/cpp/t1s/src/T1SSymbolDecodingExample.cpp b/examples/cpp/t1s/src/T1SSymbolDecodingExample.cpp index 9f85c96..4269e8b 100644 --- a/examples/cpp/t1s/src/T1SSymbolDecodingExample.cpp +++ b/examples/cpp/t1s/src/T1SSymbolDecodingExample.cpp @@ -154,12 +154,12 @@ void setupSymbolMonitoring(std::shared_ptr& device, auto ethMsg = std::static_pointer_cast(frame); - if (!ethMsg->isT1S) + if (!ethMsg->t1s) return; double timestamp_ms = ethMsg->timestamp / 1000000.0; - if (ethMsg->isT1SSymbol) { + if (ethMsg->t1s->isSymbol) { size_t numSymbols = ethMsg->data.size(); std::cout << std::fixed << std::setprecision(3) @@ -169,7 +169,7 @@ void setupSymbolMonitoring(std::shared_ptr& device, if (numSymbols > 0) { std::cout << " (" << numSymbols << " symbol" << (numSymbols > 1 ? "s" : "") << ")"; } - std::cout << " | Node ID: " << (int)ethMsg->t1sNodeId << std::endl; + std::cout << " | Node ID: " << (int)ethMsg->t1s->nodeId << std::endl; for (size_t i = 0; i < numSymbols; i++) { uint8_t symbolValue = ethMsg->data[i]; @@ -188,8 +188,8 @@ void setupSymbolMonitoring(std::shared_ptr& device, << std::dec << std::endl; } - if (numSymbols == 0 && ethMsg->t1sSymbolType != 0) { - uint8_t symbolValue = ethMsg->t1sSymbolType; + if (numSymbols == 0 && ethMsg->t1s->symbolType != 0) { + uint8_t symbolValue = ethMsg->t1s->symbolType; std::string symbolName = getSymbolName(symbolValue); stats.symbolCount++; @@ -205,20 +205,20 @@ void setupSymbolMonitoring(std::shared_ptr& device, << std::dec << " (from t1sSymbolType field)" << std::endl; } } - else if (ethMsg->isT1SBurst) { + else if (ethMsg->t1s->isBurst) { stats.burstCount++; std::cout << std::fixed << std::setprecision(3) << "[" << std::setw(12) << timestamp_ms << " ms] " << "BURST | " - << "Node ID: " << (int)ethMsg->t1sNodeId << " | " - << "Burst Count: " << (int)ethMsg->t1sBurstCount << std::endl; + << "Node ID: " << (int)ethMsg->t1s->nodeId << " | " + << "Burst Count: " << (int)ethMsg->t1s->burstCount << std::endl; } - else if (ethMsg->isT1SWake) { + else if (ethMsg->t1s->isWake) { stats.wakeCount++; std::cout << std::fixed << std::setprecision(3) << "[" << std::setw(12) << timestamp_ms << " ms] " << "WAKE signal detected | " - << "Node ID: " << (int)ethMsg->t1sNodeId << std::endl; + << "Node ID: " << (int)ethMsg->t1s->nodeId << std::endl; } else { stats.dataFrameCount++; @@ -226,7 +226,7 @@ void setupSymbolMonitoring(std::shared_ptr& device, << "[" << std::setw(12) << timestamp_ms << " ms] " << "T1S Data Frame | " << "Length: " << ethMsg->data.size() << " bytes | " - << "Node ID: " << (int)ethMsg->t1sNodeId << std::endl; + << "Node ID: " << (int)ethMsg->t1s->nodeId << std::endl; if (!ethMsg->data.empty()) { std::cout << " Data: "; diff --git a/examples/python/ethernet/ethernet_complete_example.py b/examples/python/ethernet/ethernet_complete_example.py index 29ad0c8..1701c04 100644 --- a/examples/python/ethernet/ethernet_complete_example.py +++ b/examples/python/ethernet/ethernet_complete_example.py @@ -47,14 +47,23 @@ def setup_ethernet_reception(device): def frame_handler(frame): nonlocal frame_count frame_count += 1 + dst = frame.get_destination_mac() + src = frame.get_source_mac() + et = frame.get_ether_type() + + dst_str = ":".join(f"{b:02x}" for b in dst) if dst is not None else "N/A" + src_str = ":".join(f"{b:02x}" for b in src) if src is not None else "N/A" + et_str = f"0x{et:04x}" if et is not None else "N/A" + print(f"[RX {frame_count}], " - f"Data: {[hex(b) for b in frame.data]}, " - f"Length: {len(frame.data)}") - frame_filter = icsneopy.MessageFilter(icsneopy.Network.NetID.ETHERNET_01) + f"dst={dst_str}, src={src_str}, ethertype={et_str}, " + f"Data: {[hex(b) for b in frame.data]}, " + f"Length: {len(frame.data)}") + frame_filter = icsneopy.MessageFilter(icsneopy.Network.NetID.ETHERNET_02) callback = icsneopy.MessageCallback(frame_handler, frame_filter) device.add_message_callback(callback) - print("CAN frame reception configured") + print("Ethernet frame reception configured") return 0 diff --git a/examples/python/t1s/t1s_settings.py b/examples/python/t1s/t1s_settings.py index 28e5517..576f66b 100644 --- a/examples/python/t1s/t1s_settings.py +++ b/examples/python/t1s/t1s_settings.py @@ -97,27 +97,27 @@ def display_t1s_settings(device, network): """Display T1S settings for a network.""" print(f"\t{network} T1S Settings:") - settings = device.get_settings() + settings = device.settings if not settings: print("\t Unable to read settings") return - print(f"\t PLCA Enabled: {opt_to_string(settings.get_t1s_plca_enabled(network))}") + print(f"\t PLCA Enabled: {opt_to_string(settings.is_t1s_plca_enabled(network))}") print(f"\t Local ID: {opt_to_string(settings.get_t1s_local_id(network))}") print(f"\t Max Nodes: {opt_to_string(settings.get_t1s_max_nodes(network))}") print(f"\t TX Opp Timer: {opt_to_string(settings.get_t1s_tx_opp_timer(network))}") print(f"\t Max Burst: {opt_to_string(settings.get_t1s_max_burst(network))}") print(f"\t Burst Timer: {opt_to_string(settings.get_t1s_burst_timer(network))}") - term_enabled = settings.get_t1s_termination_enabled(network) + term_enabled = settings.is_t1s_termination_enabled(network) if term_enabled is not None: print(f"\t Termination: {opt_to_string(term_enabled)}") local_id_alt = settings.get_t1s_local_id_alternate(network) if local_id_alt is not None: print(f"\t Local ID Alternate: {opt_to_string(local_id_alt)}") - print(f"\t Bus Dec Beacons: {opt_to_string(settings.get_t1s_bus_decoding_beacons_enabled(network))}") - print(f"\t Bus Dec All: {opt_to_string(settings.get_t1s_bus_decoding_all_enabled(network))}") + print(f"\t Bus Dec Beacons: {opt_to_string(settings.is_t1s_bus_decoding_beacons_enabled(network))}") + print(f"\t Bus Dec All: {opt_to_string(settings.is_t1s_bus_decoding_all_enabled(network))}") multi_id_mask = settings.get_t1s_multi_id_enable_mask(network) if multi_id_mask is not None: @@ -138,7 +138,7 @@ def configure_t1s_network(device, network): print(f"Configuring T1S Network: {network}") print("=" * 70) - settings = device.get_settings() + settings = device.settings if not settings: print("Unable to read settings") return @@ -162,7 +162,7 @@ def configure_t1s_network(device, network): burst_timer = get_uint16_input("Burst Timer (0-65535)", 64) settings.set_t1s_burst_timer(network, burst_timer) - if settings.get_t1s_termination_enabled(network) is not None: + if settings.is_t1s_termination_enabled(network) is not None: print("\n--- Termination Settings ---") term_enabled = get_user_confirmation("Enable Termination") settings.set_t1s_termination(network, term_enabled) @@ -187,10 +187,7 @@ def configure_t1s_network(device, network): multi_id = get_uint8_input(f" Multi-ID [{i}]", 0) settings.set_t1s_multi_id(network, i, multi_id) - if not device.set_settings(settings): - print("✗ Failed to update device settings") - else: - print(f"\n✓ Configuration complete for {network}") + print(f"\n[OK] Configuration staged for {network}") def main(): @@ -217,7 +214,7 @@ def main(): device = None for d in devices: - if d.get_type() == icsneopy.DeviceType.RADComet3: + if d.get_type().get_device_type() == icsneopy.DeviceType.Enum.RADComet3: device = d break @@ -233,24 +230,17 @@ def main(): print("\nOpening device... ", end="", flush=True) if not device.open(): - print("✗ Failed") + print("FAIL") return 1 - print("✓") + print("OK") - candidate_networks = [ - icsneopy.Network.NetID.AE_01, icsneopy.Network.NetID.AE_02, - icsneopy.Network.NetID.AE_03, icsneopy.Network.NetID.AE_04, - icsneopy.Network.NetID.AE_05, icsneopy.Network.NetID.AE_06, - icsneopy.Network.NetID.AE_07, icsneopy.Network.NetID.AE_08, - icsneopy.Network.NetID.AE_09, icsneopy.Network.NetID.AE_10 - ] - - settings = device.get_settings() + settings = device.settings t1s_networks = [] - for net_id in candidate_networks: - local_id = settings.get_t1s_local_id(net_id) - if local_id is not None: - t1s_networks.append(net_id) + for net in device.get_supported_tx_networks(): + if net.get_type() != icsneopy.Network.Type.AutomotiveEthernet: + continue + if settings.get_t1s_local_id(net) is not None: + t1s_networks.append(net) if not t1s_networks: print("No T1S networks found on this device") @@ -273,7 +263,7 @@ def main(): print("\nNo networks selected for configuration.") print("Closing device... ", end="", flush=True) device.close() - print("✓") + print("OK") return 0 print(f"\nConfiguring {len(networks_to_config)} network{'s' if len(networks_to_config) != 1 else ''}...") @@ -285,14 +275,14 @@ def main(): save_to_eeprom = get_user_confirmation("Save settings to EEPROM (permanent)?") print("=" * 70) - settings = device.get_settings() + settings = device.settings print(f"\nApplying settings{' to EEPROM' if save_to_eeprom else ' temporarily'}... ", end="", flush=True) success = settings.apply(not save_to_eeprom) if not success: - print("✗ Failed") + print("FAIL") device.close() return 1 - print("✓") + print("OK") print("\n" + "-" * 70) print("Updated T1S Settings:") @@ -302,7 +292,7 @@ def main(): print("Closing device... ", end="", flush=True) device.close() - print("✓") + print("OK") except KeyboardInterrupt: print("\n\nInterrupted by user") diff --git a/examples/python/t1s/t1s_symbol_decoding.py b/examples/python/t1s/t1s_symbol_decoding.py index 2a1127d..e834aa1 100644 --- a/examples/python/t1s/t1s_symbol_decoding.py +++ b/examples/python/t1s/t1s_symbol_decoding.py @@ -38,7 +38,7 @@ def get_user_confirmation(prompt): def configure_t1s_decoding(device, network, enable_symbols, enable_beacons): """Configure T1S bus decoding settings.""" - settings = device.get_settings() + settings = device.settings if not settings: raise RuntimeError("Failed to get device settings") @@ -46,21 +46,15 @@ def configure_t1s_decoding(device, network, enable_symbols, enable_beacons): if not settings.set_t1s_bus_decoding_all(network, enable_symbols): raise RuntimeError("Failed to set T1S symbol decoding") - if enable_symbols: - print(" ✓ Enabled decoding of all T1S symbols") - else: - print(" • T1S symbol decoding disabled") + print(f" [{'X' if enable_symbols else ' '}] Decoding of all T1S symbols") if not settings.set_t1s_bus_decoding_beacons(network, enable_beacons): raise RuntimeError("Failed to set T1S beacon decoding") - if enable_beacons: - print(" ✓ Enabled T1S beacon decoding") - else: - print(" • T1S beacon decoding disabled") + print(f" [{'X' if enable_beacons else ' '}] T1S beacon decoding") - if not device.set_settings(settings): + if not settings.apply(True): raise RuntimeError("Failed to apply settings to device") - print(" ✓ Settings applied successfully") + print(" [OK] Settings applied successfully") def setup_symbol_monitoring(device, network): @@ -79,18 +73,18 @@ def setup_symbol_monitoring(device, network): if not isinstance(msg, icsneopy.EthernetMessage): return - if not msg.isT1S: + if not msg.t1s: return timestamp_ms = msg.timestamp / 1000000.0 - if msg.isT1SSymbol: + if msg.t1s.isSymbol: num_symbols = len(msg.data) print(f"[{timestamp_ms:12.3f} ms] T1S Symbols", end="") if num_symbols > 0: print(f" ({num_symbols} symbol{'s' if num_symbols > 1 else ''})", end="") - print(f" | Node ID: {msg.t1sNodeId}") + print(f" | Node ID: {msg.t1s.nodeId}") for i, symbol_value in enumerate(msg.data): symbol_name = T1SSymbol.get_name(symbol_value) @@ -105,8 +99,8 @@ def setup_symbol_monitoring(device, network): print(f" [{i}] {symbol_name:10s} = 0x{symbol_value:02X}") - if num_symbols == 0 and msg.t1sSymbolType != 0: - symbol_value = msg.t1sSymbolType + if num_symbols == 0 and msg.t1s.symbolType != 0: + symbol_value = msg.t1s.symbolType symbol_name = T1SSymbol.get_name(symbol_value) state['symbol_count'] += 1 @@ -119,22 +113,22 @@ def setup_symbol_monitoring(device, network): print(f" {symbol_name:10s} = 0x{symbol_value:02X} (from t1sSymbolType field)") - elif msg.isT1SBurst: + elif msg.t1s.isBurst: state['burst_count'] += 1 print(f"[{timestamp_ms:12.3f} ms] BURST | " - f"Node ID: {msg.t1sNodeId} | " - f"Burst Count: {msg.t1sBurstCount}") + f"Node ID: {msg.t1s.nodeId} | " + f"Burst Count: {msg.t1s.burstCount}") - elif msg.isT1SWake: + elif msg.t1s.isWake: state['wake_count'] += 1 print(f"[{timestamp_ms:12.3f} ms] WAKE signal detected | " - f"Node ID: {msg.t1sNodeId}") + f"Node ID: {msg.t1s.nodeId}") else: state['data_frame_count'] += 1 print(f"[{timestamp_ms:12.3f} ms] T1S Data Frame | " f"Length: {len(msg.data)} bytes | " - f"Node ID: {msg.t1sNodeId}") + f"Node ID: {msg.t1s.nodeId}") if msg.data and len(msg.data) > 0: preview = ' '.join([f"{b:02X}" for b in msg.data[:16]]) @@ -142,7 +136,7 @@ def setup_symbol_monitoring(device, network): preview += " ..." print(f" Data: {preview}") - frame_filter = icsneopy.MessageFilter(network) + frame_filter = icsneopy.MessageFilter(network.get_net_id()) callback = icsneopy.MessageCallback(symbol_handler, frame_filter) device.add_message_callback(callback) @@ -175,7 +169,6 @@ def main(): device = None try: - MONITOR_NETWORK = icsneopy.Network.NetID.AE_02 MONITOR_DURATION = 30 print("\n" + "=" * 70) @@ -197,7 +190,7 @@ def main(): device = None for d in devices: - if d.get_type() == icsneopy.DeviceType.RADComet3: + if d.get_type().get_device_type() == icsneopy.DeviceType.Enum.RADComet3: device = d break @@ -220,28 +213,44 @@ def main(): print("\nOpening device... ", end="", flush=True) if not device.open(): - print("✗ Failed") + print("FAIL") return 1 - print("✓") + print("OK") print("Enabling message polling... ", end="", flush=True) if not device.enable_message_polling(): - print("✗ Failed") + print("FAIL") device.close() return 1 device.set_polling_message_limit(100000) - print("✓") + print("OK") - configure_t1s_decoding(device, MONITOR_NETWORK, enable_symbols, enable_beacons) + monitor_network = None + settings = device.settings + for net in device.get_supported_rx_networks(): + if net.get_type() != icsneopy.Network.Type.AutomotiveEthernet: + continue + if settings.get_t1s_local_id(net) is not None: + monitor_network = net + break + + if monitor_network is None: + print("No T1S network found on this device") + device.close() + return 1 + + print(f"Monitoring network: {monitor_network}") + + configure_t1s_decoding(device, monitor_network, enable_symbols, enable_beacons) print("Going online... ", end="", flush=True) if not device.go_online(): - print("✗ Failed") + print("FAIL") device.close() return 1 - print("✓") + print("OK") - state = setup_symbol_monitoring(device, MONITOR_NETWORK) + state = setup_symbol_monitoring(device, monitor_network) print("\n" + "-" * 70) print(f"Monitoring T1S traffic for {MONITOR_DURATION} seconds...") @@ -256,7 +265,7 @@ def main(): print("Closing device... ", end="", flush=True) device.close() time.sleep(0.1) - print("✓") + print("OK") print_statistics(state) diff --git a/include/icsneo/communication/message/ethernetmessage.h b/include/icsneo/communication/message/ethernetmessage.h index 8d0f09f..5e1203a 100644 --- a/include/icsneo/communication/message/ethernetmessage.h +++ b/include/icsneo/communication/message/ethernetmessage.h @@ -4,63 +4,89 @@ #ifdef __cplusplus #include "icsneo/communication/message/message.h" +#include #include #include #include #include #include +#include namespace icsneo { -struct MACAddress { - uint8_t data[6]; - - std::string toString() const { - std::stringstream ss; - for(size_t i = 0; i < 6; i++) { - ss << std::hex << std::setw(2) << std::setfill('0') << (int)data[i]; - if(i != 5) - ss << ':'; - } - return ss.str(); - } - - friend std::ostream& operator<<(std::ostream& os, const MACAddress& mac) { - os << mac.toString(); - return os; - } -}; +using MACAddress = std::array; class EthernetMessage : public Frame { public: // Standard Ethernet fields - bool preemptionEnabled = false; - uint8_t preemptionFlags = 0; + // Frame Check Sequence std::optional fcs; bool frameTooShort = false; bool noPadding = false; bool fcsVerified = false; bool txAborted = false; bool crcError = false; - bool isT1S = false; - - bool isT1SSymbol = false; - bool isT1SBurst = false; - bool txCollision = false; - bool isT1SWake = false; - uint8_t t1sNodeId = 0; - uint8_t t1sBurstCount = 0; - uint8_t t1sSymbolType = 0; + // T1S-specific fields + struct T1S { + T1S() {} - // Accessors - const MACAddress& getDestinationMAC() const { return *(const MACAddress*)(data.data() + 0); } - const MACAddress& getSourceMAC() const { return *(const MACAddress*)(data.data() + 6); } - uint16_t getEtherType() const { return (data[12] << 8) | data[13]; } + bool isSymbol = false; + bool isBurst = false; + bool txCollision = false; + bool isWake = false; + uint8_t nodeId = 0; + uint8_t burstCount = 0; + uint8_t symbolType = 0; + }; + std::optional t1s; + + // TSN-specific fields + // If we expand TSN we should probably do something similar to what we did above with T1S. + // IEEE 802.1Qbu frame preemption + std::optional preemptionFlags; + + // Helper functions to extract Destination MAC from the data payload + // returns std::nullopt if the data payload is not large enough + std::optional getDestinationMAC() const { + if(data.size() < 6) { + return std::nullopt; + } + MACAddress mac; + std::copy(data.begin(), data.begin() + 6, mac.begin()); + return mac; + } + + // Helper functions to extract Source MAC from the data payload + // returns std::nullopt if the data payload is not large enough + std::optional getSourceMAC() const { + if(data.size() < 12) { + return std::nullopt; + } + MACAddress mac; + std::copy(data.begin() + 6, data.begin() + 12, mac.begin()); + return mac; + } + + // Helper function to extract EtherType from the data payload + // + // EtherType is a two-octet field in an Ethernet frame (big-endian). + // It is used to indicate which protocol is encapsulated in the payload of the frame + // and is used at the receiving end by the data link layer to determine how the payload is processed. + // For example, an EtherType of 0x0800 indicates that the payload is an IPv4 packet, while 0x86DD indicates an IPv6 packet. + // + // returns std::nullopt if the data payload is not large enough + std::optional getEtherType() const { + if(data.size() < 14) { + return std::nullopt; + } + // EtherType is stored in a 2-byte network byte order (big-endian) + return static_cast((uint16_t(data[12]) << 8) | uint16_t(data[13])); + } }; } #endif // __cplusplus -#endif \ No newline at end of file +#endif // __ETHERNETMESSAGE_H_ diff --git a/include/icsneo/communication/packet/ethernetpacket.h b/include/icsneo/communication/packet/ethernetpacket.h index 2cbd75d..c15c165 100644 --- a/include/icsneo/communication/packet/ethernetpacket.h +++ b/include/icsneo/communication/packet/ethernetpacket.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace icsneo { diff --git a/include/icsneo/icsneoc2.h b/include/icsneo/icsneoc2.h index 0cc4cce..a041a24 100644 --- a/include/icsneo/icsneoc2.h +++ b/include/icsneo/icsneoc2.h @@ -44,6 +44,7 @@ typedef enum _icsneoc2_error_t { icsneoc2_error_script_load_prepare_failed, // Failed to prepare script load icsneoc2_error_close_failed, // Failed to close device icsneoc2_error_reconnect_failed, // Failed to reconnect to device + icsneoc2_error_invalid_data, // Failed to get/set data due to invalid data pointer or size // NOTE: Any new values added here should be updated in icsneoc2_error_code_get icsneoc2_error_maxsize } _icsneoc2_error_t; diff --git a/include/icsneo/icsneoc2messages.h b/include/icsneo/icsneoc2messages.h index c22b83e..04ff601 100644 --- a/include/icsneo/icsneoc2messages.h +++ b/include/icsneo/icsneoc2messages.h @@ -142,6 +142,126 @@ icsneoc2_error_t icsneoc2_message_can_props_set(icsneoc2_message_t* message, con */ icsneoc2_error_t icsneoc2_message_can_props_get(icsneoc2_message_t* message, uint64_t* arb_id, icsneoc2_message_can_flags_t* flags); +/** + * Create Ethernet message + * + * @param[out] message Pointer to icsneoc2_message_t to copy the message into. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_message_eth_create(icsneoc2_message_t** message); + +// Standard Ethernet frame flags +#define ICSNEOC2_MESSAGE_ETH_FLAGS_FRAME_TOO_SHORT 0x001 +#define ICSNEOC2_MESSAGE_ETH_FLAGS_NO_PADDING 0x002 +#define ICSNEOC2_MESSAGE_ETH_FLAGS_FCS_VERIFIED 0x004 +#define ICSNEOC2_MESSAGE_ETH_FLAGS_TX_ABORTED 0x008 +#define ICSNEOC2_MESSAGE_ETH_FLAGS_CRC_ERROR 0x010 +#define ICSNEOC2_MESSAGE_ETH_FLAGS_IS_T1S 0x020 +#define ICSNEOC2_MESSAGE_ETH_FLAGS_PREEMPTION_ENABLED 0x040 + +typedef uint64_t icsneoc2_message_eth_flags_t; + +// T1S-specific Ethernet frame flags +#define ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_SYMBOL 0x002 +#define ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_BURST 0x004 +#define ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_TX_COLLISION 0x008 +#define ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_WAKE 0x010 + +typedef uint64_t icsneoc2_message_eth_t1s_flags_t; + +/** + * Set the Ethernet specific properties of a message + * + * @param[in] message The message to modify. + * @param[in] flags Pointer to a icsneoc2_message_eth_flags_t containing the flags to set. If NULL, flags are not modified. + * @param[in] has_fcs Pointer to a bool indicating whether the FCS is present. If NULL, it's ignored. + * @param[in] fcs Pointer to a uint32_t containing the FCS value. If NULL, the FCS is not modified. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_invalid_type otherwise. + */ +icsneoc2_error_t icsneoc2_message_eth_props_set(icsneoc2_message_t* message, const icsneoc2_message_eth_flags_t* flags, const bool* has_fcs, const uint32_t* fcs); + +/** + * Get the Ethernet specific properties of a message + * + * @param[in] message The message to check. + * @param[out] flags Pointer to a icsneoc2_message_eth_flags_t to copy the flags into. If NULL, it's ignored. + * @param[out] has_fcs Pointer to a bool indicating whether the FCS is present. If NULL, it's ignored. + * @param[out] fcs Pointer to a uint32_t to copy the FCS value into. Only valid if has_fcs is true, set to 0 if FCS is not present. If NULL, it's ignored. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_invalid_type otherwise. + */ +icsneoc2_error_t icsneoc2_message_eth_props_get(icsneoc2_message_t* message, icsneoc2_message_eth_flags_t* flags, bool* has_fcs, uint32_t* fcs); + +/** + * Get the destination and/or source MAC address from an Ethernet message. + * The MAC addresses are extracted from the message data bytes. + * + * @param[in] message The message to check. + * @param[out] dst_mac Pointer to a 6-byte buffer to copy the destination MAC into. If NULL, it's ignored. + * @param[out] src_mac Pointer to a 6-byte buffer to copy the source MAC into. If NULL, it's ignored. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_invalid_type otherwise. + */ +icsneoc2_error_t icsneoc2_message_eth_mac_get(icsneoc2_message_t* message, uint8_t* dst_mac, uint8_t* src_mac); + +/** + * Helper function to get the EtherType field from an Ethernet message payload. + * + * EtherType is a two-octet field in an Ethernet frame (big-endian). + * It is used to indicate which protocol is encapsulated in the payload of the frame + * and is used at the receiving end by the data link layer to determine how the payload is processed. + * For example, an EtherType of 0x0800 indicates that the payload is an IPv4 packet, while 0x86DD indicates an IPv6 packet. + * + * @param[in] message The message to check. + * @param[out] ether_type Pointer to a uint16_t to copy the EtherType into. + * + * @note The EtherType is extracted from the message data bytes, so the message must have the data field and it must be + * large enough to contain the EtherType (at least 14 bytes). Returned value is host byte order. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters, icsneoc2_error_invalid_type, icsneoc2_error_invalid_data otherwise. + */ +icsneoc2_error_t icsneoc2_message_eth_ether_type_get(icsneoc2_message_t* message, uint16_t* ether_type); + +/** + * Set the T1S specific properties of an Ethernet message + * + * @param[in] message The message to modify. + * @param[in] flags Pointer to a icsneoc2_message_eth_t1s_flags_t containing the T1S flags to set. If NULL, flags are not modified. + * @param[in] node_id Pointer to a uint8_t containing the T1S node ID. If NULL, it's ignored. + * @param[in] burst_count Pointer to a uint8_t containing the T1S burst count. If NULL, it's ignored. + * @param[in] symbol_type Pointer to a uint8_t containing the T1S symbol type. If NULL, it's ignored. + * + * @note If all four optional parameters are NULL, the T1S-specific state is cleared. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_invalid_type otherwise. + */ +icsneoc2_error_t icsneoc2_message_eth_t1s_props_set(icsneoc2_message_t* message, const icsneoc2_message_eth_t1s_flags_t* flags, const uint8_t* node_id, const uint8_t* burst_count, const uint8_t* symbol_type); + +/** + * Get the T1S specific properties of an Ethernet message + * + * @param[in] message The message to check. + * @param[out] flags Pointer to a icsneoc2_message_eth_t1s_flags_t to copy the T1S flags into. If NULL, it's ignored. + * @param[out] node_id Pointer to a uint8_t to copy the T1S node ID into. If NULL, it's ignored. + * @param[out] burst_count Pointer to a uint8_t to copy the T1S burst count into. If NULL, it's ignored. + * @param[out] symbol_type Pointer to a uint8_t to copy the T1S symbol type into. If NULL, it's ignored. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_invalid_type otherwise. + */ +icsneoc2_error_t icsneoc2_message_eth_t1s_props_get(icsneoc2_message_t* message, icsneoc2_message_eth_t1s_flags_t* flags, uint8_t* node_id, uint8_t* burst_count, uint8_t* symbol_type); + +/** + * Check if a message is an Ethernet message + * + * @param[in] message The message to check. + * @param[out] is_ethernet Pointer to a bool to copy the Ethernet status of the message into. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_message_is_ethernet(icsneoc2_message_t* message, bool* is_ethernet); + /** * Check if a message is valid * diff --git a/include/icsneo/icsneoc2settings.h b/include/icsneo/icsneoc2settings.h index 133420d..0d606f9 100644 --- a/include/icsneo/icsneoc2settings.h +++ b/include/icsneo/icsneoc2settings.h @@ -456,6 +456,28 @@ icsneoc2_error_t icsneoc2_settings_t1s_tx_opp_timer_get(icsneoc2_device_t* devic */ icsneoc2_error_t icsneoc2_settings_t1s_tx_opp_timer_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t value); +/** + * Get the burst timer for a network that supports 10BASE-T1S. + * + * @param[in] device The device to check. + * @param[in] netid The network ID to check. + * @param[out] value Pointer to store the burst timer value. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_burst_timer_get(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t* value); + +/** + * Set the burst timer for a network that supports 10BASE-T1S. + * + * @param[in] device The device to configure. + * @param[in] netid The network ID to configure. + * @param[in] value The burst timer value to set. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_burst_timer_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t value); + /** * Get the Max burst timer for a network that supports 10BASE-T1S. * @@ -478,6 +500,140 @@ icsneoc2_error_t icsneoc2_settings_t1s_max_burst_timer_for_get(icsneoc2_device_t */ icsneoc2_error_t icsneoc2_settings_t1s_max_burst_timer_for_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t value); +/** + * Get the alternate local ID for a network that supports 10BASE-T1S. + * + * @param[in] device The device to check. + * @param[in] netid The network ID to check. + * @param[out] value Pointer to store the alternate local ID. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_local_id_alternate_get(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t* value); + +/** + * Set the alternate local ID for a network that supports 10BASE-T1S. + * + * @param[in] device The device to configure. + * @param[in] netid The network ID to configure. + * @param[in] value The alternate local ID to set. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_local_id_alternate_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t value); + +/** + * Check if T1S termination is enabled for a network that supports 10BASE-T1S. + * + * @param[in] device The device to check. + * @param[in] netid The network ID to check. + * @param[out] value Pointer to store the termination enable state. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_is_termination_enabled_for(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool* value); + +/** + * Enable or disable T1S termination for a network that supports 10BASE-T1S. + * + * @param[in] device The device to configure. + * @param[in] netid The network ID to configure. + * @param[in] value True to enable termination, false to disable. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_termination_for_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool value); + +/** + * Check if T1S bus decoding beacons are enabled for a network that supports 10BASE-T1S. + * + * @param[in] device The device to check. + * @param[in] netid The network ID to check. + * @param[out] value Pointer to store the bus decoding beacons enable state. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_is_bus_decoding_beacons_enabled_for(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool* value); + +/** + * Enable or disable T1S bus decoding beacons for a network that supports 10BASE-T1S. + * + * @param[in] device The device to configure. + * @param[in] netid The network ID to configure. + * @param[in] value True to enable bus decoding beacons, false to disable. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_bus_decoding_beacons_for_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool value); + +/** + * Check if T1S bus decoding all is enabled for a network that supports 10BASE-T1S. + * + * @param[in] device The device to check. + * @param[in] netid The network ID to check. + * @param[out] value Pointer to store the bus decoding all enable state. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_is_bus_decoding_all_enabled_for(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool* value); + +/** + * Enable or disable T1S bus decoding all for a network that supports 10BASE-T1S. + * + * @param[in] device The device to configure. + * @param[in] netid The network ID to configure. + * @param[in] value True to enable bus decoding all, false to disable. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_bus_decoding_all_for_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool value); + +/** + * Get the multi-ID enable mask for a network that supports 10BASE-T1S. + * + * @param[in] device The device to check. + * @param[in] netid The network ID to check. + * @param[out] value Pointer to store the multi-ID enable mask. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_multi_id_enable_mask_get(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t* value); + +/** + * Set the multi-ID enable mask for a network that supports 10BASE-T1S. + * + * @param[in] device The device to configure. + * @param[in] netid The network ID to configure. + * @param[in] value The multi-ID enable mask to set. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_multi_id_enable_mask_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t value); + +/** + * Get a multi-ID entry for a network that supports 10BASE-T1S. + * + * @param[in] device The device to check. + * @param[in] netid The network ID to check. + * @param[in] index The multi-ID index to get (0-6). + * @param[out] value Pointer to store the multi-ID value. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_multi_id_get(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t index, uint8_t* value); + +/** + * Set a multi-ID entry for a network that supports 10BASE-T1S. + * + * @param[in] device The device to configure. + * @param[in] netid The network ID to configure. + * @param[in] index The multi-ID index to set (0-6). + * @param[in] value The multi-ID value to set. + * + * @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise. + */ +icsneoc2_error_t icsneoc2_settings_t1s_multi_id_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, uint8_t index, uint8_t value); + /** * Set the analog output enabled. * diff --git a/test/unit/icsneoc2.cpp b/test/unit/icsneoc2.cpp index 715e94e..279d78b 100644 --- a/test/unit/icsneoc2.cpp +++ b/test/unit/icsneoc2.cpp @@ -232,6 +232,20 @@ TEST(icsneoc2, test_icsneoc2_error_invalid_parameters_and_invalid_device) ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_tx_opp_timer_set(NULL, 0, placeholderInteger8)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_max_burst_timer_for_get(NULL, 0, &placeholderInteger8)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_max_burst_timer_for_set(NULL, 0, placeholderInteger8)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_burst_timer_get(NULL, 0, &placeholderInteger8)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_burst_timer_set(NULL, 0, placeholderInteger8)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_local_id_alternate_get(NULL, 0, &placeholderInteger8)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_local_id_alternate_set(NULL, 0, placeholderInteger8)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_is_termination_enabled_for(NULL, 0, &placeholderBool)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_termination_for_set(NULL, 0, false)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_is_bus_decoding_beacons_enabled_for(NULL, 0, &placeholderBool)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_bus_decoding_beacons_for_set(NULL, 0, false)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_is_bus_decoding_all_enabled_for(NULL, 0, &placeholderBool)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_bus_decoding_all_for_set(NULL, 0, false)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_multi_id_enable_mask_get(NULL, 0, &placeholderInteger8)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_multi_id_enable_mask_set(NULL, 0, placeholderInteger8)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_multi_id_get(NULL, 0, 0, &placeholderInteger8)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_t1s_multi_id_set(NULL, 0, 0, placeholderInteger8)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_misc_io_analog_output_enabled_set(NULL, 0, placeholderInteger8)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_misc_io_analog_output_set(NULL, 0, placeholderMiscIoAnalogVoltage)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_disabled_get(NULL, &placeholderBool)); @@ -303,6 +317,16 @@ TEST(icsneoc2, test_icsneoc2_error_invalid_parameters_and_invalid_device) ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_script_status_diagnostic_error_code_get(NULL, &placeholderInteger8)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_script_status_diagnostic_error_code_count_get(NULL, &placeholderInteger8)); ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_script_status_max_coremini_size_kb_get(NULL, &placeholderInteger16)); + + // Ethernet message functions + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_eth_create(NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_eth_props_set(NULL, NULL, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_eth_props_get(NULL, NULL, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_eth_mac_get(NULL, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_eth_ether_type_get(NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_eth_t1s_props_set(NULL, NULL, NULL, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_eth_t1s_props_get(NULL, NULL, NULL, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_is_ethernet(NULL, NULL)); } TEST(icsneoc2, test_icsneoc2_devicetype_t) @@ -1036,3 +1060,228 @@ TEST(icsneoc2, test_lin_flag_bitmask_values) ASSERT_EQ(ICSNEOC2_LIN_STATUS_BREAK_ONLY, 0x80); } +TEST(icsneoc2, test_icsneoc2_eth_create) +{ + // NULL parameter should fail + ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_message_eth_create(NULL)); + + // Create an Ethernet message + icsneoc2_message_t* message = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_create(&message)); + ASSERT_NE(message, nullptr); + + // Verify it is an Ethernet message + bool is_ethernet = false; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_is_ethernet(message, &is_ethernet)); + ASSERT_TRUE(is_ethernet); + + // Verify it is NOT a CAN message + bool is_can = false; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_is_can(message, &is_can)); + ASSERT_FALSE(is_can); + + // Verify it is a frame and raw message + bool is_frame = false; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_is_frame(message, &is_frame)); + ASSERT_TRUE(is_frame); + + bool is_raw = false; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_is_raw(message, &is_raw)); + ASSERT_TRUE(is_raw); + + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_free(message)); +} + +TEST(icsneoc2, test_icsneoc2_eth_props_roundtrip) +{ + icsneoc2_message_t* message = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_create(&message)); + + // Set several flags + icsneoc2_message_eth_flags_t flags_in = + ICSNEOC2_MESSAGE_ETH_FLAGS_NO_PADDING | + ICSNEOC2_MESSAGE_ETH_FLAGS_FCS_VERIFIED | + ICSNEOC2_MESSAGE_ETH_FLAGS_TX_ABORTED; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_props_set(message, &flags_in, NULL, NULL)); + + // Get them back + icsneoc2_message_eth_flags_t flags_out = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_props_get(message, &flags_out, NULL, NULL)); + ASSERT_EQ(flags_in, flags_out); + + // Clear all flags + icsneoc2_message_eth_flags_t flags_zero = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_props_set(message, &flags_zero, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_props_get(message, &flags_out, NULL, NULL)); + ASSERT_EQ(0u, flags_out); + + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_free(message)); +} + +TEST(icsneoc2, test_icsneoc2_eth_mac_and_ethertype) +{ + icsneoc2_message_t* message = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_create(&message)); + + // Set frame data: dst MAC + src MAC + EtherType + uint8_t frame_data[] = { + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, /* Destination MAC */ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, /* Source MAC */ + 0x08, 0x00, /* EtherType (IPv4) */ + 0x01, 0x02, 0x03, 0x04 /* Payload */ + }; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_data_set(message, frame_data, sizeof(frame_data))); + + // Get MAC addresses + uint8_t dst_mac[6] = {0}; + uint8_t src_mac[6] = {0}; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_mac_get(message, dst_mac, src_mac)); + ASSERT_EQ(0, memcmp(dst_mac, frame_data, 6)); + ASSERT_EQ(0, memcmp(src_mac, frame_data + 6, 6)); + + // Get just one MAC at a time (NULL-safe) + uint8_t dst_only[6] = {0}; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_mac_get(message, dst_only, NULL)); + ASSERT_EQ(0, memcmp(dst_only, frame_data, 6)); + + uint8_t src_only[6] = {0}; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_mac_get(message, NULL, src_only)); + ASSERT_EQ(0, memcmp(src_only, frame_data + 6, 6)); + + // Get EtherType + uint16_t ether_type = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_ether_type_get(message, ðer_type)); + ASSERT_EQ(0x0800, ether_type); + + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_free(message)); +} + +TEST(icsneoc2, test_icsneoc2_eth_mac_too_short) +{ + icsneoc2_message_t* message = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_create(&message)); + + // Set data too short for MAC extraction (< 14 bytes) + uint8_t short_data[] = {0x01, 0x02, 0x03}; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_data_set(message, short_data, sizeof(short_data))); + + uint8_t dst_mac[6] = {0}; + ASSERT_EQ(icsneoc2_error_invalid_data, icsneoc2_message_eth_mac_get(message, dst_mac, NULL)); + + uint8_t src_mac[6] = {0}; + ASSERT_EQ(icsneoc2_error_invalid_data, icsneoc2_message_eth_mac_get(message, NULL, src_mac)); + + uint16_t ether_type = 0; + ASSERT_EQ(icsneoc2_error_invalid_data, icsneoc2_message_eth_ether_type_get(message, ðer_type)); + + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_free(message)); +} + +TEST(icsneoc2, test_icsneoc2_eth_t1s_props_roundtrip) +{ + icsneoc2_message_t* message = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_create(&message)); + + // Set T1S properties + icsneoc2_message_eth_t1s_flags_t flags_in = + ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_IS_T1S_SYMBOL | + ICSNEOC2_MESSAGE_ETH_T1S_FLAGS_TX_COLLISION; + uint8_t node_id = 42; + uint8_t burst_count = 7; + uint8_t symbol_type = 3; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_t1s_props_set(message, &flags_in, &node_id, &burst_count, &symbol_type)); + + // Get them back + icsneoc2_message_eth_t1s_flags_t flags_out = 0; + uint8_t node_id_out = 0, burst_count_out = 0, symbol_type_out = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_t1s_props_get(message, &flags_out, &node_id_out, &burst_count_out, &symbol_type_out)); + ASSERT_EQ(flags_in, flags_out); + ASSERT_EQ(42, node_id_out); + ASSERT_EQ(7, burst_count_out); + ASSERT_EQ(3, symbol_type_out); + + // Set just one at a time (NULL-safe) + uint8_t new_node_id = 99; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_t1s_props_set(message, NULL, &new_node_id, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_t1s_props_get(message, &flags_out, &node_id_out, &burst_count_out, &symbol_type_out)); + ASSERT_EQ(flags_in, flags_out); + ASSERT_EQ(99, node_id_out); + ASSERT_EQ(7, burst_count_out); // Unchanged + ASSERT_EQ(3, symbol_type_out); // Unchanged + + // Passing all NULL parameters clears the optional T1S state. + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_t1s_props_set(message, NULL, NULL, NULL, NULL)); + flags_out = 0xFF; + node_id_out = 0xFF; + burst_count_out = 0xFF; + symbol_type_out = 0xFF; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_t1s_props_get(message, &flags_out, &node_id_out, &burst_count_out, &symbol_type_out)); + ASSERT_EQ(0, flags_out); + ASSERT_EQ(0, node_id_out); + ASSERT_EQ(0, burst_count_out); + ASSERT_EQ(0, symbol_type_out); + + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_free(message)); +} + +TEST(icsneoc2, test_icsneoc2_eth_fcs_roundtrip) +{ + icsneoc2_message_t* message = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_create(&message)); + + // Initially, FCS should not be set + bool has_fcs = true; + uint32_t fcs = 0; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_props_get(message, NULL, &has_fcs, &fcs)); + ASSERT_FALSE(has_fcs); + + // Set an FCS value via eth_props_set + uint32_t fcs_value = 0xDEADBEEF; + has_fcs = true; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_props_set(message, NULL, &has_fcs, &fcs_value)); + + // Get it back + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_props_get(message, NULL, &has_fcs, &fcs)); + ASSERT_TRUE(has_fcs); + ASSERT_EQ(0xDEADBEEF, fcs); + + // Clear FCS + has_fcs = false; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_props_set(message, NULL, &has_fcs, NULL)); + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_eth_props_get(message, NULL, &has_fcs, &fcs)); + ASSERT_FALSE(has_fcs); + + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_free(message)); +} + +TEST(icsneoc2, test_icsneoc2_eth_invalid_type) +{ + // Create a CAN message and try to use Ethernet functions on it + icsneoc2_message_t* can_msg = nullptr; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_can_create(&can_msg)); + + bool is_ethernet = true; + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_is_ethernet(can_msg, &is_ethernet)); + ASSERT_FALSE(is_ethernet); + + icsneoc2_message_eth_flags_t flags = 0; + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_eth_props_get(can_msg, &flags, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_eth_props_set(can_msg, &flags, NULL, NULL)); + + uint8_t mac[6] = {0}; + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_eth_mac_get(can_msg, mac, NULL)); + + uint16_t ether_type = 0; + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_eth_ether_type_get(can_msg, ðer_type)); + + uint8_t val = 0; + icsneoc2_message_eth_t1s_flags_t t1s_flags = 0; + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_eth_t1s_props_get(can_msg, &t1s_flags, &val, NULL, NULL)); + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_eth_t1s_props_set(can_msg, &t1s_flags, &val, NULL, NULL)); + + ASSERT_EQ(icsneoc2_error_invalid_type, icsneoc2_message_eth_props_set(can_msg, NULL, NULL, NULL)); + + ASSERT_EQ(icsneoc2_error_success, icsneoc2_message_free(can_msg)); +} + +