GCC Code Coverage Report


Directory: ./
File: e131bridge.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 890 1020 87.3%
Functions: 50 54 92.6%
Branches: 758 1390 54.5%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16
17 #ifdef PLATFORM_OSX
18 #include <netinet/udp.h>
19 #endif
20
21 #include <arpa/inet.h>
22 #include <net/if.h>
23 #include <netinet/in.h>
24 #include <sys/socket.h>
25 #include <sys/uio.h>
26 #include <algorithm>
27 #include <atomic>
28 #include <cstdlib>
29 #include <errno.h>
30 #include <ifaddrs.h>
31 #include <inttypes.h>
32 #include <map>
33 #include <memory>
34 #include <set>
35 #include <stdio.h>
36 #include <string.h>
37 #include <string>
38 #include <unistd.h>
39 #include <utility>
40 #include <vector>
41
42 #include "EPollManager.h"
43 #include "FileMonitor.h"
44 #include "Sequence.h"
45 #include "Timers.h"
46 #include "Warnings.h"
47 #include "common.h"
48 #include "e131defs.h"
49 #include "fppversion.h"
50 #include "log.h"
51 #include "settings.h"
52 #include "channeloutput/ChannelOutputSetup.h"
53 #include "channeloutput/DDP.h"
54 #include "channeloutput/Universe.h"
55 #include "channeloutput/channeloutputthread.h"
56 #include "commands/Commands.h"
57
58 #include "channeloutput/serialutil.h"
59 #include "util/GPIOUtils.h"
60
61 #include "e131bridge.h"
62 #include "SysSocket.h"
63
64 #define BRIDGE_INVALID_UNIVERSE_INDEX 0xFFFFFF
65 // Cached "looked it up, not configured" marker, distinct from the
66 // "never looked up" state above. Internal to the universe caches.
67 #define BRIDGE_UNIVERSE_NOT_FOUND 0xFFFFFE
68
69 static int bridgeSock = -1;
70 static int ddpSock = -1;
71 static std::atomic<int> artnetSock = -1;
72 static volatile bool artnetSocketAsInput = false;
73
74 static long long last_packet_time = GetTimeMS();
75 static long long expireOffSet = 1000; // expire after 1 second
76
77 // When true, the bridge tracks the active source per universe and prefers
78 // the highest-priority sender. For E1.31 the priority comes from the packet
79 // header; for ArtNet (which has no priority field) all senders share the
80 // same priority so it reduces to first-source-wins with timeout failover.
81 // A lower-priority source only takes over if the current source stops
82 // sending for longer than expireOffSet.
83 static std::atomic<bool> sourcePriorityEnabled{false};
84
85 // Source address and received length of the ArtNet packet currently being
86 // handled. Set by Bridge_ReceiveArtNetData before each handler callback so
87 // handlers can use them without changing the public AddArtNetOpcodeHandler
88 // signature.
89 static in_addr_t currentArtNetSourceIP = 0;
90 static int currentArtNetPacketLen = 0;
91
92 // The per-universe counters are bumped by the packet path, and read and reset
93 // from the HTTP threads, so every access is a relaxed atomic one: a reset
94 // racing an increment was a data race, as was the stats read itself.
95 // UniverseEntry keeps plain fields (its layout is shared), hence the builtins.
96 106526 static inline void StatAdd(uint32_t& c, uint32_t n) {
97 106526 __atomic_fetch_add(&c, n, __ATOMIC_RELAXED);
98 106526 }
99 247217 static inline uint32_t StatLoad(const uint32_t& c) {
100 247217 return __atomic_load_n(&c, __ATOMIC_RELAXED);
101 }
102 133216 static inline void StatStore(uint32_t& c, uint32_t v) {
103 133216 __atomic_store_n(&c, v, __ATOMIC_RELAXED);
104 133216 }
105
106 // Evaluate whether to accept a packet from the given source for this
107 // universe under the priority-aware policy. Updates the entry's active
108 // source state as a side effect. Returns true if the packet should be
109 // applied; false if it should be suppressed. sourceJustSwitched is set to
110 // true when the caller should skip sequence-number error counting for this
111 // packet because the active source identifier just changed.
112 254 static inline bool EvaluateSource(UniverseEntry& entry,
113 const uint8_t* sourceId,
114 uint8_t priority,
115 bool& sourceJustSwitched) {
116 254 sourceJustSwitched = false;
117 // The active source's age is on the monotonic clock. It was the packets'
118 // wall-clock time: a step back kept a stopped source "active" for the size
119 // of the step, so the backup source stayed suppressed and the output froze
120 // on the last frame of the one that stopped.
121 254 long long packetTime = GetMonotonicTimeMS();
122
2/2
✓ Branch 0 taken 252 times.
✓ Branch 1 taken 2 times.
506 bool idMatches = entry.activeSourceValid &&
123
2/2
✓ Branch 0 taken 129 times.
✓ Branch 1 taken 123 times.
252 memcmp(entry.activeSourceId, sourceId, 16) == 0;
124
2/2
✓ Branch 0 taken 252 times.
✓ Branch 1 taken 2 times.
506 bool expired = entry.activeSourceValid &&
125
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 250 times.
252 (packetTime - entry.activeSourceLastTime) > expireOffSet;
126
127
6/6
✓ Branch 0 taken 252 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 250 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 248 times.
254 if (!entry.activeSourceValid || expired || priority > entry.activeSourcePriority) {
128
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
6 if (entry.activeSourceValid && !idMatches) {
129 4 sourceJustSwitched = true;
130 }
131 6 memcpy(entry.activeSourceId, sourceId, 16);
132 6 entry.activeSourcePriority = priority;
133 6 entry.activeSourceLastTime = packetTime;
134 6 entry.activeSourceValid = true;
135 6 return true;
136 }
137
2/2
✓ Branch 0 taken 129 times.
✓ Branch 1 taken 119 times.
248 if (idMatches) {
138 129 entry.activeSourcePriority = priority;
139 129 entry.activeSourceLastTime = packetTime;
140 129 return true;
141 }
142 // Lower-or-equal priority from a different source while the current
143 // source is still active; suppress it.
144 119 StatAdd(entry.suppressedPackets, 1);
145 119 return false;
146 }
147
148 #define MAX_MSG 64
149 // Batches of MAX_MSG read per wakeup. The sockets are level-triggered, so
150 // what is left is read on the next pass; draining until empty let a sender
151 // faster than fppd keep the main loop in here for good.
152 static constexpr int MAX_BATCHES_PER_WAKEUP = 64;
153 #define BUFSIZE 1500
154 static struct mmsghdr msgs[MAX_MSG];
155 static struct iovec iovecs[MAX_MSG];
156 static uint8_t buffers[MAX_MSG][BUFSIZE + 1];
157 static struct sockaddr_in inAddress[MAX_MSG];
158
159 // One lookup cache per protocol: E1.31 and ArtNet number their universes
160 // independently, so ArtNet universe N and E1.31 universe N are different
161 // inputs and a packet must only ever land on an entry of its own protocol.
162 // ArtNet's Port-Address is 15 bits.
163 static unsigned int E131UniverseCache[65536];
164 static unsigned int ArtNetUniverseCache[32768];
165
166 static std::vector<UniverseEntry> InputUniverses;
167 static int InputUniverseCount;
168
169 // DMX serial inputs live apart from the UDP universe list. The UDP reload
170 // path rebuilds InputUniverses from scratch, so anything else stored in that
171 // vector is destroyed (and any index captured by an epoll callback goes
172 // stale) on every ci-universes.json change. shared_ptr gives each DMX entry
173 // an identity that survives both reload paths.
174 static std::vector<std::shared_ptr<UniverseEntry>> DMXInputs;
175
176 // Guards the STRUCTURE of InputUniverses/DMXInputs. Reloads (main loop) swap
177 // the containers while GetE131UniverseBytesReceived/ResetBytesReceived run on
178 // the HTTP server's worker threads; without this a stats poll during a config
179 // reload reads a vector mid-rebuild. The packet path runs on the same
180 // main-loop thread as every mutation, so it deliberately does NOT take this -
181 // a freed vector is the danger, not a counter being bumped meanwhile.
182 static std::mutex universesStructureLock;
183
184 static std::atomic<uint64_t> ddpBytesReceived{0};
185 static std::atomic<uint32_t> ddpPacketsReceived{0};
186 static std::atomic<uint32_t> ddpErrors{0};
187
188 static uint32_t ddpLastSequence = 0;
189 static uint32_t ddpLastChannel = 0;
190 // The channel range DDP has covered. Written only by the main loop's packet
191 // handler, read by the stats endpoint from the HTTP threads.
192 static std::atomic<uint32_t> ddpMinChannel{0xFFFFFFF};
193 static std::atomic<uint32_t> ddpMaxChannel{0};
194
195 static std::atomic<uint32_t> e131Errors{0};
196 static std::atomic<uint32_t> e131SyncPackets{0};
197 static UniverseEntry unknownUniverse;
198
199 // Sync universes we've auto-joined the multicast group for, based on the
200 // sync-address field advertised in received data packets. Lets sync packets
201 // reach the bridge even when the sync universe isn't a configured input.
202 // Capped: every new one costs a getifaddrs() and a join per interface on the
203 // main loop, and the field comes from whoever sends a packet, so a stream
204 // with a different sync address in each packet would otherwise join groups
205 // (and stall the loop) without limit. A real show uses one or two.
206 static std::set<int> joinedSyncUniverses;
207 static constexpr size_t MAX_JOINED_SYNC_UNIVERSES = 16;
208 static bool syncUniverseCapLogged = false;
209
210 // Set by the receive path, read by status and the main loop.
211 static std::atomic<bool> bridgeDataReceived{ false };
212
213 static std::map<int, std::function<bool(uint8_t* data, long long packetTime)>> ArtNetOpcodeHandlers;
214
215 // The ArtNet socket is shared by everything that speaks ArtNet: the protocol
216 // fixes the port, so the bridge and any plugin extending ArtNet necessarily
217 // receive on one descriptor. Its epoll registration therefore belongs HERE and
218 // nowhere else, driven by whether any opcode handler currently wants packets.
219 //
220 // It used to be registered by each interested party instead, which does not
221 // work for a shared descriptor. EPollManager holds one callback per fd, so the
222 // second registration silently replaced the first, and the first removal took
223 // the descriptor out of the epoll set for everyone - unloading a plugin that
224 // had registered it stopped the bridge receiving ArtNet entirely. Keeping it
225 // here also means the callback is always FPP's own, never a plugin's, so an
226 // unloaded plugin can never leave the loop dispatching into an unmapped
227 // library.
228 //
229 // While a handler is waiting for a socket that could not be opened (another
230 // program holds 6454), a main-loop timer keeps calling back in here. The
231 // socket can appear without this function running at all -- the ArtNet
232 // output asks for it from the output thread on every frame -- and nothing
233 // else would put it in the epoll set: ArtNet input stayed dead after the
234 // port came free, with the warning that said so already taken down. The
235 // timer also retries the port itself when nothing else asks for it.
236 static constexpr const char* ARTNET_INPUT_RETRY_TIMER = "ArtNetInputRetry";
237 static bool artnetInputRetryRunning = false;
238
239 5725 static void UpdateArtNetInputRegistration() {
240 5725 bool wanted = !ArtNetOpcodeHandlers.empty();
241
6/6
✓ Branch 0 taken 95 times.
✓ Branch 1 taken 5630 times.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 82 times.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 5712 times.
5725 if (wanted && artnetSock < 0) {
242 // A handler wants ArtNet packets on a player that has no ArtNet input
243 // of its own, so there is no socket yet. Create it - this is what the
244 // callers who used to register the descriptor themselves were doing by
245 // calling CreateArtNetSocket(), and without it a plugin's triggers
246 // would silently stop working wherever the bridge has no ArtNet.
247 // After a failure this returns at once until its retry interval is up.
248 13 CreateArtNetSocket();
249 }
250
4/4
✓ Branch 0 taken 95 times.
✓ Branch 1 taken 5630 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 85 times.
5725 bool waiting = wanted && artnetSock < 0;
251
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5723 times.
5725 if (waiting != artnetInputRetryRunning) {
252 2 artnetInputRetryRunning = waiting;
253
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (waiting) {
254
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
3 Timers::INSTANCE.addPeriodicTimer(ARTNET_INPUT_RETRY_TIMER, 1000, []() {
255 10 UpdateArtNetInputRegistration();
256 10 });
257 } else {
258
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 Timers::INSTANCE.stopPeriodicTimer(ARTNET_INPUT_RETRY_TIMER);
259 }
260 }
261
2/2
✓ Branch 0 taken 5552 times.
✓ Branch 1 taken 173 times.
5725 if (artnetSock < 0) {
262 5552 return;
263 }
264
6/6
✓ Branch 0 taken 85 times.
✓ Branch 1 taken 88 times.
✓ Branch 2 taken 77 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 77 times.
✓ Branch 5 taken 96 times.
173 if (wanted && !artnetSocketAsInput) {
265 2236 std::function<bool(int)> f = [](int i) {
266 2159 return Bridge_ReceiveArtNetData();
267 77 };
268
1/2
✓ Branch 0 taken 77 times.
✗ Branch 1 not taken.
77 EPollManager::INSTANCE.addFileDescriptor(artnetSock, f);
269 77 artnetSocketAsInput = true;
270
6/6
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 75 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 75 times.
✓ Branch 5 taken 21 times.
173 } else if (!wanted && artnetSocketAsInput) {
271 75 EPollManager::INSTANCE.removeFileDescriptor(artnetSock);
272 75 artnetSocketAsInput = false;
273 }
274 }
275
276 2 void AddArtNetOpcodeHandler(int opCode, std::function<bool(uint8_t* data, long long packetTime)> handler) {
277 2 ArtNetOpcodeHandlers[opCode] = handler;
278 2 UpdateArtNetInputRegistration();
279 2 }
280 2 void RemoveArtNetOpcodeHandler(int opCode) {
281 2 ArtNetOpcodeHandlers.erase(opCode);
282 2 UpdateArtNetInputRegistration();
283 2 }
284
285 // Changed by input config reloads, read by the output thread.
286 static std::atomic<bool> hasUDP{ false };
287 static std::atomic<bool> hasDMX{ false };
288 173632 bool InputsEnabled() {
289
4/4
✓ Branch 0 taken 173576 times.
✓ Branch 1 taken 56 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 173575 times.
173632 return hasUDP || hasDMX;
290 }
291
292 // prototypes for functions below
293 bool Bridge_StoreData(uint8_t* bridgeBuffer, long long packetTime, int msgLen);
294 bool Bridge_StoreDDPData(uint8_t* bridgeBuffer, long long packetTime, int msgLen);
295 void BridgeShutdownUDP(bool reloading);
296 int Bridge_GetIndexFromUniverseNumber(int universe, bool artnet);
297 void InputUniversesPrint();
298 inline void SetBridgeData(uint8_t* data, int startChannel, int len, long long packetTime);
299 inline void CommitBridgeData();
300
301 // Size the receive buffer on a bridge input socket. A show pushing several
302 // hundred universes delivers a full frame as one back-to-back burst (hundreds
303 // of packets), and the main loop can stall for a frame or two doing output
304 // processing before it drains the socket. If the receive buffer is too small
305 // the kernel tail-drops that burst. A plain SO_RCVBUF request is silently
306 // clamped to net.core.rmem_max, so scale the request to the packets in a frame
307 // and try SO_RCVBUFFORCE first (fppd runs as root, so CAP_NET_ADMIN lets it
308 // bypass rmem_max); fall back to the clamped SO_RCVBUF otherwise.
309 2404 static void SetBridgeReceiveBufferSize(int sock, long packetsPerFrame) {
310 // ~4KB/packet leaves several frames of burst headroom even after the
311 // kernel's per-packet accounting overhead; clamp to a floor and ceiling.
312 2404 long desired = packetsPerFrame * 4096;
313
2/2
✓ Branch 0 taken 2398 times.
✓ Branch 1 taken 6 times.
2404 if (desired < 512 * 1024) {
314 2398 desired = 512 * 1024;
315
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 } else if (desired > 16 * 1024 * 1024) {
316 1 desired = 16 * 1024 * 1024;
317 }
318 2404 int bufSize = (int)desired;
319 2404 bool forced = false;
320 #ifdef SO_RCVBUFFORCE
321 2404 forced = setsockopt(sock, SOL_SOCKET, SO_RCVBUFFORCE, &bufSize, sizeof(bufSize)) == 0;
322 #endif
323
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2404 times.
2404 if (!forced) {
324 // Not privileged (or unsupported): plain request, clamped to rmem_max.
325 ✗ setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &bufSize, sizeof(bufSize));
326 }
327 2404 int actual = 0;
328 2404 socklen_t len = sizeof(actual);
329
1/2
✓ Branch 0 taken 2404 times.
✗ Branch 1 not taken.
2404 if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &actual, &len) == 0) {
330
1/2
✓ Branch 0 taken 2404 times.
✗ Branch 1 not taken.
2404 LogDebug(VB_E131BRIDGE, "Bridge socket %d recv buffer: requested %d, granted %d\n",
331 sock, bufSize, actual);
332 }
333 2404 }
334
335 // E1.31 and ArtNet send a packet per configured input universe. DDP input
336 // is not configured by universe -- it lands anywhere in the channel buffer --
337 // so a box fed only DDP counted no universes and got the floor, about 450
338 // full packets: two frames of a few hundred thousand channels. A sender
339 // sends the channels the outputs here use, up to 1440 to a packet, so count
340 // the packets that cover each output range.
341 1221 static long DDPPacketsPerFrame() {
342 1221 long packets = 0;
343
1/2
✓ Branch 0 taken 1221 times.
✗ Branch 1 not taken.
1221 auto ranges = GetOutputRangesSnapshot();
344
2/2
✓ Branch 0 taken 1221 times.
✓ Branch 1 taken 1221 times.
2442 for (const auto& r : *ranges) {
345 1221 packets += ((long)r.second + 1439) / 1440;
346 }
347 1221 return packets;
348 1221 }
349
350 2147 void Bridge_OutputRangesChanged() {
351
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 2061 times.
2147 if (ddpSock >= 0) {
352 86 SetBridgeReceiveBufferSize(ddpSock, DDPPacketsPerFrame());
353 }
354 2147 }
355
356 // Why ArtNet is off (or is sending from another port), as shown in the
357 // warning banner; empty while the socket is fine. The ArtNet port is fixed
358 // by the protocol, so another program holding it (a second player, xLights,
359 // a plugin) is an ordinary condition, not a fault.
360 static constexpr int WARNING_ID_ARTNET_PORT = 54;
361 static std::string artnetPortWarning;
362 // When the last attempt to open the socket failed (GetMonotonicTimeMS()), 0
363 // if it didn't. An ArtNet output asks for the socket on every frame while it
364 // has none, and every attempt used to log an error: 20-40 lines a second to
365 // the log for as long as the port stayed taken. Attempts from there are made
366 // at most every ARTNET_RETRY_MS.
367 static long long artnetFailedAt = 0;
368 static constexpr long long ARTNET_RETRY_MS = 10000;
369 static std::mutex artnetSocketMutex;
370
371 // Caller holds artnetSocketMutex.
372 2726 static void SetArtNetPortWarning(const std::string& msg) {
373
2/2
✓ Branch 0 taken 2720 times.
✓ Branch 1 taken 6 times.
2726 if (msg == artnetPortWarning) {
374 2720 return;
375 }
376
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if (!artnetPortWarning.empty()) {
377
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 WarningHolder::RemoveWarning(WARNING_ID_ARTNET_PORT, artnetPortWarning);
378 }
379 6 artnetPortWarning = msg;
380
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if (!msg.empty()) {
381 4 LogWarn(VB_E131BRIDGE, "%s\n", msg.c_str());
382
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 WarningHolder::AddWarning(WARNING_ID_ARTNET_PORT, msg);
383 }
384 }
385
386 1015 int CreateArtNetSocket(uint32_t sourceAddr, bool allowPortChange) {
387
1/2
✓ Branch 0 taken 1015 times.
✗ Branch 1 not taken.
1015 std::lock_guard<std::mutex> lock(artnetSocketMutex);
388
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 974 times.
1015 if (artnetSock >= 0) {
389 41 return artnetSock;
390 }
391 // Callers that can fall back to another port (output setup, a network
392 // change) always try: they are rare, and the fallback is what lets ArtNet
393 // output run at all while the port is taken.
394
1/2
✓ Branch 0 taken 974 times.
✗ Branch 1 not taken.
974 long long now = GetMonotonicTimeMS();
395
6/6
✓ Branch 0 taken 970 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 924 times.
✓ Branch 3 taken 46 times.
✓ Branch 4 taken 922 times.
✓ Branch 5 taken 2 times.
974 if (!allowPortChange && artnetFailedAt && now - artnetFailedAt < ARTNET_RETRY_MS) {
396 922 return -1;
397 }
398 3 auto failed = [now](const std::string& msg) {
399
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 artnetFailedAt = now ? now : 1;
400 // Logged once, when it changes; the warning says it from then on.
401 3 SetArtNetPortWarning(msg);
402 3 return -1;
403 52 };
404
405 52 int artnetSockT = OpenSocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
406
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 52 times.
52 if (artnetSockT < 0) {
407 // As for a failed bind below: run without ArtNet, don't exit().
408 ✗ return failed(std::string("ArtNet is off: could not create its socket (") + FPPstrerror(errno) + ")");
409 }
410 52 int enable = 1;
411 // need to be able to send broadcast for ArtPollReply
412 52 setsockopt(artnetSockT, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(enable));
413 52 enable = 1;
414 #ifdef PLATFORM_OSX
415 setsockopt(artnetSockT, IPPROTO_UDP, UDP_NOCKSUM, (void*)&enable, sizeof enable);
416 #else
417 52 setsockopt(artnetSockT, SOL_SOCKET, SO_NO_CHECK, (void*)&enable, sizeof enable);
418 #endif
419
1/2
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
52 SetBridgeReceiveBufferSize(artnetSockT, InputUniverseCount);
420 52 int bufSize = 512 * 1024;
421 52 setsockopt(artnetSockT, SOL_SOCKET, SO_SNDBUF, &bufSize, sizeof(bufSize));
422
423 // A local: CreateArtNetSocket runs on the output thread as well as the
424 // main loop, and a bind address shared with OpenBridgeSocket could be
425 // rewritten between filling it in and bind().
426 struct sockaddr_in addr;
427 52 memset((char*)&addr, 0, sizeof(addr));
428 52 addr.sin_family = AF_INET;
429
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 4 times.
52 addr.sin_addr.s_addr = sourceAddr == 0 ? htonl(INADDR_ANY) : (in_addr_t)sourceAddr;
430 52 addr.sin_port = htons(0x1936); // artnet port
431 52 std::string warning;
432 // Bind the socket to address/port
433
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 48 times.
52 if (bind(artnetSockT, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
434
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 std::string why = FPPstrerror(errno);
435
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
4 if (!allowPortChange) {
436
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 close(artnetSockT);
437
3/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 return failed("ArtNet is off: could not open UDP port 6454 (" + why + ")");
438 }
439 1 addr.sin_port = htons(0); // let OS pick port
440
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (bind(artnetSockT, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
441 ✗ std::string why2 = FPPstrerror(errno);
442 ✗ close(artnetSockT);
443 ✗ return failed("ArtNet is off: could not open UDP port 6454 (" + why + ") or any other port (" + why2 + ")");
444 ✗ }
445 // Output still works from here, but nothing reaches ArtNet input or
446 // ArtPoll on 6454 until the socket is opened again.
447 // Nothing retries 6454 once a socket is open on another port, so this
448 // stays up until FPPD restarts (or the output interface changes) --
449 // and says so.
450
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 warning = "ArtNet input is off and ArtNet output is sent from another port: could not open UDP port 6454 (" + why +
451
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 "). Restart FPPD once the port is free.";
452
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 }
453 49 artnetFailedAt = 0;
454
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 SetArtNetPortWarning(warning);
455
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 1 times.
49 if (warning.empty()) {
456
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 LogDebug(VB_E131BRIDGE, "ArtNet socket open on UDP port 6454\n");
457 }
458 49 artnetSock = artnetSockT;
459 49 return artnetSock;
460 1015 }
461
462 2715 void ResetArtNetSocketFailure() {
463 // A config reload may have removed whatever wanted ArtNet, and then
464 // nothing would ever retry and take the warning down. Forget the failure
465 // instead: anything that still needs the socket asks again straight away
466 // and raises the warning again if the port is still taken.
467
1/2
✓ Branch 0 taken 2715 times.
✗ Branch 1 not taken.
2715 std::lock_guard<std::mutex> lock(artnetSocketMutex);
468
2/2
✓ Branch 0 taken 2674 times.
✓ Branch 1 taken 41 times.
2715 if (artnetSock < 0) {
469 2674 artnetFailedAt = 0;
470
2/4
✓ Branch 0 taken 2674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2674 times.
✗ Branch 3 not taken.
5348 SetArtNetPortWarning("");
471 }
472 2715 }
473
474 // Network/DMX input problems. Shares its id with the ArtNet port warning
475 // above; each message is stored where it is raised and retracted by the next
476 // reload of the config that produced it.
477 static constexpr int WARNING_ID_INPUT = 54;
478
479 // Total universe entries across every row of ci-universes.json. Each row is
480 // already held to 65536 (a universe number is 16 bits), but a hundred such
481 // rows built millions of entries -- hundreds of MB on a device with 512MB.
482 // 65536 is more than any real input config: every E1.31 universe.
483 static constexpr size_t MAX_INPUT_UNIVERSES = 65536;
484 static std::string universeCapWarning;
485
486 /*
487 *
488 */
489 1922 bool LoadInputUniversesFromFile(void) {
490 // The previous load's cap warning; raised again below if still true.
491
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1922 times.
1922 if (!universeCapWarning.empty()) {
492 ✗ WarningHolder::RemoveWarning(WARNING_ID_INPUT, universeCapWarning);
493 ✗ universeCapWarning.clear();
494 }
495 // Built locally and swapped in under universesStructureLock at the end so
496 // an HTTP-thread stats reader never sees the vector mid-rebuild.
497 1922 std::vector<UniverseEntry> newUniverses;
498 1922 bool truncated = false;
499
3/6
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1922 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1922 times.
✗ Branch 5 not taken.
1922 std::string filename = FPP_DIR_CONFIG("/ci-universes.json");
500
501
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 LogDebug(VB_E131BRIDGE, "Opening File Now %s\n", filename.c_str());
502
503
3/4
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1834 times.
✓ Branch 3 taken 88 times.
1922 if (!FileExists(filename)) {
504 // Normal on any player with no bridge input configured, which is most of
505 // them -- not an error. (ci-dmx.json below already treats its own
506 // absence this way.)
507
1/2
✓ Branch 0 taken 1834 times.
✗ Branch 1 not taken.
1834 LogDebug(VB_E131BRIDGE, "Universe file %s does not exist\n",
508 filename.c_str());
509 1834 return false;
510 }
511
512
1/2
✓ Branch 0 taken 88 times.
✗ Branch 1 not taken.
88 Json::Value root;
513
3/4
✓ Branch 0 taken 88 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 87 times.
88 if (!LoadJsonFromFile(filename, root, JsonRoot::Object)) {
514
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_E131BRIDGE, "Error parsing %s\n", filename.c_str());
515 1 return false;
516 }
517
518
2/4
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 87 times.
✗ Branch 3 not taken.
87 Json::Value outputs = root["channelInputs"];
519
520 87 std::string type;
521 87 int start = 0;
522 87 int count = 0;
523
524 87 bool enabled = false;
525 // Every value comes out of a file a user or an API may have written with
526 // the wrong JSON type, and jsoncpp throws on those: an entry or a universe
527 // that can't be read is skipped (with a warning) instead of the exception
528 // reaching std::terminate during startup.
529
3/4
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 86 times.
87 if (!outputs.isArray()) {
530
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (!outputs.isNull()) {
531
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
4 WarningHolder::InvalidConfigEntry("ci-universes.json", "channelInputs is not a list");
532 }
533
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 outputs = Json::Value(Json::arrayValue);
534 }
535
3/4
✓ Branch 0 taken 173 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 86 times.
✓ Branch 3 taken 87 times.
173 for (int c = 0; c < outputs.size(); c++) {
536 try {
537
1/2
✓ Branch 0 taken 86 times.
✗ Branch 1 not taken.
86 const Json::Value& o = outputs[c];
538
5/8
✓ Branch 0 taken 85 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 85 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 85 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 85 times.
86 if (o["type"].asString() != "universes")
539 ✗ continue;
540
541
4/6
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 84 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 84 times.
85 if (o["enabled"].asInt() == 0)
542 ✗ continue;
543
544
3/4
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 82 times.
84 if (o.isMember("timeout")) {
545
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 if (!o["timeout"].isNull()) {
546
3/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 expireOffSet = o["timeout"].asInt();
547
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (expireOffSet < 10) {
548 ✗ expireOffSet = 10;
549 }
550 }
551 }
552
553
2/4
✓ Branch 0 taken 83 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 83 times.
✗ Branch 3 not taken.
83 Json::Value univs = o["universes"];
554
2/4
✓ Branch 0 taken 83 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 83 times.
83 if (!univs.isArray()) {
555 ✗ univs = Json::Value(Json::arrayValue);
556 }
557 83 enabled = true;
558
3/4
✓ Branch 0 taken 243 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✓ Branch 3 taken 83 times.
243 for (int i = 0; i < univs.size(); i++) {
559 try {
560
1/2
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
160 const Json::Value& u = univs[i];
561
2/4
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✗ Branch 3 not taken.
160 int active = u["active"].asInt();
562
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 160 times.
160 if (!active) {
563 ✗ continue;
564 }
565 // read everything before adding anything, so a bad field
566 // can't leave half a universe range behind
567
2/4
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✗ Branch 3 not taken.
160 int universe = u["id"].asInt();
568
3/6
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 160 times.
✗ Branch 5 not taken.
160 int count = u.get("universeCount", Json::Value(1)).asInt();
569
2/4
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✗ Branch 3 not taken.
160 int startChannel = u["startChannel"].asInt();
570
2/4
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✗ Branch 3 not taken.
160 int channelCount = u["channelCount"].asInt();
571
2/4
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✗ Branch 3 not taken.
160 int type = u["type"].asInt();
572
4/6
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 77 times.
✓ Branch 2 taken 83 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 83 times.
✗ Branch 5 not taken.
160 std::string address = type == 1 ? u["address"].asString() : std::string();
573
2/4
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✗ Branch 3 not taken.
160 int priority = u["priority"].asInt();
574 // universe numbers are 16 bits: a bigger count only built
575 // entries nothing can address (and a huge one, millions)
576
1/2
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
160 count = std::clamp(count, 0, 65536);
577
2/4
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 160 times.
160 if (type < 0 || type > 3) {
578 ✗ continue;
579 }
580
2/2
✓ Branch 0 taken 66767 times.
✓ Branch 1 taken 158 times.
66925 for (int x = 0; x < count; ++x) {
581
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 66765 times.
66767 if (newUniverses.size() >= MAX_INPUT_UNIVERSES) {
582 2 truncated = true;
583 2 break;
584 }
585 66765 UniverseEntry entry;
586 66765 entry.active = active;
587 66765 entry.universe = universe + x;
588 66765 entry.startAddress = startChannel;
589 66765 entry.size = channelCount;
590 66765 entry.type = type;
591
2/2
✓ Branch 0 taken 66688 times.
✓ Branch 1 taken 77 times.
66765 if (type == 1) { // UnicastAddress
592 66688 snprintf(entry.unicastAddress, sizeof(entry.unicastAddress), "%s", address.c_str());
593 } else { // Multicast, ArtNet
594 77 entry.unicastAddress[0] = 0;
595 }
596 66765 entry.priority = priority;
597 66765 startChannel += channelCount;
598
1/2
✓ Branch 0 taken 66765 times.
✗ Branch 1 not taken.
66765 newUniverses.push_back(entry);
599 66765 }
600
1/4
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
160 } catch (const std::exception& e) {
601 ✗ WarningHolder::InvalidConfigEntry("ci-universes.json", e.what());
602 ✗ }
603 }
604
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
86 } catch (const std::exception& e) {
605
3/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
9 WarningHolder::InvalidConfigEntry("ci-universes.json", e.what());
606 3 }
607 }
608
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 86 times.
87 if (truncated) {
609
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 universeCapWarning = "Channel Inputs: only the first " + std::to_string(MAX_INPUT_UNIVERSES) +
610
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 " input universes are used; the rest of ci-universes.json is ignored";
611
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_E131BRIDGE, "%s\n", universeCapWarning.c_str());
612
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 WarningHolder::AddWarning(WARNING_ID_INPUT, universeCapWarning);
613 }
614
615
1/2
✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
87 std::lock_guard<std::mutex> lg(universesStructureLock);
616 87 InputUniverses = std::move(newUniverses);
617 87 InputUniverseCount = InputUniverses.size();
618 87 return enabled;
619 1922 }
620
621 // Ranges written since the last CommitBridgeData(). A receive callback
622 // drains a burst of packets in one go; deferring the range bookkeeping to a
623 // single commit takes the sequence's range lock once per burst instead of
624 // once per packet. Every path that calls SetBridgeData must commit before
625 // returning to the epoll loop.
626 static std::vector<std::pair<uint32_t, uint32_t>> pendingBridgeRanges;
627
628 49392 inline void SetBridgeData(uint8_t* data, int startChannel, int len, long long packetTime) {
629 49392 last_packet_time = packetTime;
630 // Every received packet. Only status reads it ("bridging"), and nothing
631 // is published through it, so it needs no barriers.
632 49392 bridgeDataReceived.store(true, std::memory_order_relaxed);
633
2/2
✓ Branch 0 taken 49372 times.
✓ Branch 1 taken 20 times.
49392 if (sequence->WriteBridgeData(data, startChannel, len)) {
634 49372 pendingBridgeRanges.emplace_back(startChannel, len);
635 }
636 49392 }
637 19492 inline void CommitBridgeData() {
638
2/2
✓ Branch 0 taken 17715 times.
✓ Branch 1 taken 1777 times.
19492 if (!pendingBridgeRanges.empty()) {
639 // Expiry is on the monotonic clock (as Sequence compares it). It was
640 // the packets' wall-clock time, so a clock stepping back kept stale
641 // bridge pixels over the sequence for as long as the step; and the
642 // serial-input path, whose time already included the timeout, got it
643 // added twice.
644 17715 sequence->CommitBridgeRanges(pendingBridgeRanges, GetMonotonicTimeMS() + expireOffSet);
645 17715 pendingBridgeRanges.clear();
646 }
647 19492 }
648
649 // Add or drop multicast-group membership for the 239.255.x.y address
650 // associated with an E1.31 universe across every non-loopback IPv4
651 // interface. Op is IP_ADD_MEMBERSHIP or IP_DROP_MEMBERSHIP.
652 2 static void ChangeE131MulticastMembership(int universe, int op) {
653
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (bridgeSock < 0 || universe <= 0) {
654 ✗ return;
655 }
656 struct ip_mreq mreq;
657 char strMulticastGroup[16];
658 2 snprintf(strMulticastGroup, sizeof(strMulticastGroup), "239.255.%d.%d",
659 universe / 256, universe % 256);
660 2 mreq.imr_multiaddr.s_addr = inet_addr(strMulticastGroup);
661
662
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 const char* action = (op == IP_ADD_MEMBERSHIP) ? "Adding" : "Removing";
663
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogInfo(VB_E131BRIDGE, "%s group %s\n", action, strMulticastGroup);
664
665 struct ifaddrs* interfaces;
666
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (getifaddrs(&interfaces) < 0) {
667 ✗ return;
668 }
669 char address[16];
670 2 address[0] = 0;
671 2 int multicastJoined = 0;
672
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
12 for (struct ifaddrs* tmp = interfaces; tmp; tmp = tmp->ifa_next) {
673
3/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 6 times.
10 if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) {
674
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 GetInterfaceAddress(tmp->ifa_name, address, NULL, NULL);
675
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (strcmp(address, "127.0.0.1")) {
676 2 mreq.imr_interface.s_addr = inet_addr(address);
677
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (setsockopt(bridgeSock, IPPROTO_IP, op, &mreq, sizeof(mreq)) < 0) {
678 ✗ LogWarn(VB_E131BRIDGE, " %s multicast group %s failed on interface %s: %s\n",
679 action, strMulticastGroup, tmp->ifa_name, FPPstrerror(errno));
680 }
681 2 multicastJoined = 1;
682 }
683 }
684 }
685
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (!multicastJoined && op == IP_ADD_MEMBERSHIP) {
686 ✗ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
687 ✗ if (setsockopt(bridgeSock, IPPROTO_IP, op, &mreq, sizeof(mreq)) < 0) {
688 ✗ LogWarn(VB_E131BRIDGE, " Could not setup Multicast Group %s on default interface\n",
689 strMulticastGroup);
690 }
691 }
692 2 freeifaddrs(interfaces);
693 }
694
695 /*
696 * Read data waiting for us
697 */
698 12804 bool Bridge_ReceiveE131Data(void) {
699 // LogExcess(VB_E131BRIDGE, "Bridge_ReceiveData()\n");
700 12804 bool sync = false;
701 12804 long long packetTime = GetTimeMS();
702 12804 int msgcnt = recvmmsg(bridgeSock, msgs, MAX_MSG, MSG_DONTWAIT, nullptr);
703 12804 int batches = 1;
704
2/2
✓ Branch 0 taken 13528 times.
✓ Branch 1 taken 12804 times.
26332 while (msgcnt > 0) {
705
2/2
✓ Branch 0 taken 41994 times.
✓ Branch 1 taken 13528 times.
55522 for (int x = 0; x < msgcnt; x++) {
706 41994 sync |= Bridge_StoreData((uint8_t*)buffers[x], packetTime, msgs[x].msg_len);
707 }
708
2/2
✓ Branch 0 taken 13524 times.
✓ Branch 1 taken 4 times.
13528 msgcnt = ++batches > MAX_BATCHES_PER_WAKEUP ? 0 : recvmmsg(bridgeSock, msgs, MAX_MSG, MSG_DONTWAIT, nullptr);
709 }
710 12804 CommitBridgeData();
711 12804 return sync;
712 }
713 4445 bool Bridge_ReceiveDDPData(void) {
714 // LogExcess(VB_E131BRIDGE, "Bridge_ReceiveData()\n");
715 4445 int msgcnt = recvmmsg(ddpSock, msgs, MAX_MSG, MSG_DONTWAIT, nullptr);
716 4445 int batches = 1;
717 4445 bool sync = false;
718 4445 long long packetTime = GetTimeMS();
719
2/2
✓ Branch 0 taken 4678 times.
✓ Branch 1 taken 4445 times.
9123 while (msgcnt > 0) {
720
2/2
✓ Branch 0 taken 9366 times.
✓ Branch 1 taken 4678 times.
14044 for (int x = 0; x < msgcnt; x++) {
721 9366 sync |= Bridge_StoreDDPData((uint8_t*)buffers[x], packetTime, msgs[x].msg_len);
722 }
723
1/2
✓ Branch 0 taken 4678 times.
✗ Branch 1 not taken.
4678 msgcnt = ++batches > MAX_BATCHES_PER_WAKEUP ? 0 : recvmmsg(ddpSock, msgs, MAX_MSG, MSG_DONTWAIT, nullptr);
724 }
725 4445 CommitBridgeData();
726 4445 return sync;
727 }
728 2159 bool Bridge_ReceiveArtNetData(void) {
729 2159 int msgcnt = recvmmsg(artnetSock, msgs, MAX_MSG, MSG_DONTWAIT, nullptr);
730 2159 int batches = 1;
731 2159 bool sync = false;
732 2159 long long packetTime = GetTimeMS();
733
2/2
✓ Branch 0 taken 2451 times.
✓ Branch 1 taken 2159 times.
4610 while (msgcnt > 0) {
734
2/2
✓ Branch 0 taken 2960 times.
✓ Branch 1 taken 2451 times.
5411 for (int x = 0; x < msgcnt; x++) {
735 2960 uint8_t* bridgeBuffer = (uint8_t*)buffers[x];
736 // The buffers are reused, so bytes past msg_len are stale data
737 // from an earlier packet; too-short packets must not reach the
738 // signature/opcode checks at all.
739
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2960 times.
2960 if (msgs[x].msg_len < 12) {
740 2 continue;
741 }
742
10/18
✓ Branch 0 taken 2960 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2960 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2960 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2960 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2960 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2960 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2960 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 2960 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 2 times.
✓ Branch 17 taken 2958 times.
2960 if (bridgeBuffer[0] != 'A' || bridgeBuffer[1] != 'r' || bridgeBuffer[2] != 't' || bridgeBuffer[3] != '-' || bridgeBuffer[4] != 'N' || bridgeBuffer[5] != 'e' || bridgeBuffer[6] != 't' || bridgeBuffer[7] != 0 || bridgeBuffer[11] != 0xE) { // version must be 14
743 2 continue;
744 }
745 2958 int opCode = (bridgeBuffer[9] << 8) | bridgeBuffer[8];
746
1/2
✓ Branch 0 taken 2958 times.
✗ Branch 1 not taken.
2958 auto cb = ArtNetOpcodeHandlers.find(opCode);
747
1/2
✓ Branch 0 taken 2958 times.
✗ Branch 1 not taken.
2958 if (cb != ArtNetOpcodeHandlers.end()) {
748 2958 currentArtNetSourceIP = inAddress[x].sin_addr.s_addr;
749 2958 currentArtNetPacketLen = msgs[x].msg_len;
750
1/2
✓ Branch 0 taken 2958 times.
✗ Branch 1 not taken.
2958 sync |= cb->second(bridgeBuffer, packetTime);
751 }
752 }
753
1/2
✓ Branch 0 taken 2451 times.
✗ Branch 1 not taken.
2451 msgcnt = ++batches > MAX_BATCHES_PER_WAKEUP ? 0 : recvmmsg(artnetSock, msgs, MAX_MSG, MSG_DONTWAIT, nullptr);
754 }
755 2159 CommitBridgeData();
756 2159 return sync;
757 }
758
759 // A bridge input port that can't be opened (another program already has it)
760 // leaves that input off with a warning; fppd used to exit(1) here, which on a
761 // reload of the input config took a running show down from the main loop, and
762 // systemd's restart then failed the same way. Each (re)initialization retracts
763 // what the last one raised, so the banner clears once the port is free and the
764 // input config is saved again or fppd restarts.
765 static constexpr int WARNING_ID_BRIDGE_PORT = 72;
766 static std::vector<std::string> bridgePortWarnings;
767
768 2268 static int OpenBridgeSocket(const char* what, int port, long packetsPerFrame) {
769 2268 int sock = OpenSocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
770 2268 const char* step = "socket";
771
1/2
✓ Branch 0 taken 2268 times.
✗ Branch 1 not taken.
2268 if (sock >= 0) {
772 struct sockaddr_in addr;
773 2268 memset((char*)&addr, 0, sizeof(addr));
774 2268 addr.sin_family = AF_INET;
775 2268 addr.sin_addr.s_addr = htonl(INADDR_ANY);
776 2268 addr.sin_port = htons(port);
777
2/2
✓ Branch 0 taken 2266 times.
✓ Branch 1 taken 2 times.
2268 if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
778
1/2
✓ Branch 0 taken 2266 times.
✗ Branch 1 not taken.
2266 SetBridgeReceiveBufferSize(sock, packetsPerFrame);
779 2266 return sock;
780 }
781 2 step = "bind";
782 }
783 2 int err = errno;
784
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 LogErr(VB_E131BRIDGE, "e131bridge %s %s failed on UDP port %d: %s\n", what, step, port, FPPstrerror(err));
785
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (sock >= 0) {
786
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 close(sock);
787 }
788
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
6 std::string msg = std::string(what) + " input is off: could not open UDP port " + std::to_string(port) +
789
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
4 " (" + FPPstrerror(err) + ")";
790
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 WarningHolder::AddWarning(WARNING_ID_BRIDGE_PORT, msg);
791
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 bridgePortWarnings.push_back(msg);
792 2 return -1;
793 2 }
794
795 1922 bool Bridge_Initialize_Internal(bool& hasArtNet) {
796 1922 LogExcess(VB_E131BRIDGE, "Bridge_Initialize()\n");
797
798
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1922 times.
1924 for (const auto& w : bridgePortWarnings) {
799
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 WarningHolder::RemoveWarning(WARNING_ID_BRIDGE_PORT, w);
800 }
801 1922 bridgePortWarnings.clear();
802
803 // prepare the msg receive buffers
804 1922 memset(msgs, 0, sizeof(msgs));
805
2/2
✓ Branch 0 taken 123008 times.
✓ Branch 1 taken 1922 times.
124930 for (int i = 0; i < MAX_MSG; i++) {
806 123008 iovecs[i].iov_base = buffers[i];
807 123008 iovecs[i].iov_len = BUFSIZE;
808 123008 msgs[i].msg_hdr.msg_iov = &iovecs[i];
809 123008 msgs[i].msg_hdr.msg_iovlen = 1;
810 123008 msgs[i].msg_hdr.msg_namelen = sizeof(struct sockaddr_in);
811 123008 msgs[i].msg_hdr.msg_name = &inAddress[i];
812 }
813
814 /* Initialize our Universe Index lookup caches */
815
2/2
✓ Branch 0 taken 125960192 times.
✓ Branch 1 taken 1922 times.
125962114 for (auto& c : E131UniverseCache) {
816 125960192 c = BRIDGE_INVALID_UNIVERSE_INDEX;
817 }
818
2/2
✓ Branch 0 taken 62980096 times.
✓ Branch 1 taken 1922 times.
62982018 for (auto& c : ArtNetUniverseCache) {
819 62980096 c = BRIDGE_INVALID_UNIVERSE_INDEX;
820 }
821
822 1922 bool enabled = LoadInputUniversesFromFile();
823 1922 hasUDP = enabled;
824 1922 bool disableFakeBridges = getSettingInt("DisableFakeNetworkBridges");
825
826 1922 LogInfo(VB_E131BRIDGE, "Universe Count = %d\n", InputUniverseCount);
827 1922 InputUniversesPrint();
828
829
4/4
✓ Branch 0 taken 1839 times.
✓ Branch 1 taken 83 times.
✓ Branch 2 taken 1052 times.
✓ Branch 3 taken 787 times.
1922 if (enabled || !disableFakeBridges) {
830 1135 ddpSock = OpenBridgeSocket("DDP", DDP_PORT, DDPPacketsPerFrame());
831 }
832
833 1922 hasArtNet = false;
834 1922 bool hase131 = false;
835
2/2
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 1839 times.
1922 if (enabled) {
836
2/2
✓ Branch 0 taken 66765 times.
✓ Branch 1 taken 83 times.
66848 for (int i = 0; i < InputUniverseCount; i++) {
837
5/6
✓ Branch 0 taken 66765 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 66688 times.
✓ Branch 3 taken 77 times.
✓ Branch 4 taken 66688 times.
✓ Branch 5 taken 77 times.
66765 if (InputUniverses[i].type == E131_TYPE_MULTICAST || InputUniverses[i].type == E131_TYPE_UNICAST) {
838 66688 hase131 = true;
839
3/6
✓ Branch 0 taken 77 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 77 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 77 times.
✗ Branch 5 not taken.
77 } else if (InputUniverses[i].type == ARTNET_TYPE_BROADCAST || InputUniverses[i].type == ARTNET_TYPE_UNICAST) {
840 77 hasArtNet = true;
841 }
842 }
843 }
844
845
4/4
✓ Branch 0 taken 1841 times.
✓ Branch 1 taken 81 times.
✓ Branch 2 taken 1052 times.
✓ Branch 3 taken 789 times.
1922 if (hase131 || !disableFakeBridges) {
846 int UniverseOctet[2];
847 struct ip_mreq mreq;
848 char strMulticastGroup[16];
849
1/2
✓ Branch 0 taken 1133 times.
✗ Branch 1 not taken.
1133 bridgeSock = OpenBridgeSocket("E1.31", E131_DEST_PORT, InputUniverseCount);
850
851 // get all the addresses
852 struct ifaddrs *interfaces, *tmp;
853 1133 getifaddrs(&interfaces);
854
855 char address[16];
856 1133 address[0] = 0;
857 // Join the multicast groups (none without a socket to join them on)
858
4/4
✓ Branch 0 taken 67892 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 66761 times.
✓ Branch 3 taken 1131 times.
67894 for (int i = 0; bridgeSock >= 0 && i < InputUniverseCount; i++) {
859
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66761 times.
66761 if (InputUniverses[i].type == E131_TYPE_MULTICAST) {
860 ✗ UniverseOctet[0] = InputUniverses[i].universe / 256;
861 ✗ UniverseOctet[1] = InputUniverses[i].universe % 256;
862 ✗ snprintf(strMulticastGroup, sizeof(strMulticastGroup), "239.255.%d.%d", UniverseOctet[0], UniverseOctet[1]);
863 ✗ mreq.imr_multiaddr.s_addr = inet_addr(strMulticastGroup);
864
865 ✗ LogInfo(VB_E131BRIDGE, "Adding group %s\n", strMulticastGroup);
866
867 // add group to groups to listen for on eth0 and wlan0 if it exists
868 ✗ int multicastJoined = 0;
869 ✗ tmp = interfaces;
870 // loop through all the interfaces and subscribe to the group
871 ✗ while (tmp) {
872 // struct sockaddr_in *sin = (struct sockaddr_in *)tmp->ifa_addr;
873 // strcpy(address, inet_ntoa(sin->sin_addr));
874 ✗ if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) {
875 ✗ GetInterfaceAddress(tmp->ifa_name, address, NULL, NULL);
876 ✗ if (strcmp(address, "127.0.0.1")) {
877 ✗ LogDebug(VB_E131BRIDGE, " Adding interface %s - %s\n", tmp->ifa_name, address);
878 ✗ mreq.imr_interface.s_addr = inet_addr(address);
879 ✗ if (setsockopt(bridgeSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
880 ✗ LogWarn(VB_E131BRIDGE, " Could not setup Multicast Group for interface %s\n", tmp->ifa_name);
881 }
882 ✗ multicastJoined = 1;
883 }
884 ✗ } else if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET6) {
885 // FIXME for ipv6 multicast
886 // LogDebug(VB_E131BRIDGE, " Inet6 interface %s\n", tmp->ifa_name);
887 }
888 ✗ tmp = tmp->ifa_next;
889 }
890
891 ✗ if (!multicastJoined) {
892 ✗ LogDebug(VB_E131BRIDGE, " Binding to default interface\n");
893 ✗ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
894 ✗ if (setsockopt(bridgeSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
895 ✗ LogWarn(VB_E131BRIDGE, " Could not setup Multicast Group\n");
896 }
897 }
898 }
899 }
900 1133 freeifaddrs(interfaces);
901 }
902
903
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 1846 times.
1922 if (hasArtNet) {
904 76 CreateArtNetSocket();
905 }
906
907 1922 return enabled;
908 }
909
910 3789 static void removeMulticastGroups() {
911
2/2
✓ Branch 0 taken 66765 times.
✓ Branch 1 taken 3789 times.
70554 for (int i = 0; i < InputUniverseCount; i++) {
912
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66765 times.
66765 if (InputUniverses[i].type == E131_TYPE_MULTICAST) {
913 ✗ ChangeE131MulticastMembership(InputUniverses[i].universe, IP_DROP_MEMBERSHIP);
914 }
915 }
916
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3789 times.
3790 for (int u : joinedSyncUniverses) {
917
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 ChangeE131MulticastMembership(u, IP_DROP_MEMBERSHIP);
918 }
919 3789 joinedSyncUniverses.clear();
920 3789 syncUniverseCapLogged = false;
921 3789 }
922
923 41994 bool Bridge_StoreData(uint8_t* bridgeBuffer, long long packetTime, int msgLen) {
924 // The receive buffers are reused across packets, so anything beyond
925 // msgLen is stale data from an earlier packet. Never interpret it.
926
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 41994 times.
41994 if (msgLen <= E131_VECTOR_INDEX) {
927 ✗ e131Errors++;
928 ✗ return false;
929 }
930
4/4
✓ Branch 0 taken 41993 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 41393 times.
✓ Branch 3 taken 600 times.
41994 if ((bridgeBuffer[E131_VECTOR_INDEX] == VECTOR_ROOT_E131_DATA) &&
931 41393 (msgLen >= E131_HEADER_LENGTH) &&
932
1/2
✓ Branch 0 taken 41393 times.
✗ Branch 1 not taken.
41393 (bridgeBuffer[E131_START_CODE] == 0x00)) {
933 41393 uint32_t universe = ((int)bridgeBuffer[E131_UNIVERSE_INDEX] << 8) + bridgeBuffer[E131_UNIVERSE_INDEX + 1];
934
935 // If the sender advertises a sync universe, make sure we're subscribed
936 // to its multicast group so the sync packets reach us. Data is still
937 // applied immediately; this only ensures the sync counter updates and
938 // sets the stage for any future buffered-until-sync handling.
939 41393 int syncUniverse = ((int)bridgeBuffer[E131_SYNC_ADDRESS_INDEX] << 8) +
940 41393 bridgeBuffer[E131_SYNC_ADDRESS_INDEX + 1];
941 82788 if (syncUniverse > 0 &&
942
6/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 41391 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 41392 times.
41395 Bridge_GetIndexFromUniverseNumber(syncUniverse, false) == BRIDGE_INVALID_UNIVERSE_INDEX &&
943
3/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 !joinedSyncUniverses.contains(syncUniverse)) {
944
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (joinedSyncUniverses.size() < MAX_JOINED_SYNC_UNIVERSES) {
945
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 joinedSyncUniverses.insert(syncUniverse);
946
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 ChangeE131MulticastMembership(syncUniverse, IP_ADD_MEMBERSHIP);
947 ✗ } else if (!syncUniverseCapLogged) {
948 ✗ syncUniverseCapLogged = true;
949 ✗ LogWarn(VB_E131BRIDGE, "Not joining E1.31 sync universe %d: already joined %d sync universes\n",
950 syncUniverse, (int)MAX_JOINED_SYNC_UNIVERSES);
951 }
952 }
953
954
1/2
✓ Branch 0 taken 41393 times.
✗ Branch 1 not taken.
41393 uint32_t universeIndex = Bridge_GetIndexFromUniverseNumber(universe, false);
955
2/2
✓ Branch 0 taken 39923 times.
✓ Branch 1 taken 1470 times.
41393 if (universeIndex != BRIDGE_INVALID_UNIVERSE_INDEX) {
956 39923 bool sourceJustSwitched = false;
957
2/2
✓ Branch 0 taken 254 times.
✓ Branch 1 taken 39669 times.
39923 if (sourcePriorityEnabled.load(std::memory_order_relaxed)) {
958 uint8_t sourceId[16];
959 254 memcpy(sourceId, &bridgeBuffer[E131_CID_INDEX], E131_CID_LENGTH);
960
3/4
✓ Branch 0 taken 254 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119 times.
✓ Branch 3 taken 135 times.
254 if (!EvaluateSource(InputUniverses[universeIndex], sourceId,
961
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
254 bridgeBuffer[E131_PRIORITY_INDEX],
962 sourceJustSwitched)) {
963 119 return false;
964 }
965 }
966
967 39804 uint32_t sn = bridgeBuffer[E131_SEQUENCE_INDEX];
968
6/6
✓ Branch 0 taken 39800 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 35094 times.
✓ Branch 3 taken 4706 times.
✓ Branch 4 taken 35094 times.
✓ Branch 5 taken 4710 times.
39804 if (!sourceJustSwitched && StatLoad(InputUniverses[universeIndex].packetsReceived) != 0) {
969
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 35037 times.
35094 if (StatLoad(InputUniverses[universeIndex].lastSequenceNumber) == 255) {
970 // some wrap from 255 -> 1 and some from 255 -> 0, spec doesn't say which
971
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 57 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
57 if (sn != 0 && sn != 1) {
972 ✗ StatAdd(InputUniverses[universeIndex].errorPackets, 1);
973 }
974
2/2
✓ Branch 0 taken 18092 times.
✓ Branch 1 taken 16945 times.
35037 } else if ((StatLoad(InputUniverses[universeIndex].lastSequenceNumber) + 1) != sn) {
975 18092 StatAdd(InputUniverses[universeIndex].errorPackets, 1);
976 }
977 }
978 39804 StatStore(InputUniverses[universeIndex].lastSequenceNumber, sn);
979
980 // Only store what the packet actually carries: the smaller of the
981 // configured universe size, the bytes physically received, and the
982 // DMP property count (which includes the start code). Copying the
983 // configured size regardless (as this used to) put stale bytes
984 // from a previous packet on the wire whenever a packet was short.
985 39804 uint32_t dataLen = InputUniverses[universeIndex].size;
986 39804 dataLen = std::min(dataLen, (uint32_t)(msgLen - E131_HEADER_LENGTH));
987 39804 uint32_t propCount = ((uint32_t)bridgeBuffer[E131_COUNT_INDEX] << 8) | bridgeBuffer[E131_COUNT_INDEX + 1];
988
2/2
✓ Branch 0 taken 39504 times.
✓ Branch 1 taken 300 times.
39804 if (propCount > 0) {
989 39504 dataLen = std::min(dataLen, propCount - 1);
990 }
991 79608 SetBridgeData(&bridgeBuffer[E131_HEADER_LENGTH],
992
1/2
✓ Branch 0 taken 39804 times.
✗ Branch 1 not taken.
39804 InputUniverses[universeIndex].startAddress - 1,
993 dataLen,
994 packetTime);
995 39804 StatAdd(InputUniverses[universeIndex].bytesReceived, dataLen);
996 39804 StatAdd(InputUniverses[universeIndex].packetsReceived, 1);
997 } else {
998 1470 StatAdd(unknownUniverse.packetsReceived, 1);
999 1470 uint32_t len = bridgeBuffer[16] & 0xF;
1000 1470 len <<= 8;
1001 1470 len += bridgeBuffer[17];
1002 1470 StatAdd(unknownUniverse.bytesReceived, len);
1003
1/2
✓ Branch 0 taken 1470 times.
✗ Branch 1 not taken.
1470 LogDebug(VB_E131BRIDGE, "Received e1.31 data packet for unconfigured universe %d\n", universe);
1004 }
1005
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 600 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
41875 } else if (bridgeBuffer[E131_VECTOR_INDEX] == VECTOR_ROOT_E131_EXTENDED &&
1006 msgLen > E131_EXTENDED_PACKET_TYPE_INDEX) {
1007
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (bridgeBuffer[E131_EXTENDED_PACKET_TYPE_INDEX] == VECTOR_E131_EXTENDED_SYNCHRONIZATION) {
1008 1 e131SyncPackets++;
1009 1 return true;
1010 }
1011 ✗ e131Errors++;
1012 ✗ LogDebug(VB_E131BRIDGE, "Unknown e1.31 extended packet type %d\n", (int)bridgeBuffer[E131_EXTENDED_PACKET_TYPE_INDEX]);
1013 } else {
1014 600 e131Errors++;
1015 600 LogDebug(VB_E131BRIDGE, "Unknown e1.31 packet type %d, start code %d\n", (int)bridgeBuffer[E131_VECTOR_INDEX], (int)bridgeBuffer[E131_START_CODE]);
1016 }
1017 41874 return false;
1018 }
1019
1020 ✗ bool Bridge_HandleArtNetSync(uint8_t* bridgeBuffer, long long packetTime) {
1021 // sync packet
1022 ✗ return true;
1023 }
1024 2950 bool Bridge_StoreArtNetData(uint8_t* bridgeBuffer, long long packetTime) {
1025
2/4
✓ Branch 0 taken 2950 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2950 times.
✗ Branch 3 not taken.
2950 if (bridgeBuffer[9] == 0x50 && bridgeBuffer[8] == 0x00) {
1026 // data packet; 18 byte ArtDMX header, then the channel data
1027
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 2650 times.
2950 if (currentArtNetPacketLen < 18) {
1028 300 return false;
1029 }
1030 2650 uint32_t sn = bridgeBuffer[12];
1031
1032 2650 uint32_t univ = bridgeBuffer[15];
1033 2650 univ <<= 8;
1034 2650 univ &= 0x7F00;
1035 2650 univ |= bridgeBuffer[14];
1036
1037 2650 uint32_t len = bridgeBuffer[16];
1038 2650 len <<= 8;
1039 2650 len &= 0x7F00;
1040 2650 len += bridgeBuffer[17];
1041 // never trust the header's length beyond what actually arrived
1042 2650 len = std::min(len, (uint32_t)(currentArtNetPacketLen - 18));
1043
1/2
✓ Branch 0 taken 2650 times.
✗ Branch 1 not taken.
2650 uint32_t universeIndex = Bridge_GetIndexFromUniverseNumber(univ, true);
1044
2/2
✓ Branch 0 taken 2260 times.
✓ Branch 1 taken 390 times.
2650 if (universeIndex != BRIDGE_INVALID_UNIVERSE_INDEX) {
1045 2260 bool sourceJustSwitched = false;
1046
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2260 times.
2260 if (sourcePriorityEnabled.load(std::memory_order_relaxed)) {
1047 // ArtNet has no priority field in ArtDMX; key on source IP
1048 // and use a constant priority so all sources tie. The helper
1049 // then reduces to first-source-wins with timeout failover.
1050 ✗ uint8_t sourceId[16] = {};
1051 ✗ memcpy(sourceId, &currentArtNetSourceIP, sizeof(currentArtNetSourceIP));
1052 ✗ if (!EvaluateSource(InputUniverses[universeIndex], sourceId,
1053 100, sourceJustSwitched)) {
1054 ✗ return false;
1055 }
1056 }
1057
1058
5/6
✓ Branch 0 taken 2260 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 435 times.
✓ Branch 3 taken 1825 times.
✓ Branch 4 taken 435 times.
✓ Branch 5 taken 1825 times.
2260 if (!sourceJustSwitched && StatLoad(InputUniverses[universeIndex].packetsReceived) != 0) {
1059
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 435 times.
435 if (StatLoad(InputUniverses[universeIndex].lastSequenceNumber) == 255) {
1060 // some wrap from 255 -> 1 and some from 255 -> 0
1061 ✗ if (sn != 0 && sn != 1) {
1062 ✗ StatAdd(InputUniverses[universeIndex].errorPackets, 1);
1063 }
1064
2/2
✓ Branch 0 taken 299 times.
✓ Branch 1 taken 136 times.
435 } else if ((StatLoad(InputUniverses[universeIndex].lastSequenceNumber) + 1) != sn) {
1065 299 StatAdd(InputUniverses[universeIndex].errorPackets, 1);
1066 }
1067 }
1068 2260 StatStore(InputUniverses[universeIndex].lastSequenceNumber, sn);
1069 2260 StatAdd(InputUniverses[universeIndex].bytesReceived, std::min(InputUniverses[universeIndex].size, len));
1070 2260 StatAdd(InputUniverses[universeIndex].packetsReceived, 1);
1071
1072 4520 SetBridgeData(&bridgeBuffer[18],
1073
1/2
✓ Branch 0 taken 2260 times.
✗ Branch 1 not taken.
2260 InputUniverses[universeIndex].startAddress - 1,
1074
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2260 std::min(InputUniverses[universeIndex].size, len),
1075 packetTime);
1076
1077 } else {
1078 390 StatAdd(unknownUniverse.packetsReceived, 1);
1079 // len already holds the ArtDMX data length (15-bit field), clamped
1080 // to what was actually received.
1081 390 StatAdd(unknownUniverse.bytesReceived, len);
1082
1/2
✓ Branch 0 taken 390 times.
✗ Branch 1 not taken.
390 LogDebug(VB_E131BRIDGE, "Received ArtNet data packet for unconfigured universe %d\n", univ);
1083 }
1084 }
1085 2650 return false;
1086 }
1087 2 bool Bridge_HandleArtNetPoll(uint8_t* bridgeBuffer, long long packetTime) {
1088
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (bridgeBuffer[9] == 0x20 && bridgeBuffer[8] == 0x00) {
1089 // ArtNet Poll, need to send a reply
1090 char buf[512];
1091 2 memset(buf, 0, sizeof(buf));
1092 2 strcpy(buf, "Art-Net");
1093 2 buf[9] = 0x21; // PollReply
1094 2 buf[10] = 0; // IP
1095 2 buf[11] = 0;
1096 2 buf[12] = 0;
1097 2 buf[13] = 0;
1098 2 buf[14] = 0x36; // port
1099 2 buf[15] = 0x19;
1100
1101
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 buf[16] = std::atoi(getFPPMajorVersion());
1102
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 buf[17] = std::atoi(getFPPMinorVersion());
1103
1104 2 buf[18] = 0; // universes?
1105 2 buf[19] = 0;
1106
1107 2 buf[20] = 0; // OEM value?
1108 2 buf[21] = 0;
1109
1110 2 buf[23] = 0; // status1
1111
1112 2 buf[24] = 0; // ESTA
1113 2 buf[25] = 0;
1114
1115
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::string hostname = getSetting("HostName");
1116
1117
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (hostname == "") {
1118 ✗ hostname = "FPP";
1119 }
1120 // Zero the fields first so snprintf's truncation doesn't leave stale bytes
1121 // from a previous packet, then bounded-copy.
1122 2 memset(&buf[26], 0, 18);
1123 2 memset(&buf[44], 0, 64);
1124 2 memset(&buf[108], 0, 64);
1125 // Bounded copy — HostName is user-controlled via /api/settings and could be up to 255 chars.
1126 // Original strcpy could overflow buf[512] (18 bytes at 26, 64 bytes at 44). snprintf truncates safely.
1127 2 snprintf(&buf[26], 18, "%s", hostname.c_str()); // HOSTNAME (18 bytes)
1128 2 snprintf(&buf[44], 64, "%s", hostname.c_str()); // Description (64 bytes)
1129 // buf[108] Status stays empty — memset already zeroed it, no snprintf needed
1130
1131 // ArtPollReply: advertise the ArtNet input universes actually configured.
1132 // Previously this was hardcoded to 4 ports on universe 0 (all SwIn 0),
1133 // so a controller polling FPP always saw universe 0 regardless of
1134 // Settings > Channel Inputs (issue #2927). Populate from InputUniverses
1135 // where type is ArtNet. Non-breaking: packet stays 240 bytes, IP/port/
1136 // version/hostname unchanged — only the 16 universe-advertising bytes
1137 // (172-193) now reflect configuration. Capped at 4 ports per spec.
1138 2 std::vector<uint32_t> artNetUniverses;
1139 {
1140
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::lock_guard<std::mutex> lg(universesStructureLock);
1141
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 for (auto &ue : InputUniverses) {
1142
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (ue.type == ARTNET_TYPE_BROADCAST || ue.type == ARTNET_TYPE_UNICAST) {
1143
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 artNetUniverses.push_back(ue.universe);
1144 }
1145 }
1146 2 }
1147 2 int numPorts = std::min<int>((int)artNetUniverses.size(), 4);
1148 // Preserve historical big-endian NumPorts encoding (0,4 = 4 ports).
1149 2 buf[172] = 0;
1150 2 buf[173] = (char)numPorts;
1151
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 for (int i = 0; i < 4; ++i) {
1152 8 bool active = (i < numPorts);
1153
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 5 times.
8 buf[174 + i] = active ? (char)0xc0 : 0; // PortTypes: DMX512
1154
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 5 times.
8 buf[178 + i] = active ? (char)0x08 : 0; // GoodInput: port enabled
1155 8 buf[182 + i] = 0; // GoodOutput: no ArtNet outputs
1156 // SwIn: low 8 bits of PortAddress (exact for 0-255, best-effort >255,
1157 // far better than always-0). SwOut unused (bridge is input-only).
1158
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 5 times.
8 buf[186 + i] = active ? (char)(artNetUniverses[i] & 0xFF) : 0;
1159 8 buf[190 + i] = 0;
1160 }
1161
1162 char addressBuf[128];
1163 // get all the addresses
1164 struct ifaddrs *interfaces, *tmp;
1165 2 getifaddrs(&interfaces);
1166 2 tmp = interfaces;
1167
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
12 while (tmp) {
1168
3/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 6 times.
10 if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) {
1169 // Check if interface is UP and RUNNING before sending ArtNet poll response
1170
4/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 if (strncmp("usb", tmp->ifa_name, 3) != 0 && strncmp("lo", tmp->ifa_name, 2) != 0 && tmp->ifa_addr &&
1171
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 (tmp->ifa_flags & IFF_UP) && (tmp->ifa_flags & IFF_RUNNING)) {
1172 2 struct sockaddr_in* sain = (struct sockaddr_in*)tmp->ifa_addr;
1173 2 unsigned long s_addr = sain->sin_addr.s_addr;
1174 2 buf[13] = s_addr >> 24; // IP
1175 2 buf[12] = s_addr >> 16;
1176 2 buf[11] = s_addr >> 8;
1177 2 buf[10] = s_addr & 0xFF;
1178
1179 struct sockaddr_in s;
1180 2 memset(&s, '\0', sizeof(struct sockaddr_in));
1181 2 s.sin_family = AF_INET;
1182 2 s.sin_port = (in_port_t)htons(0x1936);
1183 2 s.sin_addr.s_addr = htonl(INADDR_BROADCAST);
1184
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 sendto(artnetSock, buf, 240, 0, (struct sockaddr*)&s, sizeof(struct sockaddr_in));
1185 }
1186 }
1187 10 tmp = tmp->ifa_next;
1188 }
1189 2 freeifaddrs(interfaces);
1190 2 }
1191 2 return false;
1192 }
1193 9366 bool Bridge_StoreDDPData(uint8_t* bridgeBuffer, long long packetTime, int msgLen) {
1194 9366 bool push = false;
1195 // The receive buffers are reused, so bytes past msgLen are stale data
1196 // from an earlier packet. A malformed packet must not force an output
1197 // cycle either, so error paths return false rather than a push flag
1198 // read from garbage.
1199
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 9066 times.
9366 if (msgLen < 4) {
1200 300 ddpErrors++;
1201 300 return false;
1202 }
1203
2/2
✓ Branch 0 taken 8766 times.
✓ Branch 1 taken 300 times.
9066 if (bridgeBuffer[3] == 1) {
1204 8766 ddpPacketsReceived++;
1205 8766 bool tc = bridgeBuffer[0] & DDP_TIMECODE_FLAG;
1206 8766 push = bridgeBuffer[0] & DDP_PUSH_FLAG;
1207
1208
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8766 times.
8766 int ddpOffset = tc ? 14 : 10;
1209
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 8466 times.
8766 if (msgLen < ddpOffset) {
1210 300 ddpErrors++;
1211 300 return false;
1212 }
1213
1214 8466 uint32_t chan = bridgeBuffer[4];
1215 8466 chan <<= 8;
1216 8466 chan += bridgeBuffer[5];
1217 8466 chan <<= 8;
1218 8466 chan += bridgeBuffer[6];
1219 8466 chan <<= 8;
1220 8466 chan += bridgeBuffer[7];
1221
1222 8466 uint32_t len = bridgeBuffer[8] << 8;
1223 8466 len += bridgeBuffer[9];
1224
1225 // Drop the packet if the length or channel map is out of bounds. Reading
1226 // `len` bytes from the receive buffer (beyond what was actually received),
1227 // or writing beyond the bridge buffer (FPPD_MAX_CHANNEL_NUM), would leak
1228 // stale data or corrupt memory. 64-bit arithmetic avoids uint32 overflow
1229 // of the channel+length sum.
1230
2/2
✓ Branch 0 taken 8166 times.
✓ Branch 1 taken 300 times.
8466 if ((uint64_t)len > (uint64_t)(msgLen - ddpOffset) ||
1231
2/2
✓ Branch 0 taken 922 times.
✓ Branch 1 taken 7244 times.
8166 (uint64_t)chan + (uint64_t)len > (uint64_t)FPPD_MAX_CHANNELS) { // not into the reserved OFF/WHITE channels
1232 1222 ddpErrors++;
1233 1222 return false;
1234 }
1235
1236 7244 uint32_t sn = bridgeBuffer[1] & 0xF;
1237
1/2
✓ Branch 0 taken 7244 times.
✗ Branch 1 not taken.
7244 if (sn) {
1238 7244 bool isErr = false;
1239
2/2
✓ Branch 0 taken 7234 times.
✓ Branch 1 taken 10 times.
7244 if (ddpLastSequence) {
1240
2/2
✓ Branch 0 taken 2211 times.
✓ Branch 1 taken 5023 times.
7234 if (sn == 1) {
1241
2/2
✓ Branch 0 taken 2004 times.
✓ Branch 1 taken 207 times.
2211 if (ddpLastSequence != 15) {
1242 2004 isErr = true;
1243 }
1244
2/2
✓ Branch 0 taken 2085 times.
✓ Branch 1 taken 2938 times.
5023 } else if ((sn - 1) != ddpLastSequence) {
1245 2085 isErr = true;
1246 }
1247 }
1248
2/2
✓ Branch 0 taken 4089 times.
✓ Branch 1 taken 3155 times.
7244 if (isErr) {
1249 4089 ddpErrors++;
1250 // printf("%d %d %d %d\n", sn, ddpLastSequence, chan, ddpLastChannel);
1251 }
1252 7244 ddpLastSequence = sn;
1253 7244 ddpLastChannel = chan + len;
1254 }
1255
1256 14488 ddpMinChannel.store(std::min(ddpMinChannel.load(std::memory_order_relaxed), chan + 1),
1257 std::memory_order_relaxed);
1258 14488 ddpMaxChannel.store(std::max(ddpMaxChannel.load(std::memory_order_relaxed), chan + len),
1259 std::memory_order_relaxed);
1260
1261 7244 SetBridgeData(&bridgeBuffer[ddpOffset],
1262 chan,
1263 len,
1264 packetTime);
1265 7244 ddpBytesReceived += len;
1266
4/4
✓ Branch 0 taken 200 times.
✓ Branch 1 taken 100 times.
✓ Branch 2 taken 100 times.
✓ Branch 3 taken 100 times.
300 } else if (bridgeBuffer[0] & 0x02 && bridgeBuffer[3] == 250) {
1267 // Queries and other non-data packets are ordinary DDP traffic FPP
1268 // doesn't answer; printed one per packet, they filled the journal.
1269 100 LogExcess(VB_E131BRIDGE, "DDP query config packet: %d\n", (int)bridgeBuffer[3]);
1270
3/4
✓ Branch 0 taken 100 times.
✓ Branch 1 taken 100 times.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
200 } else if (bridgeBuffer[0] & 0x02 && bridgeBuffer[3] == 251) {
1271 100 LogExcess(VB_E131BRIDGE, "DDP query status packet: %d\n", (int)bridgeBuffer[3]);
1272 } else {
1273 100 LogExcess(VB_E131BRIDGE, "DDP packet for id %d ignored\n", (int)bridgeBuffer[3]);
1274 }
1275 7544 return push;
1276 }
1277
1278 1072 static inline bool IsArtNetInputType(int type) {
1279
3/4
✓ Branch 0 taken 1072 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1067 times.
1072 return type == ARTNET_TYPE_BROADCAST || type == ARTNET_TYPE_UNICAST;
1280 }
1281
1282 // universe is a received packet's universe number (16 bits for E1.31, 15 for
1283 // ArtNet), so it is always inside its protocol's cache.
1284 44045 inline int Bridge_GetIndexFromUniverseNumber(int universe, bool artnet) {
1285
2/2
✓ Branch 0 taken 2650 times.
✓ Branch 1 taken 41395 times.
44045 unsigned int* cache = artnet ? ArtNetUniverseCache : E131UniverseCache;
1286 44045 unsigned int cached = cache[universe];
1287
2/2
✓ Branch 0 taken 1827 times.
✓ Branch 1 taken 42218 times.
44045 if (cached == BRIDGE_UNIVERSE_NOT_FOUND)
1288 1827 return BRIDGE_INVALID_UNIVERSE_INDEX;
1289
2/2
✓ Branch 0 taken 41112 times.
✓ Branch 1 taken 1106 times.
42218 if (cached != BRIDGE_INVALID_UNIVERSE_INDEX)
1290 41112 return cached;
1291
1292
2/2
✓ Branch 0 taken 500689 times.
✓ Branch 1 taken 35 times.
500724 for (int index = 0; index < InputUniverseCount; index++) {
1293
6/6
✓ Branch 0 taken 1072 times.
✓ Branch 1 taken 499617 times.
✓ Branch 2 taken 1071 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1071 times.
✓ Branch 5 taken 499618 times.
501761 if (universe == InputUniverses[index].universe &&
1294 1072 IsArtNetInputType(InputUniverses[index].type) == artnet) {
1295 1071 cache[universe] = index;
1296 1071 return index;
1297 }
1298 }
1299 // Cache the miss too. The sync-address check runs this for every data
1300 // packet, and the advertised sync universe is normally not a configured
1301 // input, so an uncached miss meant a full scan of the universe list per
1302 // packet. The cache is rebuilt from scratch on every UDP reload.
1303 35 cache[universe] = BRIDGE_UNIVERSE_NOT_FOUND;
1304 35 return BRIDGE_INVALID_UNIVERSE_INDEX;
1305 }
1306
1307 5376 void ResetBytesReceived() {
1308 // Callable from the HTTP server's worker threads.
1309
1/2
✓ Branch 0 taken 5376 times.
✗ Branch 1 not taken.
5376 std::lock_guard<std::mutex> lg(universesStructureLock);
1310
2/2
✓ Branch 0 taken 16080 times.
✓ Branch 1 taken 5376 times.
21456 for (int i = 0; i < InputUniverseCount; i++) {
1311 16080 StatStore(InputUniverses[i].bytesReceived, 0);
1312 16080 StatStore(InputUniverses[i].packetsReceived, 0);
1313 16080 StatStore(InputUniverses[i].errorPackets, 0);
1314 16080 StatStore(InputUniverses[i].lastSequenceNumber, 0);
1315 16080 StatStore(InputUniverses[i].suppressedPackets, 0);
1316 }
1317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5376 times.
5376 for (auto& entry : DMXInputs) {
1318 ✗ StatStore(entry->bytesReceived, 0);
1319 ✗ StatStore(entry->packetsReceived, 0);
1320 ✗ StatStore(entry->errorPackets, 0);
1321 ✗ StatStore(entry->lastSequenceNumber, 0);
1322 }
1323 5376 ddpBytesReceived = 0;
1324 5376 ddpPacketsReceived = 0;
1325 5376 ddpErrors = 0;
1326 5376 e131Errors = 0;
1327 5376 StatStore(unknownUniverse.bytesReceived, 0);
1328 5376 StatStore(unknownUniverse.packetsReceived, 0);
1329 5376 e131SyncPackets = 0;
1330 5376 }
1331
1332 10325 Json::Value GetE131UniverseBytesReceived() {
1333 // Callable from the HTTP server's worker threads; the lock keeps a config
1334 // reload from rebuilding the universe lists under this iteration. The
1335 // counters themselves are read atomically (StatLoad).
1336
1/2
✓ Branch 0 taken 10325 times.
✗ Branch 1 not taken.
10325 std::lock_guard<std::mutex> lg(universesStructureLock);
1337
1/2
✓ Branch 0 taken 10325 times.
✗ Branch 1 not taken.
10325 Json::Value result;
1338
1/2
✓ Branch 0 taken 10325 times.
✗ Branch 1 not taken.
10325 Json::Value universes(Json::arrayValue);
1339
1340 int i;
1341
1342
2/2
✓ Branch 0 taken 1946 times.
✓ Branch 1 taken 8379 times.
10325 if (ddpBytesReceived) {
1343
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 Json::Value ddpUniverse;
1344
2/4
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1946 times.
✗ Branch 3 not taken.
1946 ddpUniverse["id"] = "DDP";
1345
1346 1946 uint32_t minChannel = ddpMinChannel.load(std::memory_order_relaxed);
1347 1946 uint32_t maxChannel = ddpMaxChannel.load(std::memory_order_relaxed);
1348
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 if (maxChannel > minChannel) {
1349
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 std::stringstream ss;
1350
3/6
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1946 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1946 times.
✗ Branch 5 not taken.
1946 ss << minChannel << "-" << maxChannel;
1351
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 std::string chanRange = ss.str();
1352
2/4
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1946 times.
✗ Branch 3 not taken.
1946 ddpUniverse["startChannel"] = chanRange;
1353 1946 } else {
1354 ✗ ddpUniverse["startChannel"] = "-";
1355 }
1356
1357
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 std::stringstream ss;
1358
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 ss << ddpBytesReceived;
1359
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 std::string bytesReceived = ss.str();
1360
2/4
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1946 times.
✗ Branch 3 not taken.
1946 ddpUniverse["bytesReceived"] = bytesReceived;
1361
1362
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 std::stringstream pr;
1363
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 pr << ddpPacketsReceived;
1364
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 std::string packetsReceived = pr.str();
1365
2/4
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1946 times.
✗ Branch 3 not taken.
1946 ddpUniverse["packetsReceived"] = packetsReceived;
1366
1367
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 std::stringstream er;
1368
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 er << ddpErrors;
1369
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 std::string errors = er.str();
1370
2/4
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1946 times.
✗ Branch 3 not taken.
1946 ddpUniverse["errors"] = errors;
1371
2/4
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1946 times.
✗ Branch 3 not taken.
1946 ddpUniverse["suppressedPackets"] = "-";
1372
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 universes.append(ddpUniverse);
1373 1946 }
1374
1375
2/2
✓ Branch 0 taken 30957 times.
✓ Branch 1 taken 10325 times.
41282 for (i = 0; i < InputUniverseCount; i++) {
1376
1/2
✓ Branch 0 taken 30957 times.
✗ Branch 1 not taken.
30957 Json::Value universe;
1377
1378
2/4
✓ Branch 0 taken 30957 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30957 times.
✗ Branch 3 not taken.
30957 universe["id"] = InputUniverses[i].universe;
1379
2/4
✓ Branch 0 taken 30957 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30957 times.
✗ Branch 3 not taken.
30957 universe["startChannel"] = InputUniverses[i].startAddress;
1380
1381
2/4
✓ Branch 0 taken 30957 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30957 times.
✗ Branch 3 not taken.
30957 universe["bytesReceived"] = std::to_string(StatLoad(InputUniverses[i].bytesReceived));
1382
2/4
✓ Branch 0 taken 30957 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30957 times.
✗ Branch 3 not taken.
30957 universe["packetsReceived"] = std::to_string(StatLoad(InputUniverses[i].packetsReceived));
1383
2/4
✓ Branch 0 taken 30957 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30957 times.
✗ Branch 3 not taken.
30957 universe["errors"] = std::to_string(StatLoad(InputUniverses[i].errorPackets));
1384
2/4
✓ Branch 0 taken 30957 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30957 times.
✗ Branch 3 not taken.
30957 universe["suppressedPackets"] = std::to_string(StatLoad(InputUniverses[i].suppressedPackets));
1385
1386
1/2
✓ Branch 0 taken 30957 times.
✗ Branch 1 not taken.
30957 universes.append(universe);
1387 30957 }
1388
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10325 times.
10326 for (auto& entry : DMXInputs) {
1389
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 Json::Value universe;
1390
1391
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 universe["id"] = entry->dmxDevice;
1392
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 universe["startChannel"] = entry->startAddress;
1393
1394
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 universe["bytesReceived"] = std::to_string(StatLoad(entry->bytesReceived));
1395
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 universe["packetsReceived"] = std::to_string(StatLoad(entry->packetsReceived));
1396
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 universe["errors"] = std::to_string(StatLoad(entry->errorPackets));
1397
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 universe["suppressedPackets"] = "-";
1398
1399
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 universes.append(universe);
1400 1 }
1401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10325 times.
10325 if (e131Errors) {
1402 ✗ Json::Value universe;
1403
1404 ✗ universe["id"] = "E1.31 Errors";
1405 ✗ universe["startChannel"] = "-";
1406 ✗ universe["bytesReceived"] = "-";
1407 ✗ universe["packetsReceived"] = "-";
1408
1409 ✗ std::stringstream er;
1410 ✗ er << e131Errors;
1411 ✗ std::string errors = er.str();
1412 ✗ universe["errors"] = errors;
1413 ✗ universe["suppressedPackets"] = "-";
1414
1415 ✗ universes.append(universe);
1416 ✗ }
1417
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10325 times.
10325 if (StatLoad(unknownUniverse.packetsReceived)) {
1418 ✗ Json::Value universe;
1419
1420 ✗ universe["id"] = "Ignored";
1421 ✗ universe["startChannel"] = "-";
1422
1423 ✗ std::stringstream er;
1424 ✗ er << e131Errors;
1425 ✗ std::string errors = er.str();
1426
1427 ✗ std::stringstream ss;
1428 ✗ ss << StatLoad(unknownUniverse.bytesReceived);
1429 ✗ std::string bytesReceived = ss.str();
1430 ✗ universe["bytesReceived"] = bytesReceived;
1431
1432 ✗ std::stringstream pr;
1433 ✗ pr << StatLoad(unknownUniverse.packetsReceived);
1434 ✗ std::string packetsReceived = pr.str();
1435 ✗ universe["packetsReceived"] = packetsReceived;
1436
1437 ✗ universe["errors"] = "-";
1438 ✗ universe["suppressedPackets"] = "-";
1439
1440 ✗ universes.append(universe);
1441 ✗ }
1442
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10323 times.
10325 if (e131SyncPackets) {
1443
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 Json::Value universe;
1444
1445
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 universe["id"] = "E1.31 Sync";
1446
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 universe["startChannel"] = "-";
1447
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 universe["bytesReceived"] = "-";
1448
1449
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::stringstream er;
1450
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 er << e131SyncPackets;
1451
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::string sync = er.str();
1452
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 universe["packetsReceived"] = sync;
1453
1454
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 universe["errors"] = "-";
1455
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 universe["suppressedPackets"] = "-";
1456
1457
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 universes.append(universe);
1458 2 }
1459
1460
2/4
✓ Branch 0 taken 10325 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10325 times.
✗ Branch 3 not taken.
10325 result["universes"] = universes;
1461
1462 10325 return result;
1463 10325 }
1464
1465 1922 void InputUniversesPrint() {
1466
2/2
✓ Branch 0 taken 66765 times.
✓ Branch 1 taken 1922 times.
68687 for (int i = 0; i < InputUniverseCount; i++) {
1467
2/2
✓ Branch 0 taken 66688 times.
✓ Branch 1 taken 77 times.
66765 if (InputUniverses[i].type < 2) {
1468 66688 LogDebug(VB_E131BRIDGE, "E1.31 Universe: %d:%d:%d:%d:%d %s\n",
1469 InputUniverses[i].active,
1470 InputUniverses[i].universe,
1471 InputUniverses[i].startAddress,
1472 InputUniverses[i].size,
1473 InputUniverses[i].type,
1474 InputUniverses[i].unicastAddress);
1475 } else {
1476 77 LogDebug(VB_E131BRIDGE, "ArtNet Universe: %d:%d:%d:%d:%d %s\n",
1477 InputUniverses[i].active,
1478 InputUniverses[i].universe,
1479 InputUniverses[i].startAddress,
1480 InputUniverses[i].size,
1481 InputUniverses[i].type,
1482 InputUniverses[i].unicastAddress);
1483 }
1484 }
1485 1922 }
1486
1487 // "Received <protocol> data from <ip>" warnings for data arriving with no
1488 // input configured. Each source's warning times out 10s after it was last
1489 // raised, so it is re-raised at most every 5s while the source keeps sending,
1490 // rather than on every wakeup; and only the first few sources get their own
1491 // warning -- the source address is whatever the packet says, so a flood with
1492 // a new one per packet grew the warning list (walked on every add) without
1493 // limit. The rest share one warning. Main loop only.
1494 static constexpr int FAKE_BRIDGE_WARNING_SECONDS = 10;
1495 static constexpr long long FAKE_BRIDGE_RERAISE_MS = 5000;
1496 static constexpr size_t MAX_FAKE_BRIDGE_SOURCES = 8;
1497 struct FakeBridgeSources {
1498 std::map<in_addr_t, long long> raisedAt; // GetMonotonicTimeMS()
1499 long long othersRaisedAt = 0;
1500 };
1501 static std::map<std::string, FakeBridgeSources> fakeBridgeSources;
1502
1503 25 bool AddWarningForProtocol(int sock, const std::string& protocol) {
1504 25 FakeBridgeSources& seen = fakeBridgeSources[protocol];
1505 25 long long now = GetMonotonicTimeMS();
1506 // A source whose warning has timed out no longer holds a slot.
1507 25 std::erase_if(seen.raisedAt, [now](const auto& e) {
1508 192 return now - e.second > FAKE_BRIDGE_WARNING_SECONDS * 1000;
1509 });
1510 25 int msgcnt = recvmmsg(sock, msgs, MAX_MSG, 0, nullptr);
1511 25 int batches = 1;
1512
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 25 times.
52 while (msgcnt > 0) {
1513
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 27 times.
67 for (int x = 0; x < msgcnt; x++) {
1514 40 struct in_addr i = inAddress[x].sin_addr;
1515 40 in_addr_t at = i.s_addr;
1516
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
40 if (msgs[x].msg_len < 4) {
1517 32 continue;
1518 }
1519
3/8
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 40 times.
40 if (protocol == "DDP" && buffers[x][3] != 1) {
1520 // non pixel DDP data, possibly a broadcast discovery packet or sync packet or similar
1521 ✗ continue;
1522 }
1523
1/2
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
40 auto it = seen.raisedAt.find(at);
1524
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
40 if (it != seen.raisedAt.end()) {
1525 ✗ if (now - it->second < FAKE_BRIDGE_RERAISE_MS) {
1526 ✗ continue;
1527 }
1528 ✗ it->second = now;
1529
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 32 times.
40 } else if (seen.raisedAt.size() < MAX_FAKE_BRIDGE_SOURCES) {
1530
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 seen.raisedAt[at] = now;
1531 } else {
1532
3/4
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
32 if (seen.othersRaisedAt && now - seen.othersRaisedAt < FAKE_BRIDGE_RERAISE_MS) {
1533 31 continue;
1534 }
1535 1 seen.othersRaisedAt = now;
1536
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 std::string ne = "Received " + protocol + " data from other sources as well";
1537
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_E131BRIDGE, "%s\n", ne.c_str());
1538
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 WarningHolder::AddWarningTimeout(ne, FAKE_BRIDGE_WARNING_SECONDS);
1539 1 continue;
1540 1 }
1541 8 char abuf[INET_ADDRSTRLEN] = {0};
1542 8 inet_ntop(AF_INET, &inAddress[x].sin_addr, abuf, sizeof(abuf));
1543
3/6
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
8 std::string ne = "Received " + protocol + " data from " + abuf;
1544
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 LogDebug(VB_E131BRIDGE, "%s\n", ne.c_str());
1545
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 WarningHolder::AddWarningTimeout(ne, FAKE_BRIDGE_WARNING_SECONDS);
1546 8 }
1547
1/2
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
27 msgcnt = ++batches > MAX_BATCHES_PER_WAKEUP ? 0 : recvmmsg(sock, msgs, MAX_MSG, 0, nullptr);
1548 }
1549 25 return false;
1550 }
1551
1552 // The "could not open" warnings the last DMX reload raised, retracted by the
1553 // next one: saving the DMX input config again is how a user retries after
1554 // plugging the adapter in or fixing the device name.
1555 static std::vector<std::string> dmxInputWarnings;
1556
1557 1887 static void BridgeReloadDMXInputs() {
1558 1887 hasDMX = false;
1559
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1887 times.
1888 for (const auto& w : dmxInputWarnings) {
1560
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 WarningHolder::RemoveWarning(WARNING_ID_INPUT, w);
1561 }
1562 1887 dmxInputWarnings.clear();
1563 // remove any existing DMX inputs
1564 1887 std::vector<std::shared_ptr<UniverseEntry>> oldDMX;
1565 {
1566
1/2
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
1887 std::lock_guard<std::mutex> lg(universesStructureLock);
1567 1887 oldDMX.swap(DMXInputs);
1568 1887 }
1569
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
1887 for (auto& entry : oldDMX) {
1570 // close the serial port
1571 ✗ EPollManager::INSTANCE.removeFileDescriptor(entry->inFile);
1572 ✗ SerialClose(entry->inFile);
1573 }
1574 1887 oldDMX.clear();
1575
1576 // load the DMX inputs from the config file
1577
3/6
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1887 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1887 times.
✗ Branch 5 not taken.
1887 std::string filename = FPP_DIR_CONFIG("/ci-dmx.json");
1578
3/4
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1882 times.
1887 if (FileExists(filename)) {
1579
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 Json::Value root;
1580
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 if (LoadJsonFromFile(filename, root, JsonRoot::Object)) {
1581
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 Json::Value outputs = root["channelInputs"];
1582
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if (!outputs.isArray()) {
1583 ✗ outputs = Json::Value(Json::arrayValue);
1584 }
1585
3/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 5 times.
10 for (int c = 0; c < outputs.size(); c++) {
1586 // Read the entry inside a try: a value of the wrong JSON type
1587 // threw out of this file-monitor callback, at startup, to
1588 // std::terminate. Such an entry is skipped.
1589 5 std::string type;
1590 5 int start = 0;
1591 5 int count = 0;
1592 5 bool denabled = false;
1593 5 std::string device;
1594 try {
1595
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 const Json::Value& o = outputs[c];
1596
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
5 type = o["type"].asString();
1597
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 if (type == "dmx") {
1598
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
4 start = o["startAddress"].asInt();
1599
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 count = o["channelCount"].asInt();
1600
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 denabled = o["enabled"].asBool();
1601
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 device = o["device"].asString();
1602 }
1603
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } catch (const std::exception& e) {
1604
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
6 WarningHolder::InvalidConfigEntry("ci-dmx.json", e.what());
1605 2 continue;
1606 2 }
1607
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (type == "dmx") {
1608
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (denabled) {
1609
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::string devPath = "/dev/" + device;
1610
5/10
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
10 PinCapabilities::getPinByUART(device + "-rx").configPin("uart");
1611
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 int dmxIn = SerialOpen(devPath.c_str(), 250000, "N82", false);
1612
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (dmxIn > 0) {
1613
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 auto entry = std::make_shared<UniverseEntry>();
1614 1 entry->active = 1;
1615 1 entry->universe = 0;
1616 1 entry->startAddress = start - 1;
1617 1 entry->size = count;
1618 1 entry->type = DMX_TYPE;
1619 1 entry->inFile = dmxIn;
1620 1 entry->unicastAddress[0] = 0;
1621
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 entry->dmxDevice = device;
1622 1 entry->lastTimestamp = 0;
1623 1 entry->priority = 0;
1624 {
1625
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::lock_guard<std::mutex> lg(universesStructureLock);
1626
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 DMXInputs.push_back(entry);
1627 1 }
1628
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2 std::function<bool(int)> f = [entry, count](int i) {
1629 211 uint8_t buffer[513] = { 0, 0, 0, 0 };
1630
1/2
✓ Branch 0 taken 211 times.
✗ Branch 1 not taken.
211 int sz = read(i, buffer, sizeof(buffer));
1631 211 long long commitTime = 0;
1632
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 211 times.
295 while (sz > 0) {
1633
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 uint64_t ts = GetMonotonicTimeMicros(); // DMX break detection: an interval
1634 84 uint64_t diff = ts - entry->lastTimestamp;
1635 84 entry->lastTimestamp = ts;
1636
1637
3/6
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 84 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 84 times.
✗ Branch 5 not taken.
84 if (sz > 0 && buffer[0] == 0 && diff > 3000) {
1638 // it's a reset...
1639 84 commitTime = (ts / 1000) + expireOffSet;
1640
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 SetBridgeData(&buffer[1], entry->startAddress, std::min(sz - 1, count), commitTime);
1641 84 entry->lastIndex = sz - 1;
1642 84 StatAdd(entry->packetsReceived, 1);
1643 84 sz -= 1;
1644 ✗ } else if (diff < 3000) {
1645 ✗ int num = sz;
1646 ✗ if (count < entry->lastIndex) {
1647 ✗ num = 0;
1648 ✗ } else if (count < entry->lastIndex + sz) {
1649 ✗ num = count - entry->lastIndex;
1650 }
1651 ✗ if (num) {
1652 ✗ commitTime = (ts / 1000) + expireOffSet;
1653 ✗ SetBridgeData(buffer, entry->startAddress + entry->lastIndex, num, commitTime);
1654 }
1655 ✗ entry->lastIndex += sz;
1656 } else {
1657 ✗ sz = 0;
1658 ✗ StatAdd(entry->errorPackets, 1);
1659 }
1660 84 StatAdd(entry->bytesReceived, sz);
1661
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 sz = read(i, buffer, sizeof(buffer));
1662 }
1663
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 127 times.
211 if (commitTime) {
1664
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 CommitBridgeData();
1665 }
1666 211 return 0;
1667
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 };
1668
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 EPollManager::INSTANCE.addFileDescriptor(dmxIn, f);
1669 1 hasDMX = true;
1670 1 } else {
1671
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::string msg = "Could not open DMX input device " + device;
1672
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 WarningHolder::AddWarning(WARNING_ID_INPUT, msg);
1673
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 dmxInputWarnings.push_back(msg);
1674 1 }
1675 2 }
1676 }
1677
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 2 times.
7 }
1678 5 }
1679 5 }
1680 1887 }
1681
1682 1922 void BridgeReloadUDP() {
1683
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 ResetArtNetSocketFailure();
1684 // close the existing sockets
1685
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 BridgeShutdownUDP(true);
1686
1687 1922 bridgeDataReceived = false;
1688 1922 hasUDP = false;
1689 1922 bool hasArtNet = false;
1690
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 bool enabled = Bridge_Initialize_Internal(hasArtNet);
1691
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 bool disableFakeBridges = getSettingInt("DisableFakeNetworkBridges");
1692
2/2
✓ Branch 0 taken 1131 times.
✓ Branch 1 taken 791 times.
1922 if (bridgeSock > 0) {
1693
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 1051 times.
1131 if (enabled) {
1694 12884 std::function<bool(int)> f = [](int i) {
1695 12804 return Bridge_ReceiveE131Data();
1696 80 };
1697
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80 EPollManager::INSTANCE.addFileDescriptor(bridgeSock, f);
1698
1/2
✓ Branch 0 taken 1051 times.
✗ Branch 1 not taken.
1131 } else if (!disableFakeBridges) {
1699 25 std::function<bool(int)> f = [](int i) {
1700
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
50 return AddWarningForProtocol(i, "E1.31");
1701 1051 };
1702
1/2
✓ Branch 0 taken 1051 times.
✗ Branch 1 not taken.
1051 EPollManager::INSTANCE.addFileDescriptor(bridgeSock, f);
1703 1051 }
1704 }
1705
2/2
✓ Branch 0 taken 1135 times.
✓ Branch 1 taken 787 times.
1922 if (ddpSock > 0) {
1706
2/2
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 1052 times.
1135 if (enabled) {
1707 4528 std::function<bool(int)> f = [](int i) {
1708 4445 return Bridge_ReceiveDDPData();
1709 83 };
1710
1/2
✓ Branch 0 taken 83 times.
✗ Branch 1 not taken.
83 EPollManager::INSTANCE.addFileDescriptor(ddpSock, f);
1711
1/2
✓ Branch 0 taken 1052 times.
✗ Branch 1 not taken.
1135 } else if (!disableFakeBridges) {
1712 ✗ std::function<bool(int)> f = [](int i) {
1713 ✗ return AddWarningForProtocol(i, "DDP");
1714 1052 };
1715
1/2
✓ Branch 0 taken 1052 times.
✗ Branch 1 not taken.
1052 EPollManager::INSTANCE.addFileDescriptor(ddpSock, f);
1716 1052 }
1717 }
1718
4/4
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 1839 times.
✓ Branch 2 taken 76 times.
✓ Branch 3 taken 7 times.
1922 if (enabled && hasArtNet) {
1719 // Registering the handlers is what puts the socket in the epoll set -
1720 // see UpdateArtNetInputRegistration(). Registered even when the
1721 // socket couldn't be opened just now: that function keeps retrying,
1722 // and picks the socket up if the output thread opens it first.
1723
1/2
✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
76 ArtNetOpcodeHandlers[0x5000] = Bridge_StoreArtNetData; // ArtOutput
1724
1/2
✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
76 ArtNetOpcodeHandlers[0x5200] = Bridge_HandleArtNetSync; // ArtSync
1725
1/2
✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
76 ArtNetOpcodeHandlers[0x2000] = Bridge_HandleArtNetPoll; // ArtPoll
1726 }
1727
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 UpdateArtNetInputRegistration();
1728 1922 }
1729 3789 void BridgeShutdownUDP(bool reloading) {
1730 3789 removeMulticastGroups();
1731 {
1732 // DMX inputs live in DMXInputs, so the whole vector is UDP entries.
1733
1/2
✓ Branch 0 taken 3789 times.
✗ Branch 1 not taken.
3789 std::lock_guard<std::mutex> lg(universesStructureLock);
1734 3789 InputUniverses.clear();
1735 3789 InputUniverseCount = 0;
1736 3789 }
1737
1738 // close the existing sockets
1739
2/2
✓ Branch 0 taken 1112 times.
✓ Branch 1 taken 2677 times.
3789 if (bridgeSock >= 0) {
1740 1112 EPollManager::INSTANCE.removeFileDescriptor(bridgeSock);
1741 1112 close(bridgeSock);
1742 1112 bridgeSock = -1;
1743 }
1744
2/2
✓ Branch 0 taken 1116 times.
✓ Branch 1 taken 2673 times.
3789 if (ddpSock >= 0) {
1745 1116 EPollManager::INSTANCE.removeFileDescriptor(ddpSock);
1746 1116 close(ddpSock);
1747 1116 ddpSock = -1;
1748 }
1749 // Give back the bridge's own handlers and let the shared registration
1750 // re-evaluate, rather than pulling the descriptor out from under
1751 // everyone: a plugin may still want ArtNet delivered across a reload.
1752 // Done whether or not there is a socket: the handlers are registered
1753 // while one is still being waited for. All three go before the one
1754 // re-evaluation, which would otherwise try to open the port for the
1755 // bridge handlers still left.
1756
1/2
✓ Branch 0 taken 3789 times.
✗ Branch 1 not taken.
3789 ArtNetOpcodeHandlers.erase(0x5000);
1757
1/2
✓ Branch 0 taken 3789 times.
✗ Branch 1 not taken.
3789 ArtNetOpcodeHandlers.erase(0x5200);
1758
1/2
✓ Branch 0 taken 3789 times.
✗ Branch 1 not taken.
3789 ArtNetOpcodeHandlers.erase(0x2000);
1759 3789 UpdateArtNetInputRegistration();
1760
6/6
✓ Branch 0 taken 1867 times.
✓ Branch 1 taken 1922 times.
✓ Branch 2 taken 49 times.
✓ Branch 3 taken 1818 times.
✓ Branch 4 taken 49 times.
✓ Branch 5 taken 3740 times.
3789 if (!reloading && artnetSock >= 0) {
1761 // don't close the artNet socket if we are reloading
1762 // as outputs or inputs may still be using it
1763
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 47 times.
49 if (artnetSocketAsInput) {
1764 2 EPollManager::INSTANCE.removeFileDescriptor(artnetSock);
1765 2 artnetSocketAsInput = false;
1766 }
1767 49 close(artnetSock);
1768 49 artnetSock = -1;
1769 }
1770 3789 }
1771 1867 void Bridge_Shutdown(void) {
1772 1867 std::vector<std::shared_ptr<UniverseEntry>> oldDMX;
1773 {
1774
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 std::lock_guard<std::mutex> lg(universesStructureLock);
1775 1867 oldDMX.swap(DMXInputs);
1776 1867 }
1777
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1867 times.
1868 for (auto& entry : oldDMX) {
1778 // close the serial port
1779
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 EPollManager::INSTANCE.removeFileDescriptor(entry->inFile);
1780
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 SerialClose(entry->inFile);
1781 }
1782 1867 oldDMX.clear();
1783 1867 hasDMX = false;
1784
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 BridgeShutdownUDP(false);
1785
3/6
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1867 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1867 times.
✗ Branch 5 not taken.
7468 unregisterSettingsListener("DisableFakeNetworkBridges", "DisableFakeNetworkBridges");
1786
3/6
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1867 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1867 times.
✗ Branch 5 not taken.
7468 unregisterSettingsListener("BridgeSourcePriority", "BridgeSourcePriority");
1787
3/6
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1867 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1867 times.
✗ Branch 5 not taken.
3734 std::string udpInFile = FPP_DIR_CONFIG("/ci-universes.json");
1788
2/4
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1867 times.
✗ Branch 3 not taken.
3734 FileMonitor::INSTANCE.RemoveFile("ci-universes.json", udpInFile);
1789
3/6
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1867 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1867 times.
✗ Branch 5 not taken.
3734 std::string dmxInFile = FPP_DIR_CONFIG("/ci-dmx.json");
1790
2/4
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1867 times.
✗ Branch 3 not taken.
1867 FileMonitor::INSTANCE.RemoveFile("ci-dmx.json", dmxInFile);
1791
1792 1867 ArtNetOpcodeHandlers.clear();
1793
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1867 times.
1867 if (artnetInputRetryRunning) {
1794 ✗ artnetInputRetryRunning = false;
1795 ✗ Timers::INSTANCE.stopPeriodicTimer(ARTNET_INPUT_RETRY_TIMER);
1796 }
1797 1867 }
1798
1799 1886 void Bridge_Initialize(std::map<int, std::function<bool(int)>>& callbacks) {
1800
3/6
✓ Branch 0 taken 1886 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1886 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1886 times.
✗ Branch 5 not taken.
9110 registerSettingsListener("DisableFakeNetworkBridges", "DisableFakeNetworkBridges", [](const std::string& s) {
1801 // reload the bridges when the setting changes
1802 ✗ BridgeReloadUDP();
1803 ✗ });
1804
1/2
✓ Branch 0 taken 1886 times.
✗ Branch 1 not taken.
1886 sourcePriorityEnabled.store(getSettingInt("BridgeSourcePriority") != 0,
1805 std::memory_order_relaxed);
1806
3/6
✓ Branch 0 taken 1886 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1886 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1886 times.
✗ Branch 5 not taken.
9270 registerSettingsListener("BridgeSourcePriority", "BridgeSourcePriority", [](const std::string& s) {
1807 ✗ bool enabled = !s.empty() && s != "0";
1808 ✗ sourcePriorityEnabled.store(enabled, std::memory_order_relaxed);
1809 // Clear per-universe source state so the next packet establishes a
1810 // fresh active source under the new setting.
1811 ✗ for (int i = 0; i < InputUniverseCount; i++) {
1812 ✗ InputUniverses[i].activeSourceValid = false;
1813 ✗ InputUniverses[i].activeSourcePriority = 0;
1814 ✗ InputUniverses[i].activeSourceLastTime = 0;
1815 }
1816 ✗ });
1817
1818
3/6
✓ Branch 0 taken 1886 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1886 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1886 times.
✗ Branch 5 not taken.
2046 std::string udpInFile = FPP_DIR_CONFIG("/ci-universes.json");
1819
2/4
✓ Branch 0 taken 1886 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1886 times.
✗ Branch 3 not taken.
5498 FileMonitor::INSTANCE.AddFile("ci-universes.json", udpInFile,
1820 1886 []() {
1821 1922 BridgeReloadUDP();
1822 1922 })
1823
2/4
✓ Branch 0 taken 1886 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1886 times.
✗ Branch 3 not taken.
3772 .TriggerFileChanged("ci-universes.json", udpInFile);
1824
1825
3/6
✓ Branch 0 taken 1886 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1886 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1886 times.
✗ Branch 5 not taken.
2046 std::string dmxInFile = FPP_DIR_CONFIG("/ci-dmx.json");
1826
2/4
✓ Branch 0 taken 1886 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1886 times.
✗ Branch 3 not taken.
5338 FileMonitor::INSTANCE.AddFile("ci-dmx.json", dmxInFile,
1827 160 []() {
1828 1887 BridgeReloadDMXInputs();
1829 1887 })
1830
2/4
✓ Branch 0 taken 1886 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1886 times.
✗ Branch 3 not taken.
3772 .TriggerFileChanged("ci-dmx.json", dmxInFile);
1831 1886 }
1832
1833 137639 bool HasBridgeData() {
1834 137639 return bridgeDataReceived.load(std::memory_order_relaxed);
1835 }
1836