PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
in_memory_ethernet.cpp
Go to the documentation of this file.
1
3
5
6#include <algorithm>
7#include <stdexcept>
8
9namespace profinet::sim
10{
11
12namespace
13{
14
18bool IsBroadcastOrMulticast(const MacAddress& mac)
19{
20 return mac == MacAddress{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} ||
21 (mac[0] & 0x01U) != 0U;
22}
23
29bool ReadEthernetAddresses(const std::vector<std::uint8_t>& frame,
30 MacAddress& destination,
31 MacAddress& source)
32{
33 if (frame.size() < 12U)
34 {
35 return false;
36 }
37
38 std::copy_n(frame.begin(), destination.size(), destination.begin());
39 std::copy_n(frame.begin() + 6, source.size(), source.begin());
40 return true;
41}
42
43} // namespace
44
46 MacAddress localMacRef)
47 : bus(busRef), localMac(localMacRef)
48{
49 this->bus.Attach(this);
50}
51
56
57void InMemoryEthernetSocket::Send(const std::vector<std::uint8_t>& frame)
58{
59 {
60 std::lock_guard<std::mutex> lock(mutex);
61 if (!connected)
62 {
63 throw std::runtime_error("In-memory Ethernet socket is disconnected");
64 }
65 }
66
67 bus.Transmit(*this, frame);
68}
69
70void InMemoryEthernetSocket::SetTimeout(const std::chrono::milliseconds value)
71{
72 if (value.count() < 0)
73 {
74 throw std::invalid_argument("In-memory Ethernet socket timeout must be >= 0 ms");
75 }
76 std::lock_guard<std::mutex> lock(mutex);
77 timeout = value;
78}
79
81{
82 {
83 std::lock_guard<std::mutex> lock(mutex);
84 receiveEnabled = enabled;
85 if (!enabled)
86 {
87 receiveQueue.clear();
88 }
89 }
90 condition.notify_all();
91}
92
94{
95 std::lock_guard<std::mutex> lock(mutex);
96 return receiveEnabled;
97}
98
99std::vector<std::uint8_t> InMemoryEthernetSocket::Recv()
100{
101 std::unique_lock<std::mutex> lock(mutex);
102
103 const auto hasData = [this]
104 {
105 return !receiveQueue.empty() || !connected || !receiveEnabled;
106 };
107
108 if (!hasData())
109 {
110 if (timeout.count() == 0)
111 {
112 condition.wait(lock, hasData);
113 }
114 else
115 {
116 condition.wait_for(lock, timeout, hasData);
117 }
118 }
119
120 if (receiveQueue.empty())
121 {
122 return {};
123 }
124
125 std::vector<std::uint8_t> frame = std::move(receiveQueue.front());
126 receiveQueue.pop_front();
127 return frame;
128}
129
130void InMemoryEthernetSocket::Enqueue(const std::vector<std::uint8_t>& frame)
131{
132 {
133 std::lock_guard<std::mutex> lock(mutex);
134 if (!connected || !receiveEnabled)
135 {
136 return;
137 }
138 receiveQueue.push_back(frame);
139 }
140 condition.notify_one();
141}
142
144{
145 if (socket == nullptr)
146 {
147 throw std::invalid_argument("Cannot attach a null in-memory Ethernet socket");
148 }
149 std::lock_guard<std::mutex> lock(mutex);
150 // Multiple sockets may intentionally share one MAC (for example TX and RX
151 // sockets owned by one production CyclicController).
152 endpoints.push_back(socket);
153}
154
156{
157 {
158 std::lock_guard<std::mutex> busLock(mutex);
159 endpoints.erase(std::remove(endpoints.begin(), endpoints.end(), socket),
160 endpoints.end());
161 if (socket != nullptr)
162 {
163 std::lock_guard<std::mutex> socketLock(socket->mutex);
164 socket->connected = false;
165 socket->receiveQueue.clear();
166 }
167 }
168 if (socket != nullptr)
169 {
170 socket->condition.notify_all();
171 }
172}
173
175 const std::vector<std::uint8_t>& frame)
176{
177 MacAddress destination{};
178 MacAddress source{};
179 if (!ReadEthernetAddresses(frame, destination, source))
180 {
181 throw std::invalid_argument(
182 "In-memory Ethernet frame is shorter than an Ethernet header");
183 }
184
185 {
186 std::lock_guard<std::mutex> lock(mutex);
187 for (auto* endpoint : endpoints)
188 {
189 if (endpoint == nullptr || endpoint == &sender)
190 {
191 continue;
192 }
193
194 // A node may have multiple sockets sharing one MAC. Do not loop a frame
195 // back into another socket of the same node.
196 if (endpoint->LocalMac() == source)
197 {
198 continue;
199 }
200
201 const bool destinationMatches =
202 IsBroadcastOrMulticast(destination) || endpoint->LocalMac() == destination;
203 if (destinationMatches)
204 {
205 endpoint->Enqueue(frame);
206 }
207 }
208 }
209}
210
211std::unique_ptr<InMemoryEthernetSocket>
213{
214 return std::make_unique<InMemoryEthernetSocket>(*this, localMac);
215}
216
218{
219 std::lock_guard<std::mutex> lock(mutex);
220 return endpoints.size();
221}
222
224{
225 std::lock_guard<std::mutex> busLock(mutex);
226 for (auto* endpoint : endpoints)
227 {
228 if (endpoint == nullptr)
229 {
230 continue;
231 }
232 std::lock_guard<std::mutex> socketLock(endpoint->mutex);
233 endpoint->receiveQueue.clear();
234 }
235}
236
237} // namespace profinet::sim
Small virtual Ethernet switch suitable for deterministic tests and demos.
void Transmit(InMemoryEthernetSocket &sender, const std::vector< std::uint8_t > &frame)
Route a frame to matching endpoints.
std::size_t EndpointCount() const
Return the number of currently connected socket endpoints.
void Attach(InMemoryEthernetSocket *socket)
Attach one socket.
void Detach(InMemoryEthernetSocket *socket)
Detach one socket.
std::unique_ptr< InMemoryEthernetSocket > CreateSocket(MacAddress localMac)
Create one virtual raw-Ethernet endpoint.
void ClearQueues()
Remove all queued frames without disconnecting endpoints.
std::vector< InMemoryEthernetSocket * > endpoints
Injectable raw-Ethernet endpoint attached to an in-memory bus.
void Enqueue(const std::vector< std::uint8_t > &frame)
Queue one frame for this endpoint.
void SetTimeout(std::chrono::milliseconds timeout) override
Set the maximum time spent waiting for a frame.
void Send(const std::vector< std::uint8_t > &frame) override
Transmit one complete Ethernet frame onto the virtual bus.
~InMemoryEthernetSocket() override
Disconnect and detach the endpoint.
bool ReceiveEnabled() const noexcept
Return whether this endpoint currently accepts incoming frames.
std::vector< std::uint8_t > Recv() override
Receive one frame or an empty vector after a timeout.
void SetReceiveEnabled(bool enabled)
Enable or disable frame delivery into this endpoint receive queue.
std::deque< std::vector< std::uint8_t > > receiveQueue
InMemoryEthernetSocket(InMemoryEthernetBus &bus, MacAddress localMac)
Construct and attach a virtual endpoint.
Thread-safe in-memory Layer-2 Ethernet switch for PROFINET simulation.
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
Bytes frame