Ethernet Service API
Using the Ethernet Controller
The Ethernet Service API provides an Ethernet bus abstraction through the IEthernetController
interface.
An Ethernet controller is created by calling CreateEthernetController
given a controller name and network
name:
auto* ethernetController = participant->CreateEthernetController("Eth1", "Eth");
Ethernet controllers will only communicate within the same network.
Initialization
The Ethernet controller first has to call Activate()
before being able to
send frames. Note that Activate()
can be called in the CommunicationReadyHandler
of a LifecycleService
.
Sending Ethernet Frames
An EthernetFrame
is sent with SendFrame()
and received as an EthernetFrameEvent
. The EthernetFrame
must be setup
according to layer 2 of IEEE 802.3, with
a source and destination MAC address (6 octets each),
an optional 802.1Q tag (4 octets),
the EtherType or length (Ethernet II or IEEE 802.3, 2 octets), and
the payload to be transmitted (46 octets or more).
Note
The frame check sequence (32-bit CRC, 4 octets) is omitted. Thus, the minimum length of a frame is 60 octets.
Note
If the frame is shorter than the minimum length of 60 octets, the frame will be padded with zeros to the minimum length.
A valid frame can be setup and sent as follows:
// Prepare an Ethernet frame
const std::array<uint8_t, 6> destinationAddress{ 0xf6, 0x04, 0x68, 0x71, 0xaa, 0xc2 };
const std::array<uint8_t, 6> sourceAddress{ 0xf6, 0x04, 0x68, 0x71, 0xaa, 0xc1 };
const uint16_t etherType{ 0x0800 };
const std::string message("Ensure that the payload is long enough to constitute"
" a valid Ethernet frame ----------------------------");
const std::vector<uint8_t> payload{ message.begin(), message.end() };
EthernetFrame frame{};
std::copy(destinationAddress.begin(), destinationAddress.end(), std::back_inserter(frame.raw));
std::copy(sourceAddress.begin(), sourceAddress.end(), std::back_inserter(frame.raw));
auto etherTypeBytes = reinterpret_cast<const uint8_t*>(ðerType);
frame.raw.push_back(etherTypeBytes[1]); // We assume our platform to be little-endian
frame.raw.push_back(etherTypeBytes[0]);
std::copy(payload.begin(), payload.end(), std::back_inserter(frame.raw));
ethernetController->SendFrame(frame);
Transmission Acknowledgement
To be notified of the success or failure of the transmission, a FrameTransmitHandler
can be registered using
AddFrameTransmitHandler()
:
auto frameTransmitHandler = [](IEthernetController*, const EthernetFrameTransmitEvent& frameTransmitEvent)
{
// Handle frameTransmitEvent
};
ethernetController->AddFrameTransmitHandler(frameTransmitHandler);
An optional second parameter of AddFrameTransmitHandler()
allows to specify the status (EthernetTransmitStatus::Transmitted
, …) of the
EthernetFrameTransmitEvent
to be received. By default, each status is enabled.
Note
In a simple simulation, the EthernetTransmitStatus
of the
EthernetFrameTransmitEvent
will always be EthernetTransmitStatus::Transmitted
.
Receiving Ethernet Frame Events
An EthernetFrame
is received as an EthernetFrameEvent
consisting of a transmitId
used to identify
the acknowledgment of the frame, a timestamp and the actual EthernetFrame
.
To receive Ethernet frames, a frame handler must be registered using AddFrameHandler()
. The handler is called whenever
an Ethernet frame is received:
auto frameHandler = [](IEthernetController*, const EthernetFrameEvent& frameEvent)
{
// Handle frameEvent
};
ethernetController->AddFrameHandler(frameHandler);
An optional second parameter of AddFrameHandler()
allows to specify the direction (Tx, Rx, Tx/Rx) of the Ethernet frames to be
received. By default, only frames of Rx direction are handled.
Managing the Event Handlers
Adding a handler will return a HandlerId
which can be used to remove the handler via:
Switches
Switches can be used in a detailed simulation. Refer to the documentation of the network simulator for further information.
Receiving State Change Events
To receive state changes of an Ethernet
controller, a StateChangeHandler
must be registered using AddStateChangeHandler()
:
auto stateChangedHandler = [](IEthernetController*, const EthernetStateChangeEvent& stateChangeEvent)
{
// Handle stateChangeEvent;
};
ethernetController->AddStateChangeHandler(stateChangedHandler);
Acknowledgements
When sending frames, the EthernetTransmitStatus
of the EthernetFrameTransmitEvent
received in the
FrameTransmitHandler
will be one of the following values:
EthernetTransmitStatus::Transmitted
: Transmission was successful.EthernetTransmitStatus::ControllerInactive
: The sending Ethernet controller tried to send a frame beforeActivate()
was called.EthernetTransmitStatus::LinkDown
:Activate()
has been called but the link to another Ethernet Controller has not yet been established.EthernetTransmitStatus::Dropped
: Indicates a transmit queue overflow.EthernetTransmitStatus::InvalidFrameFormat
: The Ethernet frame is invalid, e.g., too small or too large.
API and Data Type Reference
Ethernet Controller API
-
class IEthernetController
Abstract Ethernet Controller API to be used by vECUs.
Public Types
-
using CallbackT = std::function<void(IEthernetController *controller, const MsgT &msg)>
Generic Ethernet callback method.
-
using FrameHandler = CallbackT<EthernetFrameEvent>
Callback type to indicate that an EthernetFrameEvent has been received. Cf. AddFrameHandler(FrameHandler,DirectionMask);
-
using FrameTransmitHandler = CallbackT<EthernetFrameTransmitEvent>
Callback type to indicate that an EthernetFrameTransmitEvent has been received. Cf. AddFrameTransmitHandler(FrameTransmitHandler,EthernetTransmitStatusMask);
-
using StateChangeHandler = CallbackT<EthernetStateChangeEvent>
Callback type to indicate that the EthernetState has changed. Cf. AddStateChangeHandler(StateChangeHandler);
-
using BitrateChangeHandler = CallbackT<EthernetBitrateChangeEvent>
Callback type to indicate that the link bit rate has changed. Cf. AddBitrateChangeHandler(BitrateChangeHandler);
Public Functions
-
virtual ~IEthernetController() = default
-
virtual void Activate() = 0
Activates the Ethernet controller.
Upon activation of the controller, the controller attempts to establish a link. Messages can only be sent once the link has been successfully established, cf. AddStateChangeHandler() and AddBitrateChangeHandler().
-
virtual void Deactivate() = 0
Deactivate the Ethernet controller.
Deactivate the controller and shut down the link. The controller will no longer receive messages, and it cannot send messages anymore.
-
virtual auto AddFrameHandler(FrameHandler handler, DirectionMask directionMask = static_cast<DirectionMask>(TransmitDirection::RX)) -> HandlerId = 0
Register a callback for Ethernet message reception.
The handler is called when the controller receives a new Ethernet message.
- Returns
Returns a SilKit::Util::HandlerId that can be used to remove the callback.
-
virtual void RemoveFrameHandler(HandlerId handlerId) = 0
Remove a FrameHandler by SilKit::Util::HandlerId on this controller.
- Parameters
handlerId – Identifier of the callback to be removed. Obtained upon adding to respective handler.
-
virtual auto AddFrameTransmitHandler(FrameTransmitHandler handler, EthernetTransmitStatusMask transmitStatusMask = SilKit_EthernetTransmitStatus_DefaultMask) -> HandlerId = 0
Register a callback for Ethernet transmit acknowledgments.
The handler is called when a previously sent message was successfully transmitted or when the transmission has failed. The original message is identified by the transmitId.
NB: Full support in a detailed simulation. In a simple simulation, all messages are immediately positively acknowledged.
- Returns
Returns a SilKit::Util::HandlerId that can be used to remove the callback.
-
virtual void RemoveFrameTransmitHandler(HandlerId handlerId) = 0
Remove a FrameTransmitHandler by SilKit::Util::HandlerId on this controller.
- Parameters
handlerId – Identifier of the callback to be removed. Obtained upon adding to respective handler.
-
virtual auto AddStateChangeHandler(StateChangeHandler handler) -> HandlerId = 0
Register a callback for changes of the controller state.
The handler is called when the state of the controller changes. E.g., a call to Activate() causes the controller to change from state SilKit::Services::Ethernet::EthernetState::Inactive to SilKit::Services::Ethernet::EthernetState::LinkDown. Later, when the link has been established, the state changes again from SilKit::Services::Ethernet::EthernetState::LinkDown to SilKit::Services::Ethernet::EthernetState::LinkUp. Similarly, the status changes back to SilKit::Services::Ethernet::EthernetState::Inactive upon a call to Deactivate().
- Returns
Returns a SilKit::Util::HandlerId that can be used to remove the callback.
-
virtual void RemoveStateChangeHandler(HandlerId handlerId) = 0
Remove a StateChangeHandler by SilKit::Util::HandlerId on this controller.
- Parameters
handlerId – Identifier of the callback to be removed. Obtained upon adding to respective handler.
-
virtual auto AddBitrateChangeHandler(BitrateChangeHandler handler) -> HandlerId = 0
Register a callback for changes of the link bit rate.
The handler is called when the bit rate of the connected link changes. This is typically the case when a link was successfully established, or the controller was deactivated.
- Returns
Returns a SilKit::Util::HandlerId that can be used to remove the callback.
-
virtual void RemoveBitrateChangeHandler(HandlerId handlerId) = 0
Remove a BitrateChangeHandler by SilKit::Util::HandlerId on this controller.
- Parameters
handlerId – Identifier of the callback to be removed. Obtained upon adding to respective handler.
-
virtual void SendFrame(EthernetFrame msg, void *userContext = nullptr) = 0
Send an Ethernet frame with the time provider’s current time.
If the size of the Ethernet frame is smaller than the minimum size of 60 bytes (excludes the Frame Check Sequence), it will be padded with zeros.
NB: precise timestamps are always generated by the NetworkSimulator.
- Parameters
msg – The Ethernet frame to send.
userContext – Optional user provided pointer that is reobtained in the FrameTransmitHandler.
-
using CallbackT = std::function<void(IEthernetController *controller, const MsgT &msg)>
Data Structures
-
struct EthernetFrame
An Ethernet frame (layer 2)
Public Members
-
Util::Span<const uint8_t> raw
The Ethernet raw frame without the frame check sequence.
-
Util::Span<const uint8_t> raw
-
struct EthernetFrameEvent
An Ethernet frame including the raw frame, Transmit ID and timestamp.
Public Members
-
std::chrono::nanoseconds timestamp
Reception time.
-
EthernetFrame frame
The Ethernet frame.
-
TransmitDirection direction
Receive/Transmit direction.
-
void *userContext
Optional pointer provided by user when sending the frame.
-
std::chrono::nanoseconds timestamp
-
struct EthernetFrameTransmitEvent
Publishes status of the simulated Ethernet controller.
Public Members
-
std::chrono::nanoseconds timestamp
Timestamp of the Ethernet acknowledge.
-
EthernetTransmitStatus status
Status of the EthernetTransmitRequest.
-
void *userContext
Optional pointer provided by user when sending the frame.
-
std::chrono::nanoseconds timestamp
-
struct EthernetStateChangeEvent
A state change event of the Ethernet controller.
Public Members
-
std::chrono::nanoseconds timestamp
Timestamp of the state change.
-
EthernetState state
State of the Ethernet controller.
-
std::chrono::nanoseconds timestamp
-
struct EthernetBitrateChangeEvent
A bitrate change event of the Ethernet controller.
Public Members
-
std::chrono::nanoseconds timestamp
Timestamp of the state change.
-
EthernetBitrate bitrate
Bit rate in kBit/sec of the link when in state LinkUp, otherwise zero.
-
std::chrono::nanoseconds timestamp
Enumerations and Typedefs
-
using SilKit::Services::Ethernet::EthernetBitrate = uint32_t
Bitrate in kBit/sec.
-
enum SilKit::Services::Ethernet::EthernetTransmitStatus
Acknowledgment status for an EthernetTransmitRequest.
Values:
-
enumerator Transmitted
The message was successfully transmitted on the Ethernet link.
-
enumerator ControllerInactive
The transmit request was rejected, because the Ethernet controller is not active.
-
enumerator LinkDown
The transmit request was rejected, because the Ethernet link is down.
-
enumerator Dropped
The transmit request was dropped, because the transmit queue is full.
-
enumerator InvalidFrameFormat
The given raw Ethernet frame is ill formated (e.g. frame length is too small or too large, wrong order of VLAN tags).
-
enumerator Transmitted
-
enum SilKit::Services::Ethernet::EthernetState
State of the Ethernet controller.
Values:
-
enumerator Inactive
The Ethernet controller is switched off (default after reset).
-
enumerator LinkDown
The Ethernet controller is active, but a link to another Ethernet controller in not yet established.
-
enumerator LinkUp
The Ethernet controller is active and the link to another Ethernet controller is established.
-
enumerator Inactive
Usage Examples
This section contains complete examples that show the usage and the interaction of two Ethernet controllers. Although the Ethernet controllers would typically belong to different participants and reside in different processes, their interaction is shown sequentially to demonstrate cause and effect.
Assumptions:
Variables
ethernetReceiver
,ethernetSender
are of typeIEthernetController
.All Ethernet controllers are connected to the same switch.
Simple Ethernet Sender / Receiver Example
This example shows a successful data transfer from one Ethernet controller to another.
/* Copyright (c) 2022 Vector Informatik GmbH
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
// ------------------------------------------------------------
// Receiver Setup
// Register FrameHandler to receive Ethernet messages.
auto receiver_FrameHandler =
[](IEthernetController*, const EthernetFrameEvent&) {};
ethernetReceiver->AddFrameHandler(receiver_FrameHandler);
ethernetReceiver->Activate();
// ------------------------------------------------------------
// Sender Setup
// Register FrameTransmitHandler to receive acknowledges of transmissions.
auto sender_FrameTransmitHandler =
[](IEthernetController*, const EthernetFrameTransmitEvent&) {};
ethernetSender->AddFrameTransmitHandler(sender_FrameTransmitHandler);
ethernetSender->Activate();
// ------------------------------------------------------------
// Send an Ethernet message
const std::array<uint8_t, 6> destinationAddress{0xf6, 0x04, 0x68, 0x71, 0xaa, 0xc2};
const std::array<uint8_t, 6> sourceAddress{0xf6, 0x04, 0x68, 0x71, 0xaa, 0xc1};
const std::array<uint8_t, 4> vlanTag{0x81, 0x00, 0x00, 0x00}; // optional
const std::array<uint8_t, 2> etherType{0x08, 0x00};
const std::array<uint8_t, 4> frameCheckSequence{0x00, 0x00, 0x00, 0x00}; // optional
const std::string message{"Ensure that the payload is at least 46 bytes to constitute "
"a valid Ethernet frame ------------------------------"};
const std::vector<uint8_t> payload{message.begin(), message.end()};
const std::array<uint8_t, 2> frameCheckSequence{0x00, 0x00}; // optional for
EthernetFrame frame{};
std::copy(destinationAddress.begin(), destinationAddress.end(), std::back_inserter(frame));
std::copy(sourceAddress.begin(), sourceAddress.end(), std::back_inserter(frame));
std::copy(vlanTag.begin(), vlanTag.end(), std::back_inserter(frame));
std::copy(etherType.begin(), etherType.end(), std::back_inserter(frame));
std::copy(payload.begin(), payload.end(), std::back_inserter(frame));
std::copy(frameCheckSequence.begin(), frameCheckSequence.end(), std::back_inserter(frame));
// The returned transmitId can be used to check if the ethernetFrameTransmitEvent
// that should be triggered after a successful reception has the same transmitId.
auto transmitId = ethernetSender->SendFrame(frame);
// ------------------------------------------------------------
// The following callbacks will be triggered:
// - TX confirmation for the sender.
sender_FrameTransmitHandler(ethernetSender, ethernetFrameTransmitEvent);
// with:
// - ethernetFrameTransmitEvent.transmitId == 1
// - ethernetFrameTransmitEvent.sourceMac == {0xF6, 0x04, 0x68, 0x71, 0xAA, 0xC1}
// - ethernetFrameTransmitEvent.timestamp == <Timestamp of EthernetFrame>
// - ethernetFrameTransmitEvent.status == EthernetTransmitStatus::Transmitted
// Note: In a detailed simulation, the status can also be EthernetTransmitStatus::LinkDown.
// - RX Ethernet message for the receiver.
receiver_FrameHandler(ethernetReceiver, ethernetFrameEvent);
State Transition Example
This example shows the possible state transitions for an Ethernet controller.
/* Copyright (c) 2022 Vector Informatik GmbH
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
// ------------------------------------------------------------
// Controller Setup
// The initial state for ethernetSender is EthernetState::Inactive.
// Register StateChangeHandler to receive EthernetState changes
auto sender_StateChangedHandler =
[](IEthernetController*, const EthernetStateChangeEvent& stateChangeEvent) {};
ethernetSender->AddStateChangeHandler(sender_StateChangedHandler);
// ------------------------------------------------------------
// Transition from EthernetState::Inactive to EthernetState::LinkDown.
ethernetSender->Activate();
// The StateChangeHandler callback will be triggered and call the registered handler:
sender_StateChangedHandler(ethernetSender, stateChangeEvent);
// with state == EthernetState::LinkDown
// ------------------------------------------------------------
// Transition from EthernetState::LinkDown to EthernetState::LinkUp.
// After some time, as soon as the link to the switch is successfully established,
// the StateChangeHandler callback will be triggered again and call the registered handler:
sender_StateChangedHandler(ethernetSender, stateChangeEvent);
// with state == EthernetState::LinkUp
// ------------------------------------------------------------
// Transition from EthernetState::LinkUp to EthernetState::Inactive.
ethernetSender->Deactivate();
// The StateChangeHandler callback will be triggered and call the registered handler:
sender_StateChangedHandler(ethernetSender, stateChangeEvent);
// with state == EthernetState::Inactive
Erroneous Transmissions (Detailed Simulation only)
This example shows different possible erroneous Ethernet transmissions.
/* Copyright (c) 2022 Vector Informatik GmbH
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
// ------------------------------------------------------------
// Receiver Setup
ethernetReceiver->Activate();
// ------------------------------------------------------------
// Sender Setup
// Register MessageAckHandler to receive acknowledges of transmissions
auto sender_FrameTransmitHandler =
[](IEthernetController*, const EthernetFrameTransmitEvent&) {};
ethernetSender->AddFrameTransmitHandler(sender_FrameTransmitHandler);
// ------------------------------------------------------------
// Erroneous Transmission: EthernetTransmitStatus::ControllerInactive
const std::array<uint8_t, 6> sourceAddress{0xf6, 0x04, 0x68, 0x71, 0xaa, 0xc2};
const std::array<uint8_t, 6> destinationAddress{0xf6, 0x04, 0x68, 0x71, 0xaa, 0xc1};
const std::array<uint8_t, 2> etherType{0x08, 0x00};
const std::string message{"Ensure that the payload is at least 46 bytes to constitute "
"a valid Ethernet frame ------------------------------"};
const std::vector<uint8_t> payload{ message.begin(), message.end() };
EthernetFrame frame;
std::copy(destinationAddress.begin(), destinationAddress.end(), std::back_inserter(frame));
std::copy(sourceAddress.begin(), sourceAddress.end(), std::back_inserter(frame));
std::copy(etherType.begin(), etherType.end(), std::back_inserter(frame));
std::copy(payload.begin(), payload.end(), std::back_inserter(frame));
ethernetSender->SendFrame(frame);
// The FrameTransmitHandler callback will be triggered and call the registered handler:
sender_FrameTransmitHandler(ethernetSender, frameTransmitEvent);
// with frameTransmitEvent.status == EthernetTransmitStatus::ControllerInactive
// ------------------------------------------------------------
// Erroneous Transmission: EthernetTransmitStatus::LinkDown
ethernetSender->Activate();
ethernetSender->SendFrame(ethernetFrame);
// As long as the Ethernet link is not successfully established,
// the MessageAckHandler callback will be triggered and call the registered handler:
sender_FrameTransmitHandler(ethernetSender, frameTransmitEvent);
// with frameTransmitEvent.status == EthernetTransmitStatus::LinkDown
// ------------------------------------------------------------
// Erroneous Transmission: EthernetTransmitStatus::Dropped
// Assumption: Ethernet link is already successfully established.
for (auto i = 0; i < 50; i++)
{
ethernetSender->SendFrame(ethernetFrame);
}
// Sending 50 messages directly one after the other will call the registered sender_MessageAckHandler
// positively with some EthernetTransmitStatus::Transmitted until the transmit queue overflows
// and the Ethernet messages are acknowledged with status EthernetTransmitStatus::Dropped.
// ------------------------------------------------------------
// Erroneous Transmission: EthernetTransmitStatus::InvalidFrameFormat
const std::string longMessage(4000, 'a'); // much longer than the maximum allowed Ethernet frame size of 1534 bytes
const std::vector<uint8_t> longPayload{longMessage.begin(), longMessage.end()};
EthernetFrame invalidFrame;
std::copy(destinationAddress.begin(), destinationAddress.end(), std::back_inserter(invalidFrame));
std::copy(sourceAddress.begin(), sourceAddress.end(), std::back_inserter(invalidFrame));
std::copy(etherType.begin(), etherType.end(), std::back_inserter(invalidFrame));
std::copy(longPayload.begin(), longPayload.end(), std::back_inserter(invalidFrame));
ethernetSender->SendFrame(invalidEthernetFrame);
// The MessageAckHandler callback will be triggered and call the registered handler:
sender_FrameTransmitHandler(ethernetSender, frameTransmitEvent);
// with frameTransmitEvent.status == EthernetTransmitStatus::InvalidFrameFormat,
// as the Ethernet frame size is too long.