| 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, ¤tArtNetSourceIP, 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 |