| 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 | #include "EPollManager.h" | ||
| 18 | #include "Warnings.h" // WarningHolder -- needed directly for NOPCH builds | ||
| 19 | |||
| 20 | #include <arpa/inet.h> | ||
| 21 | #include <curl/curl.h> | ||
| 22 | #include <net/if.h> | ||
| 23 | #include <netinet/in.h> | ||
| 24 | #include <sys/types.h> | ||
| 25 | #include <algorithm> | ||
| 26 | #include <chrono> | ||
| 27 | #include <cmath> | ||
| 28 | #include <condition_variable> | ||
| 29 | #include <cstddef> | ||
| 30 | #include <cstdint> | ||
| 31 | #include <cstdlib> | ||
| 32 | #include <ctime> | ||
| 33 | #include <ctype.h> | ||
| 34 | #include <deque> | ||
| 35 | #include <errno.h> | ||
| 36 | #include <functional> | ||
| 37 | #include <ifaddrs.h> | ||
| 38 | #include <list> | ||
| 39 | #include <map> | ||
| 40 | #include <math.h> | ||
| 41 | #include <memory> | ||
| 42 | #include <string_view> | ||
| 43 | #include <mutex> | ||
| 44 | #include <netdb.h> | ||
| 45 | #include <set> | ||
| 46 | #include <stdio.h> | ||
| 47 | #include <string.h> | ||
| 48 | #include <string> | ||
| 49 | #include <strings.h> | ||
| 50 | #include <thread> | ||
| 51 | #include <unistd.h> | ||
| 52 | #include <utility> | ||
| 53 | #include <vector> | ||
| 54 | |||
| 55 | #include "CurlManager.h" | ||
| 56 | #include "FileMonitor.h" | ||
| 57 | #include "NetworkController.h" | ||
| 58 | #include "NetworkMonitor.h" | ||
| 59 | #include "Player.h" | ||
| 60 | #include "Plugins.h" | ||
| 61 | #include "Sequence.h" | ||
| 62 | #include "command.h" | ||
| 63 | #include "common.h" | ||
| 64 | #include "fppversion.h" | ||
| 65 | #include "log.h" | ||
| 66 | #include "settings.h" | ||
| 67 | #include "channeloutput/ChannelOutputSetup.h" | ||
| 68 | #include "channeloutput/channeloutputthread.h" | ||
| 69 | #include "e131bridge.h" | ||
| 70 | #include "commands/Commands.h" | ||
| 71 | #include "mediaoutput/MediaOutputBase.h" | ||
| 72 | #include "mediaoutput/mediaoutput.h" | ||
| 73 | |||
| 74 | #include "MultiSync.h" | ||
| 75 | #include "Timers.h" | ||
| 76 | |||
| 77 | // Quiescence for the MultiSync settings listeners. The settings code copies | ||
| 78 | // the listeners out and calls them with no lock held, so unregistering one | ||
| 79 | // does not wait for a call already in progress (or about to start from a | ||
| 80 | // copy). Each call counts itself in here and gives up once ShutdownSync() has | ||
| 81 | // set the flag; ShutdownSync() waits for the count to drain before it closes | ||
| 82 | // the sockets ReloadSyncDestinations() works on. Defined ahead of INSTANCE so | ||
| 83 | // they outlive it during static destruction. | ||
| 84 | static std::mutex s_listenerQuiesceLock; | ||
| 85 | static std::condition_variable s_listenerQuiesceCV; | ||
| 86 | static int s_listenersInFlight = 0; | ||
| 87 | static bool s_listenersShutdown = false; | ||
| 88 | |||
| 89 | // Serializes OpenControlSockets(): it is reached lazily from several threads | ||
| 90 | // (the main loop, command and API threads), which could each see no socket | ||
| 91 | // and open one, leaking all but the last. | ||
| 92 | static std::mutex s_controlSockOpenLock; | ||
| 93 | |||
| 94 | // Addresses with an HTTP re-ping queued or running. PeriodicPing() re-queues | ||
| 95 | // every stale non-MultiSync remote on each pass, and those probes mostly sit | ||
| 96 | // out a connect timeout against boxes that are down, so without this the | ||
| 97 | // queue grows faster than it drains. Guarded by m_httpProbeLock. | ||
| 98 | static std::set<std::string> s_httpPingPending; | ||
| 99 | |||
| 100 | // The most HTTP re-pings that may wait in the queue. Every path that queues | ||
| 101 | // one is fed from the network, and the Twinkly discovery replies by anyone | ||
| 102 | // on the LAN, so the queue needs a bound of its own; real remotes and lights | ||
| 103 | // come to far fewer. | ||
| 104 | #define HTTP_PING_MAX_QUEUED 512 | ||
| 105 | |||
| 106 | // caller holds m_httpProbeLock | ||
| 107 | 158 | static void QueueHTTPPing(std::deque<std::string>& queue, const std::string& address) { | |
| 108 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
|
158 | if (queue.size() >= HTTP_PING_MAX_QUEUED) { |
| 109 | ✗ | LogDebug(VB_SYNC, "Not probing %s over HTTP: %d probes already queued\n", address.c_str(), (int)queue.size()); | |
| 110 | ✗ | return; | |
| 111 | } | ||
| 112 |
1/4✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
158 | if (s_httpPingPending.insert(address).second) { |
| 113 | 158 | queue.push_back(address); | |
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | MultiSync MultiSync::INSTANCE; | ||
| 118 | MultiSync* multiSync = &MultiSync::INSTANCE; | ||
| 119 | |||
| 120 | static const char* MULTISYNC_MULTICAST_ADDRESS = "239.70.80.80"; // 239.F.P.P | ||
| 121 | |||
| 122 | // Ping packet extraData lengths (see docs/ControlProtocol.txt). Each version | ||
| 123 | // only appends to the one before, and receivers parse by length, never by the | ||
| 124 | // version byte: every FPP release and xLights treat an unknown version as v3. | ||
| 125 | static constexpr int PING_V3_LEN = 294; | ||
| 126 | static constexpr int PING_UUID_MAX = 64; // plus its NUL | ||
| 127 | static constexpr int PING_V4_LEN = PING_V3_LEN + PING_UUID_MAX + 1; | ||
| 128 | static uint32_t MULTISYNC_MULTICAST_ADD = inet_addr(MULTISYNC_MULTICAST_ADDRESS); | ||
| 129 | |||
| 130 | 2032 | NetInterfaceInfo::NetInterfaceInfo() : | |
| 131 | 2032 | address(0), | |
| 132 | 2032 | broadcastAddress(0), | |
| 133 | 2032 | multicastSocket(-1) { | |
| 134 | 2032 | } | |
| 135 | 223 | NetInterfaceInfo::~NetInterfaceInfo() { | |
| 136 |
2/2✓ Branch 0 taken 200 times.
✓ Branch 1 taken 23 times.
|
223 | if (multicastSocket != -1) { |
| 137 | 200 | LogDebug(VB_SYNC, "Closing multicast socket for %s\n", interfaceName.c_str()); | |
| 138 | 200 | close(multicastSocket); | |
| 139 | } | ||
| 140 | 223 | } | |
| 141 | |||
| 142 | 10995 | static bool GetIPForHost(std::string& target) { | |
| 143 | // ResolveHostToIPv4() is reentrant (gethostbyname()/inet_ntoa() return | ||
| 144 | // pointers into static, per-process buffers, and MultiSync resolves from | ||
| 145 | // several threads concurrently -- DiscoverIPViaHTTP runs from a curl | ||
| 146 | // completion while the main-loop ProcessControlPacket path resolves too). | ||
| 147 | // It also caches, which matters here: this is called once per configured | ||
| 148 | // output address from three separate points during startup, and a name that | ||
| 149 | // does not resolve costs seconds every time. We still rewrite target as a | ||
| 150 | // dotted-quad, because callers depend on that form (split(target, '.') and | ||
| 151 | // inet_addr(target)). | ||
| 152 |
1/2✓ Branch 0 taken 10995 times.
✗ Branch 1 not taken.
|
10995 | std::string ip = ResolveHostToIPv4(target); |
| 153 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 10974 times.
|
10995 | if (ip.empty()) { |
| 154 | 21 | return false; | |
| 155 | } | ||
| 156 |
1/2✓ Branch 0 taken 10974 times.
✗ Branch 1 not taken.
|
10974 | target = ip; |
| 157 | 10974 | return true; | |
| 158 | 10995 | } | |
| 159 | |||
| 160 | // 169.254/16: what an interface holds when DHCP hasn't answered (yet). | ||
| 161 | 2736865 | static bool IsIPv4LinkLocal(const std::string& address) { | |
| 162 |
2/4✓ Branch 0 taken 2736865 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2736865 times.
✗ Branch 3 not taken.
|
5473730 | return startsWith(address, "169.254."); |
| 163 | } | ||
| 164 | |||
| 165 | // Adds the hosts in a comma-separated remote list setting (MultiSyncRemotes, | ||
| 166 | // MultiSyncExtraRemotes) to `remotes`. Quotes are stripped from each end of an | ||
| 167 | // entry on its own, not only as a pair: the web UI used to PUT | ||
| 168 | // MultiSyncRemotes as a JSON string, which stores the whole list quoted | ||
| 169 | // ("a,b,c", so the first entry has only an opening quote and the last only a | ||
| 170 | // closing one) and an empty list as a bare pair of quotes, while an entry | ||
| 171 | // typed with its own quotes has both. Left in, a quote fails inet_addr(), and | ||
| 172 | // a lone "" becomes a host name that costs a full DNS timeout (~4s, on the | ||
| 173 | // settings-reload thread) on every reload. | ||
| 174 | 1963 | static void AddRemotesFromSetting(const char* setting, std::set<std::string>& remotes) { | |
| 175 |
4/6✓ Branch 0 taken 1963 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1963 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 55 times.
✓ Branch 5 taken 1963 times.
|
2018 | for (auto& token : split(getSetting(setting), ',')) { |
| 176 |
1/2✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
|
55 | TrimWhiteSpace(token); |
| 177 |
8/8✓ Branch 0 taken 75 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 57 times.
✓ Branch 3 taken 18 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 53 times.
✓ Branch 6 taken 22 times.
✓ Branch 7 taken 55 times.
|
77 | while (!token.empty() && (token.front() == '"' || token.back() == '"')) { |
| 178 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 4 times.
|
22 | if (token.front() == '"') { |
| 179 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | token.erase(0, 1); |
| 180 | } | ||
| 181 |
5/6✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 4 times.
|
22 | if (!token.empty() && token.back() == '"') { |
| 182 | 18 | token.pop_back(); | |
| 183 | } | ||
| 184 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | TrimWhiteSpace(token); |
| 185 | } | ||
| 186 |
3/4✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 53 times.
✓ Branch 3 taken 2 times.
|
55 | if (token != "") { |
| 187 |
1/2✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
|
53 | remotes.insert(token); |
| 188 | } | ||
| 189 | 1963 | } | |
| 190 | 1963 | } | |
| 191 | |||
| 192 | // Whether `sys` is the device reporting (hostname, fppMode, type, uuid) from | ||
| 193 | // some other address. A ping packet carries no UUID, so a ping alone can only | ||
| 194 | // be matched on what it does carry; when both sides do know a UUID of the same | ||
| 195 | // kind (real or MAC-derived) that decides it instead, which keeps two | ||
| 196 | // controllers left on the same default hostname apart. | ||
| 197 | 2 | static bool IsSameDevice(const MultiSyncSystem& sys, const std::string& hostname, | |
| 198 | FPPMode fppMode, MultiSyncSystemType type, const std::string& uuid) { | ||
| 199 | // update() stores MASTER_MODE as PLAYER_MODE | ||
| 200 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (fppMode == MASTER_MODE) { |
| 201 | ✗ | fppMode = PLAYER_MODE; | |
| 202 | } | ||
| 203 |
4/8✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
|
2 | if (hostname.empty() || sys.hostname != hostname || sys.fppMode != fppMode) { |
| 204 | ✗ | return false; | |
| 205 | } | ||
| 206 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | auto known = [](const std::string& u) { return !u.empty() && u != "Unknown"; }; |
| 207 |
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 | if (known(uuid) && known(sys.uuid) && |
| 208 |
10/28✓ 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.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 2 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 2 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 2 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
|
10 | startsWith(uuid, MAC_UUID_PREFIX) == startsWith(sys.uuid, MAC_UUID_PREFIX)) { |
| 209 | 2 | return uuid == sys.uuid; | |
| 210 | } | ||
| 211 | ✗ | return sys.type == type; | |
| 212 | } | ||
| 213 | |||
| 214 | 9089 | void MultiSyncSystem::update(MultiSyncSystemType type, | |
| 215 | unsigned int majorVersion, unsigned int minorVersion, | ||
| 216 | FPPMode fppMode, | ||
| 217 | const std::string& address, | ||
| 218 | const std::string& hostname, | ||
| 219 | const std::string& version, | ||
| 220 | const std::string& model, | ||
| 221 | const std::string& ranges, | ||
| 222 | const std::string& uuid, | ||
| 223 | const bool multiSync, | ||
| 224 | const bool sendingMultiSync) { | ||
| 225 | // UUID precedence. A device that reports its own UUID always wins; the | ||
| 226 | // MAC-derived identity below is only ever a stand-in for one, so it must | ||
| 227 | // never overwrite a real UUID, and a real UUID arriving later must replace | ||
| 228 | // it. | ||
| 229 |
7/10✓ Branch 0 taken 9089 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 108 times.
✓ Branch 3 taken 8981 times.
✓ Branch 4 taken 108 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 108 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 108 times.
✓ Branch 9 taken 8981 times.
|
9089 | if (uuid != "Unknown" && uuid != "") { |
| 230 |
7/16✓ Branch 0 taken 108 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 108 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 94 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 14 times.
✓ Branch 8 taken 108 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 108 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
|
432 | if (!startsWith(uuid, MAC_UUID_PREFIX) || this->uuid.empty() || |
| 231 |
3/16✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 108 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 108 times.
✓ Branch 10 taken 108 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
|
108 | startsWith(this->uuid, MAC_UUID_PREFIX)) { |
| 232 |
1/2✓ Branch 0 taken 108 times.
✗ Branch 1 not taken.
|
108 | this->uuid = uuid; |
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | // Nothing reported a UUID for this device. That is the normal case for | ||
| 237 | // every controller that isn't full FPP: the ping packet carries no UUID | ||
| 238 | // field at all, so ProcessPingPacket() has none to pass on, and | ||
| 239 | // NetworkController only fills one in for an FPP instance. Without an | ||
| 240 | // identity the UI has to key the row on the hostname, which collides as | ||
| 241 | // soon as two controllers ship with the same default name and changes | ||
| 242 | // under it whenever someone renames one. | ||
| 243 | // | ||
| 244 | // The MAC is the stable identity such a device does have, and the kernel | ||
| 245 | // already knows it for anything on a directly attached subnet. Only look | ||
| 246 | // it up while we still have nothing -- once an identity of either kind is | ||
| 247 | // recorded this stops running, so it costs one small read per device | ||
| 248 | // rather than one per ping. | ||
| 249 |
2/2✓ Branch 0 taken 4534 times.
✓ Branch 1 taken 4555 times.
|
9089 | if (this->uuid.empty()) { |
| 250 | // The parameter, not this->address: on a system being created that | ||
| 251 | // member is still empty here, and is only assigned further down. | ||
| 252 |
1/2✓ Branch 0 taken 4534 times.
✗ Branch 1 not taken.
|
4534 | std::string mac = GetMacForAddress(address); |
| 253 |
2/2✓ Branch 0 taken 192 times.
✓ Branch 1 taken 4342 times.
|
4534 | if (!mac.empty()) { |
| 254 |
1/2✓ Branch 0 taken 192 times.
✗ Branch 1 not taken.
|
192 | this->uuid = MAC_UUID_PREFIX + mac; |
| 255 | } | ||
| 256 | 4534 | } | |
| 257 | |||
| 258 | // If this record is from info learned via the MultiSync protocol, | ||
| 259 | // don't allow it to be overwritten by data discovered elsewhere | ||
| 260 | // except for uuid. | ||
| 261 |
3/4✓ Branch 0 taken 3935 times.
✓ Branch 1 taken 5154 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3935 times.
|
9089 | if (this->multiSync && !multiSync) |
| 262 | ✗ | return; | |
| 263 | |||
| 264 | 9089 | this->fppMode = fppMode; | |
| 265 | 9089 | this->sendingMultiSync = sendingMultiSync; | |
| 266 |
2/2✓ Branch 0 taken 130 times.
✓ Branch 1 taken 8959 times.
|
9089 | if (fppMode == MASTER_MODE) { |
| 267 | 130 | this->fppMode = PLAYER_MODE; | |
| 268 | 130 | this->sendingMultiSync = true; | |
| 269 | } | ||
| 270 | |||
| 271 | 9089 | this->type = type; | |
| 272 | 9089 | this->majorVersion = majorVersion; | |
| 273 | 9089 | this->minorVersion = minorVersion; | |
| 274 |
1/2✓ Branch 0 taken 9089 times.
✗ Branch 1 not taken.
|
9089 | this->address = address; |
| 275 |
1/2✓ Branch 0 taken 9089 times.
✗ Branch 1 not taken.
|
9089 | this->hostname = hostname; |
| 276 |
1/2✓ Branch 0 taken 9089 times.
✗ Branch 1 not taken.
|
9089 | this->version = version; |
| 277 |
1/2✓ Branch 0 taken 9089 times.
✗ Branch 1 not taken.
|
9089 | this->model = model; |
| 278 |
9/10✓ Branch 0 taken 4745 times.
✓ Branch 1 taken 4344 times.
✓ Branch 2 taken 4745 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 213 times.
✓ Branch 5 taken 4532 times.
✓ Branch 6 taken 205 times.
✓ Branch 7 taken 8 times.
✓ Branch 8 taken 4737 times.
✓ Branch 9 taken 4352 times.
|
9089 | if (!ranges.empty() && (ranges != "0-0" || this->ranges.empty())) { |
| 279 |
1/2✓ Branch 0 taken 4737 times.
✗ Branch 1 not taken.
|
4737 | this->ranges = ranges; |
| 280 | } | ||
| 281 | |||
| 282 |
4/4✓ Branch 0 taken 5154 times.
✓ Branch 1 taken 3935 times.
✓ Branch 2 taken 4944 times.
✓ Branch 3 taken 210 times.
|
9089 | if (!this->multiSync && multiSync) { |
| 283 | 4944 | this->multiSync = true; | |
| 284 | } | ||
| 285 | |||
| 286 |
1/2✓ Branch 0 taken 9089 times.
✗ Branch 1 not taken.
|
9089 | std::vector<std::string> parts = split(address, '.'); |
| 287 | // ipa-ipd hold the legacy IPv4 octets used in the (IPv4-only) ping packet. | ||
| 288 | // An IPv6 peer has no such octets, so skip the IPv4 hostname lookup that | ||
| 289 | // would only fail and log an error; the octets stay zeroed below. | ||
| 290 |
4/6✓ Branch 0 taken 2 times.
✓ Branch 1 taken 9087 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 9089 times.
|
9089 | if (parts.size() != 4 && address.find(':') == std::string::npos) { |
| 291 | ✗ | if (!GetIPForHost(this->address)) { | |
| 292 | ✗ | LogErr(VB_SYNC, "Error looking up hostname: %s\n", address.c_str()); | |
| 293 | } else { | ||
| 294 | ✗ | parts = split(this->address, '.'); | |
| 295 | } | ||
| 296 | } | ||
| 297 |
2/2✓ Branch 0 taken 9087 times.
✓ Branch 1 taken 2 times.
|
9089 | if (parts.size() >= 4) { |
| 298 | 9087 | this->ipa = atoi(parts[0].c_str()); | |
| 299 | 9087 | this->ipb = atoi(parts[1].c_str()); | |
| 300 | 9087 | this->ipc = atoi(parts[2].c_str()); | |
| 301 | 9087 | this->ipd = atoi(parts[3].c_str()); | |
| 302 | } else { | ||
| 303 | 2 | this->ipa = 0; | |
| 304 | 2 | this->ipb = 0; | |
| 305 | 2 | this->ipc = 0; | |
| 306 | 2 | this->ipd = 0; | |
| 307 | } | ||
| 308 | |||
| 309 | // A system is a valid target for "Send MultiSync to ALL KNOWN remotes via | ||
| 310 | // Unicast" only if it's a full FPP instance (type below the 0x80 boundary | ||
| 311 | // that separates full-FPP systems from external controllers) running in | ||
| 312 | // Remote mode. This deliberately excludes WLED, ESPixelStick, Falcon and | ||
| 313 | // Experience controllers, xSchedule, and any FPP instance acting as a | ||
| 314 | // player/master. We intentionally do NOT require that the system was | ||
| 315 | // learned via the UDP MultiSync protocol so that FPP remotes discovered | ||
| 316 | // via HTTP (e.g. on a network segment where multicast is blocked - the | ||
| 317 | // very case this mode exists for) are still targeted. | ||
| 318 |
2/2✓ Branch 0 taken 9032 times.
✓ Branch 1 taken 57 times.
|
18121 | this->supportsUnicast = (this->type < kSysTypeFalconController) && |
| 319 |
2/2✓ Branch 0 taken 98 times.
✓ Branch 1 taken 8934 times.
|
9032 | (this->fppMode == REMOTE_MODE); |
| 320 | 9089 | } | |
| 321 | |||
| 322 | 141 | static void SetIfNotEmpty(Json::Value& v, const char* key, const std::string& s) { | |
| 323 |
2/2✓ Branch 0 taken 55 times.
✓ Branch 1 taken 86 times.
|
141 | if (!s.empty()) { |
| 324 |
2/4✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 55 times.
✗ Branch 3 not taken.
|
55 | v[key] = s; |
| 325 | } | ||
| 326 | 141 | } | |
| 327 | |||
| 328 | 2 | Json::Value MultiSyncSystemInfo::toJSON() const { | |
| 329 | 2 | Json::Value v; | |
| 330 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "Platform", platform); |
| 331 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "Variant", variant); |
| 332 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "SubPlatform", subPlatform); |
| 333 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "OSVersion", osVersion); |
| 334 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "OSRelease", osRelease); |
| 335 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "Kernel", kernel); |
| 336 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "HostDescription", hostDescription); |
| 337 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "backgroundColor", backgroundColor); |
| 338 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "Branch", branch); |
| 339 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "LocalGitVersion", localGitVersion); |
| 340 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "RemoteGitVersion", remoteGitVersion); |
| 341 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | SetIfNotEmpty(v, "UpgradeSource", upgradeSource); |
| 342 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (channelInputsEnabled >= 0) { |
| 343 | ✗ | v["channelInputsEnabled"] = channelInputsEnabled ? true : false; | |
| 344 | } | ||
| 345 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (channelOutputsEnabled >= 0) { |
| 346 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | v["channelOutputsEnabled"] = channelOutputsEnabled ? true : false; |
| 347 | } | ||
| 348 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (!ips.empty()) { |
| 349 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | Json::Value a(Json::arrayValue); |
| 350 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | for (auto& ip : ips) { |
| 351 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | a.append(ip); |
| 352 | } | ||
| 353 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | v["IPs"] = a; |
| 354 | 2 | } | |
| 355 | 2 | return v; | |
| 356 | ✗ | } | |
| 357 | |||
| 358 | 35994 | Json::Value MultiSyncCapeInfo::toJSON() const { | |
| 359 |
1/2✓ Branch 0 taken 35994 times.
✗ Branch 1 not taken.
|
35994 | Json::Value v; |
| 360 |
2/4✓ Branch 0 taken 35994 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35994 times.
✗ Branch 3 not taken.
|
35994 | v["present"] = present; |
| 361 |
2/2✓ Branch 0 taken 35981 times.
✓ Branch 1 taken 13 times.
|
35994 | if (!present) { |
| 362 | 35981 | return v; | |
| 363 | } | ||
| 364 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | SetIfNotEmpty(v, "id", id); |
| 365 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | SetIfNotEmpty(v, "name", name); |
| 366 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | SetIfNotEmpty(v, "description", description); |
| 367 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | SetIfNotEmpty(v, "version", version); |
| 368 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | SetIfNotEmpty(v, "designer", designer); |
| 369 | // Only emitted when the cape opts out, so the common case adds no bytes. | ||
| 370 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (!sendStats) { |
| 371 | ✗ | v["sendStats"] = 0; | |
| 372 | } | ||
| 373 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | Json::Value vendor; |
| 374 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | SetIfNotEmpty(vendor, "name", vendorName); |
| 375 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | SetIfNotEmpty(vendor, "url", vendorURL); |
| 376 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | SetIfNotEmpty(vendor, "email", vendorEmail); |
| 377 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | SetIfNotEmpty(vendor, "image", vendorImage); |
| 378 |
3/4✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 1 times.
|
13 | if (!vendor.empty()) { |
| 379 |
2/4✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
|
12 | v["vendor"] = vendor; |
| 380 | } | ||
| 381 | 13 | return v; | |
| 382 | 13 | } | |
| 383 | |||
| 384 | // A field of what a remote sent us (system info, cape info), read so that a | ||
| 385 | // value of the wrong type gives the default instead of throwing: the reply is | ||
| 386 | // parsed in a curl callback on the main loop, and {"Platform":{}} ended fppd. | ||
| 387 | 62 | static std::string ReplyString(const Json::Value& v, const char* key, const std::string& dflt = "") { | |
| 388 |
2/4✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✗ Branch 3 not taken.
|
62 | const Json::Value f = v.get(key, Json::Value()); |
| 389 |
9/16✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
✓ Branch 3 taken 23 times.
✓ Branch 4 taken 39 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 39 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 39 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 39 times.
✓ Branch 12 taken 23 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 39 times.
✗ Branch 15 not taken.
|
124 | return (f.isString() || f.isNumeric() || f.isBool()) ? f.asString() : dflt; |
| 390 | 62 | } | |
| 391 | 1 | static bool ReplyBool(const Json::Value& v, const char* key, bool dflt) { | |
| 392 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | const Json::Value f = v.get(key, Json::Value()); |
| 393 |
4/10✓ 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.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
3 | return (f.isBool() || f.isNumeric()) ? f.asBool() : dflt; |
| 394 | 2 | } | |
| 395 | 6 | static int ReplyInt(const Json::Value& v, const char* key, int dflt) { | |
| 396 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | const Json::Value f = v.get(key, Json::Value()); |
| 397 |
5/10✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 5 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
12 | return f.isConvertibleTo(Json::intValue) && !f.isNull() ? f.asInt() : dflt; |
| 398 | 6 | } | |
| 399 | |||
| 400 | 6 | MultiSyncCapeInfo MultiSyncCapeInfo::fromCapeJSON(const Json::Value& v) { | |
| 401 | 6 | MultiSyncCapeInfo cape; | |
| 402 | 6 | cape.valid = true; | |
| 403 | 6 | cape.present = true; | |
| 404 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
12 | cape.id = ReplyString(v, "id"); |
| 405 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
12 | cape.name = ReplyString(v, "name"); |
| 406 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
12 | cape.description = ReplyString(v, "description"); |
| 407 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
12 | cape.version = ReplyString(v, "version"); |
| 408 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | cape.designer = ReplyString(v, "designer"); |
| 409 | // Absent (including on every unsigned cape) means no opt-out. | ||
| 410 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | cape.sendStats = ReplyInt(v, "sendStats", 1) != 0; |
| 411 |
11/22✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 5 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 6 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 6 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 5 times.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
18 | if (JsonHas(v, "vendor") && v["vendor"].isObject()) { |
| 412 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | const Json::Value& vendor = v["vendor"]; |
| 413 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
10 | cape.vendorName = ReplyString(vendor, "name"); |
| 414 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
10 | cape.vendorURL = ReplyString(vendor, "url"); |
| 415 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
10 | cape.vendorEmail = ReplyString(vendor, "email"); |
| 416 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
10 | cape.vendorImage = ReplyString(vendor, "image"); |
| 417 | } | ||
| 418 | 6 | return cape; | |
| 419 | ✗ | } | |
| 420 | |||
| 421 | 75674 | Json::Value MultiSyncSystem::toJSON(bool local, bool timestamps) { | |
| 422 | 75674 | Json::Value system; | |
| 423 | |||
| 424 |
3/6✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 75674 times.
✗ Branch 5 not taken.
|
75674 | system["type"] = MultiSync::GetTypeString(type, local); |
| 425 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["typeId"] = (int)type; |
| 426 |
2/2✓ Branch 0 taken 71836 times.
✓ Branch 1 taken 3838 times.
|
75674 | if (timestamps) { |
| 427 |
2/4✓ Branch 0 taken 71836 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 71836 times.
✗ Branch 3 not taken.
|
71836 | system["lastSeen"] = (Json::UInt64)lastSeen; |
| 428 |
2/4✓ Branch 0 taken 71836 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 71836 times.
✗ Branch 3 not taken.
|
71836 | system["lastSeenStr"] = lastSeenStr; |
| 429 | } | ||
| 430 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["majorVersion"] = majorVersion; |
| 431 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["minorVersion"] = minorVersion; |
| 432 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["uuid"] = uuid; |
| 433 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["fppMode"] = fppMode; |
| 434 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["multisync"] = sendingMultiSync; |
| 435 | |||
| 436 |
1/2✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
|
75674 | char* s = modeToString(fppMode); |
| 437 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["fppModeString"] = s; |
| 438 | 75674 | free(s); | |
| 439 | |||
| 440 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["address"] = address; |
| 441 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["hostname"] = hostname; |
| 442 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["version"] = version; |
| 443 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["model"] = model; |
| 444 |
2/4✓ Branch 0 taken 75674 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
|
75674 | system["channelRanges"] = ranges; |
| 445 | |||
| 446 |
4/6✓ Branch 0 taken 35993 times.
✓ Branch 1 taken 39681 times.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 75674 times.
✗ Branch 5 not taken.
|
75674 | system["local"] = local ? 1 : 0; |
| 447 | |||
| 448 |
4/6✓ Branch 0 taken 63127 times.
✓ Branch 1 taken 12547 times.
✓ Branch 2 taken 75674 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 75674 times.
✗ Branch 5 not taken.
|
75674 | system["multiSyncCapable"] = multiSync ? 1 : 0; |
| 449 | |||
| 450 |
2/2✓ Branch 0 taken 35993 times.
✓ Branch 1 taken 39681 times.
|
75674 | if (local) { |
| 451 |
3/6✓ Branch 0 taken 35993 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35993 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 35993 times.
✗ Branch 5 not taken.
|
35993 | system["HostDescription"] = getSetting("HostDescription"); |
| 452 |
3/6✓ Branch 0 taken 35993 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35993 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 35993 times.
✗ Branch 5 not taken.
|
35993 | system["channelInputsEnabled"] = InputsEnabled(); |
| 453 |
3/6✓ Branch 0 taken 35993 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35993 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 35993 times.
✗ Branch 5 not taken.
|
35993 | system["channelOutputsEnabled"] = HasUniverseOutputs(); |
| 454 | } | ||
| 455 | |||
| 456 | // Detail fetched over HTTP from FPP remotes (see CheckSystemInfoRefreshes). | ||
| 457 | // Absent for the local systems -- a browser rendering the multisync page | ||
| 458 | // already has all of this for the box serving the page -- and for anything | ||
| 459 | // that isn't a full FPP instance. | ||
| 460 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 75672 times.
|
75674 | if (systemInfo.valid) { |
| 461 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | system["systemInfo"] = systemInfo.toJSON(); |
| 462 | } | ||
| 463 |
2/2✓ Branch 0 taken 35994 times.
✓ Branch 1 taken 39680 times.
|
75674 | if (capeInfo.valid) { |
| 464 |
2/4✓ Branch 0 taken 35994 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35994 times.
✗ Branch 3 not taken.
|
35994 | system["capeInfo"] = capeInfo.toJSON(); |
| 465 | } | ||
| 466 | |||
| 467 | 75674 | return system; | |
| 468 | ✗ | } | |
| 469 | |||
| 470 | /* | ||
| 471 | * | ||
| 472 | */ | ||
| 473 | 2346 | MultiSync::MultiSync() : | |
| 474 | 2346 | m_broadcastSock(-1), | |
| 475 | 2346 | m_controlSock(-1), | |
| 476 | 2346 | m_receiveSock(-1), | |
| 477 | 2346 | m_lastMediaHalfSecond(0), | |
| 478 | 2346 | m_remoteOffset(0.0), | |
| 479 | 2346 | m_lastPingTime(0), | |
| 480 | 2346 | m_lastCheckTime(0), | |
| 481 | 2346 | m_lastFrame(0), | |
| 482 | 2346 | m_sendMulticast(false), | |
| 483 |
2/4✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2346 times.
✗ Branch 3 not taken.
|
4692 | m_sendBroadcast(false) { |
| 484 | 2346 | memset(rcvBuffers, 0, sizeof(rcvBuffers)); | |
| 485 | 2346 | memset(rcvCmbuf, 0, sizeof(rcvCmbuf)); | |
| 486 | 2346 | } | |
| 487 | |||
| 488 | /* | ||
| 489 | * | ||
| 490 | */ | ||
| 491 | 458 | MultiSync::~MultiSync() { | |
| 492 | 458 | ShutdownSync(); | |
| 493 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 458 times.
|
458 | for (auto& a : m_syncStats) { |
| 494 | ✗ | MultiSyncStats* stats = (MultiSyncStats*)a.second; | |
| 495 | ✗ | delete stats; | |
| 496 | } | ||
| 497 | 458 | m_syncStats.clear(); | |
| 498 | 458 | } | |
| 499 | |||
| 500 | /* | ||
| 501 | * | ||
| 502 | */ | ||
| 503 | 1888 | int MultiSync::Init(void) { | |
| 504 |
3/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 37 times.
✓ Branch 3 taken 1851 times.
|
1888 | if (getFPPmode() == REMOTE_MODE) { |
| 505 | 37 | m_multiSyncEnabled = false; | |
| 506 | } else { | ||
| 507 |
1/2✓ Branch 0 taken 1851 times.
✗ Branch 1 not taken.
|
1851 | m_multiSyncEnabled = getSettingInt("MultiSyncEnabled", 0); |
| 508 | } | ||
| 509 | |||
| 510 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | FillInInterfaces(); |
| 511 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | FillLocalSystemInfo(); |
| 512 | |||
| 513 | // Keep the configured-output-range cache (used to backfill ranges for | ||
| 514 | // non-FPP remotes in GetSystems()) in sync with co-universes.json. Watching | ||
| 515 | // the file confines the file read + hostname resolution to actual config | ||
| 516 | // changes instead of every GetSystems() call. TriggerFileChanged() does the | ||
| 517 | // initial load now. | ||
| 518 |
3/6✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
|
2048 | std::string coUniversesFile = FPP_DIR_CONFIG("/co-universes.json"); |
| 519 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | FileMonitor::INSTANCE.AddFile("MultiSync/co-universes.json", coUniversesFile, |
| 520 | 4003 | [this]() { ReloadConfiguredOutputRanges(); }) | |
| 521 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | .TriggerFileChanged("MultiSync/co-universes.json", coUniversesFile); |
| 522 | |||
| 523 |
3/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1887 times.
|
1888 | if (!OpenReceiveSocket()) |
| 524 | 1 | return 0; | |
| 525 | |||
| 526 |
2/4✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1887 times.
|
1887 | if (!OpenBroadcastSocket()) |
| 527 | ✗ | return 0; | |
| 528 | |||
| 529 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 1855 times.
|
1887 | if (m_multiSyncEnabled) { |
| 530 |
2/4✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 32 times.
|
32 | if (!OpenControlSockets()) |
| 531 | ✗ | return 0; | |
| 532 | } | ||
| 533 | |||
| 534 | 5451 | std::function<void(NetworkMonitor::NetEventType i, int up, const std::string&)> f = [this](NetworkMonitor::NetEventType i, int up, const std::string& name) { | |
| 535 | 1677 | LogDebug(VB_SYNC, "MultiSync::NetworkChanged - Interface: %s Up: %d Msg: %d\n", name.c_str(), up, i); | |
| 536 |
3/4✓ Branch 0 taken 741 times.
✓ Branch 1 taken 936 times.
✓ Branch 2 taken 741 times.
✗ Branch 3 not taken.
|
1677 | if (i == NetworkMonitor::NetEventType::DEL_ADDR && !up) { |
| 537 | 741 | RemoveInterface(name); | |
| 538 | // The interface may still hold another address -- the DHCP lease | ||
| 539 | // that replaced a 169.254 fallback, typically -- so pick it back up | ||
| 540 | // rather than leave the interface out, and drop the deleted address | ||
| 541 | // from what we announce. | ||
| 542 |
2/2✓ Branch 0 taken 189 times.
✓ Branch 1 taken 552 times.
|
741 | if (FillInInterfaces()) { |
| 543 | 189 | setupMulticastReceive(true); | |
| 544 | } | ||
| 545 | 741 | FillLocalSystemInfo(); | |
| 546 |
3/4✓ Branch 0 taken 673 times.
✓ Branch 1 taken 263 times.
✓ Branch 2 taken 673 times.
✗ Branch 3 not taken.
|
936 | } else if (i == NetworkMonitor::NetEventType::NEW_ADDR && up) { |
| 547 | 673 | bool changed = FillInInterfaces(); | |
| 548 | 673 | setupMulticastReceive(true); | |
| 549 | // Always, not just when an interface appeared: a new address on an | ||
| 550 | // interface we already had can retire its 169.254 fallback. | ||
| 551 | 673 | changed |= FillLocalSystemInfo(); | |
| 552 |
2/2✓ Branch 0 taken 205 times.
✓ Branch 1 taken 468 times.
|
673 | if (changed) { |
| 553 | 205 | Ping(0, false); | |
| 554 | } | ||
| 555 | } | ||
| 556 | 1887 | }; | |
| 557 |
1/2✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
|
1887 | NetworkMonitor::INSTANCE.registerCallback(f); |
| 558 | |||
| 559 | // The sync send methods and the unicast remote list are picked up live. | ||
| 560 | // fppd already re-reads /media/settings whenever the web UI writes it | ||
| 561 | // (FileMonitor in fppd.cpp), so all that was missing was rebuilding the | ||
| 562 | // destination list these settings feed -- editing the remote list on the | ||
| 563 | // MultiSync page no longer needs an fppd restart to take effect. The | ||
| 564 | // control socket itself is unaffected by any of them; if it isn't open yet | ||
| 565 | // (MultiSync disabled, or nothing has needed it), OpenControlSockets() | ||
| 566 | // reads the current settings when it does open. | ||
| 567 | 2687 | for (const char* s : { "MultiSyncRemotes", "MultiSyncExtraRemotes", | |
| 568 |
2/2✓ Branch 0 taken 9435 times.
✓ Branch 1 taken 1887 times.
|
13209 | "MultiSyncBroadcast", "MultiSyncMulticast", "MultiSyncUnicast" }) { |
| 569 |
3/6✓ Branch 0 taken 9435 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9435 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9435 times.
✗ Branch 5 not taken.
|
46375 | registerSettingsListener("MultiSync", s, [this](const std::string& v) { |
| 570 | { | ||
| 571 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | std::unique_lock<std::mutex> lock(s_listenerQuiesceLock); |
| 572 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (s_listenersShutdown) { |
| 573 | ✗ | return; | |
| 574 | } | ||
| 575 | 7 | ++s_listenersInFlight; | |
| 576 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | } |
| 577 | struct InFlight { | ||
| 578 | 7 | ~InFlight() { | |
| 579 | 7 | std::unique_lock<std::mutex> lock(s_listenerQuiesceLock); | |
| 580 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | if (--s_listenersInFlight == 0) { |
| 581 | 7 | s_listenerQuiesceCV.notify_all(); | |
| 582 | } | ||
| 583 | 7 | } | |
| 584 | } inFlight; | ||
| 585 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | if (m_controlSock >= 0) { |
| 586 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | ReloadSyncDestinations(); |
| 587 | } | ||
| 588 | 7 | }); | |
| 589 | } | ||
| 590 | |||
| 591 | 1887 | return 1; | |
| 592 | 1888 | } | |
| 593 | |||
| 594 | /* | ||
| 595 | * | ||
| 596 | */ | ||
| 597 | 9092 | void MultiSync::UpdateSystem(MultiSyncSystemType type, | |
| 598 | unsigned int majorVersion, unsigned int minorVersion, | ||
| 599 | FPPMode fppMode, | ||
| 600 | const std::string& address, | ||
| 601 | const std::string& hostname, | ||
| 602 | const std::string& version, | ||
| 603 | const std::string& model, | ||
| 604 | const std::string& ranges, | ||
| 605 | const std::string& uuid, | ||
| 606 | const bool multiSync, | ||
| 607 | const bool sendingMultiSync) { | ||
| 608 |
3/4✓ Branch 0 taken 8882 times.
✓ Branch 1 taken 210 times.
✓ Branch 2 taken 9092 times.
✗ Branch 3 not taken.
|
9092 | LogDebug(VB_SYNC, "UpdateSystem(%d, %u, %u, %d, '%s', '%s', '%s', '%s', '%s', '%s', %s)\n", |
| 609 | (int)type, majorVersion, minorVersion, (int)fppMode, | ||
| 610 | address.c_str(), hostname.c_str(), version.c_str(), model.c_str(), | ||
| 611 | ranges.c_str(), uuid.c_str(), multiSync ? "true" : "false"); | ||
| 612 | |||
| 613 | char timeStr[34]; | ||
| 614 | 9092 | memset(timeStr, 0, sizeof(timeStr)); | |
| 615 | 9092 | time_t t = time(NULL); | |
| 616 | struct tm tm; | ||
| 617 | 9092 | localtime_r(&t, &tm); | |
| 618 | 9092 | snprintf(timeStr, sizeof(timeStr), "%4d-%.2d-%.2d %.2d:%.2d:%.2d", | |
| 619 | 9092 | 1900 + tm.tm_year, tm.tm_mon + 1, tm.tm_mday, | |
| 620 | tm.tm_hour, tm.tm_min, tm.tm_sec); | ||
| 621 | |||
| 622 |
1/2✓ Branch 0 taken 9092 times.
✗ Branch 1 not taken.
|
9092 | std::string ipForAddress = address; |
| 623 |
1/2✓ Branch 0 taken 9092 times.
✗ Branch 1 not taken.
|
9092 | GetIPForHost(ipForAddress); |
| 624 | |||
| 625 | // Loopback is this box finding itself, not a discoverable peer. Avahi | ||
| 626 | // resolves our own _fppd._udp advertisement to 127.0.0.1, so mDNS hands it | ||
| 627 | // straight back to us; a channel output or an HTTP discovery subnet aimed | ||
| 628 | // at localhost does the same. The resulting entry is useless to every | ||
| 629 | // consumer of the systems list -- no peer can reach it, and the multisync | ||
| 630 | // page drops the row rather than render a link a browser cannot follow. | ||
| 631 | // Reject it here, at the one place m_remoteSystems ever grows, so no | ||
| 632 | // discovery path can reintroduce it. Local interface addresses come from | ||
| 633 | // FillInInterfaces(), which already skips "lo", so this never rejects one | ||
| 634 | // of our own entries. | ||
| 635 |
7/10✓ Branch 0 taken 9092 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9090 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 9090 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 9090 times.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 9090 times.
|
9092 | if (IsLoopbackAddress(address) || IsLoopbackAddress(ipForAddress)) { |
| 636 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogDebug(VB_SYNC, "Ignoring loopback address %s in discovery\n", address.c_str()); |
| 637 | 2 | return; | |
| 638 | } | ||
| 639 | |||
| 640 |
1/2✓ Branch 0 taken 9090 times.
✗ Branch 1 not taken.
|
9090 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 641 | 9090 | bool found = false; | |
| 642 | 9090 | bool unicastChanged = false; | |
| 643 | |||
| 644 | // A device that started before DHCP answered announced itself from a | ||
| 645 | // 169.254 address, and keeps that entry here after it has a real one. The | ||
| 646 | // link-local address is only reachable from the same link, so once the | ||
| 647 | // device reports from a routable address that is the address to list: move | ||
| 648 | // the link-local entry over to it (or drop it, if the routable address | ||
| 649 | // already has its own entry) rather than listing the device twice. | ||
| 650 |
2/2✓ Branch 0 taken 9088 times.
✓ Branch 1 taken 2 times.
|
9090 | if (ipForAddress.find(':') == std::string::npos) { |
| 651 | 9088 | MultiSyncSystem* existing = nullptr; | |
| 652 |
2/2✓ Branch 0 taken 2712868 times.
✓ Branch 1 taken 7583 times.
|
2720451 | for (auto& sys : m_remoteSystems) { |
| 653 |
5/6✓ Branch 0 taken 2711363 times.
✓ Branch 1 taken 1505 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2711363 times.
✓ Branch 4 taken 1505 times.
✓ Branch 5 taken 2711363 times.
|
2712868 | if (address == sys.address || ipForAddress == sys.address) { |
| 654 | 1505 | existing = &sys; | |
| 655 | 1505 | break; | |
| 656 | } | ||
| 657 | } | ||
| 658 | // A ping carries no UUID, but the entry already at this address may know one | ||
| 659 |
1/2✓ Branch 0 taken 9088 times.
✗ Branch 1 not taken.
|
9088 | std::string knownUUID = uuid; |
| 660 |
8/10✓ Branch 0 taken 9088 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9088 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8981 times.
✓ Branch 5 taken 107 times.
✓ Branch 6 taken 1428 times.
✓ Branch 7 taken 7553 times.
✓ Branch 8 taken 1428 times.
✓ Branch 9 taken 7660 times.
|
9088 | if ((knownUUID.empty() || knownUUID == "Unknown") && existing) { |
| 661 |
1/2✓ Branch 0 taken 1428 times.
✗ Branch 1 not taken.
|
1428 | knownUUID = existing->uuid; |
| 662 | } | ||
| 663 | |||
| 664 |
3/4✓ Branch 0 taken 9088 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 9086 times.
|
9088 | if (IsIPv4LinkLocal(ipForAddress)) { |
| 665 | // The other direction: a sender that still announces its old | ||
| 666 | // 169.254 address alongside the new one (an older FPP) would | ||
| 667 | // otherwise re-add the entry on every ping. Only while the | ||
| 668 | // routable entry is current, so a device that genuinely fell back | ||
| 669 | // to link-local (lost its lease) is still picked up. | ||
| 670 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (auto& sys : m_remoteSystems) { |
| 671 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (!existing && sys.address.find(':') == std::string::npos && |
| 672 |
5/10✓ 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.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
|
3 | !IsIPv4LinkLocal(sys.address) && sys.lastSeen + 120 >= (unsigned long)t && |
| 673 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | IsSameDevice(sys, hostname, fppMode, type, knownUUID)) { |
| 674 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogDebug(VB_SYNC, "Ignoring link-local %s for %s, also seen at %s\n", |
| 675 | address.c_str(), hostname.c_str(), sys.address.c_str()); | ||
| 676 | 1 | return; | |
| 677 | } | ||
| 678 | } | ||
| 679 | } else { | ||
| 680 | 9086 | bool haveEntry = existing != nullptr; | |
| 681 |
2/2✓ Branch 0 taken 2713230 times.
✓ Branch 1 taken 9086 times.
|
2722316 | for (auto it = m_remoteSystems.begin(); it != m_remoteSystems.end();) { |
| 682 |
7/10✓ Branch 0 taken 2713230 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2713229 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 2713229 times.
✓ Branch 9 taken 1 times.
|
2713230 | if (!IsIPv4LinkLocal(it->address) || !IsSameDevice(*it, hostname, fppMode, type, knownUUID)) { |
| 683 | 2713229 | ++it; | |
| 684 | 2713229 | continue; | |
| 685 | } | ||
| 686 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (haveEntry) { |
| 687 | ✗ | LogInfo(VB_SYNC, "Removing link-local %s for %s, now at %s\n", | |
| 688 | it->address.c_str(), hostname.c_str(), ipForAddress.c_str()); | ||
| 689 | ✗ | unicastChanged |= it->supportsUnicast; | |
| 690 | ✗ | it = m_remoteSystems.erase(it); | |
| 691 | ✗ | continue; | |
| 692 | } | ||
| 693 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogInfo(VB_SYNC, "Moving %s from link-local %s to %s\n", |
| 694 | hostname.c_str(), it->address.c_str(), ipForAddress.c_str()); | ||
| 695 | // Set here rather than left to update() below: update() leaves | ||
| 696 | // a multisync-learned entry alone when the report isn't one. | ||
| 697 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | it->address = ipForAddress; |
| 698 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | std::vector<std::string> parts = split(ipForAddress, '.'); |
| 699 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (parts.size() == 4) { |
| 700 | 1 | it->ipa = atoi(parts[0].c_str()); | |
| 701 | 1 | it->ipb = atoi(parts[1].c_str()); | |
| 702 | 1 | it->ipc = atoi(parts[2].c_str()); | |
| 703 | 1 | it->ipd = atoi(parts[3].c_str()); | |
| 704 | } | ||
| 705 | // Never fetched while link-local (CheckSystemInfoRefreshes skips it) | ||
| 706 | 1 | it->infoNextFetch = 0; | |
| 707 | 1 | unicastChanged |= it->supportsUnicast; | |
| 708 | 1 | haveEntry = true; | |
| 709 | 1 | ++it; | |
| 710 | 1 | } | |
| 711 | } | ||
| 712 |
2/2✓ Branch 0 taken 9087 times.
✓ Branch 1 taken 1 times.
|
9088 | } |
| 713 | |||
| 714 |
2/2✓ Branch 0 taken 2713230 times.
✓ Branch 1 taken 9089 times.
|
2722319 | for (auto& sys : m_remoteSystems) { |
| 715 |
7/8✓ Branch 0 taken 2711410 times.
✓ Branch 1 taken 1820 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2711410 times.
✓ Branch 4 taken 316 times.
✓ Branch 5 taken 1504 times.
✓ Branch 6 taken 1504 times.
✓ Branch 7 taken 2711726 times.
|
2715366 | if ((address == sys.address || ipForAddress == sys.address) && |
| 716 |
1/2✓ Branch 0 taken 316 times.
✗ Branch 1 not taken.
|
2136 | ((hostname == sys.hostname) || |
| 717 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 316 times.
|
632 | (address == sys.hostname) || |
| 718 | 316 | (hostname == sys.address))) { | |
| 719 | 1504 | found = true; | |
| 720 | 1504 | bool wasUnicast = sys.supportsUnicast; | |
| 721 |
1/2✓ Branch 0 taken 1504 times.
✗ Branch 1 not taken.
|
1504 | sys.update(type, majorVersion, minorVersion, fppMode, address, hostname, version, model, ranges, uuid, multiSync, sendingMultiSync); |
| 722 |
1/2✓ Branch 0 taken 1504 times.
✗ Branch 1 not taken.
|
1504 | ApplyUUIDHint(sys); |
| 723 |
1/2✓ Branch 0 taken 1504 times.
✗ Branch 1 not taken.
|
1504 | sys.lastSeenStr = timeStr; |
| 724 | 1504 | sys.lastSeen = t; | |
| 725 | 1504 | unicastChanged |= (wasUnicast != sys.supportsUnicast); | |
| 726 | } | ||
| 727 | } | ||
| 728 |
2/2✓ Branch 0 taken 10119 times.
✓ Branch 1 taken 9089 times.
|
19208 | for (auto& sys : m_localSystems) { |
| 729 |
6/6✓ Branch 0 taken 4235 times.
✓ Branch 1 taken 5884 times.
✓ Branch 2 taken 4233 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 4233 times.
✓ Branch 5 taken 5886 times.
|
14354 | if ((address == sys.address) && |
| 730 | 4235 | (hostname == sys.hostname)) { | |
| 731 | 4233 | found = true; | |
| 732 |
1/2✓ Branch 0 taken 4233 times.
✗ Branch 1 not taken.
|
4233 | sys.update(type, majorVersion, minorVersion, fppMode, address, hostname, version, model, ranges, uuid, multiSync, sendingMultiSync); |
| 733 | 4233 | sys.lastSeen = t; | |
| 734 |
1/2✓ Branch 0 taken 4233 times.
✗ Branch 1 not taken.
|
4233 | sys.lastSeenStr = timeStr; |
| 735 | } | ||
| 736 | } | ||
| 737 |
2/2✓ Branch 0 taken 3352 times.
✓ Branch 1 taken 5737 times.
|
9089 | if (!found) { |
| 738 | // Every ping carrying a new address adds a system, and pings come | ||
| 739 | // from anyone on the LAN; entries are otherwise only pruned after two | ||
| 740 | // hours. Past the cap, the system seen longest ago makes room. | ||
| 741 | static constexpr size_t MAX_REMOTE_SYSTEMS = 2048; | ||
| 742 |
2/2✓ Branch 0 taken 52 times.
✓ Branch 1 taken 3300 times.
|
3352 | if (m_remoteSystems.size() >= MAX_REMOTE_SYSTEMS) { |
| 743 |
1/2✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
|
52 | auto oldest = std::min_element(m_remoteSystems.begin(), m_remoteSystems.end(), |
| 744 | 106444 | [](const MultiSyncSystem& a, const MultiSyncSystem& b) { return a.lastSeen < b.lastSeen; }); | |
| 745 |
1/2✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
|
52 | LogWarn(VB_SYNC, "More than %zu remote systems known; forgetting %s to make room\n", |
| 746 | MAX_REMOTE_SYSTEMS, oldest->address.c_str()); | ||
| 747 | 52 | unicastChanged |= oldest->supportsUnicast; | |
| 748 |
1/2✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
|
52 | m_remoteSystems.erase(oldest); |
| 749 | } | ||
| 750 | 3352 | MultiSyncSystem sys; | |
| 751 |
1/2✓ Branch 0 taken 3352 times.
✗ Branch 1 not taken.
|
3352 | sys.update(type, majorVersion, minorVersion, fppMode, address, hostname, version, model, ranges, uuid, multiSync, sendingMultiSync); |
| 752 |
1/2✓ Branch 0 taken 3352 times.
✗ Branch 1 not taken.
|
3352 | ApplyUUIDHint(sys); |
| 753 |
1/2✓ Branch 0 taken 3352 times.
✗ Branch 1 not taken.
|
3352 | sys.lastSeenStr = timeStr; |
| 754 | 3352 | sys.lastSeen = t; | |
| 755 | 3352 | unicastChanged |= sys.supportsUnicast; | |
| 756 |
1/2✓ Branch 0 taken 3352 times.
✗ Branch 1 not taken.
|
3352 | m_remoteSystems.push_back(sys); |
| 757 | 3352 | } | |
| 758 | |||
| 759 |
1/2✓ Branch 0 taken 9089 times.
✗ Branch 1 not taken.
|
9089 | ReconcileDeviceIdentity(hostname, fppMode); |
| 760 | |||
| 761 | // If a remote became (or stopped being) a unicast target, refresh the | ||
| 762 | // cached "all known remotes" destination list. The snapshot is taken here | ||
| 763 | // while we still hold m_systemsLock, but the rebuild has to run after it is | ||
| 764 | // released: UpdateUnicastDestinations() takes m_unicastUpdateLock, which is | ||
| 765 | // never acquired while m_systemsLock is held. | ||
| 766 |
4/4✓ Branch 0 taken 127 times.
✓ Branch 1 taken 8962 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 125 times.
|
9089 | bool rebuildUnicast = m_sendUnicast && unicastChanged; |
| 767 | 9089 | std::vector<std::string> unicastAddrs; | |
| 768 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 9087 times.
|
9089 | if (rebuildUnicast) { |
| 769 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | for (auto& sys : m_remoteSystems) { |
| 770 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (sys.supportsUnicast) { |
| 771 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | unicastAddrs.push_back(sys.address); |
| 772 | } | ||
| 773 | } | ||
| 774 | } | ||
| 775 |
1/2✓ Branch 0 taken 9089 times.
✗ Branch 1 not taken.
|
9089 | lock.unlock(); |
| 776 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 9087 times.
|
9089 | if (rebuildUnicast) { |
| 777 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | UpdateUnicastDestinations(unicastAddrs); |
| 778 | } | ||
| 779 |
4/4✓ Branch 0 taken 9089 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 9089 times.
✓ Branch 3 taken 3 times.
|
9093 | } |
| 780 | |||
| 781 | /* | ||
| 782 | * | ||
| 783 | */ | ||
| 784 | 3310 | MultiSyncSystemType MultiSync::ModelStringToType(std::string model) { | |
| 785 |
8/20✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3310 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3310 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 3310 times.
✓ Branch 10 taken 3310 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 3310 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 3310 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
9930 | if (startsWith(model, "Raspberry Pi Model A Rev") || (model == "Raspberry Pi Model A")) |
| 786 | ✗ | return kSysTypeFPPRaspberryPiA; | |
| 787 |
8/20✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3310 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3310 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 3310 times.
✓ Branch 10 taken 3310 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 3310 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 3310 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
9930 | if (startsWith(model, "Raspberry Pi Model B Rev") || (model == "Raspberry Pi Model B")) |
| 788 | ✗ | return kSysTypeFPPRaspberryPiB; | |
| 789 |
8/20✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3310 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3310 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 3310 times.
✓ Branch 10 taken 3310 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 3310 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 3310 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
9930 | if (startsWith(model, "Raspberry Pi Model A Plus") || (model == "Raspberry Pi Model A+")) |
| 790 | ✗ | return kSysTypeFPPRaspberryPiAPlus; | |
| 791 |
8/20✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3310 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3310 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 3310 times.
✓ Branch 10 taken 3310 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 3310 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 3310 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
9930 | if (startsWith(model, "Raspberry Pi Model B Plus") || (model == "Raspberry Pi Model B+")) |
| 792 | ✗ | return kSysTypeFPPRaspberryPiBPlus; | |
| 793 |
5/14✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3310 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3310 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 3310 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
|
16550 | if ((startsWith(model, "Raspberry Pi 2 Model B 1.1")) || |
| 794 |
6/16✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3310 times.
✓ Branch 6 taken 3310 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3310 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 3310 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
|
9930 | (startsWith(model, "Raspberry Pi 2 Model B 1.0"))) |
| 795 | ✗ | return kSysTypeFPPRaspberryPi2B; | |
| 796 |
3/6✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3310 times.
|
6620 | if (startsWith(model, "Raspberry Pi 2 Model B")) |
| 797 | ✗ | return kSysTypeFPPRaspberryPi2BNew; | |
| 798 |
8/20✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3310 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3310 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 3310 times.
✓ Branch 10 taken 3310 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 3310 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 3310 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
9930 | if (startsWith(model, "Raspberry Pi 3 Model B Rev") || (model == "Raspberry Pi 3 Model B")) |
| 799 | ✗ | return kSysTypeFPPRaspberryPi3B; | |
| 800 |
8/20✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3310 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3310 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 3310 times.
✓ Branch 10 taken 3310 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 3310 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 3310 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
9930 | if (startsWith(model, "Raspberry Pi 3 Model B Plus") || (model == "Raspberry Pi 3 Model B+")) |
| 801 | ✗ | return kSysTypeFPPRaspberryPi3BPlus; | |
| 802 |
3/6✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3310 times.
|
6620 | if (startsWith(model, "Raspberry Pi Zero Rev")) |
| 803 | ✗ | return kSysTypeFPPRaspberryPiZero; | |
| 804 |
3/6✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3310 times.
|
6620 | if (startsWith(model, "Raspberry Pi Zero W")) |
| 805 | ✗ | return kSysTypeFPPRaspberryPiZeroW; | |
| 806 |
3/6✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3310 times.
|
6620 | if (startsWith(model, "Raspberry Pi Zero 2 W")) |
| 807 | ✗ | return kSysTypeFPPRaspberryPiZero2W; | |
| 808 |
8/20✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3310 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3310 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 3310 times.
✓ Branch 10 taken 3310 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 3310 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 3310 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
9930 | if (startsWith(model, "Raspberry Pi 3 Model A Plus") || (model == "Raspberry Pi 3 Model A+")) |
| 809 | ✗ | return kSysTypeFPPRaspberryPi3APlus; | |
| 810 |
15/30✓ Branch 0 taken 3310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3307 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 3307 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3307 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 3307 times.
✓ Branch 12 taken 3307 times.
✓ Branch 13 taken 3 times.
✓ Branch 14 taken 3307 times.
✓ Branch 15 taken 3 times.
✓ Branch 16 taken 3310 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 3310 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 3 times.
✓ Branch 21 taken 3307 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
16544 | if (startsWith(model, "Raspberry Pi 4") || startsWith(model, "Raspberry Pi Compute Module 4")) |
| 811 | 3 | return kSysTypeFPPRaspberryPi4; | |
| 812 |
15/30✓ Branch 0 taken 3307 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3307 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3253 times.
✓ Branch 5 taken 54 times.
✓ Branch 6 taken 3253 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3253 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 3253 times.
✓ Branch 12 taken 3253 times.
✓ Branch 13 taken 54 times.
✓ Branch 14 taken 3253 times.
✓ Branch 15 taken 54 times.
✓ Branch 16 taken 3307 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 3307 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 54 times.
✓ Branch 21 taken 3253 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
16427 | if (startsWith(model, "Raspberry Pi 5") || startsWith(model, "Raspberry Pi Compute Module 5")) |
| 813 | 54 | return kSysTypeFPPRaspberryPi5; | |
| 814 |
3/6✓ Branch 0 taken 3253 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3253 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3253 times.
|
6506 | if (startsWith(model, "SanCloud BeagleBone Enhanced")) |
| 815 | ✗ | return kSysTypeFPPSanCloudBeagleBoneEnhanced; | |
| 816 |
4/6✓ Branch 0 taken 3253 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3253 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 21 times.
✓ Branch 5 taken 3232 times.
|
6506 | if (contains(model, "BeagleBone Black")) { |
| 817 |
3/6✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 21 times.
|
42 | if (contains(model, "Wireless")) { |
| 818 | ✗ | return kSysTypeFPPBeagleBoneBlackWireless; | |
| 819 | } | ||
| 820 | 21 | return kSysTypeFPPBeagleBoneBlack; | |
| 821 | } | ||
| 822 |
3/6✓ Branch 0 taken 3232 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3232 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3232 times.
|
6464 | if (contains(model, "BeagleBone Green")) { |
| 823 | ✗ | if (contains(model, "Wireless")) { | |
| 824 | ✗ | return kSysTypeFPPBeagleBoneGreenWireless; | |
| 825 | } | ||
| 826 | ✗ | return kSysTypeFPPBeagleBoneGreen; | |
| 827 | } | ||
| 828 | // This is fed both the device-tree model ("BeagleBoard.org PocketBeagle2") | ||
| 829 | // and, for HTTP-discovered controllers, a remote's 'Variant' setting, which | ||
| 830 | // spells the board with a space ("PocketBeagle 2", "PocketBeagle 2 | ||
| 831 | // Industrial"). Both spellings have to be caught here, and before the | ||
| 832 | // PocketBeagle test below -- otherwise a PocketBeagle 2 falls through and | ||
| 833 | // gets reported as a PocketBeagle 1. | ||
| 834 |
14/28✓ Branch 0 taken 3232 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3232 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3158 times.
✓ Branch 5 taken 74 times.
✓ Branch 6 taken 3158 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3158 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 3156 times.
✓ Branch 11 taken 2 times.
✓ Branch 12 taken 3158 times.
✓ Branch 13 taken 74 times.
✓ Branch 14 taken 3232 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 3232 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 76 times.
✓ Branch 19 taken 3156 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
|
22400 | if (contains(model, "PocketBeagle2") || contains(model, "PocketBeagle 2") || |
| 835 |
9/16✓ Branch 0 taken 3156 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3156 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3156 times.
✓ Branch 6 taken 3156 times.
✓ Branch 7 taken 76 times.
✓ Branch 8 taken 3156 times.
✓ Branch 9 taken 76 times.
✓ Branch 10 taken 3158 times.
✓ Branch 11 taken 74 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
|
9544 | contains(model, "BeaglePlay")) { |
| 836 | 76 | return kSysTypeFPPPocketBeagle2; | |
| 837 | } | ||
| 838 |
4/6✓ Branch 0 taken 3156 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3156 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 3145 times.
|
6312 | if (contains(model, "PocketBeagle")) { |
| 839 | 11 | return kSysTypeFPPPocketBeagle; | |
| 840 | } | ||
| 841 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3145 times.
|
3145 | if (model == "MacOS") { |
| 842 | ✗ | return kSysTypeMacOS; | |
| 843 | } | ||
| 844 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3145 times.
|
3145 | if (model == "Armbian") { |
| 845 | ✗ | return kSysTypeFPPArmbian; | |
| 846 | } | ||
| 847 | // FIXME, fill in the rest of the types | ||
| 848 | |||
| 849 | 3145 | return kSysTypeFPP; | |
| 850 | } | ||
| 851 | |||
| 852 | /* | ||
| 853 | * | ||
| 854 | */ | ||
| 855 | 3302 | bool MultiSync::FillLocalSystemInfo(void) { | |
| 856 | 3302 | MultiSyncSystem newSystem; | |
| 857 | |||
| 858 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | std::string model = GetHardwareModel(); |
| 859 | #ifdef PLATFORM_ARMBIAN | ||
| 860 | MultiSyncSystemType type = ModelStringToType("Armbian"); | ||
| 861 | #else | ||
| 862 |
2/4✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3302 times.
✗ Branch 3 not taken.
|
3302 | MultiSyncSystemType type = ModelStringToType(model); |
| 863 | #endif | ||
| 864 | |||
| 865 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | std::string multiSyncInterface = getSetting("MultiSyncInterface"); |
| 866 | char addressBuf[128]; | ||
| 867 | 3302 | std::list<std::string> addresses; | |
| 868 | |||
| 869 | 3302 | std::string dockerAddress; | |
| 870 |
3/4✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3140 times.
✓ Branch 3 taken 162 times.
|
3302 | if (FileExists("/etc/fpp/container")) { |
| 871 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3140 times.
|
3140 | if (getenv("FPP_DOCKER_IP")) { |
| 872 | ✗ | dockerAddress = getenv("FPP_DOCKER_IP"); | |
| 873 | ✗ | addresses.push_back(dockerAddress); | |
| 874 | } | ||
| 875 | |||
| 876 |
1/2✓ Branch 0 taken 3140 times.
✗ Branch 1 not taken.
|
3140 | std::string a = getSetting("MultiSyncExternalIPAddress"); |
| 877 |
6/8✓ Branch 0 taken 3140 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3139 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 3139 times.
|
3140 | if (a != "" && a != dockerAddress) { |
| 878 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | addresses.push_back(a); |
| 879 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | dockerAddress = a; |
| 880 | } | ||
| 881 | 3140 | } | |
| 882 | |||
| 883 |
7/10✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3302 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3302 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3301 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 3301 times.
✓ Branch 9 taken 1 times.
|
3302 | if (multiSyncInterface == "" && dockerAddress == "") { |
| 884 | // get all the addresses | ||
| 885 | struct ifaddrs *interfaces, *tmp; | ||
| 886 | 3301 | getifaddrs(&interfaces); | |
| 887 | 3301 | tmp = interfaces; | |
| 888 | 3301 | std::vector<std::pair<std::string, std::string>> ifAddrs; // interface, address | |
| 889 | 3301 | std::set<std::string> routableIfs; | |
| 890 |
2/2✓ Branch 0 taken 17850 times.
✓ Branch 1 taken 3301 times.
|
21151 | while (tmp) { |
| 891 |
3/4✓ Branch 0 taken 17850 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6923 times.
✓ Branch 3 taken 10927 times.
|
17850 | if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) { |
| 892 |
1/2✓ Branch 0 taken 6923 times.
✗ Branch 1 not taken.
|
6923 | if (strncmp("usb", tmp->ifa_name, 3) != 0) { |
| 893 | // skip the usb* interfaces as we won't support multisync on those | ||
| 894 | 6923 | memset(addressBuf, 0, sizeof(addressBuf)); | |
| 895 | |||
| 896 | 6923 | struct sockaddr_in* sa = (struct sockaddr_in*)(tmp->ifa_addr); | |
| 897 | 6923 | inet_ntop(AF_INET, &sa->sin_addr, addressBuf, INET_ADDRSTRLEN); | |
| 898 |
3/4✓ Branch 0 taken 6923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3618 times.
✓ Branch 3 taken 3305 times.
|
6923 | if (isSupportedForMultisync(addressBuf, tmp->ifa_name)) { |
| 899 |
1/2✓ Branch 0 taken 3618 times.
✗ Branch 1 not taken.
|
3618 | ifAddrs.emplace_back(tmp->ifa_name, addressBuf); |
| 900 |
3/6✓ Branch 0 taken 3618 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3618 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3618 times.
✗ Branch 5 not taken.
|
7236 | if (!IsIPv4LinkLocal(addressBuf)) { |
| 901 |
2/4✓ Branch 0 taken 3618 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3618 times.
✗ Branch 3 not taken.
|
7236 | routableIfs.insert(tmp->ifa_name); |
| 902 | } | ||
| 903 | } | ||
| 904 | } | ||
| 905 |
1/2✓ Branch 0 taken 10927 times.
✗ Branch 1 not taken.
|
10927 | } else if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET6) { |
| 906 | // FIXME for ipv6 multisync | ||
| 907 | } | ||
| 908 | 17850 | tmp = tmp->ifa_next; | |
| 909 | } | ||
| 910 | 3301 | freeifaddrs(interfaces); | |
| 911 | // A 169.254 address alongside a DHCP/static one on the same interface | ||
| 912 | // is only the fallback from before the lease arrived. Announcing it | ||
| 913 | // too just lists this box twice on every peer, once at an address | ||
| 914 | // nothing off this link can reach. | ||
| 915 |
2/2✓ Branch 0 taken 3618 times.
✓ Branch 1 taken 3301 times.
|
6919 | for (auto& [intf, addr] : ifAddrs) { |
| 916 |
3/10✓ Branch 0 taken 3618 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3618 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 3618 times.
✗ Branch 9 not taken.
|
3618 | if (!IsIPv4LinkLocal(addr) || !routableIfs.contains(intf)) { |
| 917 |
1/2✓ Branch 0 taken 3618 times.
✗ Branch 1 not taken.
|
3618 | addresses.push_back(addr); |
| 918 | } | ||
| 919 | } | ||
| 920 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
3302 | } else if (dockerAddress == "") { |
| 921 | ✗ | memset(addressBuf, 0, sizeof(addressBuf)); | |
| 922 | ✗ | GetInterfaceAddress(multiSyncInterface.c_str(), addressBuf, NULL, NULL); | |
| 923 | ✗ | addresses.push_back(addressBuf); | |
| 924 | } | ||
| 925 | |||
| 926 |
3/4✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✓ Branch 3 taken 1414 times.
|
3302 | if (m_hostname == "") { |
| 927 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | m_hostname = getSetting("HostName"); |
| 928 | } | ||
| 929 | |||
| 930 |
2/4✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3302 times.
|
3302 | if (m_hostname == "") { |
| 931 | ✗ | m_hostname = "FPP"; | |
| 932 | } | ||
| 933 | |||
| 934 | 3302 | newSystem.lastSeen = (unsigned long)time(NULL); | |
| 935 | 3302 | newSystem.type = type; | |
| 936 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | newSystem.majorVersion = atoi(getFPPMajorVersion()); |
| 937 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | newSystem.minorVersion = atoi(getFPPMinorVersion()); |
| 938 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | newSystem.hostname = m_hostname; |
| 939 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | newSystem.fppMode = getFPPmode(); |
| 940 | 3302 | newSystem.sendingMultiSync = m_multiSyncEnabled; | |
| 941 |
2/4✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3302 times.
✗ Branch 3 not taken.
|
3302 | newSystem.version = getFPPVersion(); |
| 942 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | newSystem.model = model; |
| 943 | 3302 | newSystem.ipa = 0; | |
| 944 | 3302 | newSystem.ipb = 0; | |
| 945 | 3302 | newSystem.ipc = 0; | |
| 946 | 3302 | newSystem.ipd = 0; | |
| 947 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | newSystem.uuid = getSetting("SystemUUID"); |
| 948 | |||
| 949 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | LogDebug(VB_SYNC, "Host name: %s\n", newSystem.hostname.c_str()); |
| 950 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | LogDebug(VB_SYNC, "Version: %s\n", newSystem.version.c_str()); |
| 951 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | LogDebug(VB_SYNC, "Model: %s\n", newSystem.model.c_str()); |
| 952 | |||
| 953 | // The local system's cape. Remotes are probed for theirs over HTTP (see | ||
| 954 | // FetchCapeInfo); the local entry is never probed, so without this the | ||
| 955 | // systems list showed a cape on every box except the one serving the page. | ||
| 956 | // Same file the local /api/cape answers from; absent means no cape, which | ||
| 957 | // is an answer (present=false), not "unknown". | ||
| 958 | { | ||
| 959 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | Json::Value capeJson; |
| 960 |
2/4✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3302 times.
✗ Branch 3 not taken.
|
3302 | std::string capeFile = FPP_DIR_MEDIA("/tmp/cape-info.json"); |
| 961 |
9/14✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 3297 times.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 5 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 5 times.
✓ Branch 13 taken 3297 times.
|
3302 | if (FileExists(capeFile) && LoadJsonFromFile(capeFile, capeJson) && capeJson.isObject()) { |
| 962 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | newSystem.capeInfo = MultiSyncCapeInfo::fromCapeJSON(capeJson); |
| 963 | } else { | ||
| 964 | 3297 | newSystem.capeInfo.valid = true; | |
| 965 | 3297 | newSystem.capeInfo.present = false; | |
| 966 | } | ||
| 967 | 3302 | } | |
| 968 | |||
| 969 | 3302 | bool changed = false; | |
| 970 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 971 | |||
| 972 | // Addresses this box no longer has. Every ping goes out once per local | ||
| 973 | // system, so an entry left here keeps announcing an address that is gone | ||
| 974 | // -- typically the 169.254 one held until DHCP answered. | ||
| 975 |
2/2✓ Branch 0 taken 1882 times.
✓ Branch 1 taken 3302 times.
|
5184 | for (auto it = m_localSystems.begin(); it != m_localSystems.end();) { |
| 976 |
3/4✓ Branch 0 taken 1882 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 427 times.
✓ Branch 3 taken 1455 times.
|
1882 | if (std::find(addresses.begin(), addresses.end(), it->address) == addresses.end()) { |
| 977 |
1/2✓ Branch 0 taken 427 times.
✗ Branch 1 not taken.
|
427 | LogDebug(VB_SYNC, "Removing Local System Address: %s\n", it->address.c_str()); |
| 978 | 427 | changed = true; | |
| 979 |
1/2✓ Branch 0 taken 427 times.
✗ Branch 1 not taken.
|
427 | it = m_localSystems.erase(it); |
| 980 | } else { | ||
| 981 | 1455 | ++it; | |
| 982 | } | ||
| 983 | } | ||
| 984 | |||
| 985 |
3/4✓ Branch 0 taken 3619 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3619 times.
✓ Branch 3 taken 3302 times.
|
6921 | for (auto address : addresses) { |
| 986 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3619 times.
|
3619 | if (address.empty()) { |
| 987 | // A failed GetInterfaceAddress()/inet_ntop() leaves the buffer empty; | ||
| 988 | // skip it rather than record a bogus local system. | ||
| 989 | ✗ | continue; | |
| 990 | } | ||
| 991 | 3619 | bool found = false; | |
| 992 |
2/2✓ Branch 0 taken 1976 times.
✓ Branch 1 taken 3619 times.
|
5595 | for (auto& sys : m_localSystems) { |
| 993 |
2/2✓ Branch 0 taken 1455 times.
✓ Branch 1 taken 521 times.
|
1976 | if (sys.address == address) { |
| 994 | 1455 | found = true; | |
| 995 |
1/2✓ Branch 0 taken 1455 times.
✗ Branch 1 not taken.
|
1455 | sys.capeInfo = newSystem.capeInfo; |
| 996 | } | ||
| 997 | } | ||
| 998 |
2/2✓ Branch 0 taken 2164 times.
✓ Branch 1 taken 1455 times.
|
3619 | if (!found) { |
| 999 |
1/2✓ Branch 0 taken 2164 times.
✗ Branch 1 not taken.
|
2164 | LogDebug(VB_SYNC, "Adding Local System Address: %s\n", address.c_str()); |
| 1000 | 2164 | changed = true; | |
| 1001 |
1/2✓ Branch 0 taken 2164 times.
✗ Branch 1 not taken.
|
2164 | newSystem.address = address; |
| 1002 |
1/2✓ Branch 0 taken 2164 times.
✗ Branch 1 not taken.
|
2164 | std::vector<std::string> parts = split(newSystem.address, '.'); |
| 1003 |
2/2✓ Branch 0 taken 2163 times.
✓ Branch 1 taken 1 times.
|
2164 | if (parts.size() >= 4) { |
| 1004 | 2163 | newSystem.ipa = atoi(parts[0].c_str()); | |
| 1005 | 2163 | newSystem.ipb = atoi(parts[1].c_str()); | |
| 1006 | 2163 | newSystem.ipc = atoi(parts[2].c_str()); | |
| 1007 | 2163 | newSystem.ipd = atoi(parts[3].c_str()); | |
| 1008 | } else { | ||
| 1009 | 1 | newSystem.ipa = 0; | |
| 1010 | 1 | newSystem.ipb = 0; | |
| 1011 | 1 | newSystem.ipc = 0; | |
| 1012 | 1 | newSystem.ipd = 0; | |
| 1013 | } | ||
| 1014 |
1/2✓ Branch 0 taken 2164 times.
✗ Branch 1 not taken.
|
2164 | m_localSystems.push_back(newSystem); |
| 1015 | 2164 | } | |
| 1016 |
1/2✓ Branch 0 taken 3619 times.
✗ Branch 1 not taken.
|
3619 | } |
| 1017 | 3302 | return changed; | |
| 1018 | 3302 | } | |
| 1019 | |||
| 1020 | /* | ||
| 1021 | * | ||
| 1022 | */ | ||
| 1023 | 3302 | std::string MultiSync::GetHardwareModel(void) { | |
| 1024 | #ifdef PLATFORM_OSX | ||
| 1025 | return "MacOS"; | ||
| 1026 | #else | ||
| 1027 | 3302 | std::string result; | |
| 1028 | 3302 | std::string filename; | |
| 1029 | |||
| 1030 |
3/4✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 160 times.
✓ Branch 3 taken 3142 times.
|
3302 | if (FileExists("/sys/firmware/devicetree/base/model")) |
| 1031 |
1/2✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
|
160 | filename = "/sys/firmware/devicetree/base/model"; |
| 1032 |
2/4✓ Branch 0 taken 3142 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3142 times.
✗ Branch 3 not taken.
|
3142 | else if (FileExists("/sys/class/dmi/id/product_name")) |
| 1033 |
1/2✓ Branch 0 taken 3142 times.
✗ Branch 1 not taken.
|
3142 | filename = "/sys/class/dmi/id/product_name"; |
| 1034 | |||
| 1035 |
2/4✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3302 times.
✗ Branch 3 not taken.
|
3302 | if (filename != "") { |
| 1036 | char buf[128]; | ||
| 1037 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | FILE* fd = fopen(filename.c_str(), "r"); |
| 1038 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | if (fd) { |
| 1039 |
2/4✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3302 times.
✗ Branch 3 not taken.
|
3302 | if (fgets(buf, 127, fd)) |
| 1040 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | result = buf; |
| 1041 | else | ||
| 1042 | ✗ | result = "Unknown Hardware Platform"; | |
| 1043 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | fclose(fd); |
| 1044 | } else { | ||
| 1045 | ✗ | result = "Unknown Hardware Platform"; | |
| 1046 | } | ||
| 1047 | } else { | ||
| 1048 | ✗ | result = "Unknown Hardware Platform"; | |
| 1049 | } | ||
| 1050 | |||
| 1051 |
4/6✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3302 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3142 times.
✓ Branch 5 taken 160 times.
|
6604 | if (endsWith(result, "\n")) { |
| 1052 |
3/6✓ Branch 0 taken 3142 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3142 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3142 times.
✗ Branch 5 not taken.
|
12568 | replaceAll(result, "\n", ""); |
| 1053 | } | ||
| 1054 | |||
| 1055 |
1/2✓ Branch 0 taken 3302 times.
✗ Branch 1 not taken.
|
3302 | TrimWhiteSpace(result); |
| 1056 | |||
| 1057 | 3302 | return result; | |
| 1058 | #endif | ||
| 1059 | 3302 | } | |
| 1060 | |||
| 1061 | /* | ||
| 1062 | * | ||
| 1063 | */ | ||
| 1064 | 75675 | std::string MultiSync::GetTypeString(MultiSyncSystemType type, bool local) { | |
| 1065 |
3/4✓ Branch 0 taken 35993 times.
✓ Branch 1 taken 39682 times.
✓ Branch 2 taken 35993 times.
✗ Branch 3 not taken.
|
75675 | if (local && type == kSysTypeFPP) { |
| 1066 | // unknown hardware, but we can figure out the OS version | ||
| 1067 |
1/2✓ Branch 0 taken 35993 times.
✗ Branch 1 not taken.
|
35993 | if (FileExists("/etc/os-release")) { |
| 1068 |
2/4✓ Branch 0 taken 35993 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35993 times.
✗ Branch 3 not taken.
|
35993 | std::string file = GetFileContents("/etc/os-release"); |
| 1069 |
1/2✓ Branch 0 taken 35993 times.
✗ Branch 1 not taken.
|
35993 | std::vector<std::string> lines = split(file, '\n'); |
| 1070 | 35993 | std::map<std::string, std::string> values; | |
| 1071 |
2/2✓ Branch 0 taken 359930 times.
✓ Branch 1 taken 35993 times.
|
395923 | for (auto& str : lines) { |
| 1072 | 359930 | size_t pos = str.find("="); | |
| 1073 |
1/2✓ Branch 0 taken 359930 times.
✗ Branch 1 not taken.
|
359930 | if (pos != std::string::npos) { |
| 1074 |
1/2✓ Branch 0 taken 359930 times.
✗ Branch 1 not taken.
|
359930 | std::string key = str.substr(0, pos); |
| 1075 |
1/2✓ Branch 0 taken 359930 times.
✗ Branch 1 not taken.
|
359930 | std::string val = str.substr(pos + 1); |
| 1076 |
2/2✓ Branch 0 taken 251951 times.
✓ Branch 1 taken 107979 times.
|
359930 | if (val[0] == '"') { |
| 1077 |
1/2✓ Branch 0 taken 251951 times.
✗ Branch 1 not taken.
|
251951 | val = val.substr(1, val.length() - 2); |
| 1078 | } | ||
| 1079 |
1/2✓ Branch 0 taken 359930 times.
✗ Branch 1 not taken.
|
359930 | TrimWhiteSpace(key); |
| 1080 |
1/2✓ Branch 0 taken 359930 times.
✗ Branch 1 not taken.
|
359930 | TrimWhiteSpace(val); |
| 1081 |
2/4✓ Branch 0 taken 359930 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 359930 times.
✗ Branch 3 not taken.
|
359930 | values[key] = val; |
| 1082 | 359930 | } | |
| 1083 | } | ||
| 1084 |
4/8✓ Branch 0 taken 35993 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35993 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 35993 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 35993 times.
✗ Branch 7 not taken.
|
71986 | if (values["NAME"] != "") { |
| 1085 |
4/8✓ Branch 0 taken 35993 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35993 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 35993 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 35993 times.
✗ Branch 7 not taken.
|
71986 | return "FPP (" + values["NAME"] + ")"; |
| 1086 | } | ||
| 1087 | ✗ | if (values["PRETTY_NAME"] != "") { | |
| 1088 | ✗ | return "FPP (" + values["PRETTY_NAME"] + ")"; | |
| 1089 | } | ||
| 1090 |
3/6✗ Branch 0 not taken.
✓ Branch 1 taken 35993 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 35993 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 35993 times.
|
107979 | } |
| 1091 | ✗ | return "FPP (unknown hardware)"; | |
| 1092 | } | ||
| 1093 |
15/59✓ Branch 0 taken 26 times.
✓ Branch 1 taken 39527 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 6 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✓ Branch 24 taken 3 times.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✓ Branch 35 taken 1 times.
✗ Branch 36 not taken.
✓ Branch 37 taken 10 times.
✗ Branch 38 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✓ Branch 44 taken 2 times.
✗ Branch 45 not taken.
✗ Branch 46 not taken.
✓ Branch 47 taken 2 times.
✓ Branch 48 taken 31 times.
✓ Branch 49 taken 12 times.
✗ Branch 50 not taken.
✗ Branch 51 not taken.
✓ Branch 52 taken 4 times.
✓ Branch 53 taken 20 times.
✓ Branch 54 taken 12 times.
✗ Branch 55 not taken.
✓ Branch 56 taken 20 times.
✓ Branch 57 taken 6 times.
✗ Branch 58 not taken.
|
39682 | switch (type) { |
| 1094 | 26 | case kSysTypeUnknown: | |
| 1095 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
52 | return "Unknown System Type"; |
| 1096 | 39527 | case kSysTypeFPP: | |
| 1097 |
1/2✓ Branch 0 taken 39527 times.
✗ Branch 1 not taken.
|
79054 | return "FPP (unknown hardware)"; |
| 1098 | ✗ | case kSysTypeFPPRaspberryPiA: | |
| 1099 | ✗ | return "Raspberry Pi A"; | |
| 1100 | ✗ | case kSysTypeFPPRaspberryPiB: | |
| 1101 | ✗ | return "Raspberry Pi B"; | |
| 1102 | ✗ | case kSysTypeFPPRaspberryPiAPlus: | |
| 1103 | ✗ | return "Raspberry Pi A Plus"; | |
| 1104 | ✗ | case kSysTypeFPPRaspberryPiBPlus: | |
| 1105 | ✗ | return "Raspberry Pi B+"; | |
| 1106 | ✗ | case kSysTypeFPPRaspberryPi2B: | |
| 1107 | ✗ | return "Raspberry Pi 2 B"; | |
| 1108 | ✗ | case kSysTypeFPPRaspberryPi2BNew: | |
| 1109 | ✗ | return "Raspberry Pi 2 B v1.2+"; | |
| 1110 | ✗ | case kSysTypeFPPRaspberryPi3B: | |
| 1111 | ✗ | return "Raspberry Pi 3 B"; | |
| 1112 | ✗ | case kSysTypeFPPRaspberryPi3BPlus: | |
| 1113 | ✗ | return "Raspberry Pi 3 B+"; | |
| 1114 | ✗ | case kSysTypeFPPRaspberryPiZero: | |
| 1115 | ✗ | return "Raspberry Pi Zero"; | |
| 1116 | ✗ | case kSysTypeFPPRaspberryPiZeroW: | |
| 1117 | ✗ | return "Raspberry Pi Zero W"; | |
| 1118 | ✗ | case kSysTypeFPPRaspberryPiZero2W: | |
| 1119 | ✗ | return "Raspberry Pi Zero 2 W"; | |
| 1120 | ✗ | case kSysTypeFPPRaspberryPi3APlus: | |
| 1121 | ✗ | return "Raspberry Pi 3 A+"; | |
| 1122 | 6 | case kSysTypeFPPRaspberryPi4: | |
| 1123 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
12 | return "Raspberry Pi 4"; |
| 1124 | ✗ | case kSysTypeFPPRaspberryPi5: | |
| 1125 | ✗ | return "Raspberry Pi 5"; | |
| 1126 | ✗ | case kSysTypeFalconController: | |
| 1127 | ✗ | return "Falcon Controller"; | |
| 1128 | ✗ | case kSysTypeFalconF16v2: | |
| 1129 | ✗ | return "Falcon F16v2"; | |
| 1130 | ✗ | case kSysTypeFalconF4v2_64Mb: | |
| 1131 | ✗ | return "Falcon F4v2_64Mb"; | |
| 1132 | ✗ | case kSysTypeFalconF16v2R: | |
| 1133 | ✗ | return "Falcon F16v2R"; | |
| 1134 | ✗ | case kSysTypeFalconF4v2: | |
| 1135 | ✗ | return "Falcon F4v2"; | |
| 1136 | ✗ | case kSysTypeFalconF4v3: | |
| 1137 | ✗ | return "Falcon F4v3"; | |
| 1138 | ✗ | case kSysTypeFalconF16v3: | |
| 1139 | ✗ | return "Falcon F16v3"; | |
| 1140 | ✗ | case kSysTypeFalconF48: | |
| 1141 | ✗ | return "Falcon F48"; | |
| 1142 | 3 | case kSysTypeFalconF16v4: | |
| 1143 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
6 | return "Falcon F16v4"; |
| 1144 | ✗ | case kSysTypeFalconF48v4: | |
| 1145 | ✗ | return "Falcon F48v4"; | |
| 1146 | ✗ | case kSysTypeFalconF16v5: | |
| 1147 | ✗ | return "Falcon F16v5"; | |
| 1148 | ✗ | case kSysTypeFalconF32v5: | |
| 1149 | ✗ | return "Falcon F32v5"; | |
| 1150 | ✗ | case kSysTypeFalconF48v5: | |
| 1151 | ✗ | return "Falcon F48v5"; | |
| 1152 | ✗ | case kSysTypeExperienceGP16: | |
| 1153 | ✗ | return "Genius Pixel 16"; | |
| 1154 | ✗ | case kSysTypeExperienceGP8: | |
| 1155 | ✗ | return "Genius Pixel 8"; | |
| 1156 | ✗ | case kSysTypeExperienceGLR: | |
| 1157 | ✗ | return "Genius Long Range"; | |
| 1158 | ✗ | case kSysTypeExperienceG16Pro: | |
| 1159 | ✗ | return "Genius PRO 16"; | |
| 1160 | ✗ | case kSysTypeExperienceG32Pro: | |
| 1161 | ✗ | return "Genius PRO 32"; | |
| 1162 | ✗ | case kSysTypeExperienceAURORACORE16: | |
| 1163 | ✗ | return "LOR AURORA CORE 16"; | |
| 1164 | 1 | case kSysTypeExperienceVIVID8: | |
| 1165 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
2 | return "YPS VIVID 8"; |
| 1166 | ✗ | case kSysTypeExperiencePIXELLINK4: | |
| 1167 | ✗ | return "Genius PIXEL LINK 4"; | |
| 1168 | 10 | case kSysTypeExperienceGenius: | |
| 1169 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
20 | return "Genius Controller"; |
| 1170 | ✗ | case kSysTypeOtherSystem: | |
| 1171 | ✗ | return "Other Unknown System"; | |
| 1172 | ✗ | case kSysTypeFPPBeagleBoneBlack: | |
| 1173 | ✗ | return "BeagleBone Black"; | |
| 1174 | ✗ | case kSysTypeFPPBeagleBoneBlackWireless: | |
| 1175 | ✗ | return "BeagleBone Black Wireless"; | |
| 1176 | ✗ | case kSysTypeFPPBeagleBoneGreen: | |
| 1177 | ✗ | return "BeagleBone Green"; | |
| 1178 | ✗ | case kSysTypeFPPBeagleBoneGreenWireless: | |
| 1179 | ✗ | return "BeagleBone Green Wireless"; | |
| 1180 | ✗ | case kSysTypeFPPPocketBeagle: | |
| 1181 | ✗ | return "PocketBeagle"; | |
| 1182 | 2 | case kSysTypeFPPPocketBeagle2: | |
| 1183 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
4 | return "PocketBeagle2"; |
| 1184 | ✗ | case kSysTypeFPPSanCloudBeagleBoneEnhanced: | |
| 1185 | ✗ | return "SanCloud BeagleBone Enhanced"; | |
| 1186 | ✗ | case kSysTypexSchedule: | |
| 1187 | ✗ | return "xSchedule"; | |
| 1188 | 2 | case kSysTypeESPixelStick: | |
| 1189 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
4 | return "ESPixelStick-ESP8266"; |
| 1190 | 31 | case kSysTypeESPixelStickESP32: | |
| 1191 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
62 | return "ESPixelStick-ESP32"; |
| 1192 | 12 | case kSysTypeBaldrick: | |
| 1193 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
24 | return "Baldrick"; |
| 1194 | ✗ | case kSysTypeMacOS: | |
| 1195 | ✗ | return "MacOS"; | |
| 1196 | ✗ | case kSysTypeFPPArmbian: | |
| 1197 | ✗ | return "Armbian"; | |
| 1198 | 4 | case kSysTypeSanDevices: | |
| 1199 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
8 | return "SanDevices"; |
| 1200 | 20 | case kSysTypeAlphaPix: | |
| 1201 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
40 | return "AlphaPix"; |
| 1202 | 12 | case kSysTypeHinksPix: | |
| 1203 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
24 | return "HinksPix"; |
| 1204 | ✗ | case kSysTypeDIYLEDExpress: | |
| 1205 | ✗ | return "DIYLEDExpress"; | |
| 1206 | 20 | case kSysTypeWLED: | |
| 1207 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
40 | return "WLED"; |
| 1208 | 6 | case kSysTypeTwinkly: | |
| 1209 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
12 | return "Twinkly"; |
| 1210 | ✗ | default: | |
| 1211 | ✗ | return "Unknown System Type"; | |
| 1212 | } | ||
| 1213 | } | ||
| 1214 | |||
| 1215 | 25463 | static std::string createRanges(std::vector<std::pair<uint32_t, uint32_t>> ranges, int limit) { | |
| 1216 | 25463 | bool first = true; | |
| 1217 |
1/2✓ Branch 0 taken 25463 times.
✗ Branch 1 not taken.
|
25463 | std::string range(""); |
| 1218 | char buf[64]; | ||
| 1219 | 25463 | memset(buf, 0, sizeof(buf)); | |
| 1220 |
2/2✓ Branch 0 taken 25633 times.
✓ Branch 1 taken 25463 times.
|
51096 | for (auto& a : ranges) { |
| 1221 |
2/2✓ Branch 0 taken 170 times.
✓ Branch 1 taken 25463 times.
|
25633 | if (!first) { |
| 1222 |
1/2✓ Branch 0 taken 170 times.
✗ Branch 1 not taken.
|
170 | range += ","; |
| 1223 | } | ||
| 1224 | 25633 | snprintf(buf, sizeof(buf), "%d-%d", a.first, (a.first + a.second - 1)); | |
| 1225 |
1/2✓ Branch 0 taken 25633 times.
✗ Branch 1 not taken.
|
25633 | range += buf; |
| 1226 | 25633 | first = false; | |
| 1227 | } | ||
| 1228 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 25463 times.
|
25472 | while (range.size() > limit) { |
| 1229 | // range won't fit within the space in the Ping packet, we need to shrink the range | ||
| 1230 | // we'll find the smallest gap and combine into a larger range | ||
| 1231 | 9 | int minGap = 9999999; | |
| 1232 | 9 | int minIdx = -1; | |
| 1233 | 9 | int last = ranges[0].first + ranges[0].second - 1; | |
| 1234 |
2/2✓ Branch 0 taken 90 times.
✓ Branch 1 taken 9 times.
|
99 | for (int x = 1; x < ranges.size(); x++) { |
| 1235 | 90 | int gap = ranges[x].first - last; | |
| 1236 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 72 times.
|
90 | if (gap < minGap) { |
| 1237 | 18 | minIdx = x; | |
| 1238 | 18 | minGap = gap; | |
| 1239 | } | ||
| 1240 | 90 | last = ranges[x].first + ranges[x].second - 1; | |
| 1241 | } | ||
| 1242 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (minIdx > 0) { |
| 1243 | 9 | int newLast = ranges[minIdx].first + ranges[minIdx].second; | |
| 1244 | 9 | ranges[minIdx - 1].second = newLast - ranges[minIdx - 1].first; | |
| 1245 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | ranges.erase(ranges.begin() + minIdx); |
| 1246 | } | ||
| 1247 |
2/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
|
9 | range = createRanges(ranges, 999999); |
| 1248 | } | ||
| 1249 | 25463 | return range; | |
| 1250 | ✗ | } | |
| 1251 | |||
| 1252 | // Build a map of configured-output controller address -> channel range string. | ||
| 1253 | // Output-only controllers (WLED, Falcon, etc.) generally don't report their own | ||
| 1254 | // channel ranges, but FPP knows them from its universe output configuration, so | ||
| 1255 | // we backfill the ranges from our own config (issue #2667). Each address is | ||
| 1256 | // keyed both by the raw configured value and by its resolved IP so that we can | ||
| 1257 | // match a system regardless of whether it was discovered/stored by hostname or | ||
| 1258 | // by IP. | ||
| 1259 | 1955 | static std::map<std::string, std::string> GetConfiguredOutputRanges() { | |
| 1260 | 1955 | std::map<std::string, std::vector<std::pair<uint32_t, uint32_t>>> byAddress; | |
| 1261 | |||
| 1262 |
6/10✓ Branch 0 taken 1955 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1955 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1955 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1955 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 796 times.
✓ Branch 9 taken 1159 times.
|
3910 | if (FileExists(FPP_DIR_CONFIG("/co-universes.json"))) { |
| 1263 |
4/8✓ Branch 0 taken 796 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 796 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 796 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 796 times.
✗ Branch 7 not taken.
|
796 | Json::Value outputs = LoadJsonFromFile(FPP_DIR_CONFIG("/co-universes.json"), JsonRoot::Object); |
| 1264 |
1/2✓ Branch 0 taken 796 times.
✗ Branch 1 not taken.
|
796 | const Json::Value& cos = outputs["channelOutputs"]; |
| 1265 |
8/10✓ Branch 0 taken 1589 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1588 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1588 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 793 times.
✓ Branch 7 taken 795 times.
✓ Branch 8 taken 793 times.
✓ Branch 9 taken 796 times.
|
1589 | for (Json::ArrayIndex i = 0; cos.isArray() && i < cos.size(); i++) { |
| 1266 |
1/2✓ Branch 0 taken 793 times.
✗ Branch 1 not taken.
|
793 | const Json::Value& co = cos[i]; |
| 1267 |
6/12✓ Branch 0 taken 793 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 793 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 793 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 793 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 793 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 793 times.
|
793 | if (!co.isObject() || !co["universes"].isArray()) { |
| 1268 | ✗ | continue; | |
| 1269 | } | ||
| 1270 |
8/14✓ Branch 0 taken 793 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 793 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 793 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 903 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 903 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1696 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 903 times.
✓ Branch 13 taken 793 times.
|
1696 | for (const auto& u : co["universes"]) { |
| 1271 | // Read at startup: a universe with a field of the wrong type is | ||
| 1272 | // left out here (ChannelOutputSetup reports it), not thrown on. | ||
| 1273 |
5/8✓ Branch 0 taken 903 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 902 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 902 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 901 times.
✓ Branch 7 taken 1 times.
|
1805 | if (!u.isObject() || !u["address"].isString() || |
| 1274 |
7/12✓ Branch 0 taken 901 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 901 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 900 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 900 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 900 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 900 times.
✗ Branch 11 not taken.
|
901 | !u["startChannel"].isIntegral() || !u["channelCount"].isIntegral() || |
| 1275 |
7/10✓ Branch 0 taken 902 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 900 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 900 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 900 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 900 times.
|
2705 | !u["startChannel"].isConvertibleTo(Json::uintValue) || |
| 1276 |
3/6✓ Branch 0 taken 900 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 900 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 900 times.
|
900 | !u["channelCount"].isConvertibleTo(Json::uintValue)) { |
| 1277 | 4 | continue; | |
| 1278 | } | ||
| 1279 |
2/4✓ Branch 0 taken 900 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 900 times.
✗ Branch 3 not taken.
|
900 | std::string address = u["address"].asString(); |
| 1280 |
2/4✓ Branch 0 taken 900 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 900 times.
✗ Branch 3 not taken.
|
900 | uint32_t count = u["channelCount"].asUInt(); |
| 1281 |
2/4✓ Branch 0 taken 900 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 900 times.
✗ Branch 3 not taken.
|
900 | uint32_t start = u["startChannel"].asUInt(); |
| 1282 |
6/8✓ Branch 0 taken 900 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 900 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 899 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 899 times.
|
900 | if (address.empty() || count == 0 || start == 0) { |
| 1283 | 1 | continue; | |
| 1284 | } | ||
| 1285 | // channelRanges is a zero-based convention everywhere else -- | ||
| 1286 | // see the GetRequiredChannelRange() implementations, which all | ||
| 1287 | // report (startChannel - 1). The universes in co-universes.json | ||
| 1288 | // store the user-facing one-based start channel, so convert here | ||
| 1289 | // or every backfilled remote is reported one channel high. | ||
| 1290 |
2/4✓ Branch 0 taken 899 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 899 times.
✗ Branch 3 not taken.
|
899 | byAddress[address].emplace_back(start - 1, count); |
| 1291 |
2/2✓ Branch 0 taken 899 times.
✓ Branch 1 taken 1 times.
|
900 | } |
| 1292 | } | ||
| 1293 | 796 | } | |
| 1294 | |||
| 1295 | 1955 | std::map<std::string, std::string> result; | |
| 1296 |
2/2✓ Branch 0 taken 849 times.
✓ Branch 1 taken 1955 times.
|
2804 | for (auto& [address, ranges] : byAddress) { |
| 1297 |
1/2✓ Branch 0 taken 849 times.
✗ Branch 1 not taken.
|
849 | std::sort(ranges.begin(), ranges.end()); |
| 1298 | // merge contiguous/overlapping ranges so the reported string is clean | ||
| 1299 | 849 | std::vector<std::pair<uint32_t, uint32_t>> merged; | |
| 1300 |
2/2✓ Branch 0 taken 899 times.
✓ Branch 1 taken 849 times.
|
1748 | for (const auto& r : ranges) { |
| 1301 |
6/6✓ Branch 0 taken 50 times.
✓ Branch 1 taken 849 times.
✓ Branch 2 taken 39 times.
✓ Branch 3 taken 11 times.
✓ Branch 4 taken 39 times.
✓ Branch 5 taken 860 times.
|
899 | if (!merged.empty() && r.first <= (merged.back().first + merged.back().second)) { |
| 1302 | 39 | uint32_t end = std::max(merged.back().first + merged.back().second, r.first + r.second); | |
| 1303 | 39 | merged.back().second = end - merged.back().first; | |
| 1304 | } else { | ||
| 1305 |
1/2✓ Branch 0 taken 860 times.
✗ Branch 1 not taken.
|
860 | merged.push_back(r); |
| 1306 | } | ||
| 1307 | } | ||
| 1308 |
2/4✓ Branch 0 taken 849 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 849 times.
✗ Branch 3 not taken.
|
849 | std::string rangeStr = createRanges(merged, 999999); |
| 1309 |
2/4✓ Branch 0 taken 849 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 849 times.
✗ Branch 3 not taken.
|
849 | result[address] = rangeStr; |
| 1310 |
1/2✓ Branch 0 taken 849 times.
✗ Branch 1 not taken.
|
849 | std::string ip = address; |
| 1311 |
7/8✓ Branch 0 taken 849 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 843 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 841 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 847 times.
|
849 | if (GetIPForHost(ip) && ip != address) { |
| 1312 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | result[ip] = rangeStr; |
| 1313 | } | ||
| 1314 | 849 | } | |
| 1315 | 1955 | return result; | |
| 1316 | 1955 | } | |
| 1317 | |||
| 1318 | 1955 | void MultiSync::ReloadConfiguredOutputRanges() { | |
| 1319 | // Build the map outside the lock: GetConfiguredOutputRanges() reads | ||
| 1320 | // co-universes.json and may run blocking DNS lookups to resolve configured | ||
| 1321 | // output hostnames. Then swap it in under the lock that guards GetSystems(). | ||
| 1322 | // This runs once at startup and again only when co-universes.json changes. | ||
| 1323 |
1/2✓ Branch 0 taken 1955 times.
✗ Branch 1 not taken.
|
1955 | std::map<std::string, std::string> ranges = GetConfiguredOutputRanges(); |
| 1324 |
1/2✓ Branch 0 taken 1955 times.
✗ Branch 1 not taken.
|
1955 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 1325 | 1955 | m_configuredOutputRanges = std::move(ranges); | |
| 1326 | 1955 | } | |
| 1327 | |||
| 1328 | 20341 | Json::Value MultiSync::GetSystems(bool localOnly, bool timestamps) { | |
| 1329 |
1/2✓ Branch 0 taken 20341 times.
✗ Branch 1 not taken.
|
20341 | Json::Value result; |
| 1330 |
1/2✓ Branch 0 taken 20341 times.
✗ Branch 1 not taken.
|
20341 | Json::Value systems(Json::arrayValue); |
| 1331 | |||
| 1332 |
1/2✓ Branch 0 taken 20341 times.
✗ Branch 1 not taken.
|
20341 | auto ranges = GetOutputRangesSnapshot(true); |
| 1333 |
2/4✓ Branch 0 taken 20341 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20341 times.
✗ Branch 3 not taken.
|
20341 | std::string range = createRanges(*ranges, 999999); |
| 1334 | |||
| 1335 |
1/2✓ Branch 0 taken 20341 times.
✗ Branch 1 not taken.
|
20341 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 1336 |
2/2✓ Branch 0 taken 35993 times.
✓ Branch 1 taken 20341 times.
|
56334 | for (auto& sys : m_localSystems) { |
| 1337 |
1/2✓ Branch 0 taken 35993 times.
✗ Branch 1 not taken.
|
35993 | sys.ranges = range; |
| 1338 |
2/4✓ Branch 0 taken 35993 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35993 times.
✗ Branch 3 not taken.
|
35993 | systems.append(sys.toJSON(true, timestamps)); |
| 1339 | } | ||
| 1340 |
2/2✓ Branch 0 taken 16307 times.
✓ Branch 1 taken 4034 times.
|
20341 | if (!localOnly) { |
| 1341 | // m_configuredOutputRanges is rebuilt only when co-universes.json changes | ||
| 1342 | // (see ReloadConfiguredOutputRanges()), so this is a plain in-memory map | ||
| 1343 | // lookup -- no per-call file read and, crucially, no blocking DNS. (This | ||
| 1344 | // endpoint is polled frequently by the UI and is also invoked indirectly | ||
| 1345 | // by the config backup generated on every settings change, so the old | ||
| 1346 | // resolve-per-remote-per-call cost was very visible.) We only backfill a | ||
| 1347 | // range for remotes that didn't advertise one of their own, and we do it | ||
| 1348 | // in the emitted JSON rather than mutating the stored system so a later | ||
| 1349 | // co-universes.json change is still reflected. | ||
| 1350 |
2/2✓ Branch 0 taken 39681 times.
✓ Branch 1 taken 16307 times.
|
55988 | for (auto& sys : m_remoteSystems) { |
| 1351 |
1/2✓ Branch 0 taken 39681 times.
✗ Branch 1 not taken.
|
39681 | Json::Value sysJson = sys.toJSON(false, timestamps); |
| 1352 |
9/10✓ Branch 0 taken 12258 times.
✓ Branch 1 taken 27423 times.
✓ Branch 2 taken 12258 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 114 times.
✓ Branch 5 taken 12144 times.
✓ Branch 6 taken 14 times.
✓ Branch 7 taken 27523 times.
✓ Branch 8 taken 14 times.
✓ Branch 9 taken 39667 times.
|
39681 | if ((sys.ranges.empty() || sys.ranges == "0-0") && !m_configuredOutputRanges.empty()) { |
| 1353 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | auto it = m_configuredOutputRanges.find(sys.address); |
| 1354 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
|
14 | if (it == m_configuredOutputRanges.end()) { |
| 1355 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | it = m_configuredOutputRanges.find(sys.hostname); |
| 1356 | } | ||
| 1357 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
|
14 | if (it != m_configuredOutputRanges.end()) { |
| 1358 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | sysJson["channelRanges"] = it->second; |
| 1359 | } | ||
| 1360 | } | ||
| 1361 |
1/2✓ Branch 0 taken 39681 times.
✗ Branch 1 not taken.
|
39681 | systems.append(sysJson); |
| 1362 | 39681 | } | |
| 1363 | } | ||
| 1364 |
2/4✓ Branch 0 taken 20341 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20341 times.
✗ Branch 3 not taken.
|
20341 | result["systems"] = systems; |
| 1365 | 20341 | return result; | |
| 1366 | 20341 | } | |
| 1367 | |||
| 1368 | // The packet counters are bumped as packets are processed, on the main loop, | ||
| 1369 | // without m_statsLock (only the lookup of a sender's stats takes it), while | ||
| 1370 | // GetSyncStats() reads them and ResetSyncStats() zeroes them from API threads. | ||
| 1371 | // So every access is a relaxed atomic one; MultiSyncStats keeps plain fields, | ||
| 1372 | // as MultiSync.h is a plugin-facing header. | ||
| 1373 | 11360 | static inline void BumpStat(uint32_t& c) { | |
| 1374 | 11360 | __atomic_fetch_add(&c, 1, __ATOMIC_RELAXED); | |
| 1375 | 11360 | } | |
| 1376 | |||
| 1377 | 89 | Json::Value MultiSync::GetSyncStats() { | |
| 1378 |
1/2✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
|
89 | Json::Value result; |
| 1379 |
1/2✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
|
89 | Json::Value systems(Json::arrayValue); |
| 1380 | |||
| 1381 |
1/2✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
|
89 | std::unique_lock<std::recursive_mutex> slock(m_statsLock); |
| 1382 |
2/2✓ Branch 0 taken 2130 times.
✓ Branch 1 taken 89 times.
|
2219 | for (auto& a : m_syncStats) { |
| 1383 | 2130 | MultiSyncStats* stats = (MultiSyncStats*)a.second; | |
| 1384 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | systems.append(stats->toJSON()); |
| 1385 | } | ||
| 1386 | // m_syncMaster is rewritten from the sync-receive path on the main loop, so | ||
| 1387 | // it is only ever touched under m_statsLock. Copy it out here and use the | ||
| 1388 | // copy below: m_statsLock and m_systemsLock are never held together. | ||
| 1389 |
1/2✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
|
89 | std::string syncMaster = m_syncMaster; |
| 1390 |
1/2✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
|
89 | slock.unlock(); |
| 1391 | |||
| 1392 |
2/4✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 89 times.
✗ Branch 3 not taken.
|
89 | result["systems"] = systems; |
| 1393 | |||
| 1394 | 89 | std::string masterHostname; | |
| 1395 | |||
| 1396 |
1/2✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
|
89 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 1397 |
2/2✓ Branch 0 taken 462 times.
✓ Branch 1 taken 89 times.
|
551 | for (auto& sys : m_remoteSystems) { |
| 1398 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 462 times.
|
462 | if (sys.address == syncMaster) |
| 1399 | ✗ | masterHostname = sys.hostname; | |
| 1400 | } | ||
| 1401 |
1/2✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
|
89 | lock.unlock(); |
| 1402 | |||
| 1403 |
2/4✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 89 times.
✗ Branch 3 not taken.
|
89 | result["masterIP"] = syncMaster; |
| 1404 |
2/4✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 89 times.
✗ Branch 3 not taken.
|
89 | result["masterHostname"] = masterHostname; |
| 1405 | |||
| 1406 | 89 | return result; | |
| 1407 | 89 | } | |
| 1408 | |||
| 1409 | 2 | void MultiSync::ResetSyncStats() { | |
| 1410 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::unique_lock<std::recursive_mutex> slock(m_statsLock); |
| 1411 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | for (auto& a : m_syncStats) { |
| 1412 | 2 | MultiSyncStats* stats = (MultiSyncStats*)a.second; | |
| 1413 | 2 | __atomic_store_n(&stats->pktCommand, 0, __ATOMIC_RELAXED); | |
| 1414 | 2 | __atomic_store_n(&stats->pktSyncSeqOpen, 0, __ATOMIC_RELAXED); | |
| 1415 | 2 | __atomic_store_n(&stats->pktSyncSeqStart, 0, __ATOMIC_RELAXED); | |
| 1416 | 2 | __atomic_store_n(&stats->pktSyncSeqStop, 0, __ATOMIC_RELAXED); | |
| 1417 | 2 | __atomic_store_n(&stats->pktSyncSeqSync, 0, __ATOMIC_RELAXED); | |
| 1418 | 2 | __atomic_store_n(&stats->pktSyncMedOpen, 0, __ATOMIC_RELAXED); | |
| 1419 | 2 | __atomic_store_n(&stats->pktSyncMedStart, 0, __ATOMIC_RELAXED); | |
| 1420 | 2 | __atomic_store_n(&stats->pktSyncMedStop, 0, __ATOMIC_RELAXED); | |
| 1421 | 2 | __atomic_store_n(&stats->pktSyncMedSync, 0, __ATOMIC_RELAXED); | |
| 1422 | 2 | __atomic_store_n(&stats->pktBlank, 0, __ATOMIC_RELAXED); | |
| 1423 | 2 | __atomic_store_n(&stats->pktPing, 0, __ATOMIC_RELAXED); | |
| 1424 | 2 | __atomic_store_n(&stats->pktPlugin, 0, __ATOMIC_RELAXED); | |
| 1425 | 2 | __atomic_store_n(&stats->pktFPPCommand, 0, __ATOMIC_RELAXED); | |
| 1426 | 2 | __atomic_store_n(&stats->pktError, 0, __ATOMIC_RELAXED); | |
| 1427 | } | ||
| 1428 | 2 | } | |
| 1429 | |||
| 1430 | 1885 | void MultiSync::Discover() { | |
| 1431 | 1885 | Ping(1); | |
| 1432 | 1885 | PerformHTTPDiscovery(); | |
| 1433 | 1885 | PerformTwinklyDiscovery(); | |
| 1434 | 1885 | } | |
| 1435 | |||
| 1436 | 1885 | void MultiSync::PerformHTTPDiscovery() { | |
| 1437 |
1/2✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
|
1885 | std::string subnetsStr = getSetting("MultiSyncHTTPSubnets"); |
| 1438 | |||
| 1439 |
6/10✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1885 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1885 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1885 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 729 times.
✓ Branch 9 taken 1156 times.
|
3770 | if (FileExists(FPP_DIR_CONFIG("/co-universes.json"))) { |
| 1440 |
4/8✓ Branch 0 taken 729 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 729 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 729 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 729 times.
✗ Branch 7 not taken.
|
729 | Json::Value outputs = LoadJsonFromFile(FPP_DIR_CONFIG("/co-universes.json"), JsonRoot::Object); |
| 1441 |
1/2✓ Branch 0 taken 729 times.
✗ Branch 1 not taken.
|
729 | const Json::Value& cos = outputs["channelOutputs"]; |
| 1442 |
8/10✓ Branch 0 taken 1455 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1454 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1454 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 726 times.
✓ Branch 7 taken 728 times.
✓ Branch 8 taken 726 times.
✓ Branch 9 taken 729 times.
|
1455 | for (Json::ArrayIndex co = 0; cos.isArray() && co < cos.size(); co++) { |
| 1443 |
8/16✓ Branch 0 taken 726 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 726 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 726 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 726 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 726 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 726 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 726 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 726 times.
|
726 | if (!cos[co].isObject() || !cos[co]["universes"].isArray()) { |
| 1444 | ✗ | continue; | |
| 1445 | } | ||
| 1446 |
2/4✓ Branch 0 taken 726 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 726 times.
✗ Branch 3 not taken.
|
726 | const Json::Value& universes = cos[co]["universes"]; |
| 1447 |
3/4✓ Branch 0 taken 1538 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 812 times.
✓ Branch 3 taken 726 times.
|
1538 | for (Json::ArrayIndex i = 0; i < universes.size(); i++) { |
| 1448 | // shape-checked: this runs at startup, and a wrong type threw | ||
| 1449 |
11/16✓ Branch 0 taken 812 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 812 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 811 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 811 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 811 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 811 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 810 times.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 810 times.
✓ Branch 15 taken 2 times.
|
812 | if (universes[i].isObject() && universes[i]["address"].isString()) { |
| 1450 |
3/4✓ Branch 0 taken 810 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 84 times.
✓ Branch 3 taken 726 times.
|
810 | if (subnetsStr != "") { |
| 1451 |
1/2✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
|
84 | subnetsStr += ","; |
| 1452 | } | ||
| 1453 |
4/8✓ Branch 0 taken 810 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 810 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 810 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 810 times.
✗ Branch 7 not taken.
|
810 | subnetsStr += universes[i]["address"].asString(); |
| 1454 | } | ||
| 1455 | } | ||
| 1456 | } | ||
| 1457 | 729 | } | |
| 1458 | |||
| 1459 |
3/4✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 746 times.
✓ Branch 3 taken 1139 times.
|
1885 | if (subnetsStr != "") { |
| 1460 |
1/2✓ Branch 0 taken 746 times.
✗ Branch 1 not taken.
|
746 | std::vector<std::string> tokens = split(subnetsStr, ','); |
| 1461 | 746 | std::set<std::string> subnets; | |
| 1462 | 746 | std::set<std::string> exacts; | |
| 1463 | // This runs at startup: an unbounded "10.0.0.0/8" (16.7M addresses, | ||
| 1464 | // held twice) or "[1-100000000]" ran fppd out of memory on every | ||
| 1465 | // start. A /16 is the most a scan may cover, octet ranges stay | ||
| 1466 | // within 0..255, and the total is capped. | ||
| 1467 | static constexpr size_t MAX_DISCOVERY_ADDRESSES = 65536; | ||
| 1468 | 746 | bool truncated = false; | |
| 1469 | 488 | auto addAddress = [&subnets, &truncated](const std::string& a) { | |
| 1470 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 488 times.
|
488 | if (subnets.size() >= MAX_DISCOVERY_ADDRESSES) { |
| 1471 | ✗ | truncated = true; | |
| 1472 | ✗ | return false; | |
| 1473 | } | ||
| 1474 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
488 | subnets.insert(a); |
| 1475 | 488 | return true; | |
| 1476 | 746 | }; | |
| 1477 |
3/4✓ Branch 0 taken 864 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 864 times.
✓ Branch 3 taken 746 times.
|
1610 | for (std::string token : tokens) { |
| 1478 |
1/2✓ Branch 0 taken 864 times.
✗ Branch 1 not taken.
|
864 | TrimWhiteSpace(token); |
| 1479 |
2/4✓ Branch 0 taken 864 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 864 times.
✗ Branch 3 not taken.
|
864 | if (token != "") { |
| 1480 | 864 | size_t pos = token.find('['); | |
| 1481 | 864 | size_t posSlash = token.find('/'); | |
| 1482 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 862 times.
|
864 | if (pos != std::string::npos) { |
| 1483 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::string rng = token.substr(pos + 1); |
| 1484 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | token = token.substr(0, pos); |
| 1485 | 2 | size_t pos2 = rng.find(']'); | |
| 1486 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::string postfix = ""; |
| 1487 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (pos2 != (rng.size() - 1)) { |
| 1488 | ✗ | postfix = rng.substr(pos2 + 1); | |
| 1489 | } | ||
| 1490 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::vector<std::string> r1 = split(rng, ','); |
| 1491 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | for (const auto& a : r1) { |
| 1492 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::vector<std::string> r = split(a, '-'); |
| 1493 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (r.empty()) { |
| 1494 | ✗ | continue; | |
| 1495 | } | ||
| 1496 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | int start = std::clamp(atoi(r[0].c_str()), 0, 255); |
| 1497 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | int end = r.size() == 2 ? std::clamp(atoi(r[1].c_str()), 0, 255) : start; |
| 1498 |
2/2✓ Branch 0 taken 306 times.
✓ Branch 1 taken 2 times.
|
308 | for (int x = start; x <= end; x++) { |
| 1499 |
4/8✓ Branch 0 taken 306 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 306 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 306 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 306 times.
|
306 | if (!addAddress(token + std::to_string(x) + postfix)) { |
| 1500 | ✗ | break; | |
| 1501 | } | ||
| 1502 | } | ||
| 1503 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | } |
| 1504 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 861 times.
|
864 | } else if (posSlash != std::string::npos) { |
| 1505 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | std::vector<std::string> parts = split(token, '/'); |
| 1506 | 1 | int prefix = 32; | |
| 1507 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (parts.size() == 2) { |
| 1508 | 1 | prefix = atoi(parts[1].c_str()); | |
| 1509 | } | ||
| 1510 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | if (prefix < 16 || prefix > 32) { |
| 1511 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogWarn(VB_SYNC, "MultiSync HTTP discovery: ignoring %s, only /16 to /32 can be scanned\n", |
| 1512 | token.c_str()); | ||
| 1513 | 1 | continue; | |
| 1514 | } | ||
| 1515 | ✗ | int ips = 1 << (32 - prefix); | |
| 1516 | ✗ | unsigned long firstIP = htonl(inet_addr(parts[0].c_str())); | |
| 1517 | // If prefix is a /31 or /32, we scan all IPs, otherwise skip network | ||
| 1518 | // and broadcast | ||
| 1519 | ✗ | if (prefix <= 30) { | |
| 1520 | ✗ | ips -= 2; | |
| 1521 | ✗ | firstIP++; | |
| 1522 | } | ||
| 1523 | struct in_addr ia; | ||
| 1524 | char ip[16]; | ||
| 1525 | ✗ | for (int i = 0; i < ips; i++) { | |
| 1526 | ✗ | ia.s_addr = ntohl(firstIP + i); | |
| 1527 | ✗ | inet_ntop(AF_INET, &ia, ip, sizeof(ip)); | |
| 1528 | ✗ | if (!addAddress(ip)) { | |
| 1529 | ✗ | break; | |
| 1530 | } | ||
| 1531 | } | ||
| 1532 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | } else { |
| 1533 |
1/2✓ Branch 0 taken 861 times.
✗ Branch 1 not taken.
|
861 | std::string address2 = token; |
| 1534 |
1/2✓ Branch 0 taken 861 times.
✗ Branch 1 not taken.
|
861 | GetIPForHost(address2); |
| 1535 |
7/10✓ Branch 0 taken 861 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 182 times.
✓ Branch 3 taken 679 times.
✓ Branch 4 taken 182 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 182 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 182 times.
✓ Branch 9 taken 679 times.
|
861 | if (isSupportedForMultisync(address2.c_str(), "") && addAddress(token)) { |
| 1536 |
1/2✓ Branch 0 taken 182 times.
✗ Branch 1 not taken.
|
182 | exacts.insert(token); |
| 1537 | } | ||
| 1538 | 861 | } | |
| 1539 | } | ||
| 1540 |
2/2✓ Branch 0 taken 863 times.
✓ Branch 1 taken 1 times.
|
864 | } |
| 1541 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 746 times.
|
746 | if (truncated) { |
| 1542 | ✗ | LogWarn(VB_SYNC, "MultiSync HTTP discovery: more than %zu addresses configured, scanning only the first %zu\n", | |
| 1543 | MAX_DISCOVERY_ADDRESSES, MAX_DISCOVERY_ADDRESSES); | ||
| 1544 | } | ||
| 1545 |
2/2✓ Branch 0 taken 101 times.
✓ Branch 1 taken 645 times.
|
746 | if (!subnets.empty()) { |
| 1546 |
1/2✓ Branch 0 taken 101 times.
✗ Branch 1 not taken.
|
101 | DiscoverViaHTTP(subnets, exacts); |
| 1547 | } | ||
| 1548 | 746 | } | |
| 1549 | 1885 | } | |
| 1550 | |||
| 1551 | // Twinkly's own discovery protocol: a short datagram to UDP 5555, to which every | ||
| 1552 | // light on the segment replies with its address and name. One broadcast | ||
| 1553 | // enumerates all of them, with none of the per-address cost of an HTTP sweep - | ||
| 1554 | // which matters here because they cannot be found any other way. They publish | ||
| 1555 | // nothing over mDNS or SSDP and their embedded server answers "/" with a 404, so | ||
| 1556 | // a light that is not already a configured channel output is otherwise invisible. | ||
| 1557 | // | ||
| 1558 | // Nothing here (or in the detector this hands off to) authenticates. That is | ||
| 1559 | // deliberate: a Twinkly keeps exactly ONE verified token, so logging in would | ||
| 1560 | // invalidate the token a running Twinkly channel output is streaming with. | ||
| 1561 | #define TWINKLY_DISCOVERY_PORT 5555 | ||
| 1562 | static const char TWINKLY_DISCOVERY_MSG[] = { 0x01, 'd', 'i', 's', 'c', 'o', 'v', 'e', 'r' }; | ||
| 1563 | // Replies are taken only this long after a discovery broadcast, and from at | ||
| 1564 | // most this many addresses per broadcast. The socket stays open for the life | ||
| 1565 | // of fppd and its port is in every broadcast, so without them any host on the | ||
| 1566 | // LAN could grow the seen set and the probe queue by one entry per source | ||
| 1567 | // address it sent from, for as long as fppd ran. The lights answer within | ||
| 1568 | // milliseconds. | ||
| 1569 | #define TWINKLY_REPLY_WINDOW_MS 5000 | ||
| 1570 | #define TWINKLY_MAX_REPLIES 128 | ||
| 1571 | static std::atomic<long long> s_twinklyRepliesUntilMS{ 0 }; | ||
| 1572 | |||
| 1573 | 1885 | bool MultiSync::OpenTwinklyDiscoverySocket() { | |
| 1574 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1885 times.
|
1885 | if (m_twinklyDiscoverySock >= 0) { |
| 1575 | ✗ | return true; | |
| 1576 | } | ||
| 1577 | 1885 | int sock = OpenSocket(AF_INET, SOCK_DGRAM, 0); | |
| 1578 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1885 times.
|
1885 | if (sock < 0) { |
| 1579 | ✗ | LogWarn(VB_SYNC, "Could not open Twinkly discovery socket: %s\n", FPPstrerror(errno)); | |
| 1580 | ✗ | return false; | |
| 1581 | } | ||
| 1582 | 1885 | int broadcast = 1; | |
| 1583 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1885 times.
|
1885 | if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)) < 0) { |
| 1584 | ✗ | LogWarn(VB_SYNC, "Could not set SO_BROADCAST for Twinkly discovery: %s\n", FPPstrerror(errno)); | |
| 1585 | ✗ | close(sock); | |
| 1586 | ✗ | return false; | |
| 1587 | } | ||
| 1588 | 1885 | m_twinklyDiscoverySock = sock; | |
| 1589 | 3805 | std::function<bool(int)> f = [this](int i) { | |
| 1590 | 35 | return ProcessTwinklyDiscoveryReplies(); | |
| 1591 | 1885 | }; | |
| 1592 |
1/2✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
|
1885 | EPollManager::INSTANCE.addFileDescriptor(m_twinklyDiscoverySock, f); |
| 1593 | 1885 | return true; | |
| 1594 | 1885 | } | |
| 1595 | |||
| 1596 | 1885 | void MultiSync::PerformTwinklyDiscovery() { | |
| 1597 |
2/4✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1885 times.
|
1885 | if (!OpenTwinklyDiscoverySocket()) { |
| 1598 | ✗ | return; | |
| 1599 | } | ||
| 1600 | { | ||
| 1601 |
1/2✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
|
1885 | std::unique_lock<std::mutex> lock(m_httpProbeLock); |
| 1602 | 1885 | m_twinklyDiscoverySeen.clear(); | |
| 1603 | 1885 | } | |
| 1604 |
1/2✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
|
1885 | s_twinklyRepliesUntilMS = GetMonotonicTimeMS() + TWINKLY_REPLY_WINDOW_MS; |
| 1605 | |||
| 1606 | // Under m_socketLock, which the sync sends need every frame: never block. | ||
| 1607 |
1/2✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
|
1885 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 1608 |
2/2✓ Branch 0 taken 1802 times.
✓ Branch 1 taken 1885 times.
|
3687 | for (auto& a : m_interfaces) { |
| 1609 | struct sockaddr_in bda; | ||
| 1610 | 1802 | memset((void*)&bda, 0, sizeof(struct sockaddr_in)); | |
| 1611 | 1802 | bda.sin_family = AF_INET; | |
| 1612 | 1802 | bda.sin_port = htons(TWINKLY_DISCOVERY_PORT); | |
| 1613 | 1802 | bda.sin_addr.s_addr = a.second.broadcastAddress; | |
| 1614 | |||
| 1615 |
1/2✓ Branch 0 taken 1802 times.
✗ Branch 1 not taken.
|
1802 | if (sendto(m_twinklyDiscoverySock, TWINKLY_DISCOVERY_MSG, sizeof(TWINKLY_DISCOVERY_MSG), MSG_DONTWAIT, |
| 1616 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1802 times.
|
1802 | (struct sockaddr*)&bda, sizeof(struct sockaddr_in)) < 0) { |
| 1617 | ✗ | LogDebug(VB_SYNC, "Could not send Twinkly discovery on %s: %s\n", | |
| 1618 | a.second.interfaceName.c_str(), FPPstrerror(errno)); | ||
| 1619 | } | ||
| 1620 | } | ||
| 1621 | 1885 | } | |
| 1622 | |||
| 1623 | // A reply is the device's own address (4 bytes, least significant first), then | ||
| 1624 | // "OK", then its name. The source address of the datagram is what gets probed | ||
| 1625 | // rather than that embedded copy: it is the address that actually answered, and | ||
| 1626 | // so the one we have to talk to. The payload is only inspected far enough to | ||
| 1627 | // be reasonably sure this is a light rather than something else listening on | ||
| 1628 | // 5555 -- the HTTP detector is what confirms it. | ||
| 1629 | // | ||
| 1630 | // Always returns false: a true return from an EPollManager callback is fppd's | ||
| 1631 | // signal that bridge data is ready to push out, which this is not. | ||
| 1632 | 35 | bool MultiSync::ProcessTwinklyDiscoveryReplies() { | |
| 1633 | char buf[256]; | ||
| 1634 | // a bounded number of replies a wakeup, like the other sockets the main | ||
| 1635 | // loop reads; the socket is level-triggered, so the rest come next pass | ||
| 1636 |
1/2✓ Branch 0 taken 379 times.
✗ Branch 1 not taken.
|
379 | for (int n = 0; n < 1024; n++) { |
| 1637 | struct sockaddr_in src; | ||
| 1638 | 379 | memset(&src, 0, sizeof(src)); | |
| 1639 | 379 | socklen_t slen = sizeof(src); | |
| 1640 |
1/2✓ Branch 0 taken 379 times.
✗ Branch 1 not taken.
|
379 | ssize_t len = recvfrom(m_twinklyDiscoverySock, buf, sizeof(buf), MSG_DONTWAIT, |
| 1641 | (struct sockaddr*)&src, &slen); | ||
| 1642 |
2/2✓ Branch 0 taken 35 times.
✓ Branch 1 taken 344 times.
|
379 | if (len <= 0) { |
| 1643 | 35 | return false; | |
| 1644 | } | ||
| 1645 |
8/12✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 344 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 344 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 344 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 20 times.
✓ Branch 9 taken 324 times.
✓ Branch 10 taken 20 times.
✓ Branch 11 taken 324 times.
|
344 | if (len < 7 || buf[4] != 'O' || buf[5] != 'K' || GetMonotonicTimeMS() > s_twinklyRepliesUntilMS) { |
| 1646 | 192 | continue; | |
| 1647 | } | ||
| 1648 | 324 | char ipStr[INET_ADDRSTRLEN] = { 0 }; | |
| 1649 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 324 times.
|
324 | if (!inet_ntop(AF_INET, &src.sin_addr, ipStr, sizeof(ipStr))) { |
| 1650 | ✗ | continue; | |
| 1651 | } | ||
| 1652 |
1/2✓ Branch 0 taken 324 times.
✗ Branch 1 not taken.
|
324 | std::string address(ipStr); |
| 1653 | { | ||
| 1654 |
1/2✓ Branch 0 taken 324 times.
✗ Branch 1 not taken.
|
324 | std::unique_lock<std::mutex> lock(m_httpProbeLock); |
| 1655 |
2/2✓ Branch 0 taken 172 times.
✓ Branch 1 taken 152 times.
|
324 | if (m_twinklyDiscoverySeen.size() >= TWINKLY_MAX_REPLIES) { |
| 1656 | 172 | continue; | |
| 1657 | } | ||
| 1658 |
2/4✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 152 times.
|
152 | if (!m_twinklyDiscoverySeen.insert(address).second) { |
| 1659 | // the lights answer each broadcast more than once | ||
| 1660 | ✗ | continue; | |
| 1661 | } | ||
| 1662 |
2/2✓ Branch 0 taken 152 times.
✓ Branch 1 taken 172 times.
|
324 | } |
| 1663 |
1/2✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
|
152 | LogInfo(VB_SYNC, "Twinkly device answered discovery at %s\n", address.c_str()); |
| 1664 | // Through the queue, under the in-flight cap. Started directly, one | ||
| 1665 | // per answering address, a burst of replies -- or of datagrams | ||
| 1666 | // claiming to be replies, from as many addresses -- started that many | ||
| 1667 | // HTTP probes at once. | ||
| 1668 | { | ||
| 1669 |
1/2✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
|
152 | std::unique_lock<std::mutex> lock(m_httpProbeLock); |
| 1670 |
1/2✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
|
152 | QueueHTTPPing(m_httpPingQueue, address); |
| 1671 | 152 | } | |
| 1672 |
1/2✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
|
152 | PumpHTTPPings(); |
| 1673 |
2/2✓ Branch 0 taken 152 times.
✓ Branch 1 taken 172 times.
|
324 | } |
| 1674 | ✗ | return false; | |
| 1675 | } | ||
| 1676 | |||
| 1677 | 115 | void MultiSync::DiscoverIPViaHTTP(const std::string& ip, const std::string& html, bool allowUnknown) { | |
| 1678 |
1/2✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
|
115 | LogDebug(VB_SYNC, "Checking HTTP response from %s\n", ip.c_str()); |
| 1679 | |||
| 1680 |
1/2✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
|
115 | if (html.size()) { |
| 1681 |
1/2✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
|
115 | std::string d = html; |
| 1682 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 114 times.
|
115 | if (d.size() > 500) { |
| 1683 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | d = d.substr(0, 500); |
| 1684 | } | ||
| 1685 |
1/2✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
|
115 | LogExcess(VB_SYNC, "IP: %s Resp: %s\n", ip.c_str(), d.c_str()); |
| 1686 | 115 | } | |
| 1687 | |||
| 1688 |
1/2✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
|
115 | std::string address2 = ip; |
| 1689 |
1/2✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
|
115 | GetIPForHost(address2); |
| 1690 | |||
| 1691 |
5/10✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 115 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 115 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 115 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 115 times.
|
115 | if (!isSupportedForMultisync(ip.c_str(), "") || !isSupportedForMultisync(address2.c_str(), "")) { |
| 1692 | ✗ | if (allowUnknown) { | |
| 1693 | ✗ | UpdateSystem(kSysTypeUnknown, 0, 0, UNKNOWN_MODE, ip, ip, "Unknown", "Unknown", "0-0", "Unknown", false, false); | |
| 1694 | } | ||
| 1695 | ✗ | FinishHTTPDiscovery(); | |
| 1696 | ✗ | return; | |
| 1697 | } | ||
| 1698 | |||
| 1699 |
3/8✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 115 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 115 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
115 | NetworkController::DetectControllerViaHTML(ip, html, [this, ip, allowUnknown](NetworkController* nc) { |
| 1700 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 68 times.
|
108 | if (nc) { |
| 1701 | 40 | UpdateSystem(nc->typeId, nc->majorVersion, nc->minorVersion, | |
| 1702 | 40 | nc->systemMode, nc->ip, nc->hostname, nc->version, | |
| 1703 | 40 | nc->typeStr, nc->ranges, nc->uuid, false, nc->sendingMultiSync); | |
| 1704 | 40 | ApplyPeerUUIDs(nc->peerUUIDs); | |
| 1705 |
1/2✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
|
40 | delete nc; |
| 1706 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 50 times.
|
68 | } else if (allowUnknown) { |
| 1707 |
5/10✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 18 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 18 times.
✗ Branch 9 not taken.
|
162 | UpdateSystem(kSysTypeUnknown, 0, 0, UNKNOWN_MODE, ip, ip, "Unknown", "Unknown", "0-0", "Unknown", false, false); |
| 1708 | } | ||
| 1709 | 108 | FinishHTTPDiscovery(); | |
| 1710 | 108 | }); | |
| 1711 |
1/2✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
|
115 | } |
| 1712 | |||
| 1713 | // How many addresses in the configured HTTP scan are probed at once. The | ||
| 1714 | // version this replaced put every address of every configured subnet on one | ||
| 1715 | // curl multi handle in a single burst and blocked until the last one answered; | ||
| 1716 | // a /24 is 254 sockets, and several subnets are common. That is now spread out, | ||
| 1717 | // because it shares the multi handle with the rest of fppd rather than owning a | ||
| 1718 | // private one -- but it can still be wide, since almost every address in a scan | ||
| 1719 | // has nothing on it and simply burns the connect timeout. | ||
| 1720 | #define HTTP_DISCOVERY_MAX_IN_FLIGHT 48 | ||
| 1721 | |||
| 1722 | // The discovery probe cannot be a plain addGet(): it has to keep the options | ||
| 1723 | // the hand-rolled multi handle set. HTTP09_ALLOWED is the load-bearing one -- | ||
| 1724 | // some of the older controllers this is trying to identify answer with a bare | ||
| 1725 | // HTTP/0.9 body, which curl rejects by default. The timeouts are far tighter | ||
| 1726 | // than CurlManager's defaults for the reason above: most addresses are dead. | ||
| 1727 | 354 | static void discoveryGet(const std::string& url, std::function<void(int rc, const std::string& resp)>&& callback) { | |
| 1728 | 354 | CURL* curl = CurlManager::INSTANCE.createCurl(url); | |
| 1729 | // a device merely on the LAN can stream an endless body; detection only | ||
| 1730 | // ever looks at the start of a page, so a cut-off one is still fine | ||
| 1731 | 354 | CurlManager::limitResponseSize(curl); | |
| 1732 | 354 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 1000L); | |
| 1733 | 354 | curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 5000L); | |
| 1734 | 354 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); | |
| 1735 | 354 | curl_easy_setopt(curl, CURLOPT_TCP_FASTOPEN, 1L); | |
| 1736 | 354 | curl_easy_setopt(curl, CURLOPT_HTTP09_ALLOWED, 1L); | |
| 1737 |
4/8✓ Branch 0 taken 354 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 354 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 354 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 354 times.
✗ Branch 7 not taken.
|
354 | CurlManager::INSTANCE.addCURL(url, curl, [callback](CURL* c) { |
| 1738 | 291 | CurlManager::CurlPrivateData* data = nullptr; | |
| 1739 | 291 | long rc = 0; | |
| 1740 |
1/2✓ Branch 0 taken 291 times.
✗ Branch 1 not taken.
|
291 | curl_easy_getinfo(c, CURLINFO_PRIVATE, &data); |
| 1741 |
1/2✓ Branch 0 taken 291 times.
✗ Branch 1 not taken.
|
291 | curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &rc); |
| 1742 | 291 | std::string resp; | |
| 1743 |
5/6✓ Branch 0 taken 291 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 115 times.
✓ Branch 3 taken 176 times.
✓ Branch 4 taken 115 times.
✓ Branch 5 taken 176 times.
|
291 | if (data && !data->resp.empty()) { |
| 1744 |
1/2✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
|
115 | resp.assign(reinterpret_cast<char*>(data->resp.data()), data->resp.size()); |
| 1745 | } | ||
| 1746 |
1/2✓ Branch 0 taken 291 times.
✗ Branch 1 not taken.
|
291 | callback((int)rc, resp); |
| 1747 | 291 | }); | |
| 1748 | 354 | } | |
| 1749 | |||
| 1750 | 101 | void MultiSync::DiscoverViaHTTP(const std::set<std::string>& ipSet, const std::set<std::string>& exacts) { | |
| 1751 | { | ||
| 1752 |
1/2✓ Branch 0 taken 101 times.
✗ Branch 1 not taken.
|
101 | std::unique_lock<std::mutex> lock(m_httpProbeLock); |
| 1753 |
2/2✓ Branch 0 taken 465 times.
✓ Branch 1 taken 101 times.
|
566 | for (auto& ip : ipSet) { |
| 1754 |
1/2✓ Branch 0 taken 465 times.
✗ Branch 1 not taken.
|
465 | LogExcess(VB_SYNC, " %s\n", ip.c_str()); |
| 1755 |
2/4✓ Branch 0 taken 465 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 465 times.
✗ Branch 3 not taken.
|
465 | m_httpDiscoveryQueue.emplace_back(ip, exacts.find(ip) != exacts.end()); |
| 1756 | } | ||
| 1757 | 101 | } | |
| 1758 | 101 | PumpHTTPDiscovery(); | |
| 1759 | 101 | } | |
| 1760 | |||
| 1761 | // Starts scan probes until the cap is reached or the queue runs dry. Called | ||
| 1762 | // from DiscoverViaHTTP() and again from each completion. | ||
| 1763 | 739 | void MultiSync::PumpHTTPDiscovery() { | |
| 1764 | while (true) { | ||
| 1765 | 739 | std::string ip; | |
| 1766 | 739 | bool exact = false; | |
| 1767 | { | ||
| 1768 |
1/2✓ Branch 0 taken 739 times.
✗ Branch 1 not taken.
|
739 | std::unique_lock<std::mutex> lock(m_httpProbeLock); |
| 1769 |
6/6✓ Branch 0 taken 454 times.
✓ Branch 1 taken 285 times.
✓ Branch 2 taken 100 times.
✓ Branch 3 taken 354 times.
✓ Branch 4 taken 385 times.
✓ Branch 5 taken 354 times.
|
739 | if (m_httpDiscoveryQueue.empty() || m_httpDiscoveriesInFlight >= HTTP_DISCOVERY_MAX_IN_FLIGHT) { |
| 1770 | 770 | return; | |
| 1771 | } | ||
| 1772 |
1/2✓ Branch 0 taken 354 times.
✗ Branch 1 not taken.
|
354 | std::tie(ip, exact) = m_httpDiscoveryQueue.front(); |
| 1773 | 354 | m_httpDiscoveryQueue.pop_front(); | |
| 1774 | 354 | ++m_httpDiscoveriesInFlight; | |
| 1775 |
2/2✓ Branch 0 taken 354 times.
✓ Branch 1 taken 385 times.
|
739 | } |
| 1776 | |||
| 1777 | // ip may be a hostname, so this must not be a fixed-size buffer | ||
| 1778 | // (a fixed buffer would silently truncate hostnames and they'd | ||
| 1779 | // never get discovered - see issue #2667) | ||
| 1780 |
5/12✓ Branch 0 taken 354 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 354 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 354 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 354 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 354 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
1062 | discoveryGet(buildHttpURL(ip, "/"), [this, ip, exact](int rc, const std::string& resp) { |
| 1781 | // A 200 is the normal answer. rc == 0 with a body is the HTTP/0.9 | ||
| 1782 | // case -- there is no status line to report -- and is equally good; | ||
| 1783 | // rc == 0 with nothing is a transfer that never connected. This is | ||
| 1784 | // the same accept/reject the CURLcode-based version made. | ||
| 1785 | // | ||
| 1786 | // An `exact` address is one that was configured rather than swept | ||
| 1787 | // for - a channel output's destination, or an entry the user typed - | ||
| 1788 | // so anything that answered at all is worth identifying. Twinkly | ||
| 1789 | // lights need that: they have no web UI and serve a 404 at "/", and | ||
| 1790 | // only a follow-up call to their own API says what they are. The | ||
| 1791 | // broad sweep stays 200-only, so a subnet full of unrelated devices | ||
| 1792 | // that 404 does not drag the whole detector chain through each one. | ||
| 1793 |
11/12✓ Branch 0 taken 185 times.
✓ Branch 1 taken 106 times.
✓ Branch 2 taken 176 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 176 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 89 times.
✓ Branch 7 taken 96 times.
✓ Branch 8 taken 9 times.
✓ Branch 9 taken 80 times.
✓ Branch 10 taken 115 times.
✓ Branch 11 taken 176 times.
|
291 | if (rc == 200 || (rc == 0 && !resp.empty()) || (exact && !resp.empty())) { |
| 1794 | 115 | LogDebug(VB_SYNC, "IP %s completed with code %d\n", ip.c_str(), rc); | |
| 1795 | 115 | DiscoverIPViaHTTP(ip, resp, exact); | |
| 1796 | 115 | return; // DiscoverIPViaHTTP() releases the slot | |
| 1797 | } | ||
| 1798 | 176 | LogDebug(VB_SYNC, "No/Error response from %s. Response code: %d\n", ip.c_str(), rc); | |
| 1799 | 176 | FinishHTTPDiscovery(); | |
| 1800 | }); | ||
| 1801 |
2/2✓ Branch 0 taken 354 times.
✓ Branch 1 taken 385 times.
|
1093 | } |
| 1802 | } | ||
| 1803 | |||
| 1804 | 284 | void MultiSync::FinishHTTPDiscovery() { | |
| 1805 | { | ||
| 1806 |
1/2✓ Branch 0 taken 284 times.
✗ Branch 1 not taken.
|
284 | std::unique_lock<std::mutex> lock(m_httpProbeLock); |
| 1807 | 284 | --m_httpDiscoveriesInFlight; | |
| 1808 | 284 | } | |
| 1809 | 284 | PumpHTTPDiscovery(); | |
| 1810 | 284 | } | |
| 1811 | |||
| 1812 | 4034 | void MultiSync::WriteRuntimeInfoFile() { | |
| 1813 |
1/2✓ Branch 0 taken 4034 times.
✗ Branch 1 not taken.
|
4034 | Json::Value v = GetSystems(true, false); |
| 1814 |
2/4✓ Branch 0 taken 4034 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4034 times.
✗ Branch 3 not taken.
|
4034 | std::string filename = FPP_DIR_MEDIA("/fpp-info.json"); |
| 1815 |
2/4✓ Branch 0 taken 4034 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4034 times.
✗ Branch 3 not taken.
|
4034 | Json::Value systems = v["systems"]; |
| 1816 | |||
| 1817 | // If there is no network, then delete the file | ||
| 1818 | // to avoid confusion. | ||
| 1819 |
3/4✓ Branch 0 taken 4034 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 363 times.
✓ Branch 3 taken 3671 times.
|
4034 | if (systems.empty()) { |
| 1820 | // remove() returns 0 on success; a file that was never written is | ||
| 1821 | // not a failure. | ||
| 1822 |
3/6✓ Branch 0 taken 363 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 363 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 363 times.
|
363 | if (remove(filename.c_str()) != 0 && errno != ENOENT) { |
| 1823 | ✗ | LogWarn(VB_ALL, "Failed to remove file %s: %s\n", filename.c_str(), FPPstrerror(errno)); | |
| 1824 | } | ||
| 1825 |
1/2✓ Branch 0 taken 363 times.
✗ Branch 1 not taken.
|
363 | LogInfo(VB_ALL, "Not creating %s as no IP addresses found\n", filename.c_str()); |
| 1826 | 363 | return; | |
| 1827 | } | ||
| 1828 | |||
| 1829 |
1/2✓ Branch 0 taken 3671 times.
✗ Branch 1 not taken.
|
3671 | std::string addresses = ""; |
| 1830 |
3/4✓ Branch 0 taken 7509 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3838 times.
✓ Branch 3 taken 3671 times.
|
7509 | for (int x = 0; x < systems.size(); x++) { |
| 1831 |
3/4✓ Branch 0 taken 3838 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 167 times.
✓ Branch 3 taken 3671 times.
|
3838 | if (addresses != "") { |
| 1832 |
1/2✓ Branch 0 taken 167 times.
✗ Branch 1 not taken.
|
167 | addresses += ","; |
| 1833 | } | ||
| 1834 |
4/8✓ Branch 0 taken 3838 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3838 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3838 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3838 times.
✗ Branch 7 not taken.
|
3838 | addresses += systems[x]["address"].asString(); |
| 1835 | } | ||
| 1836 |
2/4✓ Branch 0 taken 3671 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3671 times.
✗ Branch 3 not taken.
|
3671 | Json::Value local = systems[0]; |
| 1837 |
1/2✓ Branch 0 taken 3671 times.
✗ Branch 1 not taken.
|
3671 | local.removeMember("address"); |
| 1838 |
2/4✓ Branch 0 taken 3671 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3671 times.
✗ Branch 3 not taken.
|
3671 | local["addresses"] = addresses; |
| 1839 | |||
| 1840 |
2/4✓ Branch 0 taken 3671 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3671 times.
✗ Branch 3 not taken.
|
3671 | SaveJsonToFile(local, filename); |
| 1841 |
6/6✓ Branch 0 taken 3671 times.
✓ Branch 1 taken 363 times.
✓ Branch 2 taken 3671 times.
✓ Branch 3 taken 363 times.
✓ Branch 4 taken 3671 times.
✓ Branch 5 taken 363 times.
|
4760 | } |
| 1842 | |||
| 1843 | /* | ||
| 1844 | * | ||
| 1845 | */ | ||
| 1846 | 4264 | void MultiSync::Ping(int discover, bool broadcast) { | |
| 1847 |
1/2✓ Branch 0 taken 4264 times.
✗ Branch 1 not taken.
|
4264 | LogDebug(VB_SYNC, "MultiSync::Ping(%d, %d)\n", discover, broadcast); |
| 1848 | 4264 | time_t t = time(NULL); | |
| 1849 | 4264 | m_lastPingTime = (unsigned long)t; | |
| 1850 | |||
| 1851 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4264 times.
|
4264 | if (m_broadcastSock < 0) { |
| 1852 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send ping packet but control socket is not open.\n"); | |
| 1853 | ✗ | return; | |
| 1854 | } | ||
| 1855 |
1/2✓ Branch 0 taken 4264 times.
✗ Branch 1 not taken.
|
4264 | setupMulticastReceive(discover ? true : false); |
| 1856 | |||
| 1857 | // update the range for local systems so it's accurate | ||
| 1858 |
1/2✓ Branch 0 taken 4264 times.
✗ Branch 1 not taken.
|
4264 | auto ranges = GetOutputRangesSnapshot(true); |
| 1859 |
2/4✓ Branch 0 taken 4264 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4264 times.
✗ Branch 3 not taken.
|
4264 | std::string range = createRanges(*ranges, 120); |
| 1860 | |||
| 1861 | // Build every packet under m_systemsLock and send once it is released. | ||
| 1862 | // Dropping the lock mid-loop instead would leave the loop iterating | ||
| 1863 | // m_localSystems while a network change (FillLocalSystemInfo) rewrites it. | ||
| 1864 | 4264 | std::vector<std::vector<char>> packets; | |
| 1865 | { | ||
| 1866 |
1/2✓ Branch 0 taken 4264 times.
✗ Branch 1 not taken.
|
4264 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 1867 |
2/2✓ Branch 0 taken 4256 times.
✓ Branch 1 taken 4264 times.
|
8520 | for (auto& sys : m_localSystems) { |
| 1868 |
1/2✓ Branch 0 taken 4256 times.
✗ Branch 1 not taken.
|
4256 | std::vector<char> outBuf(768, 0); |
| 1869 |
1/2✓ Branch 0 taken 4256 times.
✗ Branch 1 not taken.
|
4256 | sys.ranges = range; |
| 1870 |
2/4✓ Branch 0 taken 4256 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4256 times.
✗ Branch 3 not taken.
|
4256 | outBuf.resize(CreatePingPacket(sys, outBuf.data(), discover)); |
| 1871 |
1/2✓ Branch 0 taken 4256 times.
✗ Branch 1 not taken.
|
4256 | packets.push_back(std::move(outBuf)); |
| 1872 | 4256 | } | |
| 1873 | 4264 | } | |
| 1874 |
2/2✓ Branch 0 taken 4256 times.
✓ Branch 1 taken 4264 times.
|
8520 | for (auto& outBuf : packets) { |
| 1875 |
2/2✓ Branch 0 taken 3867 times.
✓ Branch 1 taken 389 times.
|
4256 | if (broadcast) { |
| 1876 |
1/2✓ Branch 0 taken 3867 times.
✗ Branch 1 not taken.
|
3867 | SendBroadcastPacket(outBuf.data(), outBuf.size()); |
| 1877 | } else { | ||
| 1878 |
1/2✓ Branch 0 taken 389 times.
✗ Branch 1 not taken.
|
389 | SendMulticastPacket(outBuf.data(), outBuf.size()); |
| 1879 | } | ||
| 1880 | } | ||
| 1881 | |||
| 1882 |
2/2✓ Branch 0 taken 1885 times.
✓ Branch 1 taken 2379 times.
|
4264 | if (discover) { |
| 1883 | 1885 | std::set<std::string> remotes; | |
| 1884 |
1/2✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
|
1885 | AddRemotesFromSetting("MultiSyncExtraRemotes", remotes); |
| 1885 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1885 times.
|
1897 | for (auto& remote : remotes) { |
| 1886 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | PingSingleRemote(remote.c_str(), 1); |
| 1887 | } | ||
| 1888 | 1885 | } | |
| 1889 | 4264 | } | |
| 1890 | 25736 | void MultiSync::PeriodicPing() { | |
| 1891 | 25736 | time_t t = time(NULL); | |
| 1892 |
2/2✓ Branch 0 taken 1194 times.
✓ Branch 1 taken 24542 times.
|
25736 | if (m_lastCheckTime == 0) { |
| 1893 | 1194 | m_lastCheckTime = (unsigned long)t; | |
| 1894 | } | ||
| 1895 | 25736 | unsigned long lpt = m_lastPingTime + 60 * 60; | |
| 1896 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 25724 times.
|
25736 | if (lpt < (unsigned long)t) { |
| 1897 | // once an hour, we'll send a ping letting everyone know we're still here | ||
| 1898 | // mark ourselves as seen | ||
| 1899 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 1900 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 12 times.
|
25 | for (auto& sys : m_localSystems) { |
| 1901 | 13 | sys.lastSeen = (unsigned long)t; | |
| 1902 | } | ||
| 1903 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | lock.unlock(); |
| 1904 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | Ping(); |
| 1905 | 12 | } | |
| 1906 | // every minute we'll loop through real quick and check for remote instances | ||
| 1907 | // we haven't heard from in a while | ||
| 1908 | 25736 | lpt = m_lastCheckTime + 60; | |
| 1909 | 25736 | bool superLongGap = false; | |
| 1910 |
2/2✓ Branch 0 taken 51 times.
✓ Branch 1 taken 25685 times.
|
25736 | if (lpt < (unsigned long)t) { |
| 1911 | 51 | m_lastCheckTime = (unsigned long)t; | |
| 1912 | // anything we haven't heard from in 80 minutes we will re-ping to force | ||
| 1913 | 51 | unsigned long timeoutRePing = (unsigned long)t - 60 * 80; | |
| 1914 | // anything we haven't heard from in 2 hours we will remove. That would | ||
| 1915 | // have caused at least 4 pings to have been sent. If it has responded | ||
| 1916 | // to any of those 4, it's got to be down/gone. Remove it. | ||
| 1917 | 51 | unsigned long timeoutRemove = (unsigned long)t - 60 * 120; | |
| 1918 | // if we hadn't heard from them in 10 hours, it's likely a clock change | ||
| 1919 | // event, we'll resend a ping out to all to hopefully get everything | ||
| 1920 | // updated and new timestamps | ||
| 1921 | 51 | unsigned long timeoutRePingAll = (unsigned long)t - 60 * 600; | |
| 1922 |
1/2✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
|
51 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 1923 | 51 | bool unicastChanged = false; | |
| 1924 | // Collect the addresses (by value) and queue the HTTP probes after the | ||
| 1925 | // loop rather than from inside it. The probes are asynchronous now, but | ||
| 1926 | // the reason for the two-step is unchanged: a probe that resolves fast | ||
| 1927 | // reaches UpdateSystem, which can push_back onto m_remoteSystems (the | ||
| 1928 | // recursive mutex does not protect against our own thread) and | ||
| 1929 | // reallocate the vector out from under `it`. | ||
| 1930 | 51 | std::vector<std::string> httpPingAddresses; | |
| 1931 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 51 times.
|
59 | for (auto it = m_remoteSystems.begin(); it != m_remoteSystems.end();) { |
| 1932 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (it->lastSeen < timeoutRemove) { |
| 1933 | ✗ | LogInfo(VB_SYNC, "Have not seen %s in over 2 hours, removing\n", it->address.c_str()); | |
| 1934 | ✗ | if (it->lastSeen < timeoutRePingAll) { | |
| 1935 | ✗ | superLongGap = true; | |
| 1936 | } | ||
| 1937 | ✗ | unicastChanged |= it->supportsUnicast; | |
| 1938 | ✗ | it = m_remoteSystems.erase(it); | |
| 1939 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
|
8 | } else if (it->lastSeen < timeoutRePing) { |
| 1940 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (it->multiSync) { |
| 1941 | ✗ | PingSingleRemote(*it, 1); | |
| 1942 | } else { | ||
| 1943 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | httpPingAddresses.push_back(it->address); |
| 1944 | } | ||
| 1945 | 6 | ++it; | |
| 1946 | } else { | ||
| 1947 | 2 | ++it; | |
| 1948 | } | ||
| 1949 | } | ||
| 1950 | // Drop any removed remotes from the cached unicast destination list. | ||
| 1951 | // Snapshot the surviving targets under m_systemsLock, rebuild after | ||
| 1952 | // releasing it -- m_unicastUpdateLock is never acquired while | ||
| 1953 | // m_systemsLock is held. | ||
| 1954 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
51 | bool rebuildUnicast = m_sendUnicast && unicastChanged; |
| 1955 | 51 | std::vector<std::string> unicastAddrs; | |
| 1956 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (rebuildUnicast) { |
| 1957 | ✗ | for (auto& sys : m_remoteSystems) { | |
| 1958 | ✗ | if (sys.supportsUnicast) { | |
| 1959 | ✗ | unicastAddrs.push_back(sys.address); | |
| 1960 | } | ||
| 1961 | } | ||
| 1962 | } | ||
| 1963 |
1/2✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
|
51 | lock.unlock(); |
| 1964 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (rebuildUnicast) { |
| 1965 | ✗ | UpdateUnicastDestinations(unicastAddrs); | |
| 1966 | } | ||
| 1967 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 50 times.
|
51 | if (!httpPingAddresses.empty()) { |
| 1968 | // HTTP probes of remotes we haven't heard from in a while. These | ||
| 1969 | // used to be blocking curl calls run on a detached thread, because | ||
| 1970 | // doing them inline on the fppd main loop -- the same thread that | ||
| 1971 | // drains the MultiSync control socket -- stalled sync-packet | ||
| 1972 | // processing for the length of the timeouts, which on a remote | ||
| 1973 | // showed up as the output freezing for a second or two and then | ||
| 1974 | // jumping to catch up. They now go through CurlManager, so there is | ||
| 1975 | // nothing to stall and no thread to keep off the loop. | ||
| 1976 | { | ||
| 1977 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | std::unique_lock<std::mutex> plock(m_httpProbeLock); |
| 1978 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
|
7 | for (auto& address : httpPingAddresses) { |
| 1979 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | QueueHTTPPing(m_httpPingQueue, address); |
| 1980 | } | ||
| 1981 | 1 | } | |
| 1982 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | PumpHTTPPings(); |
| 1983 | } | ||
| 1984 | 51 | } | |
| 1985 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 25736 times.
|
25736 | if (superLongGap) { |
| 1986 | ✗ | Ping(1); | |
| 1987 | } | ||
| 1988 | |||
| 1989 | 25736 | CheckSystemInfoRefreshes(); | |
| 1990 | 25736 | } | |
| 1991 | |||
| 1992 | // How often a remote's HTTP-fetched detail is refreshed, how soon a failed | ||
| 1993 | // attempt is retried, and how many fetches may be outstanding at once. The | ||
| 1994 | // cap matters on a large show network: without it the first sweep after | ||
| 1995 | // discovery would open a connection to every remote at the same moment, from a | ||
| 1996 | // player that may be a single-core BeagleBone. The rest are simply picked up | ||
| 1997 | // by a later sweep. | ||
| 1998 | #define INFO_REFRESH_INTERVAL (30 * 60) | ||
| 1999 | #define INFO_RETRY_INTERVAL (5 * 60) | ||
| 2000 | #define INFO_SCAN_INTERVAL 5 | ||
| 2001 | #define INFO_MAX_IN_FLIGHT 4 | ||
| 2002 | |||
| 2003 | // Every fetch is delayed by a random slice of a window that grows with the size | ||
| 2004 | // of the known fleet. INFO_MAX_IN_FLIGHT bounds what one player sends, but the | ||
| 2005 | // dangerous direction is the other one: every peer sees the same discover ping, | ||
| 2006 | // so on a 100-instance show a single fppd restart would otherwise land ~200 HTTP | ||
| 2007 | // requests on the box that just came up -- the box least able to serve them, and | ||
| 2008 | // on a BeagleBone that is a real stall. A whole-show power-on is the same | ||
| 2009 | // pattern squared, with every box both hammering and being hammered. Budgeting | ||
| 2010 | // roughly one second of window per known system holds the aggregate arrival rate | ||
| 2011 | // at the target near two requests a second no matter how large the show is. | ||
| 2012 | #define INFO_JITTER_BASE 5 | ||
| 2013 | #define INFO_JITTER_MAX 300 | ||
| 2014 | |||
| 2015 | // The multisync page needs a fair amount of slow-changing detail about every | ||
| 2016 | // remote -- OS version, host description, background color, git branch/commit, | ||
| 2017 | // whether channel inputs/outputs are enabled, what cape is installed. It used | ||
| 2018 | // to fetch all of it from the browser, at least one HTTP request per remote per | ||
| 2019 | // page load, which is why the table visibly reflowed as the answers trickled | ||
| 2020 | // in. fppd already knows every remote and can collect it once per remote per | ||
| 2021 | // INFO_REFRESH_INTERVAL over the async CurlManager, so the UI gets it in the | ||
| 2022 | // very first GetSystems() response. | ||
| 2023 | // | ||
| 2024 | // Runs on the fppd main loop (from PeriodicPing). It must stay non-blocking: | ||
| 2025 | // the same loop feeds the sync output, which is why every fetch goes through | ||
| 2026 | // CurlManager. | ||
| 2027 | 25736 | void MultiSync::CheckSystemInfoRefreshes() { | |
| 2028 | 25736 | time_t now = time(nullptr); | |
| 2029 |
2/2✓ Branch 0 taken 23123 times.
✓ Branch 1 taken 2613 times.
|
25736 | if (now < m_nextInfoScan) { |
| 2030 | 23123 | return; | |
| 2031 | } | ||
| 2032 | 2613 | m_nextInfoScan = now + INFO_SCAN_INTERVAL; | |
| 2033 | |||
| 2034 | 2613 | std::vector<std::string> toFetch; | |
| 2035 | { | ||
| 2036 |
1/2✓ Branch 0 taken 2613 times.
✗ Branch 1 not taken.
|
2613 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 2037 | // We routinely discover ourselves as a "remote" -- over loopback, and | ||
| 2038 | // over any address that isn't one of the interface addresses in | ||
| 2039 | // m_localSystems (a global IPv6 address, typically). Those entries | ||
| 2040 | // merge into the local row in the UI, which already has everything | ||
| 2041 | // about the box serving the page, so probing them is pure waste. | ||
| 2042 | 2613 | std::set<std::string> localUUIDs; | |
| 2043 |
2/2✓ Branch 0 taken 2377 times.
✓ Branch 1 taken 2613 times.
|
4990 | for (auto& sys : m_localSystems) { |
| 2044 |
4/8✓ Branch 0 taken 2377 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2377 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2377 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2377 times.
|
2377 | if (!sys.uuid.empty() && sys.uuid != "Unknown") { |
| 2045 | ✗ | localUUIDs.insert(sys.uuid); | |
| 2046 | } | ||
| 2047 | } | ||
| 2048 |
2/2✓ Branch 0 taken 1307 times.
✓ Branch 1 taken 2613 times.
|
3920 | for (size_t idx = 0; idx < m_remoteSystems.size(); ++idx) { |
| 2049 | 1307 | MultiSyncSystem& sys = m_remoteSystems[idx]; | |
| 2050 | // Only full FPP instances: these are FPP's own API endpoints, and | ||
| 2051 | // anything at or above kSysTypeFalconController is a third-party | ||
| 2052 | // controller that would just 404 (or worse, be confused by) them. | ||
| 2053 |
4/4✓ Branch 0 taken 950 times.
✓ Branch 1 taken 357 times.
✓ Branch 2 taken 55 times.
✓ Branch 3 taken 895 times.
|
1307 | if (sys.type == kSysTypeUnknown || sys.type >= kSysTypeFalconController) { |
| 2054 | 1304 | continue; | |
| 2055 | } | ||
| 2056 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 895 times.
|
895 | if (sys.infoFetchPending) { |
| 2057 | ✗ | continue; | |
| 2058 | } | ||
| 2059 |
2/4✓ Branch 0 taken 895 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 895 times.
|
895 | if (localUUIDs.find(sys.uuid) != localUUIDs.end()) { |
| 2060 | ✗ | continue; | |
| 2061 | } | ||
| 2062 | // Link-local addresses are reachable from here but not from a | ||
| 2063 | // browser (the IPv6 zone id is specific to this host), which is why | ||
| 2064 | // the multisync page drops those rows. Loopback never reaches this | ||
| 2065 | // list at all -- UpdateSystem() rejects it. | ||
| 2066 |
15/30✓ Branch 0 taken 895 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 895 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 894 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 894 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 894 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 894 times.
✓ Branch 12 taken 894 times.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 894 times.
✓ Branch 15 taken 1 times.
✓ Branch 16 taken 895 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 895 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 1 times.
✓ Branch 21 taken 894 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
4473 | if (startsWith(sys.address, "169.254.") || startsWith(sys.address, "fe80:")) { |
| 2067 | 1 | continue; | |
| 2068 | } | ||
| 2069 | // A device with more than one NIC has one entry per address. Act | ||
| 2070 | // on it once and stamp every sibling entry the same way, otherwise | ||
| 2071 | // the next sweep would pick the siblings up separately; | ||
| 2072 | // ForEachRemoteMatching() fans the single answer back out to all of | ||
| 2073 | // them. | ||
| 2074 |
1/2✓ Branch 0 taken 894 times.
✗ Branch 1 not taken.
|
894 | std::string uuid = sys.uuid; |
| 2075 |
2/4✓ Branch 0 taken 894 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 894 times.
|
894 | if (uuid == "Unknown") { |
| 2076 | ✗ | uuid.clear(); | |
| 2077 | } | ||
| 2078 |
1/2✓ Branch 0 taken 894 times.
✗ Branch 1 not taken.
|
894 | std::string address = sys.address; |
| 2079 | 892 | auto stampSiblings = [&](const std::function<void(MultiSyncSystem&)>& apply) { | |
| 2080 |
2/2✓ Branch 0 taken 599862 times.
✓ Branch 1 taken 892 times.
|
600754 | for (auto& other : m_remoteSystems) { |
| 2081 |
8/8✓ Branch 0 taken 598970 times.
✓ Branch 1 taken 892 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 598962 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 893 times.
✓ Branch 7 taken 598969 times.
|
599862 | if (other.address == address || (!uuid.empty() && other.uuid == uuid)) { |
| 2082 |
1/2✓ Branch 0 taken 893 times.
✗ Branch 1 not taken.
|
893 | apply(other); |
| 2083 | } | ||
| 2084 | } | ||
| 2085 | 892 | }; | |
| 2086 | |||
| 2087 | // infoNextFetch == 0 means due but unscheduled: a remote we have | ||
| 2088 | // just discovered, or one InvalidateSystemInfo() marked stale after | ||
| 2089 | // its discover ping. A changed version string means the remote was | ||
| 2090 | // upgraded under us, which rewrites most of what we cached -- and on | ||
| 2091 | // a fleet upgrade that fires everywhere at once, so it earns the | ||
| 2092 | // same treatment. Either way, pick a jittered time and come back | ||
| 2093 | // for it on a later sweep instead of fetching right now. | ||
| 2094 |
5/6✓ Branch 0 taken 5 times.
✓ Branch 1 taken 889 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 889 times.
✓ Branch 5 taken 5 times.
|
894 | if (sys.infoNextFetch == 0 || sys.infoFetchedVersion != sys.version) { |
| 2095 | // Entry count, not device count: a multi-NIC box contributes | ||
| 2096 | // several, as do non-FPP controllers. That only ever widens | ||
| 2097 | // the window, which is the safe direction. | ||
| 2098 | 889 | int window = INFO_JITTER_BASE + (int)m_remoteSystems.size(); | |
| 2099 |
2/2✓ Branch 0 taken 768 times.
✓ Branch 1 taken 121 times.
|
889 | if (window > INFO_JITTER_MAX) { |
| 2100 | 768 | window = INFO_JITTER_MAX; | |
| 2101 | } | ||
| 2102 |
1/2✓ Branch 0 taken 889 times.
✗ Branch 1 not taken.
|
889 | time_t when = now + (FPPrand() % window); |
| 2103 |
1/2✓ Branch 0 taken 889 times.
✗ Branch 1 not taken.
|
889 | stampSiblings([&](MultiSyncSystem& other) { |
| 2104 | 890 | other.infoNextFetch = when; | |
| 2105 | 890 | other.infoFetchedVersion = other.version; | |
| 2106 | 890 | }); | |
| 2107 | 889 | continue; | |
| 2108 | 889 | } | |
| 2109 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
|
5 | if (now < sys.infoNextFetch) { |
| 2110 | 2 | continue; | |
| 2111 | } | ||
| 2112 | // Cap only what is actually sent. Scheduling above is free and has | ||
| 2113 | // to keep running even while the cap is reached, or a large fleet | ||
| 2114 | // would never get its jittered times assigned. Anything held back | ||
| 2115 | // here is simply picked up by the next sweep. | ||
| 2116 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if ((int)toFetch.size() + m_infoFetchesInFlight >= INFO_MAX_IN_FLIGHT) { |
| 2117 | ✗ | continue; | |
| 2118 | } | ||
| 2119 | |||
| 2120 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | stampSiblings([&](MultiSyncSystem& other) { |
| 2121 | 3 | other.infoFetchPending = true; | |
| 2122 | 3 | other.infoFetchedVersion = other.version; | |
| 2123 | 3 | }); | |
| 2124 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | toFetch.push_back(address); |
| 2125 |
4/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 891 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 891 times.
|
1785 | } |
| 2126 | 2613 | } | |
| 2127 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2613 times.
|
2616 | for (auto& address : toFetch) { |
| 2128 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | FetchSystemInfo(address); |
| 2129 | } | ||
| 2130 | 2613 | } | |
| 2131 | |||
| 2132 | 64 | void MultiSync::InvalidateSystemInfo(const std::string& address) { | |
| 2133 |
1/2✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
|
64 | ForEachRemoteMatching(address, [](MultiSyncSystem& sys) { |
| 2134 | // Due, but unscheduled: the next sweep gives it a jittered time. Never | ||
| 2135 | // fetch straight from here -- this runs for every peer that heard the | ||
| 2136 | // same discover ping, which is exactly the pile-on being avoided. | ||
| 2137 | 64 | sys.infoNextFetch = 0; | |
| 2138 | 64 | }); | |
| 2139 | 64 | } | |
| 2140 | |||
| 2141 | // One device, one identity. | ||
| 2142 | // | ||
| 2143 | // A box with several addresses gets an entry per address, and a real UUID only | ||
| 2144 | // ever reaches the entry that was actually probed for it -- UpdateSystem() | ||
| 2145 | // matches on the address. The others keep whatever they were given, which | ||
| 2146 | // since MAC-derived identities exist means a stand-in rather than nothing. Two | ||
| 2147 | // different identities for one device is worse than none: the UI keys a row on | ||
| 2148 | // the UUID and draws the device twice, and the statistics count it twice, which | ||
| 2149 | // defeats the deduplication the identity exists to provide. | ||
| 2150 | // | ||
| 2151 | // So once any entry knows a real UUID, every other entry for that device adopts | ||
| 2152 | // it. Guarded the way the UI guards its own hostname fallback: a hostname | ||
| 2153 | // claimed by more than one real UUID is not identifying a device at all -- two | ||
| 2154 | // controllers left on the default name -- so in that case nothing is touched. | ||
| 2155 | // | ||
| 2156 | // This is only about filling in for entries that have no identity of their own. | ||
| 2157 | // A real UUID is never overwritten by another. | ||
| 2158 | // Records identities one device reported for OTHER addresses. Some vendors | ||
| 2159 | // describe their neighbours but not themselves, so this is the only place those | ||
| 2160 | // devices' identities ever come from. | ||
| 2161 | // | ||
| 2162 | // The hints are kept, not applied once and dropped. Discovery reaches | ||
| 2163 | // addresses in whatever order it reaches them, so the neighbour describing a | ||
| 2164 | // device is very often probed BEFORE that device is found -- applying only to | ||
| 2165 | // what exists right now would silently lose exactly the identities that arrive | ||
| 2166 | // early. UpdateSystem() consults the map for every system it touches, so the | ||
| 2167 | // two orders behave the same. | ||
| 2168 | // | ||
| 2169 | // A hint never creates a system: a name in somebody's peer list is not evidence | ||
| 2170 | // that a device is on this network, and a system discovery has not found has no | ||
| 2171 | // business appearing in the list. | ||
| 2172 | 44 | void MultiSync::ApplyPeerUUIDs(const std::map<std::string, std::string>& peerUUIDs) { | |
| 2173 |
2/2✓ Branch 0 taken 43 times.
✓ Branch 1 taken 1 times.
|
44 | if (peerUUIDs.empty()) { |
| 2174 | 43 | return; | |
| 2175 | } | ||
| 2176 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 2177 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
|
4 | for (const auto& [addr, uuid] : peerUUIDs) { |
| 2178 |
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 | if (!addr.empty() && !uuid.empty()) { |
| 2179 | // One device's reply decides how many entries it lists, so a | ||
| 2180 | // device (or anything answering as one) could grow this for good. | ||
| 2181 | // Far more than any network's worth of devices. | ||
| 2182 |
2/8✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
|
3 | if (m_peerUUIDHints.size() >= 4096 && !m_peerUUIDHints.contains(addr)) { |
| 2183 | ✗ | continue; | |
| 2184 | } | ||
| 2185 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | m_peerUUIDHints[addr] = uuid; |
| 2186 | } | ||
| 2187 | } | ||
| 2188 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | for (auto& sys : m_remoteSystems) { |
| 2189 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | ApplyUUIDHint(sys); |
| 2190 | } | ||
| 2191 | 1 | } | |
| 2192 | |||
| 2193 | // Precedence matches MultiSyncSystem::update(): a device's own UUID always | ||
| 2194 | // wins, and a MAC-derived stand-in may only replace another stand-in. | ||
| 2195 | // | ||
| 2196 | // That a hint OVERRIDES an ARP-derived stand-in is deliberate, and it matters: | ||
| 2197 | // the two are not the same value. An ESP32 running on Ethernet uses base+3 as | ||
| 2198 | // its interface address, so ARP sees a MAC three higher than the one the device | ||
| 2199 | // reports for itself (measured on WLED and on two Baldricks; Genius, which | ||
| 2200 | // reports its interface address rather than the base, matches ARP exactly). | ||
| 2201 | // Both are stable identities for the device -- but they are DIFFERENT stable | ||
| 2202 | // identities, and letting a device carry whichever one its observer happened to | ||
| 2203 | // obtain would split it in two, which is the failure this whole path exists to | ||
| 2204 | // remove. So the vendor-reported value wins everywhere it is available, and | ||
| 2205 | // ARP stays the fallback for devices no detector could identify at all. | ||
| 2206 | 4858 | void MultiSync::ApplyUUIDHint(MultiSyncSystem& sys) { | |
| 2207 |
12/18✓ Branch 0 taken 516 times.
✓ Branch 1 taken 4342 times.
✓ Branch 2 taken 516 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 516 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 308 times.
✓ Branch 7 taken 208 times.
✓ Branch 8 taken 516 times.
✓ Branch 9 taken 4342 times.
✓ Branch 10 taken 516 times.
✓ Branch 11 taken 4342 times.
✓ Branch 12 taken 308 times.
✓ Branch 13 taken 4550 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
5890 | if (!sys.uuid.empty() && !startsWith(sys.uuid, MAC_UUID_PREFIX)) { |
| 2208 | 308 | return; | |
| 2209 | } | ||
| 2210 |
1/2✓ Branch 0 taken 4550 times.
✗ Branch 1 not taken.
|
4550 | auto it = m_peerUUIDHints.find(sys.address); |
| 2211 |
5/6✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4549 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 4549 times.
|
4550 | if (it != m_peerUUIDHints.end() && !it->second.empty()) { |
| 2212 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | sys.uuid = it->second; |
| 2213 | } | ||
| 2214 | } | ||
| 2215 | |||
| 2216 | 9089 | void MultiSync::ReconcileDeviceIdentity(const std::string& hostname, FPPMode fppMode) { | |
| 2217 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9089 times.
|
9089 | if (hostname.empty()) { |
| 2218 | 8782 | return; | |
| 2219 | } | ||
| 2220 | 5165 | auto isStandIn = [](const std::string& u) { | |
| 2221 |
10/16✓ Branch 0 taken 825 times.
✓ Branch 1 taken 4340 times.
✓ Branch 2 taken 825 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 825 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 207 times.
✓ Branch 7 taken 618 times.
✓ Branch 8 taken 825 times.
✓ Branch 9 taken 4340 times.
✓ Branch 10 taken 825 times.
✓ Branch 11 taken 4340 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
|
6815 | return u.empty() || startsWith(u, MAC_UUID_PREFIX); |
| 2222 | }; | ||
| 2223 | |||
| 2224 | 9089 | std::string real; | |
| 2225 |
2/2✓ Branch 0 taken 2716529 times.
✓ Branch 1 taken 9087 times.
|
2725616 | for (auto& sys : m_remoteSystems) { |
| 2226 |
9/10✓ Branch 0 taken 4876 times.
✓ Branch 1 taken 2711653 times.
✓ Branch 2 taken 4857 times.
✓ Branch 3 taken 19 times.
✓ Branch 4 taken 4857 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4546 times.
✓ Branch 7 taken 311 times.
✓ Branch 8 taken 2716218 times.
✓ Branch 9 taken 311 times.
|
2716529 | if (sys.hostname != hostname || sys.fppMode != fppMode || isStandIn(sys.uuid)) { |
| 2227 | 2716218 | continue; | |
| 2228 | } | ||
| 2229 |
2/2✓ Branch 0 taken 309 times.
✓ Branch 1 taken 2 times.
|
311 | if (real.empty()) { |
| 2230 |
1/2✓ Branch 0 taken 309 times.
✗ Branch 1 not taken.
|
309 | real = sys.uuid; |
| 2231 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | } else if (real != sys.uuid) { |
| 2232 | 2 | return; // hostname shared by more than one device | |
| 2233 | } | ||
| 2234 | } | ||
| 2235 |
2/2✓ Branch 0 taken 8780 times.
✓ Branch 1 taken 307 times.
|
9087 | if (real.empty()) { |
| 2236 | 8780 | return; | |
| 2237 | } | ||
| 2238 |
2/2✓ Branch 0 taken 1194 times.
✓ Branch 1 taken 307 times.
|
1501 | for (auto& sys : m_remoteSystems) { |
| 2239 |
8/10✓ Branch 0 taken 308 times.
✓ Branch 1 taken 886 times.
✓ Branch 2 taken 308 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 308 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 307 times.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 1193 times.
|
1194 | if (sys.hostname == hostname && sys.fppMode == fppMode && isStandIn(sys.uuid)) { |
| 2240 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | sys.uuid = real; |
| 2241 | } | ||
| 2242 | } | ||
| 2243 |
2/2✓ Branch 0 taken 307 times.
✓ Branch 1 taken 8782 times.
|
9089 | } |
| 2244 | |||
| 2245 | 67 | void MultiSync::ForEachRemoteMatching(const std::string& address, | |
| 2246 | const std::function<void(MultiSyncSystem&)>& apply) { | ||
| 2247 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 2248 | 67 | std::string uuid; | |
| 2249 |
1/2✓ Branch 0 taken 73 times.
✗ Branch 1 not taken.
|
73 | for (auto& sys : m_remoteSystems) { |
| 2250 |
2/2✓ Branch 0 taken 67 times.
✓ Branch 1 taken 6 times.
|
73 | if (sys.address == address) { |
| 2251 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | uuid = sys.uuid; |
| 2252 | 67 | break; | |
| 2253 | } | ||
| 2254 | } | ||
| 2255 |
2/4✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
|
67 | if (uuid == "Unknown") { |
| 2256 | ✗ | uuid.clear(); | |
| 2257 | } | ||
| 2258 |
2/2✓ Branch 0 taken 93 times.
✓ Branch 1 taken 67 times.
|
160 | for (auto& sys : m_remoteSystems) { |
| 2259 |
7/8✓ Branch 0 taken 26 times.
✓ Branch 1 taken 67 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 67 times.
✓ Branch 7 taken 26 times.
|
93 | if (sys.address == address || (!uuid.empty() && sys.uuid == uuid)) { |
| 2260 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | apply(sys); |
| 2261 | } | ||
| 2262 | } | ||
| 2263 | 67 | } | |
| 2264 | |||
| 2265 | 3 | void MultiSync::FetchSystemInfo(const std::string& address) { | |
| 2266 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | std::string url = buildHttpURL(address, "/api/system/info"); |
| 2267 | 3 | ++m_infoFetchesInFlight; | |
| 2268 | // `address` is captured by value on purpose: m_remoteSystems is a vector of | ||
| 2269 | // values that UpdateSystem() push_back()s into, so any pointer or reference | ||
| 2270 | // to an entry can be dangling by the time this callback runs. Everything | ||
| 2271 | // below re-finds the entries by address instead. | ||
| 2272 |
5/10✓ 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.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
|
9 | CurlManager::INSTANCE.addGet(url, [this, address](int rc, const std::string& resp) { |
| 2273 | 2 | --m_infoFetchesInFlight; | |
| 2274 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | Json::Value v; |
| 2275 |
6/8✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
|
2 | if (rc != 200 || !LoadJsonFromString(resp, v, JsonRoot::Object)) { |
| 2276 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogDebug(VB_SYNC, "Could not fetch system info from %s (rc: %d)\n", address.c_str(), rc); |
| 2277 | 1 | time_t retryAt = time(nullptr) + INFO_RETRY_INTERVAL; | |
| 2278 |
1/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | ForEachRemoteMatching(address, [retryAt](MultiSyncSystem& sys) { |
| 2279 | 1 | sys.infoFetchPending = false; | |
| 2280 | // A zero here means InvalidateSystemInfo() ran while this fetch | ||
| 2281 | // was in flight -- the remote restarted mid-request, so what we | ||
| 2282 | // just read may already be wrong. Leave it zero and let the | ||
| 2283 | // sweep schedule a fresh attempt. | ||
| 2284 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (sys.infoNextFetch != 0) { |
| 2285 | 1 | sys.infoNextFetch = retryAt; | |
| 2286 | } | ||
| 2287 | 1 | }); | |
| 2288 | 1 | return; | |
| 2289 | } | ||
| 2290 | |||
| 2291 | 1 | MultiSyncSystemInfo info; | |
| 2292 | 1 | info.valid = true; | |
| 2293 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.platform = ReplyString(v, "Platform"); |
| 2294 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.variant = ReplyString(v, "Variant"); |
| 2295 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.subPlatform = ReplyString(v, "SubPlatform"); |
| 2296 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.osVersion = ReplyString(v, "OSVersion"); |
| 2297 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.osRelease = ReplyString(v, "OSRelease"); |
| 2298 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.kernel = ReplyString(v, "Kernel"); |
| 2299 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.hostDescription = ReplyString(v, "HostDescription"); |
| 2300 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.backgroundColor = ReplyString(v, "backgroundColor"); |
| 2301 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.branch = ReplyString(v, "Branch"); |
| 2302 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.localGitVersion = ReplyString(v, "LocalGitVersion"); |
| 2303 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.remoteGitVersion = ReplyString(v, "RemoteGitVersion"); |
| 2304 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
2 | info.upgradeSource = ReplyString(v, "UpgradeSource"); |
| 2305 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
|
2 | if (JsonHas(v, "channelInputsEnabled")) { |
| 2306 | ✗ | info.channelInputsEnabled = ReplyBool(v, "channelInputsEnabled", false) ? 1 : 0; | |
| 2307 | } | ||
| 2308 |
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.
|
2 | if (JsonHas(v, "channelOutputsEnabled")) { |
| 2309 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
1 | info.channelOutputsEnabled = ReplyBool(v, "channelOutputsEnabled", false) ? 1 : 0; |
| 2310 | } | ||
| 2311 |
9/22✓ 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.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
|
3 | if (JsonHas(v, "IPs") && v["IPs"].isArray()) { |
| 2312 |
8/14✓ 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.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2 times.
✓ Branch 13 taken 1 times.
|
3 | for (const auto& ip : v["IPs"]) { |
| 2313 |
3/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
|
2 | if (ip.isString()) { |
| 2314 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | info.ips.push_back(ip.asString()); |
| 2315 | } | ||
| 2316 | } | ||
| 2317 | } | ||
| 2318 | |||
| 2319 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | ForEachRemoteMatching(address, [&info](MultiSyncSystem& sys) { |
| 2320 | 1 | sys.systemInfo = info; | |
| 2321 | 1 | }); | |
| 2322 | |||
| 2323 | // Chain the cape fetch rather than issuing both at once: it reuses the | ||
| 2324 | // connection just established and halves the peak request count. | ||
| 2325 | // infoFetchPending stays set until it finishes so the sweep above does | ||
| 2326 | // not re-queue this remote in between. | ||
| 2327 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | FetchCapeInfo(address); |
| 2328 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | }); |
| 2329 | 3 | } | |
| 2330 | |||
| 2331 | 1 | void MultiSync::FetchCapeInfo(const std::string& address) { | |
| 2332 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | std::string url = buildHttpURL(address, "/api/cape"); |
| 2333 | 1 | ++m_infoFetchesInFlight; | |
| 2334 |
5/10✓ 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.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
|
3 | CurlManager::INSTANCE.addGet(url, [this, address](int rc, const std::string& resp) { |
| 2335 | 1 | --m_infoFetchesInFlight; | |
| 2336 | 1 | MultiSyncCapeInfo cape; | |
| 2337 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | Json::Value v; |
| 2338 |
4/8✓ 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.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
|
1 | if (rc == 200 && LoadJsonFromString(resp, v, JsonRoot::Object)) { |
| 2339 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | cape = MultiSyncCapeInfo::fromCapeJSON(v); |
| 2340 | ✗ | } else if (rc == 404) { | |
| 2341 | // GetCapeInfo() answers 404 with {"id": "No Cape!"} when nothing is | ||
| 2342 | // installed. Cache that: it is an answer, not a failure. (An FPP | ||
| 2343 | // old enough to predate the route would 404 the same way and be | ||
| 2344 | // recorded as having no cape, which is what the UI shows for it | ||
| 2345 | // anyway.) | ||
| 2346 | ✗ | cape.valid = true; | |
| 2347 | ✗ | cape.present = false; | |
| 2348 | } else { | ||
| 2349 | // Anything else (connection failure, or an FPP old enough not to | ||
| 2350 | // have /api/cape at all) leaves capeInfo unset and untrusted. | ||
| 2351 | ✗ | LogDebug(VB_SYNC, "Could not fetch cape info from %s (rc: %d)\n", address.c_str(), rc); | |
| 2352 | } | ||
| 2353 | |||
| 2354 | // End of the chain, so this is where the next refresh is scheduled -- | ||
| 2355 | // the system info the caller already stored is good either way, so a | ||
| 2356 | // cape that could not be read doesn't earn the shorter retry interval. | ||
| 2357 | 1 | time_t nextAt = time(nullptr) + INFO_REFRESH_INTERVAL; | |
| 2358 |
1/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | ForEachRemoteMatching(address, [&cape, nextAt](MultiSyncSystem& sys) { |
| 2359 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (cape.valid) { |
| 2360 | 1 | sys.capeInfo = cape; | |
| 2361 | } | ||
| 2362 | 1 | sys.infoFetchPending = false; | |
| 2363 | // Zero means the remote sent a discover ping while this fetch was | ||
| 2364 | // in flight, i.e. it restarted mid-request and what we just stored | ||
| 2365 | // may already be stale. Don't paper over that with a 30 minute | ||
| 2366 | // interval; leave it due so the sweep reschedules. | ||
| 2367 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (sys.infoNextFetch != 0) { |
| 2368 | 1 | sys.infoNextFetch = nextAt; | |
| 2369 | } | ||
| 2370 | 1 | }); | |
| 2371 | 1 | }); | |
| 2372 | 1 | } | |
| 2373 | |||
| 2374 | // How many HTTP probes of unresponsive remotes may be outstanding at once. | ||
| 2375 | // Small on purpose: these run against boxes that are quite possibly down, so | ||
| 2376 | // most of them sit there burning the connect timeout, and the player doing the | ||
| 2377 | // probing may be a single-core BeagleBone. | ||
| 2378 | #define HTTP_PING_MAX_IN_FLIGHT 4 | ||
| 2379 | |||
| 2380 | // Starts queued probes until the cap is reached or the queue runs dry. Called | ||
| 2381 | // from PeriodicPing() on the main loop and again from each completion, which may | ||
| 2382 | // be on another thread -- see m_httpProbeLock. | ||
| 2383 | 489 | void MultiSync::PumpHTTPPings() { | |
| 2384 | while (true) { | ||
| 2385 | 489 | std::string address; | |
| 2386 | { | ||
| 2387 |
1/2✓ Branch 0 taken 489 times.
✗ Branch 1 not taken.
|
489 | std::unique_lock<std::mutex> lock(m_httpProbeLock); |
| 2388 |
6/6✓ Branch 0 taken 445 times.
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 288 times.
✓ Branch 3 taken 157 times.
✓ Branch 4 taken 332 times.
✓ Branch 5 taken 157 times.
|
489 | if (m_httpPingQueue.empty() || m_httpPingsInFlight >= HTTP_PING_MAX_IN_FLIGHT) { |
| 2389 | 664 | return; | |
| 2390 | } | ||
| 2391 |
1/2✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
|
157 | address = m_httpPingQueue.front(); |
| 2392 | 157 | m_httpPingQueue.pop_front(); | |
| 2393 | // Claimed here, under the same lock as the cap test, so a second | ||
| 2394 | // pumper cannot read a stale count and overshoot. | ||
| 2395 | 157 | ++m_httpPingsInFlight; | |
| 2396 |
2/2✓ Branch 0 taken 157 times.
✓ Branch 1 taken 332 times.
|
489 | } |
| 2397 |
1/2✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
|
157 | StartHTTPPing(address); |
| 2398 |
2/2✓ Branch 0 taken 157 times.
✓ Branch 1 taken 332 times.
|
646 | } |
| 2399 | } | ||
| 2400 | |||
| 2401 | // Probe one address now, outside the queue. MDNSManager calls this the moment | ||
| 2402 | // it resolves a host, and that probe must not be deferred behind a sweep -- but | ||
| 2403 | // it still has to be counted, or the cap above is measured against a number that | ||
| 2404 | // does not describe reality. (It is the entry point every caller outside this | ||
| 2405 | // file uses, which is why the accounting lives here rather than in the pump.) | ||
| 2406 | 26 | void MultiSync::PingSingleRemoteViaHTTP(const std::string& address) { | |
| 2407 | { | ||
| 2408 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
26 | std::unique_lock<std::mutex> lock(m_httpProbeLock); |
| 2409 | 26 | ++m_httpPingsInFlight; | |
| 2410 | 26 | } | |
| 2411 | 26 | StartHTTPPing(address); | |
| 2412 | 26 | } | |
| 2413 | |||
| 2414 | // Asks whatever is at `address` for its front page and hands the answer to the | ||
| 2415 | // controller detectors. Both halves are asynchronous, so this returns as soon | ||
| 2416 | // as the request is queued. The caller must already have claimed an in-flight | ||
| 2417 | // slot; whichever path below finishes the probe releases it. | ||
| 2418 | 183 | void MultiSync::StartHTTPPing(const std::string& address) { | |
| 2419 |
7/14✓ Branch 0 taken 183 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 183 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 183 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 183 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 183 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 183 times.
✗ Branch 13 not taken.
|
915 | CurlManager::INSTANCE.addGet(buildHttpURL(address), [this, address](int rc, const std::string& resp) { |
| 2420 | // The address may be queued for a re-ping again from here on: the | ||
| 2421 | // answer (or the failure) is in. | ||
| 2422 | { | ||
| 2423 |
1/2✓ Branch 0 taken 179 times.
✗ Branch 1 not taken.
|
179 | std::unique_lock<std::mutex> lock(m_httpProbeLock); |
| 2424 |
1/2✓ Branch 0 taken 179 times.
✗ Branch 1 not taken.
|
179 | s_httpPingPending.erase(address); |
| 2425 | 179 | } | |
| 2426 | // rc == 0 is a transfer that never got an HTTP response at all; any | ||
| 2427 | // status code means something answered, and the old blocking probe | ||
| 2428 | // likewise fed a non-200 body to the detectors rather than dropping it. | ||
| 2429 |
5/6✓ Branch 0 taken 152 times.
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 152 times.
✓ Branch 4 taken 27 times.
✓ Branch 5 taken 152 times.
|
179 | if (rc == 0 || resp.empty()) { |
| 2430 | 27 | FinishHTTPPing(); | |
| 2431 | 27 | return; | |
| 2432 | } | ||
| 2433 |
3/6✓ Branch 0 taken 152 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 152 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 152 times.
✗ Branch 5 not taken.
|
152 | NetworkController::DetectControllerViaHTML(address, resp, [this, address](NetworkController* nc) { |
| 2434 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 148 times.
|
152 | if (nc) { |
| 2435 | 4 | UpdateSystem(nc->typeId, nc->majorVersion, nc->minorVersion, | |
| 2436 | 4 | nc->systemMode, nc->ip, nc->hostname, nc->version, | |
| 2437 | 4 | nc->typeStr, nc->ranges, nc->uuid, false, nc->sendingMultiSync); | |
| 2438 | 4 | ApplyPeerUUIDs(nc->peerUUIDs); | |
| 2439 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | delete nc; |
| 2440 | } else { | ||
| 2441 |
4/10✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 148 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 148 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
1480 | UpdateSystem(kSysTypeUnknown, 0, 0, UNKNOWN_MODE, address, |
| 2442 |
1/2✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
|
148 | address, "Unknown", "Unknown", "0-0", "Unknown", false, false); |
| 2443 | } | ||
| 2444 | 152 | FinishHTTPPing(); | |
| 2445 | 152 | }); | |
| 2446 | }); | ||
| 2447 | 183 | } | |
| 2448 | |||
| 2449 | // Releases the in-flight slot and starts whatever is next. Kept separate | ||
| 2450 | // because the probe can end at either of two points above. | ||
| 2451 | 179 | void MultiSync::FinishHTTPPing() { | |
| 2452 | { | ||
| 2453 |
1/2✓ Branch 0 taken 179 times.
✗ Branch 1 not taken.
|
179 | std::unique_lock<std::mutex> lock(m_httpProbeLock); |
| 2454 | 179 | --m_httpPingsInFlight; | |
| 2455 | 179 | } | |
| 2456 | 179 | PumpHTTPPings(); | |
| 2457 | 179 | } | |
| 2458 | |||
| 2459 | // Answering a discover ping: one broadcast ping plus a unicast one to the | ||
| 2460 | // asker. This used to sleep up to 6ms on the main loop per discover ping | ||
| 2461 | // (so the remotes' answers are spread out) and answer every one, so a stream | ||
| 2462 | // of them stalled output and turned every packet into several. The answer | ||
| 2463 | // now goes out from a one-shot timer a few ms later, and every discover ping | ||
| 2464 | // that arrives while one is pending joins it: one broadcast, plus a unicast to | ||
| 2465 | // each distinct asker (bounded). | ||
| 2466 | static std::mutex s_discoverReplyLock; | ||
| 2467 | static std::set<std::string> s_discoverReplyTargets; | ||
| 2468 | static bool s_discoverReplyArmed = false; | ||
| 2469 | |||
| 2470 | 70 | void MultiSync::QueueDiscoverReply(const std::string& address, const std::string& srcIp) { | |
| 2471 |
1/2✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
|
70 | std::lock_guard<std::mutex> lock(s_discoverReplyLock); |
| 2472 |
2/2✓ Branch 0 taken 140 times.
✓ Branch 1 taken 70 times.
|
210 | for (const std::string* a : { &address, &srcIp }) { |
| 2473 |
3/6✓ Branch 0 taken 140 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 140 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 140 times.
✗ Branch 5 not taken.
|
140 | if (!a->empty() && s_discoverReplyTargets.size() < 64) { |
| 2474 |
1/2✓ Branch 0 taken 140 times.
✗ Branch 1 not taken.
|
140 | s_discoverReplyTargets.insert(*a); |
| 2475 | } | ||
| 2476 | } | ||
| 2477 |
2/2✓ Branch 0 taken 55 times.
✓ Branch 1 taken 15 times.
|
70 | if (s_discoverReplyArmed) { |
| 2478 | 55 | return; | |
| 2479 | } | ||
| 2480 | 15 | s_discoverReplyArmed = true; | |
| 2481 | // a slight random delay so all the remotes don't answer at the same time | ||
| 2482 |
4/8✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 15 times.
✗ Branch 7 not taken.
|
45 | Timers::INSTANCE.addTimer("MultiSyncDiscoverReply", GetTimeMS() + 1 + FPPrand() % 6, [this]() { |
| 2483 | 15 | std::set<std::string> targets; | |
| 2484 | { | ||
| 2485 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | std::lock_guard<std::mutex> lock(s_discoverReplyLock); |
| 2486 | 15 | targets.swap(s_discoverReplyTargets); | |
| 2487 | 15 | s_discoverReplyArmed = false; | |
| 2488 | 15 | } | |
| 2489 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | Ping(); |
| 2490 |
2/2✓ Branch 0 taken 36 times.
✓ Branch 1 taken 15 times.
|
51 | for (const auto& t : targets) { |
| 2491 |
1/2✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
|
36 | PingSingleRemote(t.c_str(), 0); |
| 2492 | } | ||
| 2493 | 15 | }); | |
| 2494 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 55 times.
|
70 | } |
| 2495 | |||
| 2496 | 74 | void MultiSync::PingSingleRemote(const char* address, int discover) { | |
| 2497 | 74 | MultiSyncSystem sys; | |
| 2498 | |||
| 2499 |
1/2✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
|
74 | sys.address = address; |
| 2500 | |||
| 2501 |
1/2✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
|
74 | PingSingleRemote(sys, discover); |
| 2502 | 74 | } | |
| 2503 | |||
| 2504 | 74 | void MultiSync::PingSingleRemote(MultiSyncSystem& sys, int discover) { | |
| 2505 | char outBuf[512]; | ||
| 2506 | 74 | memset(outBuf, 0, sizeof(outBuf)); | |
| 2507 | int len; | ||
| 2508 | { | ||
| 2509 |
1/2✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
|
74 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 2510 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
|
74 | if (m_localSystems.empty()) { |
| 2511 | ✗ | return; | |
| 2512 | } | ||
| 2513 |
1/2✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
|
74 | len = CreatePingPacket(m_localSystems[0], outBuf, discover); |
| 2514 |
1/2✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
|
74 | } |
| 2515 |
1/2✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
|
74 | SendUnicastPacket(sys.address, outBuf, len); |
| 2516 | } | ||
| 2517 | 4330 | int MultiSync::CreatePingPacket(MultiSyncSystem& sysInfo, char* outBuf, int discover) { | |
| 2518 | 4330 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2519 | 4330 | InitControlPacket(cpkt); | |
| 2520 | |||
| 2521 | 4330 | cpkt->pktType = CTRL_PKT_PING; | |
| 2522 | 4330 | cpkt->extraDataLen = PING_V4_LEN; | |
| 2523 | |||
| 2524 | 4330 | unsigned char* ed = (unsigned char*)(outBuf + 7); | |
| 2525 | 4330 | memset(ed, 0, cpkt->extraDataLen); | |
| 2526 | |||
| 2527 | 4330 | ed[0] = 4; // ping version 4 | |
| 2528 |
2/2✓ Branch 0 taken 1840 times.
✓ Branch 1 taken 2490 times.
|
4330 | ed[1] = discover > 0 ? 1 : 0; // 0 = ping, 1 = discover |
| 2529 | 4330 | ed[2] = sysInfo.type; | |
| 2530 | 4330 | ed[3] = (sysInfo.majorVersion & 0xFF00) >> 8; | |
| 2531 | 4330 | ed[4] = (sysInfo.majorVersion & 0x00FF); | |
| 2532 | 4330 | ed[5] = (sysInfo.minorVersion & 0xFF00) >> 8; | |
| 2533 | 4330 | ed[6] = (sysInfo.minorVersion & 0x00FF); | |
| 2534 | 4330 | ed[7] = sysInfo.fppMode; | |
| 2535 |
2/2✓ Branch 0 taken 106 times.
✓ Branch 1 taken 4224 times.
|
4330 | if (sysInfo.sendingMultiSync) { |
| 2536 | 106 | ed[7] |= 0x04; | |
| 2537 | } | ||
| 2538 | 4330 | ed[8] = sysInfo.ipa; | |
| 2539 | 4330 | ed[9] = sysInfo.ipb; | |
| 2540 | 4330 | ed[10] = sysInfo.ipc; | |
| 2541 | 4330 | ed[11] = sysInfo.ipd; | |
| 2542 | |||
| 2543 | 4330 | strncpy((char*)(ed + 12), sysInfo.hostname.c_str(), 64); | |
| 2544 | 4330 | strncpy((char*)(ed + 77), sysInfo.version.c_str(), 40); | |
| 2545 | 4330 | strncpy((char*)(ed + 118), sysInfo.model.c_str(), 40); | |
| 2546 | 4330 | strncpy((char*)(ed + 159), sysInfo.ranges.c_str(), 120); | |
| 2547 | // v4: appended after the whole v3 payload, which receivers that predate | ||
| 2548 | // it never read past. "Unknown" is getSetting()'s answer for no UUID. | ||
| 2549 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4330 times.
|
4330 | if (sysInfo.uuid != "Unknown") { |
| 2550 | ✗ | strncpy((char*)(ed + PING_V3_LEN), sysInfo.uuid.c_str(), PING_UUID_MAX); | |
| 2551 | } | ||
| 2552 | 4330 | return sizeof(ControlPkt) + cpkt->extraDataLen; | |
| 2553 | } | ||
| 2554 | |||
| 2555 | // The most a sender may put after the ControlPkt header. Receivers read each | ||
| 2556 | // datagram into a MAX_MS_RCV_BUFSIZE buffer and drop anything that arrives | ||
| 2557 | // truncated, so a larger packet is sent but never acted on. Every sender | ||
| 2558 | // checks against this one limit and refuses (with an error) rather than send | ||
| 2559 | // something no remote will accept. | ||
| 2560 | static constexpr size_t MAX_MS_PAYLOAD = MAX_MS_RCV_BUFSIZE - sizeof(ControlPkt); | ||
| 2561 | |||
| 2562 | // The sync senders below write filename into a SyncPkt within a 2048-byte | ||
| 2563 | // outBuf; a filename that wouldn't fit in a packet must be rejected before the | ||
| 2564 | // strcpy rather than silently truncated or overflowed. sizeof(SyncPkt) | ||
| 2565 | // already counts the filename's terminating NUL. | ||
| 2566 | 7536 | static bool FitsInSyncPacket(const std::string& filename) { | |
| 2567 | 7536 | return (sizeof(SyncPkt) + filename.length()) <= MAX_MS_PAYLOAD; | |
| 2568 | } | ||
| 2569 | |||
| 2570 | 33 | void MultiSync::SendSeqOpenPacket(const std::string& filename) { | |
| 2571 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
|
33 | if (filename.empty()) { |
| 2572 | ✗ | return; | |
| 2573 | } | ||
| 2574 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
|
33 | if (!FitsInSyncPacket(filename)) { |
| 2575 | ✗ | LogErr(VB_SYNC, "ERROR: SendSeqOpenPacket filename '%s' is too long to send\n", filename.c_str()); | |
| 2576 | ✗ | return; | |
| 2577 | } | ||
| 2578 | |||
| 2579 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
|
33 | if (m_controlSock < 0) { |
| 2580 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send start packet but sync socket is not open.\n"); | |
| 2581 | ✗ | return; | |
| 2582 | } | ||
| 2583 | |||
| 2584 |
1/2✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
|
33 | LogDebug(VB_SYNC, "SendSeqOpenPacket('%s')\n", filename.c_str()); |
| 2585 |
2/4✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 33 times.
|
33 | for (auto a : getPluginsCopy()) { |
| 2586 | ✗ | a->SendSeqOpenPacket(filename); | |
| 2587 | 33 | } | |
| 2588 | 33 | __atomic_store_n(&m_lastFrame, -1, __ATOMIC_RELAXED); | |
| 2589 | 33 | __atomic_store_n(&m_lastFrameSent, -1, __ATOMIC_RELAXED); | |
| 2590 | |||
| 2591 | char outBuf[2048]; | ||
| 2592 | 33 | bzero(outBuf, sizeof(outBuf)); | |
| 2593 | |||
| 2594 | 33 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2595 | 33 | SyncPkt* spkt = (SyncPkt*)(outBuf + sizeof(ControlPkt)); | |
| 2596 | |||
| 2597 |
1/2✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
|
33 | InitControlPacket(cpkt); |
| 2598 | |||
| 2599 | 33 | cpkt->pktType = CTRL_PKT_SYNC; | |
| 2600 | 33 | cpkt->extraDataLen = sizeof(SyncPkt) + filename.length(); | |
| 2601 | |||
| 2602 | 33 | spkt->pktType = SYNC_PKT_OPEN; | |
| 2603 | 33 | spkt->fileType = SYNC_FILE_SEQ; | |
| 2604 | 33 | spkt->frameNumber = 0; | |
| 2605 | 33 | spkt->secondsElapsed = 0; | |
| 2606 | 33 | strcpy(spkt->filename, filename.c_str()); | |
| 2607 | |||
| 2608 |
1/2✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
|
33 | SendControlPacket(outBuf, sizeof(ControlPkt) + sizeof(SyncPkt) + filename.length()); |
| 2609 | } | ||
| 2610 | |||
| 2611 | 32 | void MultiSync::SendSeqSyncStartPacket(const std::string& filename) { | |
| 2612 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | if (filename.empty()) { |
| 2613 | ✗ | return; | |
| 2614 | } | ||
| 2615 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | if (!FitsInSyncPacket(filename)) { |
| 2616 | ✗ | LogErr(VB_SYNC, "ERROR: SendSeqSyncStartPacket filename '%s' is too long to send\n", filename.c_str()); | |
| 2617 | ✗ | return; | |
| 2618 | } | ||
| 2619 | |||
| 2620 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | if (m_controlSock < 0) { |
| 2621 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send start packet but sync socket is not open.\n"); | |
| 2622 | ✗ | return; | |
| 2623 | } | ||
| 2624 | |||
| 2625 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | LogDebug(VB_SYNC, "SendSeqSyncStartPacket('%s')\n", filename.c_str()); |
| 2626 |
2/4✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 32 times.
|
32 | for (auto a : getPluginsCopy()) { |
| 2627 | ✗ | a->SendSeqSyncStartPacket(filename); | |
| 2628 | 32 | } | |
| 2629 | 32 | __atomic_store_n(&m_lastFrame, -1, __ATOMIC_RELAXED); | |
| 2630 | 32 | __atomic_store_n(&m_lastFrameSent, -1, __ATOMIC_RELAXED); | |
| 2631 | |||
| 2632 | char outBuf[2048]; | ||
| 2633 | 32 | bzero(outBuf, sizeof(outBuf)); | |
| 2634 | |||
| 2635 | 32 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2636 | 32 | SyncPkt* spkt = (SyncPkt*)(outBuf + sizeof(ControlPkt)); | |
| 2637 | |||
| 2638 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | InitControlPacket(cpkt); |
| 2639 | |||
| 2640 | 32 | cpkt->pktType = CTRL_PKT_SYNC; | |
| 2641 | 32 | cpkt->extraDataLen = sizeof(SyncPkt) + filename.length(); | |
| 2642 | |||
| 2643 | 32 | spkt->pktType = SYNC_PKT_START; | |
| 2644 | 32 | spkt->fileType = SYNC_FILE_SEQ; | |
| 2645 | 32 | spkt->frameNumber = 0; | |
| 2646 | 32 | spkt->secondsElapsed = 0; | |
| 2647 | 32 | strcpy(spkt->filename, filename.c_str()); | |
| 2648 | |||
| 2649 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | SendControlPacket(outBuf, sizeof(ControlPkt) + sizeof(SyncPkt) + filename.length()); |
| 2650 | } | ||
| 2651 | |||
| 2652 | /* | ||
| 2653 | * | ||
| 2654 | */ | ||
| 2655 | 227 | void MultiSync::SendSeqSyncStopPacket(const std::string& filename) { | |
| 2656 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 227 times.
|
227 | if (filename.empty()) { |
| 2657 | ✗ | return; | |
| 2658 | } | ||
| 2659 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 227 times.
|
227 | if (!FitsInSyncPacket(filename)) { |
| 2660 | ✗ | LogErr(VB_SYNC, "ERROR: SendSeqSyncStopPacket filename '%s' is too long to send\n", filename.c_str()); | |
| 2661 | ✗ | return; | |
| 2662 | } | ||
| 2663 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 227 times.
|
227 | if (m_controlSock < 0) { |
| 2664 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send stop packet but sync socket is not open.\n"); | |
| 2665 | ✗ | return; | |
| 2666 | } | ||
| 2667 |
1/2✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
|
227 | LogDebug(VB_SYNC, "SendSeqSyncStopPacket(%s)\n", filename.c_str()); |
| 2668 | |||
| 2669 |
2/4✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 227 times.
|
227 | for (auto a : getPluginsCopy()) { |
| 2670 | ✗ | a->SendSeqSyncStopPacket(filename); | |
| 2671 | 227 | } | |
| 2672 | |||
| 2673 | char outBuf[2048]; | ||
| 2674 | 227 | bzero(outBuf, sizeof(outBuf)); | |
| 2675 | |||
| 2676 | 227 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2677 | 227 | SyncPkt* spkt = (SyncPkt*)(outBuf + sizeof(ControlPkt)); | |
| 2678 | |||
| 2679 |
1/2✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
|
227 | InitControlPacket(cpkt); |
| 2680 | |||
| 2681 | 227 | cpkt->pktType = CTRL_PKT_SYNC; | |
| 2682 | 227 | cpkt->extraDataLen = sizeof(SyncPkt) + filename.length(); | |
| 2683 | |||
| 2684 | 227 | spkt->pktType = SYNC_PKT_STOP; | |
| 2685 | 227 | spkt->fileType = SYNC_FILE_SEQ; | |
| 2686 | 227 | spkt->frameNumber = 0; | |
| 2687 | 227 | spkt->secondsElapsed = 0; | |
| 2688 | 227 | strcpy(spkt->filename, filename.c_str()); | |
| 2689 | |||
| 2690 |
1/2✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
|
227 | SendControlPacket(outBuf, sizeof(ControlPkt) + sizeof(SyncPkt) + filename.length()); |
| 2691 | |||
| 2692 | 227 | __atomic_store_n(&m_lastFrame, -1, __ATOMIC_RELAXED); | |
| 2693 | 227 | __atomic_store_n(&m_lastFrameSent, -1, __ATOMIC_RELAXED); | |
| 2694 | } | ||
| 2695 | |||
| 2696 | /* | ||
| 2697 | * | ||
| 2698 | */ | ||
| 2699 | 4827 | void MultiSync::SendSeqSyncPacket(const std::string& filename, int frames, float seconds) { | |
| 2700 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4827 times.
|
4827 | if (filename.empty()) { |
| 2701 | 4153 | return; | |
| 2702 | } | ||
| 2703 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4827 times.
|
4827 | if (!FitsInSyncPacket(filename)) { |
| 2704 | ✗ | LogErr(VB_SYNC, "ERROR: SendSeqSyncPacket filename '%s' is too long to send\n", filename.c_str()); | |
| 2705 | ✗ | return; | |
| 2706 | } | ||
| 2707 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4827 times.
|
4827 | if (m_controlSock < 0) { |
| 2708 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send sync packet but sync socket is not open.\n"); | |
| 2709 | ✗ | return; | |
| 2710 | } | ||
| 2711 |
3/4✓ Branch 0 taken 4827 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 543 times.
✓ Branch 3 taken 4827 times.
|
5370 | for (auto a : getPluginsCopy()) { |
| 2712 |
1/2✓ Branch 0 taken 543 times.
✗ Branch 1 not taken.
|
543 | a->SendSeqSyncPacket(filename, frames, seconds); |
| 2713 | 4827 | } | |
| 2714 | // The output thread sends these while the sequence's open, start and | ||
| 2715 | // stop -- on the playlist's thread -- reset them, so every access is a | ||
| 2716 | // relaxed atomic one (the members are plain ints in a plugin-facing | ||
| 2717 | // header). | ||
| 2718 | 4827 | __atomic_store_n(&m_lastFrame, frames, __ATOMIC_RELAXED); | |
| 2719 | 4827 | int diff = frames - __atomic_load_n(&m_lastFrameSent, __ATOMIC_RELAXED); | |
| 2720 |
2/2✓ Branch 0 taken 3948 times.
✓ Branch 1 taken 879 times.
|
4827 | if (frames > 32) { |
| 2721 | // after 32 frames, we send every 10 | ||
| 2722 | // that's either twice a second (50ms sequences) or 4 times (25ms) | ||
| 2723 |
2/2✓ Branch 0 taken 3517 times.
✓ Branch 1 taken 431 times.
|
3948 | if (diff < 10) { |
| 2724 | 3517 | return; | |
| 2725 | } | ||
| 2726 |
4/4✓ Branch 0 taken 874 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 636 times.
✓ Branch 3 taken 238 times.
|
879 | } else if (frames && diff < 4) { |
| 2727 | // under 32 frames, we send every 4 | ||
| 2728 | 636 | return; | |
| 2729 | } | ||
| 2730 | 674 | __atomic_store_n(&m_lastFrameSent, frames, __ATOMIC_RELAXED); | |
| 2731 | |||
| 2732 |
1/2✓ Branch 0 taken 674 times.
✗ Branch 1 not taken.
|
674 | LogDebug(VB_SYNC, "SendSeqSyncPacket( '%s', %d, %.2f)\n", |
| 2733 | filename.c_str(), frames, seconds); | ||
| 2734 | char outBuf[2048]; | ||
| 2735 | 674 | bzero(outBuf, sizeof(outBuf)); | |
| 2736 | |||
| 2737 | 674 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2738 | 674 | SyncPkt* spkt = (SyncPkt*)(outBuf + sizeof(ControlPkt)); | |
| 2739 | |||
| 2740 |
1/2✓ Branch 0 taken 674 times.
✗ Branch 1 not taken.
|
674 | InitControlPacket(cpkt); |
| 2741 | |||
| 2742 | 674 | cpkt->pktType = CTRL_PKT_SYNC; | |
| 2743 | 674 | cpkt->extraDataLen = sizeof(SyncPkt) + filename.length(); | |
| 2744 | |||
| 2745 | 674 | spkt->pktType = SYNC_PKT_SYNC; | |
| 2746 | 674 | spkt->fileType = SYNC_FILE_SEQ; | |
| 2747 | 674 | spkt->frameNumber = frames; | |
| 2748 | 674 | spkt->secondsElapsed = seconds; | |
| 2749 | 674 | strcpy(spkt->filename, filename.c_str()); | |
| 2750 | |||
| 2751 |
1/2✓ Branch 0 taken 674 times.
✗ Branch 1 not taken.
|
674 | SendControlPacket(outBuf, sizeof(ControlPkt) + sizeof(SyncPkt) + filename.length()); |
| 2752 | } | ||
| 2753 | |||
| 2754 | 8 | void MultiSync::SendMediaOpenPacket(const std::string& filename) { | |
| 2755 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (filename.empty()) { |
| 2756 | ✗ | return; | |
| 2757 | } | ||
| 2758 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (!FitsInSyncPacket(filename)) { |
| 2759 | ✗ | LogErr(VB_SYNC, "ERROR: SendMediaOpenPacket filename '%s' is too long to send\n", filename.c_str()); | |
| 2760 | ✗ | return; | |
| 2761 | } | ||
| 2762 | |||
| 2763 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (m_controlSock < 0) { |
| 2764 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send start packet but sync socket is not open.\n"); | |
| 2765 | ✗ | return; | |
| 2766 | } | ||
| 2767 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | LogDebug(VB_SYNC, "SendMediaOpenPacket('%s')\n", filename.c_str()); |
| 2768 | |||
| 2769 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | for (auto a : getPluginsCopy()) { |
| 2770 | ✗ | a->SendMediaOpenPacket(filename); | |
| 2771 | 8 | } | |
| 2772 | |||
| 2773 | 8 | m_lastMediaHalfSecond = 0; | |
| 2774 | |||
| 2775 | char outBuf[2048]; | ||
| 2776 | 8 | bzero(outBuf, sizeof(outBuf)); | |
| 2777 | |||
| 2778 | 8 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2779 | 8 | SyncPkt* spkt = (SyncPkt*)(outBuf + sizeof(ControlPkt)); | |
| 2780 | |||
| 2781 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | InitControlPacket(cpkt); |
| 2782 | |||
| 2783 | 8 | cpkt->pktType = CTRL_PKT_SYNC; | |
| 2784 | 8 | cpkt->extraDataLen = sizeof(SyncPkt) + filename.length(); | |
| 2785 | |||
| 2786 | 8 | spkt->pktType = SYNC_PKT_OPEN; | |
| 2787 | 8 | spkt->fileType = SYNC_FILE_MEDIA; | |
| 2788 | 8 | spkt->frameNumber = 0; | |
| 2789 | 8 | spkt->secondsElapsed = 0; | |
| 2790 | 8 | strcpy(spkt->filename, filename.c_str()); | |
| 2791 | |||
| 2792 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | SendControlPacket(outBuf, sizeof(ControlPkt) + sizeof(SyncPkt) + filename.length()); |
| 2793 | } | ||
| 2794 | 8 | void MultiSync::SendMediaSyncStartPacket(const std::string& filename) { | |
| 2795 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (filename.empty()) { |
| 2796 | ✗ | return; | |
| 2797 | } | ||
| 2798 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (!FitsInSyncPacket(filename)) { |
| 2799 | ✗ | LogErr(VB_SYNC, "ERROR: SendMediaSyncStartPacket filename '%s' is too long to send\n", filename.c_str()); | |
| 2800 | ✗ | return; | |
| 2801 | } | ||
| 2802 | |||
| 2803 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (m_controlSock < 0) { |
| 2804 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send start packet but sync socket is not open.\n"); | |
| 2805 | ✗ | return; | |
| 2806 | } | ||
| 2807 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | LogDebug(VB_SYNC, "SendMediaSyncStartPacket('%s')\n", filename.c_str()); |
| 2808 | |||
| 2809 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | for (auto a : getPluginsCopy()) { |
| 2810 | ✗ | a->SendMediaSyncStartPacket(filename); | |
| 2811 | 8 | } | |
| 2812 | |||
| 2813 | 8 | m_lastMediaHalfSecond = 0; | |
| 2814 | |||
| 2815 | char outBuf[2048]; | ||
| 2816 | 8 | bzero(outBuf, sizeof(outBuf)); | |
| 2817 | |||
| 2818 | 8 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2819 | 8 | SyncPkt* spkt = (SyncPkt*)(outBuf + sizeof(ControlPkt)); | |
| 2820 | |||
| 2821 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | InitControlPacket(cpkt); |
| 2822 | |||
| 2823 | 8 | cpkt->pktType = CTRL_PKT_SYNC; | |
| 2824 | 8 | cpkt->extraDataLen = sizeof(SyncPkt) + filename.length(); | |
| 2825 | |||
| 2826 | 8 | spkt->pktType = SYNC_PKT_START; | |
| 2827 | 8 | spkt->fileType = SYNC_FILE_MEDIA; | |
| 2828 | 8 | spkt->frameNumber = 0; | |
| 2829 | 8 | spkt->secondsElapsed = 0; | |
| 2830 | 8 | strcpy(spkt->filename, filename.c_str()); | |
| 2831 | |||
| 2832 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | SendControlPacket(outBuf, sizeof(ControlPkt) + sizeof(SyncPkt) + filename.length()); |
| 2833 | } | ||
| 2834 | |||
| 2835 | /* | ||
| 2836 | * | ||
| 2837 | */ | ||
| 2838 | 8 | void MultiSync::SendMediaSyncStopPacket(const std::string& filename) { | |
| 2839 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (filename.empty()) { |
| 2840 | ✗ | return; | |
| 2841 | } | ||
| 2842 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (!FitsInSyncPacket(filename)) { |
| 2843 | ✗ | LogErr(VB_SYNC, "ERROR: SendMediaSyncStopPacket filename '%s' is too long to send\n", filename.c_str()); | |
| 2844 | ✗ | return; | |
| 2845 | } | ||
| 2846 | |||
| 2847 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (m_controlSock < 0) { |
| 2848 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send stop packet but sync socket is not open.\n"); | |
| 2849 | ✗ | return; | |
| 2850 | } | ||
| 2851 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | LogDebug(VB_SYNC, "SendMediaSyncStopPacket(%s)\n", filename.c_str()); |
| 2852 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | for (auto a : getPluginsCopy()) { |
| 2853 | ✗ | a->SendMediaSyncStopPacket(filename); | |
| 2854 | 8 | } | |
| 2855 | |||
| 2856 | char outBuf[2048]; | ||
| 2857 | 8 | bzero(outBuf, sizeof(outBuf)); | |
| 2858 | |||
| 2859 | 8 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2860 | 8 | SyncPkt* spkt = (SyncPkt*)(outBuf + sizeof(ControlPkt)); | |
| 2861 | |||
| 2862 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | InitControlPacket(cpkt); |
| 2863 | |||
| 2864 | 8 | cpkt->pktType = CTRL_PKT_SYNC; | |
| 2865 | 8 | cpkt->extraDataLen = sizeof(SyncPkt) + filename.length(); | |
| 2866 | |||
| 2867 | 8 | spkt->pktType = SYNC_PKT_STOP; | |
| 2868 | 8 | spkt->fileType = SYNC_FILE_MEDIA; | |
| 2869 | 8 | spkt->frameNumber = 0; | |
| 2870 | 8 | spkt->secondsElapsed = 0; | |
| 2871 | 8 | strcpy(spkt->filename, filename.c_str()); | |
| 2872 | |||
| 2873 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | SendControlPacket(outBuf, sizeof(ControlPkt) + sizeof(SyncPkt) + filename.length()); |
| 2874 | } | ||
| 2875 | |||
| 2876 | /* | ||
| 2877 | * | ||
| 2878 | */ | ||
| 2879 | 2393 | void MultiSync::SendMediaSyncPacket(const std::string& filename, float seconds) { | |
| 2880 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2393 times.
|
2393 | if (filename.empty()) { |
| 2881 | 2354 | return; | |
| 2882 | } | ||
| 2883 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2393 times.
|
2393 | if (!FitsInSyncPacket(filename)) { |
| 2884 | ✗ | LogErr(VB_SYNC, "ERROR: SendMediaSyncPacket filename '%s' is too long to send\n", filename.c_str()); | |
| 2885 | ✗ | return; | |
| 2886 | } | ||
| 2887 | |||
| 2888 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2393 times.
|
2393 | if (m_controlSock < 0) { |
| 2889 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send sync packet but sync socket is not open.\n"); | |
| 2890 | ✗ | return; | |
| 2891 | } | ||
| 2892 | |||
| 2893 |
2/4✓ Branch 0 taken 2393 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2393 times.
|
2393 | for (auto a : getPluginsCopy()) { |
| 2894 | ✗ | a->SendMediaSyncPacket(filename, seconds); | |
| 2895 | 2393 | } | |
| 2896 | |||
| 2897 | 2393 | int curTS = (seconds * 2.0f); | |
| 2898 |
2/2✓ Branch 0 taken 2354 times.
✓ Branch 1 taken 39 times.
|
2393 | if (m_lastMediaHalfSecond == curTS) { |
| 2899 | // not time to send | ||
| 2900 | 2354 | return; | |
| 2901 | } | ||
| 2902 | 39 | m_lastMediaHalfSecond = curTS; | |
| 2903 | |||
| 2904 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | LogExcess(VB_SYNC, "SendMediaSyncPacket( '%s', %.2f)\n", |
| 2905 | filename.c_str(), seconds); | ||
| 2906 | |||
| 2907 | char outBuf[2048]; | ||
| 2908 | 39 | bzero(outBuf, sizeof(outBuf)); | |
| 2909 | |||
| 2910 | 39 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2911 | 39 | SyncPkt* spkt = (SyncPkt*)(outBuf + sizeof(ControlPkt)); | |
| 2912 | |||
| 2913 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | InitControlPacket(cpkt); |
| 2914 | |||
| 2915 | 39 | cpkt->pktType = CTRL_PKT_SYNC; | |
| 2916 | 39 | cpkt->extraDataLen = sizeof(SyncPkt) + filename.length(); | |
| 2917 | |||
| 2918 | 39 | spkt->pktType = SYNC_PKT_SYNC; | |
| 2919 | 39 | spkt->fileType = SYNC_FILE_MEDIA; | |
| 2920 | 39 | spkt->frameNumber = 0; | |
| 2921 | 39 | spkt->secondsElapsed = seconds; | |
| 2922 | 39 | strcpy(spkt->filename, filename.c_str()); | |
| 2923 | |||
| 2924 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | SendControlPacket(outBuf, sizeof(ControlPkt) + sizeof(SyncPkt) + filename.length()); |
| 2925 | } | ||
| 2926 | |||
| 2927 | 2 | void MultiSync::SendPluginData(const std::string& name, const uint8_t* data, int len) { | |
| 2928 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (name.empty()) { |
| 2929 | 1 | return; | |
| 2930 | } | ||
| 2931 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | for (auto a : getPluginsCopy()) { |
| 2932 | ✗ | a->SendPluginData(name, data, len); | |
| 2933 | 2 | } | |
| 2934 | |||
| 2935 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogDebug(VB_SYNC, "SendPluginData('%s')\n", name.c_str()); |
| 2936 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (m_controlSock < 0) { |
| 2937 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send plugin data packet but control socket is not open.\n"); | |
| 2938 | ✗ | return; | |
| 2939 | } | ||
| 2940 | |||
| 2941 | char outBuf[2048]; | ||
| 2942 | 2 | bzero(outBuf, sizeof(outBuf)); | |
| 2943 | |||
| 2944 | 2 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2945 | 2 | CommandPkt* epkt = (CommandPkt*)(outBuf + sizeof(ControlPkt)); | |
| 2946 | |||
| 2947 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | InitControlPacket(cpkt); |
| 2948 | 2 | int nlen = strlen(name.c_str()) + 1; // add the null | |
| 2949 |
3/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
|
2 | if (len < 0 || ((size_t)nlen + (size_t)len) > MAX_MS_PAYLOAD) { |
| 2950 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogErr(VB_SYNC, "ERROR: Plugin data packet for '%s' (%d bytes) is too large to send\n", |
| 2951 | name.c_str(), len); | ||
| 2952 | 1 | return; | |
| 2953 | } | ||
| 2954 | 1 | cpkt->pktType = CTRL_PKT_PLUGIN; | |
| 2955 | 1 | cpkt->extraDataLen = len + nlen; | |
| 2956 | |||
| 2957 | 1 | strcpy(epkt->command, name.c_str()); | |
| 2958 | 1 | memcpy(&epkt->command[nlen], data, len); | |
| 2959 | |||
| 2960 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | SendControlPacket(outBuf, sizeof(ControlPkt) + len + nlen); |
| 2961 | } | ||
| 2962 | |||
| 2963 | /* | ||
| 2964 | * | ||
| 2965 | */ | ||
| 2966 | 67 | void MultiSync::SendBlankingDataPacket(void) { | |
| 2967 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | LogDebug(VB_SYNC, "SendBlankingDataPacket()\n"); |
| 2968 | |||
| 2969 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
|
67 | if (m_controlSock < 0) { |
| 2970 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send blanking data packet but control socket is not open.\n"); | |
| 2971 | ✗ | return; | |
| 2972 | } | ||
| 2973 |
2/4✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
|
67 | for (auto a : getPluginsCopy()) { |
| 2974 | ✗ | a->SendBlankingDataPacket(); | |
| 2975 | 67 | } | |
| 2976 | char outBuf[2048]; | ||
| 2977 | 67 | bzero(outBuf, sizeof(outBuf)); | |
| 2978 | |||
| 2979 | 67 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 2980 | |||
| 2981 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | InitControlPacket(cpkt); |
| 2982 | |||
| 2983 | 67 | cpkt->pktType = CTRL_PKT_BLANK; | |
| 2984 | 67 | cpkt->extraDataLen = 0; | |
| 2985 | |||
| 2986 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | SendControlPacket(outBuf, sizeof(ControlPkt)); |
| 2987 | } | ||
| 2988 | 9796 | std::vector<MultiSyncPlugin*> MultiSync::getPluginsCopy() { | |
| 2989 |
1/2✓ Branch 0 taken 9796 times.
✗ Branch 1 not taken.
|
9796 | std::unique_lock<std::mutex> lock(m_pluginsLock); |
| 2990 |
1/2✓ Branch 0 taken 9796 times.
✗ Branch 1 not taken.
|
19592 | return m_plugins; |
| 2991 | 9796 | } | |
| 2992 | 24000 | void MultiSync::addMultiSyncPlugin(MultiSyncPlugin* p) { | |
| 2993 |
1/2✓ Branch 0 taken 24000 times.
✗ Branch 1 not taken.
|
24000 | std::unique_lock<std::mutex> lock(m_pluginsLock); |
| 2994 |
1/2✓ Branch 0 taken 24000 times.
✗ Branch 1 not taken.
|
24000 | m_plugins.push_back(p); |
| 2995 | 24000 | } | |
| 2996 | 24000 | void MultiSync::removeMultiSyncPlugin(MultiSyncPlugin* p) { | |
| 2997 |
1/2✓ Branch 0 taken 24000 times.
✗ Branch 1 not taken.
|
24000 | std::unique_lock<std::mutex> lock(m_pluginsLock); |
| 2998 |
1/2✓ Branch 0 taken 24000 times.
✗ Branch 1 not taken.
|
24000 | auto a = std::find(m_plugins.begin(), m_plugins.end(), p); |
| 2999 |
1/2✓ Branch 0 taken 24000 times.
✗ Branch 1 not taken.
|
24000 | if (a != m_plugins.end()) { |
| 3000 |
1/2✓ Branch 0 taken 24000 times.
✗ Branch 1 not taken.
|
24000 | m_plugins.erase(a); |
| 3001 | } | ||
| 3002 | 24000 | } | |
| 3003 | |||
| 3004 | /* | ||
| 3005 | * | ||
| 3006 | */ | ||
| 3007 | 2325 | void MultiSync::ShutdownSync(void) { | |
| 3008 |
1/2✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
|
2325 | LogDebug(VB_SYNC, "ShutdownSync()\n"); |
| 3009 | |||
| 3010 | // Idempotent, like WLEDAudioSync::Cleanup(): fppd's shutdown calls this and | ||
| 3011 | // ~MultiSync() calls it again at static-destruction time, by which point the | ||
| 3012 | // global SettingsConfig may already be gone -- locking its destroyed mutex | ||
| 3013 | // would throw out of a noexcept destructor. Unregistering is not a | ||
| 3014 | // barrier: listeners are called from a copy with no settings lock held, so | ||
| 3015 | // one may still be running ReloadSyncDestinations() on the settings-reload | ||
| 3016 | // thread, or be about to start. Close the gate and wait for those to | ||
| 3017 | // finish before the sockets below are closed. | ||
| 3018 |
1/2✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
|
2325 | if (!m_settingsListenersRemoved.exchange(true)) { |
| 3019 | 13950 | for (const char* s : { "MultiSyncRemotes", "MultiSyncExtraRemotes", | |
| 3020 |
2/2✓ Branch 0 taken 11625 times.
✓ Branch 1 taken 2325 times.
|
16275 | "MultiSyncBroadcast", "MultiSyncMulticast", "MultiSyncUnicast" }) { |
| 3021 |
3/6✓ Branch 0 taken 11625 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11625 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11625 times.
✗ Branch 5 not taken.
|
46500 | unregisterSettingsListener("MultiSync", s); |
| 3022 | } | ||
| 3023 |
1/2✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
|
2325 | std::unique_lock<std::mutex> lock(s_listenerQuiesceLock); |
| 3024 | 2325 | s_listenersShutdown = true; | |
| 3025 |
1/2✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
|
4650 | s_listenerQuiesceCV.wait(lock, []() { return s_listenersInFlight == 0; }); |
| 3026 | 2325 | } | |
| 3027 | |||
| 3028 | { | ||
| 3029 |
1/2✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
|
2325 | std::unique_lock<std::mutex> pluginLock(m_pluginsLock); |
| 3030 |
1/2✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
|
2325 | auto plugins = m_plugins; |
| 3031 |
1/2✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
|
2325 | pluginLock.unlock(); |
| 3032 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2325 times.
|
2325 | for (auto a : plugins) { |
| 3033 | ✗ | a->ShutdownSync(); | |
| 3034 | } | ||
| 3035 | 2325 | } | |
| 3036 | { | ||
| 3037 |
1/2✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
|
2325 | std::unique_lock<std::mutex> pluginLock(m_pluginsLock); |
| 3038 | 2325 | m_plugins.clear(); | |
| 3039 | 2325 | } | |
| 3040 | |||
| 3041 |
1/2✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
|
2325 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3042 |
2/2✓ Branch 0 taken 1867 times.
✓ Branch 1 taken 458 times.
|
2325 | if (m_broadcastSock >= 0) { |
| 3043 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | close(m_broadcastSock); |
| 3044 | 1867 | m_broadcastSock = -1; | |
| 3045 | } | ||
| 3046 | |||
| 3047 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 2293 times.
|
2325 | if (m_controlSock >= 0) { |
| 3048 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | close(m_controlSock); |
| 3049 | 32 | m_controlSock = -1; | |
| 3050 | } | ||
| 3051 | |||
| 3052 |
2/2✓ Branch 0 taken 1867 times.
✓ Branch 1 taken 458 times.
|
2325 | if (m_twinklyDiscoverySock >= 0) { |
| 3053 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | EPollManager::INSTANCE.removeFileDescriptor(m_twinklyDiscoverySock); |
| 3054 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | close(m_twinklyDiscoverySock); |
| 3055 | 1867 | m_twinklyDiscoverySock = -1; | |
| 3056 | } | ||
| 3057 | |||
| 3058 |
2/2✓ Branch 0 taken 1867 times.
✓ Branch 1 taken 458 times.
|
2325 | if (m_receiveSock >= 0) { |
| 3059 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | close(m_receiveSock); |
| 3060 | 1867 | m_receiveSock = -1; | |
| 3061 | } | ||
| 3062 | 2325 | } | |
| 3063 | |||
| 3064 | /* | ||
| 3065 | * | ||
| 3066 | */ | ||
| 3067 | 1887 | int MultiSync::OpenBroadcastSocket(void) { | |
| 3068 |
1/2✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
|
1887 | LogDebug(VB_SYNC, "OpenBroadcastSocket()\n"); |
| 3069 | |||
| 3070 | 1887 | m_broadcastSock = OpenSocket(AF_INET, SOCK_DGRAM, 0); | |
| 3071 | |||
| 3072 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
|
1887 | if (m_broadcastSock < 0) { |
| 3073 | ✗ | LogErr(VB_SYNC, "Error opening MultiSync broadcast socket\n"); | |
| 3074 | ✗ | WarningHolder::AddWarning(42, "MultiSync: could not open broadcast socket"); | |
| 3075 | ✗ | return 0; | |
| 3076 | } | ||
| 3077 | |||
| 3078 | 1887 | char loopch = 0; | |
| 3079 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
|
1887 | if (setsockopt(m_broadcastSock, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&loopch, sizeof(loopch)) < 0) { |
| 3080 | ✗ | LogErr(VB_SYNC, "Error setting IP_MULTICAST_LOOP: \n", | |
| 3081 | FPPstrerror(errno)); | ||
| 3082 | ✗ | return 0; | |
| 3083 | } | ||
| 3084 | |||
| 3085 | 1887 | int broadcast = 1; | |
| 3086 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
|
1887 | if (setsockopt(m_broadcastSock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)) < 0) { |
| 3087 | ✗ | LogErr(VB_SYNC, "Error setting SO_BROADCAST: \n", FPPstrerror(errno)); | |
| 3088 | ✗ | return 0; | |
| 3089 | } | ||
| 3090 | |||
| 3091 | 1887 | return 1; | |
| 3092 | } | ||
| 3093 | |||
| 3094 | /* | ||
| 3095 | * | ||
| 3096 | */ | ||
| 3097 | 32 | int MultiSync::OpenControlSockets() { | |
| 3098 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | LogDebug(VB_SYNC, "OpenControlSockets()\n"); |
| 3099 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | std::unique_lock<std::mutex> openLock(s_controlSockOpenLock); |
| 3100 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | if (m_controlSock >= 0) { |
| 3101 | ✗ | return 1; | |
| 3102 | } | ||
| 3103 | |||
| 3104 | // Set the socket up fully before publishing it, so a sender never sees a | ||
| 3105 | // half-configured one, and a failed setsockopt() doesn't leave it behind | ||
| 3106 | // as if the open had succeeded. | ||
| 3107 | 32 | int sock = OpenSocket(AF_INET, SOCK_DGRAM, 0); | |
| 3108 | |||
| 3109 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | if (sock < 0) { |
| 3110 | ✗ | LogErr(VB_SYNC, "Error opening MultiSync socket\n"); | |
| 3111 | ✗ | WarningHolder::AddWarning(42, "MultiSync: could not open control socket"); | |
| 3112 | ✗ | return 0; | |
| 3113 | } | ||
| 3114 | |||
| 3115 | 32 | char loopch = 0; | |
| 3116 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&loopch, sizeof(loopch)) < 0) { |
| 3117 | ✗ | LogErr(VB_SYNC, "Error setting IP_MULTICAST_LOOP: %s\n", | |
| 3118 | FPPstrerror(errno)); | ||
| 3119 | ✗ | close(sock); | |
| 3120 | ✗ | return 0; | |
| 3121 | } | ||
| 3122 | |||
| 3123 | { | ||
| 3124 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3125 | 32 | m_controlSock = sock; | |
| 3126 | 32 | } | |
| 3127 | // The destination reload resolves hostnames, so don't make other threads | ||
| 3128 | // wanting the socket wait on it; they can use the socket already. | ||
| 3129 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | openLock.unlock(); |
| 3130 | |||
| 3131 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | ReloadSyncDestinations(); |
| 3132 | |||
| 3133 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | FillInInterfaces(); |
| 3134 | |||
| 3135 | 32 | return 1; | |
| 3136 | 32 | } | |
| 3137 | |||
| 3138 | 39 | void MultiSync::ReloadSyncDestinations() { | |
| 3139 | // Same lock UpdateUnicastDestinations() uses, for the same reason: the | ||
| 3140 | // resolve step below is slow and runs without m_socketLock, so two | ||
| 3141 | // overlapping reloads could otherwise finish out of order and leave the | ||
| 3142 | // older result in place. Holding it also keeps UpdateUnicastDestinations() | ||
| 3143 | // from reading m_destAddr for its dedupe set mid-swap. Nothing in this | ||
| 3144 | // function may take m_systemsLock while it is held; it is released before | ||
| 3145 | // the UpdateUnicastDestinations() call at the end, which snapshots the | ||
| 3146 | // remote list under m_systemsLock and then takes this lock itself. | ||
| 3147 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | std::unique_lock<std::mutex> updateLock(m_unicastUpdateLock); |
| 3148 | |||
| 3149 | 39 | std::set<std::string> remotes; | |
| 3150 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | AddRemotesFromSetting("MultiSyncRemotes", remotes); |
| 3151 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | AddRemotesFromSetting("MultiSyncExtraRemotes", remotes); |
| 3152 | |||
| 3153 | // Assign rather than |=: this runs again on every settings change, so a | ||
| 3154 | // method the user just turned off has to go back to false. | ||
| 3155 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | bool sendBroadcast = getSettingInt("MultiSyncBroadcast") != 0; |
| 3156 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | bool sendMulticast = getSettingInt("MultiSyncMulticast") != 0; |
| 3157 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | bool sendUnicast = getSettingInt("MultiSyncUnicast") != 0; |
| 3158 |
10/12✓ Branch 0 taken 8 times.
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 3 times.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 37 times.
|
39 | if (remotes.empty() && !sendBroadcast && !sendMulticast && !sendUnicast && m_multiSyncEnabled) { |
| 3159 | // No explicit remotes or send method configured; default to multicast. | ||
| 3160 | // A list that names no host (only quotes or commas, which is how the | ||
| 3161 | // old UI saved an empty one) counts as no remotes. | ||
| 3162 | 2 | sendMulticast = true; | |
| 3163 | } | ||
| 3164 | |||
| 3165 | // Resolve into a local list first: getaddrinfo() below is an unbounded | ||
| 3166 | // network lookup and must not run while m_socketLock is held, since that | ||
| 3167 | // lock is taken by the send path on the output thread every frame. | ||
| 3168 | 39 | std::vector<struct sockaddr_in> newAddrs; | |
| 3169 |
2/2✓ Branch 0 taken 41 times.
✓ Branch 1 taken 39 times.
|
80 | for (auto& s : remotes) { |
| 3170 |
1/2✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
|
41 | LogDebug(VB_SYNC, "Setting up Remote Sync for %s\n", s.c_str()); |
| 3171 | struct sockaddr_in newRemote; | ||
| 3172 | 41 | memset(&newRemote, 0, sizeof(newRemote)); | |
| 3173 | |||
| 3174 | 41 | newRemote.sin_family = AF_INET; | |
| 3175 | 41 | newRemote.sin_port = htons(FPP_CTRL_PORT); | |
| 3176 | |||
| 3177 | // A letter (or a space) means this is a hostname and has to be resolved; | ||
| 3178 | // anything else is a dotted-quad to parse directly. The test used to | ||
| 3179 | // read `... == s.end()`, i.e. "contains no letters", which is backwards: | ||
| 3180 | // hostnames took the inet_addr() path, came back INADDR_NONE, and were | ||
| 3181 | // then installed as a destination of 255.255.255.255 -- so every entry | ||
| 3182 | // that wasn't already an IP address quietly broadcast its sync packets. | ||
| 3183 |
4/6✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 364 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 364 times.
|
406 | bool isHostname = std::find_if(s.begin(), s.end(), [](char c) { return (isalpha(c) || (c == ' ')); }) != s.end(); |
| 3184 | 41 | bool valid = true; | |
| 3185 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 40 times.
|
41 | if (isHostname) { |
| 3186 | // Reentrant and cached, unlike gethostbyname(), which shares a | ||
| 3187 | // single static hostent across the process. | ||
| 3188 | 1 | uint32_t resolved = 0; | |
| 3189 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
1 | if (!ResolveHostToIPv4(s, resolved)) { |
| 3190 | ✗ | LogErr(VB_SYNC, | |
| 3191 | "Error looking up Remote hostname: %s\n", | ||
| 3192 | s.c_str()); | ||
| 3193 | ✗ | valid = false; | |
| 3194 | } else { | ||
| 3195 | 1 | newRemote.sin_addr.s_addr = resolved; | |
| 3196 | } | ||
| 3197 | } else { | ||
| 3198 | 40 | newRemote.sin_addr.s_addr = inet_addr(s.c_str()); | |
| 3199 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
|
40 | if (newRemote.sin_addr.s_addr == INADDR_NONE) { |
| 3200 | ✗ | LogErr(VB_SYNC, "Error parsing Remote IP address: %s\n", s.c_str()); | |
| 3201 | ✗ | valid = false; | |
| 3202 | } | ||
| 3203 | } | ||
| 3204 |
1/2✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
|
41 | if (valid) { |
| 3205 |
1/2✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
|
41 | newAddrs.push_back(newRemote); |
| 3206 | } | ||
| 3207 | } | ||
| 3208 | |||
| 3209 | 39 | m_sendBroadcast = sendBroadcast; | |
| 3210 | 39 | m_sendMulticast = sendMulticast; | |
| 3211 | 39 | m_sendUnicast = sendUnicast; | |
| 3212 | |||
| 3213 | { | ||
| 3214 | // Swap the whole list in at once. Each mmsghdr points at its own | ||
| 3215 | // element of m_destAddr, so the two vectors must be rebuilt together | ||
| 3216 | // and never observed half-updated by SendControlPacketViaMsgs(). | ||
| 3217 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3218 | 39 | m_destAddr = std::move(newAddrs); | |
| 3219 | 39 | m_destMsgs.clear(); | |
| 3220 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | m_destMsgs.reserve(m_destAddr.size()); |
| 3221 |
2/2✓ Branch 0 taken 41 times.
✓ Branch 1 taken 39 times.
|
80 | for (size_t x = 0; x < m_destAddr.size(); x++) { |
| 3222 | struct mmsghdr msg; | ||
| 3223 | 41 | memset(&msg, 0, sizeof(msg)); | |
| 3224 | |||
| 3225 | 41 | msg.msg_hdr.msg_name = &m_destAddr[x]; | |
| 3226 | 41 | msg.msg_hdr.msg_namelen = sizeof(sockaddr_in); | |
| 3227 | 41 | msg.msg_hdr.msg_iov = &m_destIovec; | |
| 3228 | 41 | msg.msg_hdr.msg_iovlen = 1; | |
| 3229 | 41 | msg.msg_len = 0; | |
| 3230 |
1/2✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
|
41 | m_destMsgs.push_back(msg); |
| 3231 | } | ||
| 3232 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | LogDebug(VB_SYNC, "%d Remote Sync systems configured\n", |
| 3233 | (int)m_destAddr.size()); | ||
| 3234 | |||
| 3235 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 37 times.
|
39 | if (!sendUnicast) { |
| 3236 | // "Send to ALL KNOWN remotes" is off, so drop that list rather than | ||
| 3237 | // leaving a stale copy behind for the next time it is turned on. | ||
| 3238 | 2 | m_unicastDestMsgs.clear(); | |
| 3239 | 2 | m_unicastDestAddr.clear(); | |
| 3240 | } | ||
| 3241 | 39 | } | |
| 3242 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | updateLock.unlock(); |
| 3243 | |||
| 3244 | // Rebuild the "all known remotes" list too: it dedupes itself against | ||
| 3245 | // m_destAddr, which just changed. Must run with both locks released -- | ||
| 3246 | // UpdateUnicastDestinations() snapshots the remote list under m_systemsLock | ||
| 3247 | // and then takes m_unicastUpdateLock and m_socketLock itself. | ||
| 3248 |
2/2✓ Branch 0 taken 37 times.
✓ Branch 1 taken 2 times.
|
39 | if (sendUnicast) { |
| 3249 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | UpdateUnicastDestinations(); |
| 3250 | } | ||
| 3251 | 39 | } | |
| 3252 | |||
| 3253 | // Malformed control packets come from whatever else is on the MultiSync port | ||
| 3254 | // (another program, an incompatible version), as fast as it sends them, and | ||
| 3255 | // each was logged with a hex dump: a line or more per packet. Log the first | ||
| 3256 | // few in each minute, then say how many more were ignored. | ||
| 3257 | static std::mutex badPacketLock; | ||
| 3258 | static long long badPacketWindowMS = 0; | ||
| 3259 | static int badPacketsLogged = 0; | ||
| 3260 | static int badPacketsSuppressed = 0; | ||
| 3261 | static constexpr int BAD_PACKETS_LOGGED_PER_MINUTE = 5; | ||
| 3262 | |||
| 3263 | 1211 | static bool ShouldLogBadPacket() { | |
| 3264 |
1/2✓ Branch 0 taken 1211 times.
✗ Branch 1 not taken.
|
1211 | std::lock_guard<std::mutex> l(badPacketLock); |
| 3265 |
1/2✓ Branch 0 taken 1211 times.
✗ Branch 1 not taken.
|
1211 | long long now = GetMonotonicTimeMS(); |
| 3266 |
3/4✓ Branch 0 taken 1207 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1207 times.
|
1211 | if (!badPacketWindowMS || now - badPacketWindowMS >= 60000) { |
| 3267 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (badPacketsSuppressed) { |
| 3268 | ✗ | LogWarn(VB_SYNC, "%d more malformed MultiSync packets ignored in the last minute\n", badPacketsSuppressed); | |
| 3269 | } | ||
| 3270 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | badPacketWindowMS = now ? now : 1; |
| 3271 | 4 | badPacketsLogged = 0; | |
| 3272 | 4 | badPacketsSuppressed = 0; | |
| 3273 | } | ||
| 3274 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1191 times.
|
1211 | if (badPacketsLogged < BAD_PACKETS_LOGGED_PER_MINUTE) { |
| 3275 | 20 | badPacketsLogged++; | |
| 3276 | 20 | return true; | |
| 3277 | } | ||
| 3278 | 1191 | badPacketsSuppressed++; | |
| 3279 | 1191 | return false; | |
| 3280 | 1211 | } | |
| 3281 | |||
| 3282 | // Undelivered sync packets since the last log line about them, and when that | ||
| 3283 | // was (GetMonotonicTimeMS()). Guarded by m_socketLock; file-static rather | ||
| 3284 | // than members because MultiSync's layout is plugin ABI. A remote that is | ||
| 3285 | // gone stays gone: logging every failed packet wrote a line per sync packet | ||
| 3286 | // (several a second) to the SD card for the whole show. | ||
| 3287 | static int undeliveredSinceLog = 0; | ||
| 3288 | static long long undeliveredLoggedAt = 0; | ||
| 3289 | static constexpr long long UNDELIVERED_LOG_INTERVAL_MS = 60000; | ||
| 3290 | |||
| 3291 | // The same for the broadcast and multicast sends (ENOBUFS on Wi-Fi, an | ||
| 3292 | // interface that is down or firewalled): logged when it starts, then once a | ||
| 3293 | // minute with a count, rather than for every sync packet on every interface. | ||
| 3294 | // Callers hold m_socketLock. | ||
| 3295 | static int sendFailuresSinceLog = 0; | ||
| 3296 | static long long sendFailureLoggedAt = 0; | ||
| 3297 | |||
| 3298 | 182 | static void LogSendFailure(int err) { | |
| 3299 | 182 | sendFailuresSinceLog++; | |
| 3300 | 182 | long long now = GetMonotonicTimeMS(); | |
| 3301 |
3/4✓ Branch 0 taken 179 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 179 times.
|
182 | if (!sendFailureLoggedAt || now - sendFailureLoggedAt >= UNDELIVERED_LOG_INTERVAL_MS) { |
| 3302 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | LogErr(VB_SYNC, "Error: Unable to send packet: %s (%d failed sends%s)\n", FPPstrerror(err), |
| 3303 | sendFailuresSinceLog, sendFailureLoggedAt ? " in the last minute" : ""); | ||
| 3304 | 3 | sendFailuresSinceLog = 0; | |
| 3305 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | sendFailureLoggedAt = now ? now : 1; |
| 3306 | } | ||
| 3307 | 182 | } | |
| 3308 | |||
| 3309 | // How long one sync send may keep retrying a destination whose socket buffer is | ||
| 3310 | // full, before skipping it. These sends run on the channel output thread ahead | ||
| 3311 | // of the frame's channel data, and under m_socketLock, which the main loop's | ||
| 3312 | // media sync sends also take -- so the bound covers both. See | ||
| 3313 | // SendControlPacketViaMsgs() for why a dropped sync packet is the cheap side. | ||
| 3314 | static constexpr long long SEND_BUDGET_MS = 2; | ||
| 3315 | |||
| 3316 | // One datagram, never blocking for longer than the budget left since startTime. | ||
| 3317 | // A blocking sendto() here once waited for good: Wi-Fi stopped transmitting | ||
| 3318 | // mid-show, the multicast socket's buffer filled, and the channel output thread | ||
| 3319 | // slept in sendto() holding m_socketLock while the main loop queued up behind | ||
| 3320 | // it -- all pixel output froze until a power cycle (issue #3072). | ||
| 3321 | // Returns 0, or the errno of the failed send. | ||
| 3322 | 7194 | static int SendDatagramBounded(int sock, const void* buf, int len, const struct sockaddr_in& addr, | |
| 3323 | long long startTime) { | ||
| 3324 | while (true) { | ||
| 3325 |
2/2✓ Branch 0 taken 6153 times.
✓ Branch 1 taken 1041 times.
|
7194 | if (sendto(sock, buf, len, MSG_DONTWAIT, (const struct sockaddr*)&addr, sizeof(addr)) >= 0) { |
| 3326 | 6153 | return 0; | |
| 3327 | } | ||
| 3328 | 1041 | int err = errno; | |
| 3329 |
4/6✓ Branch 0 taken 175 times.
✓ Branch 1 taken 866 times.
✓ Branch 2 taken 175 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 175 times.
✗ Branch 5 not taken.
|
1041 | bool transient = err == EAGAIN || err == EWOULDBLOCK || err == ENOBUFS; |
| 3330 |
5/6✓ Branch 0 taken 1041 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 182 times.
✓ Branch 3 taken 859 times.
✓ Branch 4 taken 182 times.
✓ Branch 5 taken 859 times.
|
1041 | if (!transient || (GetMonotonicTimeMS() - startTime) >= SEND_BUDGET_MS) { |
| 3331 | 182 | return err; | |
| 3332 | } | ||
| 3333 |
1/2✓ Branch 0 taken 859 times.
✗ Branch 1 not taken.
|
859 | std::this_thread::sleep_for(std::chrono::microseconds(250)); |
| 3334 | 859 | } | |
| 3335 | } | ||
| 3336 | |||
| 3337 | 2172 | void MultiSync::SendControlPacketViaMsgs(std::vector<struct mmsghdr>& msgs, struct iovec& iovec, void* outBuf, int len) { | |
| 3338 | // The msgs vector (and the sockaddrs its entries point at) may be rebuilt | ||
| 3339 | // concurrently by UpdateUnicastDestinations(), so read its size, point the | ||
| 3340 | // shared iovec at our buffer, and send all while holding m_socketLock. | ||
| 3341 |
1/2✓ Branch 0 taken 2172 times.
✗ Branch 1 not taken.
|
2172 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3342 | 2172 | int msgCount = msgs.size(); | |
| 3343 |
2/2✓ Branch 0 taken 1425 times.
✓ Branch 1 taken 747 times.
|
2172 | if (msgCount == 0) { |
| 3344 | 1425 | return; | |
| 3345 | } | ||
| 3346 | 747 | iovec.iov_base = outBuf; | |
| 3347 | 747 | iovec.iov_len = len; | |
| 3348 | |||
| 3349 | // sendmmsg stops at the first message it cannot send and reports how many | ||
| 3350 | // it did send, so each pass simply resumes at the destination that stopped | ||
| 3351 | // it. | ||
| 3352 | // | ||
| 3353 | // This runs on the channel output thread ahead of the frame's channel data, | ||
| 3354 | // so the whole call is bounded well inside a frame. The bound matters more | ||
| 3355 | // than it looks: a remote that has dropped off the LAN leaves its neighbour | ||
| 3356 | // entry unresolved, and the kernel then answers sends to it with EAGAIN | ||
| 3357 | // (not EHOSTUNREACH) for as long as it is gone - measured here at ~97% of | ||
| 3358 | // sends. Retrying that for long turns one dead remote into a per-frame | ||
| 3359 | // stall for every controller, which is exactly what the budget prevents. | ||
| 3360 | // A skipped sync packet is cheap by comparison: the next frame sends another. | ||
| 3361 | 747 | int outputCount = 0; | |
| 3362 | 747 | int undelivered = 0; | |
| 3363 | // monotonic: on the wall clock a step back during the budget kept this | ||
| 3364 | // loop retrying, on the channel output thread, for the size of the step | ||
| 3365 |
1/2✓ Branch 0 taken 747 times.
✗ Branch 1 not taken.
|
747 | long long startTime = GetMonotonicTimeMS(); |
| 3366 |
2/2✓ Branch 0 taken 779 times.
✓ Branch 1 taken 747 times.
|
1526 | while (outputCount < msgCount) { |
| 3367 |
1/2✓ Branch 0 taken 779 times.
✗ Branch 1 not taken.
|
779 | int sent = sendmmsg(m_controlSock, &msgs[outputCount], msgCount - outputCount, MSG_DONTWAIT); |
| 3368 |
2/2✓ Branch 0 taken 728 times.
✓ Branch 1 taken 51 times.
|
779 | if (sent > 0) { |
| 3369 | 728 | outputCount += sent; | |
| 3370 | 728 | continue; | |
| 3371 | } | ||
| 3372 |
4/8✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 51 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 51 times.
|
51 | bool transient = (sent == 0) || errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS; |
| 3373 |
2/8✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 51 times.
|
51 | if (transient && (GetMonotonicTimeMS() - startTime) < SEND_BUDGET_MS) { |
| 3374 | // Genuine buffer pressure clears in well under the budget. | ||
| 3375 | ✗ | std::this_thread::sleep_for(std::chrono::microseconds(250)); | |
| 3376 | ✗ | continue; | |
| 3377 | } | ||
| 3378 | // Either a hard error for this destination, or it is still refusing the | ||
| 3379 | // packet after the budget. Step past it rather than retrying or | ||
| 3380 | // bailing out entirely, so the remaining healthy remotes still get this | ||
| 3381 | // frame's packet - one unreachable remote must not desync the rest. | ||
| 3382 | 51 | ++outputCount; | |
| 3383 | 51 | ++undelivered; | |
| 3384 | } | ||
| 3385 |
2/2✓ Branch 0 taken 51 times.
✓ Branch 1 taken 696 times.
|
747 | if (undelivered) { |
| 3386 | 51 | int err = errno; | |
| 3387 | 51 | undeliveredSinceLog += undelivered; | |
| 3388 |
1/2✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
|
51 | long long now = GetMonotonicTimeMS(); |
| 3389 |
3/4✓ Branch 0 taken 48 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
|
51 | if (!undeliveredLoggedAt || now - undeliveredLoggedAt >= UNDELIVERED_LOG_INTERVAL_MS) { |
| 3390 |
3/6✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | LogErr(VB_SYNC, "Error: Unable to send multisync packet: %s (%d/%d sent; %d undelivered %s)\n", |
| 3391 | FPPstrerror(err), msgCount - undelivered, msgCount, undeliveredSinceLog, | ||
| 3392 | undeliveredLoggedAt ? "in the last minute" : ""); | ||
| 3393 | 3 | undeliveredSinceLog = 0; | |
| 3394 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | undeliveredLoggedAt = now ? now : 1; |
| 3395 | } | ||
| 3396 | } | ||
| 3397 |
2/2✓ Branch 0 taken 747 times.
✓ Branch 1 taken 1425 times.
|
2172 | } |
| 3398 | |||
| 3399 | // A broadcast is looped back to this host's own sockets (multicast is not: | ||
| 3400 | // IP_MULTICAST_LOOP is off). An FPP command sent to all hosts is already run | ||
| 3401 | // locally by its sender, and plugin data is meant for the other hosts, so the | ||
| 3402 | // copy that comes back must not be dispatched a second time. Remember what | ||
| 3403 | // was just sent and drop the matching echo. Only those two types: other | ||
| 3404 | // local senders (scripts, tools, tests on this box) still get through. | ||
| 3405 | static std::mutex s_ownSendsLock; | ||
| 3406 | static std::deque<std::pair<size_t, long long>> s_ownSends; // hash, monotonic ms | ||
| 3407 | |||
| 3408 | 8724 | static bool IsEchoCandidate(const void* buf, int len) { | |
| 3409 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8724 times.
|
8724 | if (len < (int)sizeof(ControlPkt)) { |
| 3410 | ✗ | return false; | |
| 3411 | } | ||
| 3412 | 8724 | uint8_t type = ((const ControlPkt*)buf)->pktType; | |
| 3413 |
3/4✓ Branch 0 taken 8712 times.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8712 times.
|
8724 | return type == CTRL_PKT_FPPCOMMAND || type == CTRL_PKT_PLUGIN; |
| 3414 | } | ||
| 3415 | 18 | static size_t HashPacket(const void* buf, int len) { | |
| 3416 | 18 | return std::hash<std::string_view>{}(std::string_view((const char*)buf, len)); | |
| 3417 | } | ||
| 3418 | 9 | static void RememberOwnSend(const void* buf, int len) { | |
| 3419 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | long long now = GetMonotonicTimeMS(); |
| 3420 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | std::lock_guard<std::mutex> lock(s_ownSendsLock); |
| 3421 |
5/8✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 9 times.
|
9 | while (!s_ownSends.empty() && (s_ownSends.size() >= 64 || now - s_ownSends.front().second > 2000)) { |
| 3422 | ✗ | s_ownSends.pop_front(); | |
| 3423 | } | ||
| 3424 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | s_ownSends.emplace_back(HashPacket(buf, len), now); |
| 3425 | 9 | } | |
| 3426 | 9 | static bool IsOwnEcho(const void* buf, int len) { | |
| 3427 | 9 | size_t h = HashPacket(buf, len); | |
| 3428 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | long long now = GetMonotonicTimeMS(); |
| 3429 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | std::lock_guard<std::mutex> lock(s_ownSendsLock); |
| 3430 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | for (auto it = s_ownSends.begin(); it != s_ownSends.end(); ++it) { |
| 3431 |
3/6✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
|
9 | if (it->first == h && now - it->second <= 2000) { |
| 3432 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | s_ownSends.erase(it); |
| 3433 | 9 | return true; | |
| 3434 | } | ||
| 3435 | } | ||
| 3436 | ✗ | return false; | |
| 3437 | 9 | } | |
| 3438 | |||
| 3439 | 1102 | void MultiSync::SendControlPacket(void* outBuf, int len) { | |
| 3440 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1102 times.
|
1102 | if (WillLog(LOG_EXCESSIVE, VB_SYNC)) { |
| 3441 | ✗ | LogExcess(VB_SYNC, "SendControlPacket()\n"); | |
| 3442 | ✗ | HexDump("Sending Control packet with contents:", outBuf, len, VB_SYNC); | |
| 3443 | } | ||
| 3444 | |||
| 3445 | // Statically-configured unicast remotes (MultiSyncRemotes/ExtraRemotes). | ||
| 3446 | 1102 | SendControlPacketViaMsgs(m_destMsgs, m_destIovec, outBuf, len); | |
| 3447 | |||
| 3448 | // All known FPP remotes in Remote mode (MultiSyncUnicast setting). | ||
| 3449 |
2/2✓ Branch 0 taken 1070 times.
✓ Branch 1 taken 32 times.
|
1102 | if (m_sendUnicast) { |
| 3450 | 1070 | SendControlPacketViaMsgs(m_unicastDestMsgs, m_unicastDestIovec, outBuf, len); | |
| 3451 | } | ||
| 3452 |
2/2✓ Branch 0 taken 348 times.
✓ Branch 1 taken 754 times.
|
1102 | if (m_sendMulticast) { |
| 3453 | 348 | SendMulticastPacket(outBuf, len); | |
| 3454 | } | ||
| 3455 |
2/2✓ Branch 0 taken 320 times.
✓ Branch 1 taken 782 times.
|
1102 | if (m_sendBroadcast) { |
| 3456 | 320 | SendBroadcastPacket(outBuf, len); | |
| 3457 | } | ||
| 3458 | 1102 | } | |
| 3459 | |||
| 3460 | 37 | void MultiSync::UpdateUnicastDestinations() { | |
| 3461 | // Snapshot the addresses to resolve while holding m_systemsLock only | ||
| 3462 | // briefly, then release it before the rebuild. The rebuild takes | ||
| 3463 | // m_unicastUpdateLock, which must never be acquired while m_systemsLock is | ||
| 3464 | // held (see the overload below), so the snapshot and the rebuild cannot | ||
| 3465 | // share a lock scope. | ||
| 3466 | 37 | std::vector<std::string> addrsToResolve; | |
| 3467 | { | ||
| 3468 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 3469 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 37 times.
|
39 | for (auto& sys : m_remoteSystems) { |
| 3470 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (sys.supportsUnicast) { |
| 3471 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | addrsToResolve.push_back(sys.address); |
| 3472 | } | ||
| 3473 | } | ||
| 3474 | 37 | } | |
| 3475 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | UpdateUnicastDestinations(addrsToResolve); |
| 3476 | 37 | } | |
| 3477 | |||
| 3478 | 39 | void MultiSync::UpdateUnicastDestinations(const std::vector<std::string>& addrsToResolve) { | |
| 3479 | // Serialize rebuilds against each other -- DNS resolution below happens | ||
| 3480 | // without any other lock held, so without this, two overlapping calls could | ||
| 3481 | // interleave and the slower resolution could finish last and clobber a | ||
| 3482 | // newer result. Callers must not hold m_systemsLock here: ReloadSyncDestinations() | ||
| 3483 | // holds this lock and its tail reaches m_systemsLock through the no-arg | ||
| 3484 | // overload, so taking the two in the other order would deadlock. | ||
| 3485 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | std::unique_lock<std::mutex> updateLock(m_unicastUpdateLock); |
| 3486 | |||
| 3487 | // getaddrinfo() (via GetIPForHost()) is an unbounded, potentially slow | ||
| 3488 | // DNS/mDNS lookup, which is why the caller resolves from a snapshot rather | ||
| 3489 | // than iterating m_remoteSystems here: resolving under m_systemsLock used to | ||
| 3490 | // stall every other m_systemsLock caller (notably the frequently-polled | ||
| 3491 | // GetSystems()) for as long as the lookup took. GetIPForHost() itself is | ||
| 3492 | // reentrant/thread-safe, so it's fine to call without any MultiSync lock held. | ||
| 3493 | 39 | std::vector<struct sockaddr_in> newAddrs; | |
| 3494 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 39 times.
|
42 | for (auto& address : addrsToResolve) { |
| 3495 | struct sockaddr_in addr; | ||
| 3496 | 3 | memset(&addr, 0, sizeof(addr)); | |
| 3497 | 3 | addr.sin_family = AF_INET; | |
| 3498 | 3 | addr.sin_port = htons(FPP_CTRL_PORT); | |
| 3499 | |||
| 3500 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | std::string ipAd = address; |
| 3501 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | if (GetIPForHost(ipAd)) { |
| 3502 | 3 | addr.sin_addr.s_addr = inet_addr(ipAd.c_str()); | |
| 3503 | } else { | ||
| 3504 | ✗ | addr.sin_addr.s_addr = inet_addr(address.c_str()); | |
| 3505 | } | ||
| 3506 | // Skip anything that didn't resolve to a usable IPv4 address | ||
| 3507 | // (e.g. an IPv6-only peer); sockaddr_in can't represent it. | ||
| 3508 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
3 | if (addr.sin_addr.s_addr == INADDR_NONE || addr.sin_addr.s_addr == 0) { |
| 3509 | ✗ | continue; | |
| 3510 | } | ||
| 3511 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | newAddrs.push_back(addr); |
| 3512 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | } |
| 3513 | |||
| 3514 | // Build the new sockaddr list, then swap it into place under m_socketLock. | ||
| 3515 | // m_socketLock is the innermost lock here and is never held while acquiring | ||
| 3516 | // m_systemsLock, matching the m_systemsLock -> m_socketLock order used by Ping(). | ||
| 3517 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3518 | // Skip any address already covered by the statically-configured remote list | ||
| 3519 | // (m_destAddr, rebuilt by ReloadSyncDestinations(), which holds the same | ||
| 3520 | // m_unicastUpdateLock we hold here while it swaps) so | ||
| 3521 | // a remote that is both individually selected for unicast AND picked up by | ||
| 3522 | // "all known remotes" only receives each packet once. The same set also | ||
| 3523 | // dedupes the all-known list against itself (e.g. a remote known under both | ||
| 3524 | // a hostname and an IP). | ||
| 3525 | 39 | std::set<uint32_t> seen; | |
| 3526 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 39 times.
|
81 | for (auto& s : m_destAddr) { |
| 3527 |
1/2✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
|
42 | seen.insert(s.sin_addr.s_addr); |
| 3528 | } | ||
| 3529 | 39 | m_unicastDestAddr.clear(); | |
| 3530 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | m_unicastDestAddr.reserve(newAddrs.size()); |
| 3531 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 39 times.
|
42 | for (auto& addr : newAddrs) { |
| 3532 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | if (seen.insert(addr.sin_addr.s_addr).second) { |
| 3533 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | m_unicastDestAddr.push_back(addr); |
| 3534 | } | ||
| 3535 | } | ||
| 3536 | 39 | m_unicastDestMsgs.clear(); | |
| 3537 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | m_unicastDestMsgs.reserve(m_unicastDestAddr.size()); |
| 3538 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 39 times.
|
42 | for (size_t x = 0; x < m_unicastDestAddr.size(); x++) { |
| 3539 | struct mmsghdr msg; | ||
| 3540 | 3 | memset(&msg, 0, sizeof(msg)); | |
| 3541 | 3 | msg.msg_hdr.msg_name = &m_unicastDestAddr[x]; | |
| 3542 | 3 | msg.msg_hdr.msg_namelen = sizeof(sockaddr_in); | |
| 3543 | 3 | msg.msg_hdr.msg_iov = &m_unicastDestIovec; | |
| 3544 | 3 | msg.msg_hdr.msg_iovlen = 1; | |
| 3545 | 3 | msg.msg_len = 0; | |
| 3546 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | m_unicastDestMsgs.push_back(msg); |
| 3547 | } | ||
| 3548 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | LogDebug(VB_SYNC, "%d unicast MultiSync destinations (all known remotes)\n", |
| 3549 | (int)m_unicastDestMsgs.size()); | ||
| 3550 | 39 | } | |
| 3551 | 4187 | void MultiSync::SendBroadcastPacket(void* outBuf, int len) { | |
| 3552 |
2/4✓ Branch 0 taken 4187 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4187 times.
|
4187 | if (WillLog(LOG_EXCESSIVE, VB_SYNC)) { |
| 3553 | ✗ | HexDump("Sending Broadcast packet with contents:", outBuf, len, VB_SYNC); | |
| 3554 | } | ||
| 3555 | |||
| 3556 | 4187 | bool echoes = IsEchoCandidate(outBuf, len); | |
| 3557 |
1/2✓ Branch 0 taken 4187 times.
✗ Branch 1 not taken.
|
4187 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3558 |
1/2✓ Branch 0 taken 4187 times.
✗ Branch 1 not taken.
|
4187 | long long startTime = GetMonotonicTimeMS(); |
| 3559 |
2/2✓ Branch 0 taken 4861 times.
✓ Branch 1 taken 4187 times.
|
9048 | for (auto& a : m_interfaces) { |
| 3560 | struct sockaddr_in bda; | ||
| 3561 | 4861 | memset((void*)&bda, 0, sizeof(struct sockaddr_in)); | |
| 3562 | 4861 | bda.sin_family = AF_INET; | |
| 3563 | 4861 | bda.sin_port = htons(FPP_CTRL_PORT); | |
| 3564 | 4861 | bda.sin_addr.s_addr = a.second.broadcastAddress; | |
| 3565 | |||
| 3566 | // One datagram per interface, and each comes back to us: remember | ||
| 3567 | // each one, or the second interface's echo runs the command again. | ||
| 3568 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 4852 times.
|
4861 | if (echoes) { |
| 3569 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | RememberOwnSend(outBuf, len); |
| 3570 | } | ||
| 3571 | |||
| 3572 |
3/4✓ Branch 0 taken 4861 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 4829 times.
|
4861 | if (int err = SendDatagramBounded(m_broadcastSock, outBuf, len, bda, startTime)) |
| 3573 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | LogSendFailure(err); |
| 3574 | } | ||
| 3575 | 4187 | } | |
| 3576 | 737 | void MultiSync::SendMulticastPacket(void* outBuf, int len) { | |
| 3577 |
1/2✓ Branch 0 taken 737 times.
✗ Branch 1 not taken.
|
737 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3578 |
1/2✓ Branch 0 taken 737 times.
✗ Branch 1 not taken.
|
737 | long long startTime = GetMonotonicTimeMS(); |
| 3579 |
2/2✓ Branch 0 taken 1474 times.
✓ Branch 1 taken 737 times.
|
2211 | for (auto& a : m_interfaces) { |
| 3580 | struct sockaddr_in bda; | ||
| 3581 | 1474 | memset((void*)&bda, 0, sizeof(struct sockaddr_in)); | |
| 3582 | 1474 | bda.sin_family = AF_INET; | |
| 3583 | 1474 | bda.sin_port = htons(FPP_CTRL_PORT); | |
| 3584 | 1474 | bda.sin_addr.s_addr = MULTISYNC_MULTICAST_ADD; | |
| 3585 | |||
| 3586 |
2/2✓ Branch 0 taken 220 times.
✓ Branch 1 taken 1254 times.
|
1474 | if (a.second.multicastSocket == -1) { |
| 3587 | // create the socket | ||
| 3588 | 220 | a.second.multicastSocket = OpenSocket(AF_INET, SOCK_DGRAM, 0); | |
| 3589 | |||
| 3590 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 220 times.
|
220 | if (a.second.multicastSocket < 0) { |
| 3591 | ✗ | LogErr(VB_SYNC, "Error opening Multicast socket for %s\n", a.second.interfaceName.c_str()); | |
| 3592 | } else { | ||
| 3593 | 220 | char loopch = 0; | |
| 3594 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 220 times.
|
220 | if (setsockopt(a.second.multicastSocket, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&loopch, sizeof(loopch)) < 0) { |
| 3595 | ✗ | LogErr(VB_SYNC, "Error setting IP_MULTICAST_LOOP for %s: %s\n", a.second.interfaceName.c_str(), FPPstrerror(errno)); | |
| 3596 | } | ||
| 3597 | #ifdef PLATFORM_OSX | ||
| 3598 | int idx = if_nametoindex(a.second.interfaceName.c_str()); | ||
| 3599 | if (setsockopt(a.second.multicastSocket, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx)) < 0) { | ||
| 3600 | LogErr(VB_SYNC, "Error setting IP_MULTICAST Device for %s: %s\n", a.second.interfaceName.c_str(), FPPstrerror(errno)); | ||
| 3601 | } | ||
| 3602 | #else | ||
| 3603 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 219 times.
|
220 | if (setsockopt(a.second.multicastSocket, SOL_SOCKET, SO_BINDTODEVICE, a.second.interfaceName.c_str(), a.second.interfaceName.size()) < 0) { |
| 3604 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | LogErr(VB_SYNC, "Error setting IP_MULTICAST Device for %s: %s\n", a.second.interfaceName.c_str(), FPPstrerror(errno)); |
| 3605 | } | ||
| 3606 | #endif | ||
| 3607 | } | ||
| 3608 | } | ||
| 3609 | |||
| 3610 |
1/2✓ Branch 0 taken 1474 times.
✗ Branch 1 not taken.
|
1474 | if (a.second.multicastSocket >= 0) { |
| 3611 |
3/4✓ Branch 0 taken 1474 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 150 times.
✓ Branch 3 taken 1324 times.
|
1474 | if (int err = SendDatagramBounded(a.second.multicastSocket, outBuf, len, bda, startTime)) |
| 3612 |
1/2✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
|
150 | LogSendFailure(err); |
| 3613 | } | ||
| 3614 | } | ||
| 3615 | 737 | } | |
| 3616 | 75 | void MultiSync::SendUnicastPacket(const std::string& address, void* outBuf, int len) { | |
| 3617 |
2/4✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 75 times.
|
75 | if (WillLog(LOG_EXCESSIVE, VB_SYNC)) { |
| 3618 | ✗ | LogExcess(VB_SYNC, "SendControlPacket()\n"); | |
| 3619 | ✗ | HexDump("Sending Control packet with contents:", outBuf, len, VB_SYNC); | |
| 3620 | } | ||
| 3621 | struct sockaddr_in dest_addr; | ||
| 3622 | 75 | memset(&dest_addr, 0, sizeof(dest_addr)); | |
| 3623 | 75 | dest_addr.sin_family = AF_INET; | |
| 3624 | |||
| 3625 |
1/2✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
|
75 | std::string ipAd = address; |
| 3626 |
3/4✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 67 times.
✓ Branch 3 taken 8 times.
|
75 | if (GetIPForHost(ipAd)) { |
| 3627 | 67 | dest_addr.sin_addr.s_addr = inet_addr(ipAd.c_str()); | |
| 3628 | } else { | ||
| 3629 | 8 | dest_addr.sin_addr.s_addr = inet_addr(address.c_str()); | |
| 3630 | } | ||
| 3631 | 75 | dest_addr.sin_port = htons(FPP_CTRL_PORT); | |
| 3632 |
1/2✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
|
75 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3633 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 68 times.
|
75 | if (m_controlSock >= 0) { |
| 3634 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | sendto(m_controlSock, outBuf, len, MSG_DONTWAIT, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); |
| 3635 | } else { | ||
| 3636 | 68 | int uSock = OpenSocket(AF_INET, SOCK_DGRAM, 0); | |
| 3637 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
|
68 | if (uSock < 0) { |
| 3638 | ✗ | LogErr(VB_SYNC, "Error opening Unicast socket\n"); | |
| 3639 | ✗ | return; | |
| 3640 | } | ||
| 3641 | |||
| 3642 |
1/2✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
|
68 | sendto(uSock, outBuf, len, MSG_DONTWAIT, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); |
| 3643 |
1/2✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
|
68 | close(uSock); |
| 3644 | } | ||
| 3645 |
2/4✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75 times.
✗ Branch 3 not taken.
|
75 | } |
| 3646 | |||
| 3647 | 3334 | bool MultiSync::FillInInterfaces() { | |
| 3648 | struct ifaddrs *interfaces, *tmp; | ||
| 3649 | 3334 | getifaddrs(&interfaces); | |
| 3650 | 3334 | tmp = interfaces; | |
| 3651 | |||
| 3652 | 3334 | bool change = false; | |
| 3653 | // An interface can hold its 169.254 fallback and a DHCP/static address at | ||
| 3654 | // the same time. Only one is kept per interface, so prefer the routable | ||
| 3655 | // one, whatever order getifaddrs() lists them in. | ||
| 3656 | 3334 | std::set<std::string> haveRoutable; | |
| 3657 | |||
| 3658 |
1/2✓ Branch 0 taken 3334 times.
✗ Branch 1 not taken.
|
3334 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3659 | 3334 | std::map<std::string, std::string> before; | |
| 3660 |
2/2✓ Branch 0 taken 2597 times.
✓ Branch 1 taken 3334 times.
|
5931 | for (auto& [name, info] : m_interfaces) { |
| 3661 |
2/4✓ Branch 0 taken 2597 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2597 times.
✗ Branch 3 not taken.
|
2597 | before[name] = info.interfaceAddress; |
| 3662 | } | ||
| 3663 |
2/2✓ Branch 0 taken 18037 times.
✓ Branch 1 taken 3334 times.
|
21371 | while (tmp) { |
| 3664 |
3/4✓ Branch 0 taken 18037 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6993 times.
✓ Branch 3 taken 11044 times.
|
18037 | if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) { |
| 3665 | // Check if interface is UP and RUNNING before adding it | ||
| 3666 |
1/2✓ Branch 0 taken 6993 times.
✗ Branch 1 not taken.
|
6993 | if (isSupportedForMultisync("", tmp->ifa_name) && |
| 3667 |
5/6✓ Branch 0 taken 3655 times.
✓ Branch 1 taken 3338 times.
✓ Branch 2 taken 3655 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3655 times.
✓ Branch 5 taken 3338 times.
|
10648 | (tmp->ifa_flags & IFF_UP) && |
| 3668 |
1/2✓ Branch 0 taken 3655 times.
✗ Branch 1 not taken.
|
3655 | (tmp->ifa_flags & IFF_RUNNING)) { |
| 3669 | // skip the usb* interfaces as we won't support multisync on those | ||
| 3670 | #ifdef PLATFORM_OSX | ||
| 3671 | struct sockaddr_in* ba = (struct sockaddr_in*)(tmp->ifa_dstaddr); | ||
| 3672 | #else | ||
| 3673 | 3655 | struct sockaddr_in* ba = (struct sockaddr_in*)(tmp->ifa_ifu.ifu_broadaddr); | |
| 3674 | #endif | ||
| 3675 | 3655 | struct sockaddr_in* sa = (struct sockaddr_in*)(tmp->ifa_addr); | |
| 3676 | 3655 | char abuf[INET_ADDRSTRLEN] = {0}; | |
| 3677 | 3655 | inet_ntop(AF_INET, &sa->sin_addr, abuf, sizeof(abuf)); | |
| 3678 |
8/30✓ Branch 0 taken 3655 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3655 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3655 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 3655 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 3655 times.
✓ Branch 16 taken 3655 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 3655 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✓ Branch 21 taken 3655 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
10965 | if (IsIPv4LinkLocal(abuf) && haveRoutable.contains(tmp->ifa_name)) { |
| 3679 | ✗ | tmp = tmp->ifa_next; | |
| 3680 | ✗ | continue; | |
| 3681 | } | ||
| 3682 |
3/6✓ Branch 0 taken 3655 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3655 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3655 times.
✗ Branch 5 not taken.
|
7310 | if (!IsIPv4LinkLocal(abuf)) { |
| 3683 |
2/4✓ Branch 0 taken 3655 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3655 times.
✗ Branch 3 not taken.
|
7310 | haveRoutable.insert(tmp->ifa_name); |
| 3684 | } | ||
| 3685 | |||
| 3686 |
2/4✓ Branch 0 taken 3655 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3655 times.
✗ Branch 3 not taken.
|
7310 | NetInterfaceInfo& info = m_interfaces[tmp->ifa_name]; |
| 3687 |
1/2✓ Branch 0 taken 3655 times.
✗ Branch 1 not taken.
|
3655 | change |= info.interfaceName == ""; |
| 3688 |
1/2✓ Branch 0 taken 3655 times.
✗ Branch 1 not taken.
|
3655 | info.interfaceName = tmp->ifa_name; |
| 3689 |
1/2✓ Branch 0 taken 3655 times.
✗ Branch 1 not taken.
|
3655 | info.interfaceAddress = abuf; |
| 3690 | 3655 | info.address = sa->sin_addr.s_addr; | |
| 3691 | 3655 | info.broadcastAddress = ba->sin_addr.s_addr; | |
| 3692 | } | ||
| 3693 |
1/2✓ Branch 0 taken 11044 times.
✗ Branch 1 not taken.
|
11044 | } else if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET6) { |
| 3694 | // FIXME for ipv6 multisync | ||
| 3695 | } | ||
| 3696 | 18037 | tmp = tmp->ifa_next; | |
| 3697 | } | ||
| 3698 | 3334 | freeifaddrs(interfaces); | |
| 3699 |
2/2✓ Branch 0 taken 4629 times.
✓ Branch 1 taken 3334 times.
|
7963 | for (auto& [name, info] : m_interfaces) { |
| 3700 |
1/2✓ Branch 0 taken 4629 times.
✗ Branch 1 not taken.
|
4629 | auto it = before.find(name); |
| 3701 |
4/4✓ Branch 0 taken 2597 times.
✓ Branch 1 taken 2032 times.
✓ Branch 2 taken 144 times.
✓ Branch 3 taken 2453 times.
|
4629 | change |= it == before.end() || it->second != info.interfaceAddress; |
| 3702 | } | ||
| 3703 | 3334 | return change; | |
| 3704 | 3334 | } | |
| 3705 | 741 | bool MultiSync::RemoveInterface(const std::string& interface) { | |
| 3706 |
1/2✓ Branch 0 taken 741 times.
✗ Branch 1 not taken.
|
741 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3707 |
1/2✓ Branch 0 taken 741 times.
✗ Branch 1 not taken.
|
741 | auto it = m_interfaces.find(interface); |
| 3708 |
2/2✓ Branch 0 taken 223 times.
✓ Branch 1 taken 518 times.
|
741 | if (it != m_interfaces.end()) { |
| 3709 |
1/2✓ Branch 0 taken 223 times.
✗ Branch 1 not taken.
|
223 | LogDebug(VB_SYNC, "Removing interface %s - %s\n", it->second.interfaceName.c_str(), it->second.interfaceAddress.c_str()); |
| 3710 | |||
| 3711 | struct ip_mreq mreq; | ||
| 3712 | 223 | memset(&mreq, 0, sizeof(mreq)); | |
| 3713 | 223 | mreq.imr_multiaddr.s_addr = inet_addr(MULTISYNC_MULTICAST_ADDRESS); | |
| 3714 | 223 | mreq.imr_interface.s_addr = it->second.address; | |
| 3715 | 223 | int rc = 0; | |
| 3716 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 200 times.
|
223 | if ((rc = setsockopt(m_receiveSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq))) < 0) { |
| 3717 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | LogDebug(VB_SYNC, " Did not drop Multicast membership for interface %s - %s\n", it->second.interfaceName.c_str(), it->second.interfaceAddress.c_str()); |
| 3718 | } | ||
| 3719 |
1/2✓ Branch 0 taken 223 times.
✗ Branch 1 not taken.
|
223 | m_interfaces.erase(it); |
| 3720 | 223 | return true; | |
| 3721 | } | ||
| 3722 | 518 | return false; | |
| 3723 | 741 | } | |
| 3724 | |||
| 3725 | /* | ||
| 3726 | * | ||
| 3727 | */ | ||
| 3728 | 5435 | void MultiSync::InitControlPacket(ControlPkt* pkt) { | |
| 3729 | 5435 | bzero(pkt, sizeof(ControlPkt)); | |
| 3730 | |||
| 3731 | 5435 | pkt->fppd[0] = 'F'; | |
| 3732 | 5435 | pkt->fppd[1] = 'P'; | |
| 3733 | 5435 | pkt->fppd[2] = 'P'; | |
| 3734 | 5435 | pkt->fppd[3] = 'D'; | |
| 3735 | 5435 | pkt->pktType = 0; | |
| 3736 | 5435 | pkt->extraDataLen = 0; | |
| 3737 | 5435 | } | |
| 3738 | |||
| 3739 | /* | ||
| 3740 | * | ||
| 3741 | */ | ||
| 3742 | 1888 | int MultiSync::OpenReceiveSocket(void) { | |
| 3743 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | LogDebug(VB_SYNC, "OpenReceiveSocket()\n"); |
| 3744 | |||
| 3745 | int UniverseOctet[2]; | ||
| 3746 | int i; | ||
| 3747 | char strMulticastGroup[16]; | ||
| 3748 | |||
| 3749 | /* set up socket */ | ||
| 3750 | 1888 | m_receiveSock = OpenSocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); | |
| 3751 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1888 times.
|
1888 | if (m_receiveSock < 0) { |
| 3752 | ✗ | LogErr(VB_SYNC, "Error opening Receive socket; %s\n", FPPstrerror(errno)); | |
| 3753 | ✗ | WarningHolder::AddWarning(42, "MultiSync: could not open receive socket"); | |
| 3754 | ✗ | return 0; | |
| 3755 | } | ||
| 3756 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | LogDebug(VB_SYNC, "Receive socket: %d\n", m_receiveSock); |
| 3757 | |||
| 3758 | // The default buffer (212992 bytes) holds about 166 datagrams -- each | ||
| 3759 | // costs ~1.3KB of it whatever its size -- so any stall of the main loop | ||
| 3760 | // (an output reload, a burst of pings from a large show) dropped sync and | ||
| 3761 | // ping packets in the kernel. 1MB (the kernel doubles it) holds ~1600. | ||
| 3762 | // A plain SO_RCVBUF is clamped to rmem_max, which only fppinit raises (to | ||
| 3763 | // 8MB): where it hasn't run, the request came back at the default. So | ||
| 3764 | // force it first, as the bridge sockets do (fppd runs as root). | ||
| 3765 | 1888 | int rcvBuf = 1024 * 1024; | |
| 3766 | 1888 | bool forced = false; | |
| 3767 | #ifdef SO_RCVBUFFORCE | ||
| 3768 | 1888 | forced = setsockopt(m_receiveSock, SOL_SOCKET, SO_RCVBUFFORCE, &rcvBuf, sizeof(rcvBuf)) == 0; | |
| 3769 | #endif | ||
| 3770 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 1888 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1888 times.
|
1888 | if (!forced && setsockopt(m_receiveSock, SOL_SOCKET, SO_RCVBUF, &rcvBuf, sizeof(rcvBuf)) < 0) { |
| 3771 | ✗ | LogWarn(VB_SYNC, "Error setting SO_RCVBUF on the receive socket; %s\n", FPPstrerror(errno)); | |
| 3772 | } | ||
| 3773 | |||
| 3774 | 1888 | bzero((char*)&m_receiveSrcAddr, sizeof(m_receiveSrcAddr)); | |
| 3775 | 1888 | m_receiveSrcAddr.sin_family = AF_INET; | |
| 3776 | 1888 | m_receiveSrcAddr.sin_addr.s_addr = htonl(INADDR_ANY); | |
| 3777 | 1888 | m_receiveSrcAddr.sin_port = htons(FPP_CTRL_PORT); | |
| 3778 | |||
| 3779 | 1888 | int optval = 1; | |
| 3780 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1888 times.
|
1888 | if (setsockopt(m_receiveSock, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) < 0) { |
| 3781 | ✗ | LogErr(VB_SYNC, "Error turning on SO_REUSEPORT; %s\n", FPPstrerror(errno)); | |
| 3782 | ✗ | return 0; | |
| 3783 | } | ||
| 3784 | |||
| 3785 | // Bind the socket to address/port | ||
| 3786 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1887 times.
|
1888 | if (bind(m_receiveSock, (struct sockaddr*)&m_receiveSrcAddr, sizeof(m_receiveSrcAddr)) < 0) { |
| 3787 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | LogErr(VB_SYNC, "Error binding socket; %s\n", FPPstrerror(errno)); |
| 3788 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
3 | WarningHolder::AddWarning(42, "MultiSync: could not bind receive socket (port in use?)"); |
| 3789 | 1 | return 0; | |
| 3790 | } | ||
| 3791 | |||
| 3792 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
|
1887 | if (setsockopt(m_receiveSock, IPPROTO_IP, IP_PKTINFO, &optval, sizeof(optval)) < 0) { |
| 3793 | ✗ | LogErr(VB_SYNC, "Error calling setsockopt; %s\n", FPPstrerror(errno)); | |
| 3794 | ✗ | return 0; | |
| 3795 | } | ||
| 3796 | |||
| 3797 |
3/4✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 37 times.
✓ Branch 3 taken 1850 times.
|
1887 | if (getFPPmode() == REMOTE_MODE) { |
| 3798 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | int remoteOffsetInt = getSettingInt("remoteOffset"); |
| 3799 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 36 times.
|
37 | if (remoteOffsetInt) |
| 3800 | 1 | m_remoteOffset = (float)remoteOffsetInt * -0.001; | |
| 3801 | else | ||
| 3802 | 36 | m_remoteOffset = 0.0; | |
| 3803 | |||
| 3804 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | LogDebug(VB_SYNC, "Using remoteOffset of %.3f\n", m_remoteOffset); |
| 3805 | } | ||
| 3806 | |||
| 3807 | 1887 | memset(rcvMsgs, 0, sizeof(rcvMsgs)); | |
| 3808 |
2/2✓ Branch 0 taken 22644 times.
✓ Branch 1 taken 1887 times.
|
24531 | for (int i = 0; i < MAX_MS_RCV_MSG; i++) { |
| 3809 | 22644 | rcvIovecs[i].iov_base = rcvBuffers[i]; | |
| 3810 | 22644 | rcvIovecs[i].iov_len = MAX_MS_RCV_BUFSIZE; | |
| 3811 | 22644 | rcvMsgs[i].msg_hdr.msg_iov = &rcvIovecs[i]; | |
| 3812 | 22644 | rcvMsgs[i].msg_hdr.msg_iovlen = 1; | |
| 3813 | 22644 | rcvMsgs[i].msg_hdr.msg_name = &rcvSrcAddr[i]; | |
| 3814 | 22644 | rcvMsgs[i].msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); | |
| 3815 | 22644 | rcvMsgs[i].msg_hdr.msg_control = &rcvCmbuf[i]; | |
| 3816 | 22644 | rcvMsgs[i].msg_hdr.msg_controllen = 0x100; | |
| 3817 | } | ||
| 3818 | |||
| 3819 |
1/2✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
|
1887 | setupMulticastReceive(false); |
| 3820 | 1887 | return 1; | |
| 3821 | } | ||
| 3822 | 15007 | bool MultiSync::isSupportedForMultisync(const char* address, const char* intface) { | |
| 3823 |
7/8✓ Branch 0 taken 8014 times.
✓ Branch 1 taken 6993 times.
✓ Branch 2 taken 8009 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 3980 times.
✓ Branch 5 taken 4029 times.
✓ Branch 6 taken 3980 times.
✗ Branch 7 not taken.
|
15007 | if (strlen(address) > 3 && address[0] == '1' && address[1] == '2' && address[2] == '7') { |
| 3824 | // the entire 127.* subnet is localhost | ||
| 3825 | 3980 | return false; | |
| 3826 | } | ||
| 3827 |
5/8✓ Branch 0 taken 11027 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7685 times.
✓ Branch 3 taken 3342 times.
✓ Branch 4 taken 7685 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 7685 times.
|
11027 | if (!strncmp(intface, "usb", 3) || !strcmp(intface, "lo") || !strncmp(intface, "tether", 6) || !strncmp(intface, "SoftAp", 6)) { |
| 3828 | 3342 | return false; | |
| 3829 | } | ||
| 3830 | 7685 | return true; | |
| 3831 | } | ||
| 3832 | |||
| 3833 | 7013 | void MultiSync::setupMulticastReceive(bool cycle) { | |
| 3834 |
1/2✓ Branch 0 taken 7013 times.
✗ Branch 1 not taken.
|
7013 | LogDebug(VB_SYNC, "setupMulticastReceive()\n"); |
| 3835 | // loop through all the interfaces and subscribe to the group | ||
| 3836 |
1/2✓ Branch 0 taken 7013 times.
✗ Branch 1 not taken.
|
7013 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 3837 |
2/2✓ Branch 0 taken 7798 times.
✓ Branch 1 taken 7013 times.
|
14811 | for (auto& a : m_interfaces) { |
| 3838 |
1/2✓ Branch 0 taken 7798 times.
✗ Branch 1 not taken.
|
7798 | LogDebug(VB_SYNC, " Adding interface %s - %s\n", a.second.interfaceName.c_str(), a.second.interfaceAddress.c_str()); |
| 3839 | struct ip_mreq mreq; | ||
| 3840 | 7798 | memset(&mreq, 0, sizeof(mreq)); | |
| 3841 | 7798 | mreq.imr_multiaddr.s_addr = inet_addr(MULTISYNC_MULTICAST_ADDRESS); | |
| 3842 | 7798 | mreq.imr_interface.s_addr = a.second.address; | |
| 3843 | 7798 | int rc = 0; | |
| 3844 |
2/2✓ Branch 0 taken 3519 times.
✓ Branch 1 taken 4279 times.
|
7798 | if (cycle) { |
| 3845 | 3519 | setsockopt(m_receiveSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); | |
| 3846 | } | ||
| 3847 |
2/2✓ Branch 0 taken 2938 times.
✓ Branch 1 taken 4860 times.
|
7798 | if ((rc = setsockopt(m_receiveSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) < 0) { |
| 3848 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2938 times.
|
2938 | if (m_broadcastSock < 0) { |
| 3849 | // first time through, log as warning, otherwise error is likely due to already being subscribed | ||
| 3850 | ✗ | LogWarn(VB_SYNC, " Could not setup Multicast Group for interface %s rc: %d\n", a.second.interfaceName.c_str(), rc); | |
| 3851 | } else { | ||
| 3852 |
1/2✓ Branch 0 taken 2938 times.
✗ Branch 1 not taken.
|
2938 | LogDebug(VB_SYNC, " Could not setup Multicast Group for interface %s rc: %d\n", a.second.interfaceName.c_str(), rc); |
| 3853 | } | ||
| 3854 | } | ||
| 3855 | } | ||
| 3856 | 7013 | } | |
| 3857 | |||
| 3858 | // True if a later message in this recvmmsg batch supersedes message i (same | ||
| 3859 | // sync file type and sync packet type), so only the newest need be acted on. | ||
| 3860 | // Entries are one per received message, with a null buffer for messages that | ||
| 3861 | // were not received or are too short to hold a ControlPkt, so the indexes line | ||
| 3862 | // up with the msg indexes the caller uses. Only packets long enough to hold | ||
| 3863 | // the SyncPkt that follows the header can be compared. | ||
| 3864 | 19613 | static bool shouldSkipPacket(int i, int num, const std::vector<std::pair<unsigned char*, int>>& rcvBuffers) { | |
| 3865 | 19613 | constexpr int MIN_SYNC_LEN = (int)(sizeof(ControlPkt) + sizeof(SyncPkt)); | |
| 3866 |
5/6✓ Branch 0 taken 19613 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5813 times.
✓ Branch 3 taken 13800 times.
✓ Branch 4 taken 5813 times.
✓ Branch 5 taken 13800 times.
|
19613 | if (rcvBuffers[i].first == nullptr || rcvBuffers[i].second < MIN_SYNC_LEN) { |
| 3867 | 5813 | return false; | |
| 3868 | } | ||
| 3869 | 13800 | ControlPkt* pkt = (ControlPkt*)(rcvBuffers[i].first); | |
| 3870 |
2/2✓ Branch 0 taken 9875 times.
✓ Branch 1 taken 3925 times.
|
13800 | if (pkt->pktType != CTRL_PKT_SYNC) { |
| 3871 | 9875 | return false; | |
| 3872 | } | ||
| 3873 | 3925 | SyncPkt* spkt = (SyncPkt*)(((char*)pkt) + sizeof(ControlPkt)); | |
| 3874 |
2/2✓ Branch 0 taken 975 times.
✓ Branch 1 taken 3374 times.
|
4349 | for (int x = i + 1; x < num; x++) { |
| 3875 |
5/6✓ Branch 0 taken 975 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 934 times.
✓ Branch 4 taken 41 times.
✓ Branch 5 taken 934 times.
|
975 | if (rcvBuffers[x].first == nullptr || rcvBuffers[x].second < MIN_SYNC_LEN) { |
| 3876 | 41 | continue; | |
| 3877 | } | ||
| 3878 | 934 | ControlPkt* npkt = (ControlPkt*)(rcvBuffers[x].first); | |
| 3879 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 890 times.
|
934 | if (npkt->pktType != CTRL_PKT_SYNC) { |
| 3880 | 44 | continue; | |
| 3881 | } | ||
| 3882 | 890 | SyncPkt* snpkt = (SyncPkt*)(((char*)npkt) + sizeof(ControlPkt)); | |
| 3883 |
4/4✓ Branch 0 taken 633 times.
✓ Branch 1 taken 257 times.
✓ Branch 2 taken 551 times.
✓ Branch 3 taken 82 times.
|
890 | if (spkt->fileType == snpkt->fileType && spkt->pktType == snpkt->pktType) { |
| 3884 | 551 | return true; | |
| 3885 | } | ||
| 3886 | } | ||
| 3887 | 3374 | return false; | |
| 3888 | } | ||
| 3889 | |||
| 3890 | /* | ||
| 3891 | * | ||
| 3892 | */ | ||
| 3893 | 5625 | void MultiSync::ProcessControlPacket(bool pingOnly) { | |
| 3894 | 5625 | LogExcess(VB_SYNC, "ProcessControlPacket()\n"); | |
| 3895 | |||
| 3896 | ControlPkt* pkt; | ||
| 3897 | |||
| 3898 | 5625 | int msgcnt = recvmmsg(m_receiveSock, rcvMsgs, MAX_MS_RCV_MSG, MSG_DONTWAIT, nullptr); | |
| 3899 | // Batches read per wakeup: the socket is level-triggered, so the rest is | ||
| 3900 | // read on the next pass. Draining until empty let a flood of packets keep | ||
| 3901 | // the main loop in here for as long as it lasted. | ||
| 3902 | 5625 | int batches = 1; | |
| 3903 |
2/2✓ Branch 0 taken 6765 times.
✓ Branch 1 taken 5625 times.
|
12390 | while (msgcnt > 0) { |
| 3904 | // One entry per received message, buffer and length, so that the | ||
| 3905 | // indexes shouldSkipPacket() walks are the same msg indexes used below. | ||
| 3906 | // Messages that failed or are too short for a ControlPkt get a null | ||
| 3907 | // placeholder rather than being left out of the vector. | ||
| 3908 | 6765 | std::vector<std::pair<unsigned char*, int>> v; | |
| 3909 |
1/2✓ Branch 0 taken 6765 times.
✗ Branch 1 not taken.
|
6765 | v.reserve(msgcnt); |
| 3910 |
2/2✓ Branch 0 taken 19743 times.
✓ Branch 1 taken 6765 times.
|
26508 | for (int msg = 0; msg < msgcnt; msg++) { |
| 3911 | 19743 | int len = rcvMsgs[msg].msg_len; | |
| 3912 |
2/2✓ Branch 0 taken 130 times.
✓ Branch 1 taken 19613 times.
|
19743 | if (len < (int)sizeof(ControlPkt)) { |
| 3913 |
1/2✓ Branch 0 taken 130 times.
✗ Branch 1 not taken.
|
130 | v.emplace_back(nullptr, 0); |
| 3914 | 130 | continue; | |
| 3915 | } | ||
| 3916 | 19613 | unsigned char* inBuf = rcvBuffers[msg]; | |
| 3917 | 19613 | inBuf[len] = 0; | |
| 3918 |
1/2✓ Branch 0 taken 19613 times.
✗ Branch 1 not taken.
|
19613 | v.emplace_back(inBuf, len); |
| 3919 | } | ||
| 3920 |
1/2✓ Branch 0 taken 6765 times.
✗ Branch 1 not taken.
|
6765 | LogExcess(VB_SYNC, "ProcessControlPacket msgcnt: %d\n", msgcnt); |
| 3921 |
2/2✓ Branch 0 taken 19743 times.
✓ Branch 1 taken 6765 times.
|
26508 | for (int msg = 0; msg < msgcnt; msg++) { |
| 3922 | 19743 | int len = rcvMsgs[msg].msg_len; | |
| 3923 |
2/2✓ Branch 0 taken 56 times.
✓ Branch 1 taken 19687 times.
|
19743 | if (len <= 0) { |
| 3924 | // an empty datagram: recvmmsg() itself succeeded (a failure | ||
| 3925 | // ends the batch), so there is no error to report | ||
| 3926 | 1596 | continue; | |
| 3927 | } | ||
| 3928 | 19687 | unsigned char* inBuf = rcvBuffers[msg]; | |
| 3929 | |||
| 3930 |
2/2✓ Branch 0 taken 74 times.
✓ Branch 1 taken 19613 times.
|
19687 | if (len < sizeof(ControlPkt)) { |
| 3931 |
3/4✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 72 times.
|
74 | if (ShouldLogBadPacket()) { |
| 3932 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogErr(VB_SYNC, "Error: Received control packet too short\n"); |
| 3933 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | HexDump("Received data:", (void*)inBuf, len, VB_SYNC); |
| 3934 | } | ||
| 3935 | 74 | continue; | |
| 3936 | } | ||
| 3937 |
2/2✓ Branch 0 taken 551 times.
✓ Branch 1 taken 19062 times.
|
19613 | if (shouldSkipPacket(msg, msgcnt, v)) { |
| 3938 |
1/2✓ Branch 0 taken 551 times.
✗ Branch 1 not taken.
|
551 | LogExcess(VB_SYNC, "Skipping sync packet %d/%d\n", msg, msgcnt); |
| 3939 | 551 | continue; | |
| 3940 | } | ||
| 3941 | |||
| 3942 | char tmpIP[INET_ADDRSTRLEN]; | ||
| 3943 | 19062 | inet_ntop(rcvSrcAddr[msg].ss_family, &(((struct sockaddr_in*)&rcvSrcAddr[msg])->sin_addr), tmpIP, INET_ADDRSTRLEN); | |
| 3944 |
1/2✓ Branch 0 taken 19062 times.
✗ Branch 1 not taken.
|
19062 | std::string sourceIP(tmpIP); |
| 3945 | 19062 | std::string hostname; | |
| 3946 | |||
| 3947 | // Both system lists have to be read under m_systemsLock: UpdateSystem() | ||
| 3948 | // runs on the HTTP/mDNS probe threads and can push_back onto | ||
| 3949 | // m_remoteSystems, reallocating it out from under this loop. The | ||
| 3950 | // stats map work below then runs under m_statsLock alone -- the two | ||
| 3951 | // locks are never held at the same time. | ||
| 3952 | 19062 | bool isLocal = false; | |
| 3953 | { | ||
| 3954 |
1/2✓ Branch 0 taken 19062 times.
✗ Branch 1 not taken.
|
19062 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 3955 |
2/2✓ Branch 0 taken 19422 times.
✓ Branch 1 taken 19062 times.
|
38484 | for (auto& sys : m_localSystems) { |
| 3956 |
2/2✓ Branch 0 taken 4537 times.
✓ Branch 1 taken 14885 times.
|
19422 | if (sys.address == sourceIP) { |
| 3957 | 4537 | isLocal = true; | |
| 3958 | } | ||
| 3959 | } | ||
| 3960 |
2/2✓ Branch 0 taken 14525 times.
✓ Branch 1 taken 4537 times.
|
19062 | if (!isLocal) { |
| 3961 |
2/2✓ Branch 0 taken 2710403 times.
✓ Branch 1 taken 14525 times.
|
2724928 | for (auto& sys : m_remoteSystems) { |
| 3962 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2710402 times.
|
2710403 | if (sys.address == sourceIP) |
| 3963 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | hostname = sys.hostname; |
| 3964 | } | ||
| 3965 | } | ||
| 3966 | 19062 | } | |
| 3967 | |||
| 3968 | 19062 | pkt = (ControlPkt*)inBuf; | |
| 3969 | |||
| 3970 |
2/2✓ Branch 0 taken 18956 times.
✓ Branch 1 taken 106 times.
|
19062 | if ((pkt->fppd[0] != 'F') || |
| 3971 |
1/2✓ Branch 0 taken 18956 times.
✗ Branch 1 not taken.
|
18956 | (pkt->fppd[1] != 'P') || |
| 3972 |
1/2✓ Branch 0 taken 18956 times.
✗ Branch 1 not taken.
|
18956 | (pkt->fppd[2] != 'P') || |
| 3973 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18956 times.
|
18956 | (pkt->fppd[3] != 'D')) { |
| 3974 |
3/4✓ Branch 0 taken 106 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 100 times.
|
106 | if (ShouldLogBadPacket()) { |
| 3975 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | LogErr(VB_SYNC, "Error: Invalid Received Control Packet, missing 'FPPD' header\n"); |
| 3976 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | HexDump("Received data:", (void*)inBuf, len, VB_SYNC); |
| 3977 | } | ||
| 3978 | 106 | continue; | |
| 3979 | } | ||
| 3980 | |||
| 3981 |
2/2✓ Branch 0 taken 800 times.
✓ Branch 1 taken 18156 times.
|
18956 | if (len != (sizeof(ControlPkt) + pkt->extraDataLen)) { |
| 3982 |
3/4✓ Branch 0 taken 800 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 790 times.
|
800 | if (ShouldLogBadPacket()) { |
| 3983 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | LogErr(VB_SYNC, "Error: Expected %d data bytes, received %d\n", |
| 3984 | pkt->extraDataLen, len - sizeof(ControlPkt)); | ||
| 3985 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | HexDump("Received data:", (void*)inBuf, len, VB_SYNC); |
| 3986 | } | ||
| 3987 | 800 | continue; | |
| 3988 | } | ||
| 3989 | |||
| 3990 |
2/4✓ Branch 0 taken 18156 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18156 times.
|
18156 | if (WillLog(LOG_EXCESSIVE, VB_SYNC)) { |
| 3991 | ✗ | HexDump("Received MultiSync packet with contents:", (void*)inBuf, len, VB_SYNC); | |
| 3992 | } | ||
| 3993 | |||
| 3994 | // Per-sender stats, created only for a well-formed packet (any | ||
| 3995 | // datagram from a new, possibly spoofed, source used to add one), | ||
| 3996 | // and only up to MAX_SYNC_STATS senders. Entries are never freed | ||
| 3997 | // (their pointers are used after m_statsLock is released), so a | ||
| 3998 | // sender past the cap simply isn't tracked. | ||
| 3999 | static constexpr size_t MAX_SYNC_STATS = 1024; | ||
| 4000 |
3/6✓ Branch 0 taken 18156 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18156 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18156 times.
✗ Branch 5 not taken.
|
54468 | MultiSyncStats tempStats("", ""); |
| 4001 | 18156 | MultiSyncStats* stats = &tempStats; | |
| 4002 |
2/2✓ Branch 0 taken 13619 times.
✓ Branch 1 taken 4537 times.
|
18156 | if (!isLocal) { |
| 4003 |
1/2✓ Branch 0 taken 13619 times.
✗ Branch 1 not taken.
|
13619 | std::unique_lock<std::recursive_mutex> slock(m_statsLock); |
| 4004 |
1/2✓ Branch 0 taken 13619 times.
✗ Branch 1 not taken.
|
13619 | auto a = m_syncStats.find(sourceIP); |
| 4005 |
2/2✓ Branch 0 taken 12425 times.
✓ Branch 1 taken 1194 times.
|
13619 | if (a != m_syncStats.end()) { |
| 4006 | 12425 | stats = (MultiSyncStats*)a->second; | |
| 4007 |
2/2✓ Branch 0 taken 1118 times.
✓ Branch 1 taken 76 times.
|
1194 | } else if (m_syncStats.size() < MAX_SYNC_STATS) { |
| 4008 |
5/12✓ Branch 0 taken 1118 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1118 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1118 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1118 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 1118 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
1118 | stats = new MultiSyncStats(sourceIP, hostname); |
| 4009 |
1/2✓ Branch 0 taken 1118 times.
✗ Branch 1 not taken.
|
1118 | m_syncStats[sourceIP] = stats; |
| 4010 | } | ||
| 4011 | 13619 | stats->lastReceiveTime = time(NULL); | |
| 4012 | 13619 | } | |
| 4013 | |||
| 4014 |
8/10✓ Branch 0 taken 4537 times.
✓ Branch 1 taken 13619 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 4528 times.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 9 times.
✓ Branch 9 taken 18147 times.
|
18156 | if (isLocal && IsEchoCandidate(inBuf, len) && IsOwnEcho(inBuf, len)) { |
| 4015 | 9 | continue; // our own broadcast, looped back (see IsOwnEcho) | |
| 4016 | } | ||
| 4017 | |||
| 4018 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 18147 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
18147 | if (!pingOnly || pkt->pktType == CTRL_PKT_PING) { |
| 4019 |
7/7✓ Branch 0 taken 31 times.
✓ Branch 1 taken 8788 times.
✓ Branch 2 taken 92 times.
✓ Branch 3 taken 9045 times.
✓ Branch 4 taken 28 times.
✓ Branch 5 taken 59 times.
✓ Branch 6 taken 104 times.
|
18147 | switch (pkt->pktType) { |
| 4020 | 31 | case CTRL_PKT_CMD: | |
| 4021 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | ProcessCommandPacket(pkt, len, stats); |
| 4022 | 31 | break; | |
| 4023 | 8788 | case CTRL_PKT_SYNC: | |
| 4024 |
3/4✓ Branch 0 taken 8788 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2220 times.
✓ Branch 3 taken 6568 times.
|
8788 | if (getFPPmode() == REMOTE_MODE) { |
| 4025 |
1/2✓ Branch 0 taken 2220 times.
✗ Branch 1 not taken.
|
2220 | ProcessSyncPacket(pkt, len, stats); |
| 4026 | } | ||
| 4027 | 8788 | break; | |
| 4028 | 92 | case CTRL_PKT_BLANK: | |
| 4029 |
3/4✓ Branch 0 taken 92 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 83 times.
|
92 | if (getFPPmode() == REMOTE_MODE) { |
| 4030 |
2/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
|
9 | for (auto a : getPluginsCopy()) { |
| 4031 | ✗ | a->ReceivedBlankingDataPacket(); | |
| 4032 | 9 | } | |
| 4033 | 9 | BumpStat(stats->pktBlank); | |
| 4034 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | sequence->SendBlankingData(); |
| 4035 | } | ||
| 4036 | 92 | break; | |
| 4037 | 9045 | case CTRL_PKT_PING: { | |
| 4038 | 9045 | struct sockaddr_in* inAddr = (struct sockaddr_in*)(&rcvSrcAddr[msg]); | |
| 4039 | 9045 | char ipbuf[INET_ADDRSTRLEN] = {0}; | |
| 4040 | 9045 | inet_ntop(AF_INET, &inAddr->sin_addr, ipbuf, sizeof(ipbuf)); | |
| 4041 |
1/2✓ Branch 0 taken 9045 times.
✗ Branch 1 not taken.
|
9045 | std::string ip = ipbuf; |
| 4042 |
1/2✓ Branch 0 taken 9045 times.
✗ Branch 1 not taken.
|
9045 | ProcessPingPacket(pkt, len, sourceIP, stats, ip); |
| 4043 | 9045 | break; | |
| 4044 | 9045 | } | |
| 4045 | 28 | case CTRL_PKT_PLUGIN: | |
| 4046 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | ProcessPluginPacket(pkt, len, stats); |
| 4047 | 28 | break; | |
| 4048 | 59 | case CTRL_PKT_FPPCOMMAND: | |
| 4049 |
1/2✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
|
59 | ProcessFPPCommandPacket(pkt, len, stats); |
| 4050 | 59 | break; | |
| 4051 | } | ||
| 4052 | } | ||
| 4053 |
6/6✓ Branch 0 taken 18147 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 18147 times.
✓ Branch 3 taken 915 times.
✓ Branch 4 taken 18147 times.
✓ Branch 5 taken 915 times.
|
19986 | } |
| 4054 |
3/4✓ Branch 0 taken 6755 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 6755 times.
✗ Branch 3 not taken.
|
6765 | msgcnt = ++batches > 64 ? 0 : recvmmsg(m_receiveSock, rcvMsgs, MAX_MS_RCV_MSG, MSG_DONTWAIT, nullptr); |
| 4055 | 6765 | } | |
| 4056 | 5625 | } | |
| 4057 | |||
| 4058 | 48 | void MultiSync::OpenSyncedSequence(const std::string& filename) { | |
| 4059 | 48 | LogDebug(VB_SYNC, "OpenSyncedSequence(%s)\n", filename.c_str()); | |
| 4060 | |||
| 4061 |
2/4✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
|
48 | for (auto a : getPluginsCopy()) { |
| 4062 | ✗ | a->ReceivedSeqOpenPacket(filename); | |
| 4063 | 48 | } | |
| 4064 | 48 | ResetMasterPosition(); | |
| 4065 | 48 | sequence->OpenSequenceFile(filename); | |
| 4066 | 48 | } | |
| 4067 | |||
| 4068 | 43 | void MultiSync::StartSyncedSequence(const std::string& filename) { | |
| 4069 | 43 | LogDebug(VB_SYNC, "StartSyncedSequence(%s)\n", filename.c_str()); | |
| 4070 | |||
| 4071 | 43 | ResetMasterPosition(); | |
| 4072 |
2/4✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 43 times.
|
43 | for (auto a : getPluginsCopy()) { |
| 4073 | ✗ | a->ReceivedSeqSyncStartPacket(filename); | |
| 4074 | 43 | } | |
| 4075 |
8/20✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 43 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 43 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 43 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 43 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 43 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 43 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 43 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
129 | if (!sequence->IsSequenceRunning(filename) && !sequence->IsSequenceRunning("fallback.fseq")) { |
| 4076 | 43 | sequence->StartSequence(filename, 0); | |
| 4077 | } | ||
| 4078 | 43 | } | |
| 4079 | |||
| 4080 | 29 | void MultiSync::StopSyncedSequence(const std::string& filename) { | |
| 4081 | 29 | LogDebug(VB_SYNC, "StopSyncedSequence(%s)\n", filename.c_str()); | |
| 4082 |
2/4✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
|
29 | for (auto a : getPluginsCopy()) { |
| 4083 | ✗ | a->ReceivedSeqSyncStopPacket(filename); | |
| 4084 | 29 | } | |
| 4085 | 29 | sequence->CloseIfOpen(filename); | |
| 4086 | 29 | } | |
| 4087 | ✗ | void MultiSync::SyncStopAll() { | |
| 4088 | ✗ | LogDebug(VB_SYNC, "SyncStopAll()\n"); | |
| 4089 | ✗ | sequence->CloseSequenceFile(); | |
| 4090 | ✗ | if (!mediaOutput) { | |
| 4091 | ✗ | return; | |
| 4092 | } | ||
| 4093 | ✗ | CloseMediaOutput(); | |
| 4094 | } | ||
| 4095 | |||
| 4096 | ✗ | void MultiSync::SyncPlaylistToMS(uint64_t ms, const std::string& pl, bool sendSyncPackets) { | |
| 4097 | ✗ | SyncPlaylistToMS(ms, -1, pl, sendSyncPackets); | |
| 4098 | ✗ | } | |
| 4099 | |||
| 4100 | 2 | void MultiSync::SyncPlaylistToMS(uint64_t ms, int pos, const std::string& pl, bool sendSyncPackets) { | |
| 4101 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | if (Player::INSTANCE.GetPlaylistName() != pl) { |
| 4102 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (pl == "") { |
| 4103 | ✗ | return; | |
| 4104 | } | ||
| 4105 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | Player::INSTANCE.Load(pl); |
| 4106 | } | ||
| 4107 | |||
| 4108 | 2 | int desiredpos = pos; | |
| 4109 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (pos <= -1) { |
| 4110 | ✗ | desiredpos = Player::INSTANCE.FindPosForMS(ms, pos == -2); | |
| 4111 | } | ||
| 4112 | |||
| 4113 | 2 | float seconds = ms; | |
| 4114 | 2 | seconds /= 1000; | |
| 4115 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (desiredpos >= 0) { |
| 4116 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | if (m_controlSock < 0 && sendSyncPackets) { |
| 4117 | ✗ | OpenControlSockets(); | |
| 4118 | } | ||
| 4119 | |||
| 4120 | 2 | std::string seq, med; | |
| 4121 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | Player::INSTANCE.GetFilenamesForPos(desiredpos, seq, med); |
| 4122 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | if (seq != "") { |
| 4123 | ✗ | if (!sequence->IsSequenceRunning(seq)) { | |
| 4124 | ✗ | if (sequence->IsSequenceRunning()) { | |
| 4125 | ✗ | if (sendSyncPackets) | |
| 4126 | ✗ | SendSeqSyncStopPacket(sequence->GetSeqFilenameCopy()); | |
| 4127 | ✗ | sequence->CloseSequenceFile(); | |
| 4128 | } | ||
| 4129 | ✗ | ResetMasterPosition(); | |
| 4130 | ✗ | sequence->OpenSequenceFile(seq.c_str()); | |
| 4131 | ✗ | if (sendSyncPackets) | |
| 4132 | ✗ | SendSeqOpenPacket(seq); | |
| 4133 | ✗ | int frame = ms / sequence->GetSeqStepTime(); | |
| 4134 | ✗ | sequence->StartSequence(seq.c_str(), frame); | |
| 4135 | ✗ | if (sendSyncPackets) | |
| 4136 | ✗ | SendSeqSyncStartPacket(seq); | |
| 4137 | } else { | ||
| 4138 | ✗ | int frame = ms / sequence->GetSeqStepTime(); | |
| 4139 | ✗ | SyncSyncedSequence(seq.c_str(), frame, seconds); | |
| 4140 | ✗ | if (sendSyncPackets) | |
| 4141 | ✗ | SendSeqSyncPacket(seq, frame, seconds); | |
| 4142 | } | ||
| 4143 | } | ||
| 4144 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | if (med != "") { |
| 4145 | ✗ | if (mediaOutput && !MatchesRunningMediaFilename(med.c_str())) { | |
| 4146 | ✗ | StopSyncedMedia(med.c_str()); | |
| 4147 | ✗ | if (sendSyncPackets) | |
| 4148 | ✗ | SendMediaSyncStopPacket(med); | |
| 4149 | } | ||
| 4150 | ✗ | if (!mediaOutput) { | |
| 4151 | ✗ | OpenSyncedMedia(med.c_str()); | |
| 4152 | ✗ | if (sendSyncPackets) | |
| 4153 | ✗ | SendMediaOpenPacket(med); | |
| 4154 | ✗ | StartSyncedMedia(med.c_str()); | |
| 4155 | ✗ | if (sendSyncPackets) | |
| 4156 | ✗ | SendMediaSyncStartPacket(med); | |
| 4157 | } | ||
| 4158 | ✗ | UpdateMasterMediaPosition(med.c_str(), seconds); | |
| 4159 | ✗ | if (sendSyncPackets) | |
| 4160 | ✗ | SendMediaSyncPacket(med, seconds); | |
| 4161 | } | ||
| 4162 | 2 | } | |
| 4163 | } | ||
| 4164 | |||
| 4165 | /* | ||
| 4166 | * | ||
| 4167 | */ | ||
| 4168 | 858 | void MultiSync::SyncSyncedSequence(const std::string& filename, int frameNumber, float secondsElapsed) { | |
| 4169 | 858 | LogExcess(VB_SYNC, "SyncSyncedSequence('%s', %d, %.2f)\n", | |
| 4170 | filename.c_str(), frameNumber, secondsElapsed); | ||
| 4171 | |||
| 4172 |
2/4✓ Branch 0 taken 858 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 858 times.
|
858 | for (auto a : getPluginsCopy()) { |
| 4173 | ✗ | a->ReceivedSeqSyncPacket(filename, frameNumber, secondsElapsed); | |
| 4174 | 858 | } | |
| 4175 |
12/20✓ Branch 0 taken 858 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209 times.
✓ Branch 3 taken 649 times.
✓ Branch 4 taken 209 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 209 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 209 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 209 times.
✓ Branch 11 taken 649 times.
✓ Branch 12 taken 209 times.
✓ Branch 13 taken 649 times.
✓ Branch 14 taken 209 times.
✓ Branch 15 taken 649 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
1276 | if (!sequence->IsSequenceRunning(filename) && !sequence->IsSequenceRunning("fallback.fseq")) { |
| 4176 | 209 | sequence->StartSequence(filename, frameNumber); | |
| 4177 | } | ||
| 4178 |
2/2✓ Branch 0 taken 652 times.
✓ Branch 1 taken 206 times.
|
858 | if (sequence->IsSequenceRunning(filename)) { |
| 4179 | 652 | double framePosition = frameNumber; | |
| 4180 |
2/2✓ Branch 0 taken 650 times.
✓ Branch 1 taken 2 times.
|
652 | if (secondsElapsed > 0.0001f) { |
| 4181 | // recalculate the frame number based on the seconds, which carry | ||
| 4182 | // remoteOffset: kept whole, the lights would move by the offset | ||
| 4183 | // rounded to a frame (+30 ms at 20 fps moved them 50 ms). | ||
| 4184 | 650 | framePosition = secondsElapsed * 1000.0 / sequence->GetSeqStepTime(); | |
| 4185 | 650 | frameNumber = std::round(framePosition); | |
| 4186 | } | ||
| 4187 | 652 | UpdateMasterPosition(frameNumber, framePosition); | |
| 4188 | } | ||
| 4189 | 858 | } | |
| 4190 | |||
| 4191 | 22 | void MultiSync::OpenSyncedMedia(const std::string& filename) { | |
| 4192 | 22 | LogDebug(VB_SYNC, "OpenSyncedMedia(%s)\n", filename.c_str()); | |
| 4193 | |||
| 4194 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (mediaOutput) { |
| 4195 | ✗ | LogDebug(VB_SYNC, "Start media %s received while playing media %s\n", | |
| 4196 | filename.c_str(), mediaOutput->m_mediaFilename.c_str()); | ||
| 4197 | |||
| 4198 | ✗ | CloseMediaOutput(); | |
| 4199 | } | ||
| 4200 |
2/4✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
|
22 | for (auto a : getPluginsCopy()) { |
| 4201 | ✗ | a->ReceivedMediaOpenPacket(filename); | |
| 4202 | 22 | } | |
| 4203 | |||
| 4204 | 22 | m_lastSyncedMediaPacketMS = (uint64_t)GetTimeMS(); | |
| 4205 | 22 | OpenMediaOutput(filename); | |
| 4206 | 22 | } | |
| 4207 | |||
| 4208 | 21 | void MultiSync::StartSyncedMedia(const std::string& filename, float secondsElapsed) { | |
| 4209 | 21 | LogDebug(VB_SYNC, "StartSyncedMedia(%s, %.2f)\n", filename.c_str(), secondsElapsed); | |
| 4210 |
2/4✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
|
21 | for (auto a : getPluginsCopy()) { |
| 4211 | ✗ | a->ReceivedMediaSyncStartPacket(filename); | |
| 4212 | 21 | } | |
| 4213 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 17 times.
|
21 | int msTime = (secondsElapsed > 0.0f) ? (int)(secondsElapsed * 1000.0f) : 0; |
| 4214 | 21 | m_lastSyncedMediaPacketMS = (uint64_t)GetTimeMS(); | |
| 4215 | 21 | StartMediaOutput(filename, msTime); | |
| 4216 | 21 | } | |
| 4217 | |||
| 4218 | /* | ||
| 4219 | * | ||
| 4220 | */ | ||
| 4221 | 20 | void MultiSync::StopSyncedMedia(const std::string& filename) { | |
| 4222 | 20 | LogDebug(VB_SYNC, "StopSyncedMedia(%s)\n", filename.c_str()); | |
| 4223 |
2/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 20 times.
|
20 | for (auto a : getPluginsCopy()) { |
| 4224 | ✗ | a->ReceivedMediaSyncStopPacket(filename); | |
| 4225 | 20 | } | |
| 4226 | |||
| 4227 | 20 | m_lastSyncedMediaPacketMS = 0; | |
| 4228 | |||
| 4229 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 16 times.
|
20 | if (!mediaOutput) { |
| 4230 | 4 | return; | |
| 4231 | } | ||
| 4232 | |||
| 4233 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | if (MatchesRunningMediaFilename(filename)) { |
| 4234 | 16 | LogDebug(VB_SYNC, "Stopping synced media: %s\n", mediaOutput->m_mediaFilename.c_str()); | |
| 4235 | 16 | CloseMediaOutput(); | |
| 4236 | } | ||
| 4237 | } | ||
| 4238 | |||
| 4239 | /* | ||
| 4240 | * Close a synced media output that has run to its end while the player has | ||
| 4241 | * gone away (issue #2727: a wedged-decoder restart on the player left both | ||
| 4242 | * remotes holding an EOS'd pipeline forever -- slot still active, /dev/video10 | ||
| 4243 | * and dmabufs still held -- while the UI happily reported "playing"). | ||
| 4244 | * | ||
| 4245 | * The trigger is deliberately NOT "sync packets went quiet". A remote's media | ||
| 4246 | * free-runs on its own clock, so a network glitch mid-clip is harmless: the | ||
| 4247 | * clip keeps playing and UpdateMasterMediaPosition nudges it back when packets | ||
| 4248 | * resume. Tearing that down on packet silence would turn a glitch into a | ||
| 4249 | * visible outage. The unrecoverable state is a clip that has ENDED with the | ||
| 4250 | * player gone -- nothing but a sync packet can ever move it forward again. | ||
| 4251 | * | ||
| 4252 | * The normal end-of-clip-to-next-clip gap on a remote is ~13ms, so the default | ||
| 4253 | * grace of 10s is ~750x the real-world gap; a glitch straddling a clip boundary | ||
| 4254 | * costs at most the last frame going away early, and the next START packet | ||
| 4255 | * opens a fresh output as usual. | ||
| 4256 | */ | ||
| 4257 | 7771 | void MultiSync::CheckSyncedMediaIdleTimeout() { | |
| 4258 | 7771 | uint64_t last = m_lastSyncedMediaPacketMS; | |
| 4259 |
2/2✓ Branch 0 taken 7525 times.
✓ Branch 1 taken 246 times.
|
7771 | if (last == 0) { |
| 4260 | 7771 | return; // no synced media open | |
| 4261 | } | ||
| 4262 | // 0 disables the watchdog | ||
| 4263 |
1/2✓ Branch 0 taken 246 times.
✗ Branch 1 not taken.
|
246 | int timeoutSec = getSettingInt("RemoteSyncedMediaIdleTimeout", 10); |
| 4264 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 246 times.
|
246 | if (timeoutSec <= 0) { |
| 4265 | ✗ | return; | |
| 4266 | } | ||
| 4267 |
2/4✓ Branch 0 taken 246 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 246 times.
✗ Branch 3 not taken.
|
246 | if (((uint64_t)GetTimeMS() - last) < (uint64_t)(timeoutSec * 1000)) { |
| 4268 | 246 | return; | |
| 4269 | } | ||
| 4270 | |||
| 4271 | ✗ | std::string filename; | |
| 4272 | { | ||
| 4273 | ✗ | std::unique_lock<std::mutex> lock(mediaOutputLock); | |
| 4274 | ✗ | if (!mediaOutput) { | |
| 4275 | ✗ | m_lastSyncedMediaPacketMS = 0; | |
| 4276 | ✗ | return; | |
| 4277 | } | ||
| 4278 | ✗ | if (mediaOutput->IsPlaying()) { | |
| 4279 | ✗ | return; // still playing -- free-running through a sync gap is fine | |
| 4280 | } | ||
| 4281 | ✗ | filename = mediaOutput->m_mediaFilename; | |
| 4282 | ✗ | } | |
| 4283 | |||
| 4284 | ✗ | LogWarn(VB_SYNC, "Synced media '%s' finished and no sync packet received in %d seconds " | |
| 4285 | "- player appears to have gone away, closing media output\n", | ||
| 4286 | filename.c_str(), timeoutSec); | ||
| 4287 | ✗ | m_lastSyncedMediaPacketMS = 0; | |
| 4288 | ✗ | CloseMediaOutput(); | |
| 4289 | ✗ | } | |
| 4290 | |||
| 4291 | /* | ||
| 4292 | * | ||
| 4293 | */ | ||
| 4294 | 1114 | void MultiSync::SyncSyncedMedia(const std::string& filename, int frameNumber, float secondsElapsed) { | |
| 4295 | 1114 | LogExcess(VB_SYNC, "SyncSyncedMedia('%s', %d, %.2f)\n", | |
| 4296 | filename.c_str(), frameNumber, secondsElapsed); | ||
| 4297 |
2/4✓ Branch 0 taken 1114 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1114 times.
|
1114 | for (auto a : getPluginsCopy()) { |
| 4298 | ✗ | a->ReceivedMediaSyncPacket(filename, secondsElapsed); | |
| 4299 | 1114 | } | |
| 4300 | |||
| 4301 | 1114 | m_lastSyncedMediaPacketMS = (uint64_t)GetTimeMS(); | |
| 4302 | 1114 | UpdateMasterMediaPosition(filename, secondsElapsed); | |
| 4303 | 1114 | } | |
| 4304 | |||
| 4305 | /* | ||
| 4306 | * | ||
| 4307 | */ | ||
| 4308 | // A file name from a sync packet, as a name relative to the media folder it is | ||
| 4309 | // looked up in, or false if it can't be one. Masters send what they play: a | ||
| 4310 | // name, a path below the folder, or -- for a random media item -- the full | ||
| 4311 | // path on the master. That full path is cut down to what follows its | ||
| 4312 | // music/videos/sequences folder, which also makes it work on a remote whose | ||
| 4313 | // media directory is elsewhere. Anything else absolute, and any "..", is | ||
| 4314 | // refused: the name is network input, and a remote opened whatever existing | ||
| 4315 | // file it named. | ||
| 4316 | 2217 | static bool SyncFilenameToMediaRelative(std::string& name, uint8_t fileType) { | |
| 4317 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2217 times.
|
2217 | if (name.empty()) { |
| 4318 | ✗ | return false; | |
| 4319 | } | ||
| 4320 |
2/2✓ Branch 0 taken 29 times.
✓ Branch 1 taken 2188 times.
|
2217 | if (name[0] == '/') { |
| 4321 | static const char* const seqDirs[] = { "/sequences/", nullptr }; | ||
| 4322 | static const char* const mediaDirs[] = { "/music/", "/videos/", nullptr }; | ||
| 4323 | 29 | bool found = false; | |
| 4324 |
5/6✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 58 times.
✓ Branch 3 taken 16 times.
✓ Branch 4 taken 45 times.
✓ Branch 5 taken 13 times.
|
74 | for (const char* const* d = (fileType == SYNC_FILE_SEQ) ? seqDirs : mediaDirs; *d && !found; d++) { |
| 4325 | 45 | size_t pos = name.find(*d); | |
| 4326 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 32 times.
|
45 | if (pos != std::string::npos) { |
| 4327 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | name = name.substr(pos + strlen(*d)); |
| 4328 | 13 | found = true; | |
| 4329 | } | ||
| 4330 | } | ||
| 4331 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 13 times.
|
29 | if (!found) { |
| 4332 | 16 | return false; | |
| 4333 | } | ||
| 4334 | } | ||
| 4335 | 2201 | size_t start = 0; | |
| 4336 |
2/2✓ Branch 0 taken 2201 times.
✓ Branch 1 taken 2185 times.
|
4386 | while (start <= name.size()) { |
| 4337 | 2201 | size_t end = name.find('/', start); | |
| 4338 |
2/2✓ Branch 0 taken 2185 times.
✓ Branch 1 taken 16 times.
|
2201 | if (end == std::string::npos) { |
| 4339 | 2185 | end = name.size(); | |
| 4340 | } | ||
| 4341 |
5/6✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2185 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 2185 times.
|
2201 | if (name.compare(start, end - start, "..") == 0 && end - start == 2) { |
| 4342 | 16 | return false; | |
| 4343 | } | ||
| 4344 | 2185 | start = end + 1; | |
| 4345 | } | ||
| 4346 |
2/4✓ Branch 0 taken 2185 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2185 times.
✗ Branch 3 not taken.
|
2185 | return !name.empty() && name.back() != '/'; |
| 4347 | } | ||
| 4348 | |||
| 4349 | 2220 | void MultiSync::ProcessSyncPacket(ControlPkt* pkt, int len, MultiSyncStats* stats) { | |
| 4350 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2217 times.
|
2220 | if (pkt->extraDataLen < sizeof(SyncPkt)) { |
| 4351 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
3 | if (ShouldLogBadPacket()) { |
| 4352 | ✗ | LogErr(VB_SYNC, "Error: Invalid length of received sync packet\n"); | |
| 4353 | ✗ | HexDump("Received data:", (void*)pkt, len, VB_SYNC); | |
| 4354 | } | ||
| 4355 | 3 | BumpStat(stats->pktError); | |
| 4356 | 35 | return; | |
| 4357 | } | ||
| 4358 | |||
| 4359 | { | ||
| 4360 | // Read by GetSyncStats() on an API thread; m_statsLock is the lock the | ||
| 4361 | // two sides share. Do not reach for m_systemsLock while holding it. | ||
| 4362 |
1/2✓ Branch 0 taken 2217 times.
✗ Branch 1 not taken.
|
2217 | std::unique_lock<std::recursive_mutex> slock(m_statsLock); |
| 4363 |
1/2✓ Branch 0 taken 2217 times.
✗ Branch 1 not taken.
|
2217 | m_syncMaster = stats->sourceIP; |
| 4364 | 2217 | } | |
| 4365 | |||
| 4366 | 2217 | SyncPkt* spkt = (SyncPkt*)(((char*)pkt) + sizeof(ControlPkt)); | |
| 4367 | |||
| 4368 | // filename is a flexible char[1]: read it no further than the packet goes, | ||
| 4369 | // terminated or not. | ||
| 4370 | 2217 | size_t nameMax = pkt->extraDataLen - offsetof(SyncPkt, filename); | |
| 4371 |
1/2✓ Branch 0 taken 2217 times.
✗ Branch 1 not taken.
|
3185 | std::string filename(spkt->filename, strnlen(spkt->filename, nameMax)); |
| 4372 | 2217 | float secondsIn = spkt->secondsElapsed; | |
| 4373 | |||
| 4374 |
2/4✓ Branch 0 taken 2217 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2217 times.
✗ Branch 3 not taken.
|
2217 | LogDebug(VB_SYNC, "ProcessSyncPacket() filename: %s type: %d filetype: %d frameNumber: %d secondsElapsed: %0.2f\n", |
| 4375 | TruncateForLog(filename).c_str(), spkt->pktType, spkt->fileType, spkt->frameNumber, secondsIn); | ||
| 4376 | |||
| 4377 |
3/4✓ Branch 0 taken 2217 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 2185 times.
|
2217 | if (!SyncFilenameToMediaRelative(filename, spkt->fileType)) { |
| 4378 | // A master sends one of these several times a second; say it once. | ||
| 4379 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
32 | static std::string s_lastRefused; |
| 4380 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 25 times.
|
32 | if (filename != s_lastRefused) { |
| 4381 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | s_lastRefused = filename; |
| 4382 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | LogWarn(VB_SYNC, "Ignoring sync packets from %s for '%s': not a name in the media folders\n", |
| 4383 | stats->sourceIP.c_str(), TruncateForLog(filename).c_str()); | ||
| 4384 | } | ||
| 4385 | 32 | BumpStat(stats->pktError); | |
| 4386 | 32 | return; | |
| 4387 | } | ||
| 4388 | // A NaN passes every "< 0" test and the int conversions downstream are | ||
| 4389 | // undefined for it, as for anything past INT_MAX milliseconds. | ||
| 4390 |
6/6✓ Branch 0 taken 2177 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 2171 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 2171 times.
|
2185 | if (!std::isfinite(secondsIn) || secondsIn < 0.0f) { |
| 4391 | 14 | secondsIn = 0.0f; | |
| 4392 |
2/2✓ Branch 0 taken 45 times.
✓ Branch 1 taken 2126 times.
|
2171 | } else if (secondsIn > 86400.0f) { |
| 4393 | 45 | secondsIn = 86400.0f; | |
| 4394 | } | ||
| 4395 | // The same bound for the frame number, at the fastest frame rate (1 ms): | ||
| 4396 | // it is unsigned on the wire and an int from here on, so 2^31 and up went | ||
| 4397 | // negative, and the frameNumber - 1 downstream overflowed at INT_MIN. | ||
| 4398 | 2185 | uint32_t wireFrame = spkt->frameNumber; | |
| 4399 | 2185 | const int frameNumber = (int)std::min<uint32_t>(wireFrame, 86400u * 1000u); | |
| 4400 | |||
| 4401 | 2185 | float secondsElapsed = 0.0; | |
| 4402 | |||
| 4403 |
2/2✓ Branch 0 taken 983 times.
✓ Branch 1 taken 1202 times.
|
2185 | if (spkt->fileType == SYNC_FILE_SEQ) { |
| 4404 |
5/5✓ Branch 0 taken 48 times.
✓ Branch 1 taken 43 times.
✓ Branch 2 taken 29 times.
✓ Branch 3 taken 858 times.
✓ Branch 4 taken 5 times.
|
983 | switch (spkt->pktType) { |
| 4405 | 48 | case SYNC_PKT_OPEN: | |
| 4406 |
1/2✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
|
48 | OpenSyncedSequence(filename); |
| 4407 | 48 | BumpStat(stats->pktSyncSeqOpen); | |
| 4408 | 48 | break; | |
| 4409 | 43 | case SYNC_PKT_START: | |
| 4410 |
1/2✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
|
43 | StartSyncedSequence(filename); |
| 4411 | 43 | BumpStat(stats->pktSyncSeqStart); | |
| 4412 | 43 | break; | |
| 4413 | 29 | case SYNC_PKT_STOP: | |
| 4414 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | StopSyncedSequence(filename); |
| 4415 | 29 | BumpStat(stats->pktSyncSeqStop); | |
| 4416 | 29 | break; | |
| 4417 | 858 | case SYNC_PKT_SYNC: | |
| 4418 | 858 | secondsElapsed = secondsIn - m_remoteOffset; | |
| 4419 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 858 times.
|
858 | if (secondsElapsed < 0) |
| 4420 | ✗ | secondsElapsed = 0.0; | |
| 4421 | |||
| 4422 |
1/2✓ Branch 0 taken 858 times.
✗ Branch 1 not taken.
|
858 | SyncSyncedSequence(filename, |
| 4423 | frameNumber, secondsElapsed); | ||
| 4424 | 858 | BumpStat(stats->pktSyncSeqSync); | |
| 4425 | 858 | break; | |
| 4426 | } | ||
| 4427 |
2/2✓ Branch 0 taken 1180 times.
✓ Branch 1 taken 22 times.
|
1202 | } else if (spkt->fileType == SYNC_FILE_MEDIA) { |
| 4428 |
5/5✓ Branch 0 taken 22 times.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 1114 times.
✓ Branch 4 taken 3 times.
|
1180 | switch (spkt->pktType) { |
| 4429 | 22 | case SYNC_PKT_OPEN: | |
| 4430 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | OpenSyncedMedia(filename); |
| 4431 | 22 | BumpStat(stats->pktSyncMedOpen); | |
| 4432 | 22 | break; | |
| 4433 | 21 | case SYNC_PKT_START: | |
| 4434 | 21 | secondsElapsed = secondsIn - m_remoteOffset; | |
| 4435 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
|
21 | if (secondsElapsed < 0) |
| 4436 | ✗ | secondsElapsed = 0.0; | |
| 4437 | |||
| 4438 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | StartSyncedMedia(filename, secondsElapsed); |
| 4439 | 21 | BumpStat(stats->pktSyncMedStart); | |
| 4440 | 21 | break; | |
| 4441 | 20 | case SYNC_PKT_STOP: | |
| 4442 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | StopSyncedMedia(filename); |
| 4443 | 20 | BumpStat(stats->pktSyncMedStop); | |
| 4444 | 20 | break; | |
| 4445 | 1114 | case SYNC_PKT_SYNC: | |
| 4446 | 1114 | secondsElapsed = secondsIn - m_remoteOffset; | |
| 4447 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1114 times.
|
1114 | if (secondsElapsed < 0) |
| 4448 | ✗ | secondsElapsed = 0.0; | |
| 4449 | |||
| 4450 |
1/2✓ Branch 0 taken 1114 times.
✗ Branch 1 not taken.
|
1114 | SyncSyncedMedia(filename, |
| 4451 | frameNumber, secondsElapsed); | ||
| 4452 | 1114 | BumpStat(stats->pktSyncMedSync); | |
| 4453 | 1114 | break; | |
| 4454 | } | ||
| 4455 | } | ||
| 4456 |
2/2✓ Branch 0 taken 2185 times.
✓ Branch 1 taken 32 times.
|
2217 | } |
| 4457 | |||
| 4458 | /* | ||
| 4459 | * | ||
| 4460 | */ | ||
| 4461 | 31 | void MultiSync::ProcessCommandPacket(ControlPkt* pkt, int len, MultiSyncStats* stats) { | |
| 4462 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | LogDebug(VB_SYNC, "ProcessCommandPacket()\n"); |
| 4463 | |||
| 4464 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 25 times.
|
31 | if (pkt->extraDataLen < sizeof(CommandPkt)) { |
| 4465 |
3/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4 times.
|
6 | if (ShouldLogBadPacket()) { |
| 4466 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogErr(VB_SYNC, "Error: Invalid length of received command packet\n"); |
| 4467 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | HexDump("Received data:", (void*)pkt, len, VB_SYNC); |
| 4468 | } | ||
| 4469 | 6 | BumpStat(stats->pktError); | |
| 4470 | 6 | return; | |
| 4471 | } | ||
| 4472 | |||
| 4473 | 25 | BumpStat(stats->pktCommand); | |
| 4474 | |||
| 4475 | 25 | CommandPkt* cpkt = (CommandPkt*)(((char*)pkt) + sizeof(ControlPkt)); | |
| 4476 | |||
| 4477 | // CommandPkt is a flexible char[1] with no length field of its own, so the | ||
| 4478 | // command text is only as terminated as the sender chose to make it. Bound | ||
| 4479 | // the read by the packet's own extraDataLen before handing it to the log | ||
| 4480 | // (which is remote-supplied and unbounded, hence TruncateForLog too). | ||
| 4481 |
3/6✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
|
50 | LogDebug(VB_COMMAND, "Legacy command \"%s\" received from remote host %s (%s)\n", |
| 4482 | TruncateForLog(std::string(cpkt->command, strnlen(cpkt->command, pkt->extraDataLen))).c_str(), | ||
| 4483 | stats->hostname.c_str(), stats->sourceIP.c_str()); | ||
| 4484 | |||
| 4485 | char response[1500]; | ||
| 4486 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | char* r2 = ProcessCommand(cpkt->command, response); |
| 4487 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
|
25 | if (r2) { |
| 4488 | ✗ | free(r2); | |
| 4489 | } | ||
| 4490 | } | ||
| 4491 | |||
| 4492 | /* | ||
| 4493 | * | ||
| 4494 | */ | ||
| 4495 | 9045 | void MultiSync::ProcessPingPacket(ControlPkt* pkt, int len, const std::string& srcIp, MultiSyncStats* stats, const std::string& incomingIp) { | |
| 4496 |
1/2✓ Branch 0 taken 9045 times.
✗ Branch 1 not taken.
|
9045 | LogDebug(VB_SYNC, "ProcessPingPacket()\n"); |
| 4497 | |||
| 4498 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 9003 times.
|
9045 | if (pkt->extraDataLen < 169) { // v1 packet length |
| 4499 |
2/4✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
|
42 | if (ShouldLogBadPacket()) { |
| 4500 | ✗ | LogErr(VB_SYNC, "ERROR: Invalid length of received Ping packet\n"); | |
| 4501 | ✗ | HexDump("Received data:", (void*)pkt, len, VB_SYNC); | |
| 4502 | } | ||
| 4503 | 42 | BumpStat(stats->pktError); | |
| 4504 | 156 | return; | |
| 4505 | } | ||
| 4506 | |||
| 4507 | 9003 | unsigned char* extraData = (unsigned char*)(((char*)pkt) + sizeof(ControlPkt)); | |
| 4508 | |||
| 4509 | 9003 | unsigned char pingVersion = extraData[0]; | |
| 4510 | |||
| 4511 |
4/4✓ Branch 0 taken 72 times.
✓ Branch 1 taken 8931 times.
✓ Branch 2 taken 66 times.
✓ Branch 3 taken 6 times.
|
9003 | if ((pingVersion == 1) && (pkt->extraDataLen > 169)) { |
| 4512 |
2/4✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 66 times.
|
66 | if (ShouldLogBadPacket()) { |
| 4513 | ✗ | LogErr(VB_SYNC, "ERROR: Ping v1 packet too long: %d\n", pkt->extraDataLen); | |
| 4514 | ✗ | HexDump("Received data:", (void*)pkt, len, VB_SYNC); | |
| 4515 | } | ||
| 4516 | 66 | BumpStat(stats->pktError); | |
| 4517 | 66 | return; | |
| 4518 | } | ||
| 4519 |
4/4✓ Branch 0 taken 72 times.
✓ Branch 1 taken 8865 times.
✓ Branch 2 taken 48 times.
✓ Branch 3 taken 24 times.
|
8937 | if ((pingVersion == 2) && (pkt->extraDataLen > 214)) { |
| 4520 |
2/4✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 48 times.
|
48 | if (ShouldLogBadPacket()) { |
| 4521 | ✗ | LogErr(VB_SYNC, "ERROR: Ping v2 packet too long %d\n", pkt->extraDataLen); | |
| 4522 | ✗ | HexDump("Received data:", (void*)pkt, len, VB_SYNC); | |
| 4523 | } | ||
| 4524 | 48 | BumpStat(stats->pktError); | |
| 4525 | 48 | return; | |
| 4526 | } | ||
| 4527 | 8889 | int discover = extraData[1]; | |
| 4528 | |||
| 4529 | 8889 | MultiSyncSystemType type = (MultiSyncSystemType)extraData[2]; | |
| 4530 | |||
| 4531 | 8889 | unsigned int majorVersion = ((unsigned int)extraData[3] << 8) | extraData[4]; | |
| 4532 | 8889 | unsigned int minorVersion = ((unsigned int)extraData[5] << 8) | extraData[6]; | |
| 4533 | |||
| 4534 | 8889 | FPPMode systemMode = (FPPMode)extraData[7]; | |
| 4535 | |||
| 4536 | 8889 | bool isInstance = true; | |
| 4537 | 8889 | std::string address; | |
| 4538 |
5/8✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8881 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
|
8889 | if (extraData[8] == 0 && extraData[9] == 0 && extraData[10] == 0 && extraData[11] == 0) { |
| 4539 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
|
8 | if (discover) { |
| 4540 | // No ip address in packet, this is a ping/discovery packet | ||
| 4541 | // from something (xLights?) that is just trying to | ||
| 4542 | // get a list of FPP instances, we won't record this | ||
| 4543 | 7 | isInstance = false; | |
| 4544 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | address = "0.0.0.0"; |
| 4545 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | } else if (!incomingIp.empty()) { |
| 4546 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | address = incomingIp; |
| 4547 | } else { | ||
| 4548 | ✗ | isInstance = false; | |
| 4549 | ✗ | address = "0.0.0.0"; | |
| 4550 | } | ||
| 4551 | } else { | ||
| 4552 | char addrStr[16]; | ||
| 4553 | 8881 | memset(addrStr, 0, sizeof(addrStr)); | |
| 4554 | 8881 | snprintf(addrStr, 16, "%d.%d.%d.%d", extraData[8], extraData[9], | |
| 4555 | 8881 | extraData[10], extraData[11]); | |
| 4556 |
1/2✓ Branch 0 taken 8881 times.
✗ Branch 1 not taken.
|
8881 | address = addrStr; |
| 4557 | } | ||
| 4558 | |||
| 4559 | char tmpStr[128]; | ||
| 4560 | // Bounded copy of a packet string field. The packet tail is not guaranteed | ||
| 4561 | // to be NUL-terminated and may be longer than tmpStr, so clamp to both the | ||
| 4562 | // field's own size and the destination buffer to avoid a stack overflow / | ||
| 4563 | // over-read. | ||
| 4564 | 44155 | auto copyField = [&](int fieldOffset) -> std::string { | |
| 4565 | 44155 | int avail = (int)pkt->extraDataLen - fieldOffset; | |
| 4566 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44155 times.
|
44155 | if (avail < 0) { |
| 4567 | ✗ | avail = 0; | |
| 4568 | } | ||
| 4569 | 44155 | int toCopy = std::min(avail, (int)(sizeof(tmpStr) - 1)); | |
| 4570 | 44155 | memset(tmpStr, 0, sizeof(tmpStr)); | |
| 4571 | 44155 | memcpy(tmpStr, (char*)(extraData + fieldOffset), toCopy); | |
| 4572 |
1/2✓ Branch 0 taken 44155 times.
✗ Branch 1 not taken.
|
88310 | return std::string(tmpStr); |
| 4573 | 8889 | }; | |
| 4574 |
1/2✓ Branch 0 taken 8889 times.
✗ Branch 1 not taken.
|
8889 | std::string hostname = copyField(12); |
| 4575 |
1/2✓ Branch 0 taken 8889 times.
✗ Branch 1 not taken.
|
8889 | std::string version = copyField(77); |
| 4576 |
1/2✓ Branch 0 taken 8889 times.
✗ Branch 1 not taken.
|
8889 | std::string typeStr = copyField(118); |
| 4577 | // End of v1 packet fields | ||
| 4578 | |||
| 4579 | 8889 | std::string ranges; | |
| 4580 |
2/2✓ Branch 0 taken 8853 times.
✓ Branch 1 taken 36 times.
|
8889 | if ((pkt->extraDataLen) > 169) { |
| 4581 |
1/2✓ Branch 0 taken 8853 times.
✗ Branch 1 not taken.
|
8853 | ranges = copyField(166 - 7); |
| 4582 | } | ||
| 4583 | |||
| 4584 | 8889 | bool isLocal = false; | |
| 4585 | { | ||
| 4586 | // Only m_interfaces needs m_socketLock; released before UpdateSystem() | ||
| 4587 | // below so it isn't held across a call that can re-enter m_socketLock | ||
| 4588 | // via UpdateUnicastDestinations() (m_socketLock is non-recursive). | ||
| 4589 |
1/2✓ Branch 0 taken 8889 times.
✗ Branch 1 not taken.
|
8889 | std::unique_lock<std::mutex> lock(m_socketLock); |
| 4590 |
2/2✓ Branch 0 taken 9711 times.
✓ Branch 1 taken 8889 times.
|
18600 | for (auto& a : m_interfaces) { |
| 4591 |
2/2✓ Branch 0 taken 4237 times.
✓ Branch 1 taken 5474 times.
|
9711 | if (address == a.second.interfaceAddress) { |
| 4592 | 4237 | isLocal = true; | |
| 4593 | } | ||
| 4594 | } | ||
| 4595 | 8889 | } | |
| 4596 | |||
| 4597 | // v4: the sender's SystemUUID, so every address a device pings from can be | ||
| 4598 | // tied to the one device without an HTTP round trip. Gated on length | ||
| 4599 | // rather than version, so anything that appends further still carries it. | ||
| 4600 | 8889 | std::string pktUUID; | |
| 4601 |
2/2✓ Branch 0 taken 8635 times.
✓ Branch 1 taken 254 times.
|
8889 | if (pkt->extraDataLen >= PING_V4_LEN) { |
| 4602 |
2/4✓ Branch 0 taken 8635 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8635 times.
✗ Branch 3 not taken.
|
8635 | pktUUID = copyField(PING_V3_LEN).substr(0, PING_UUID_MAX); |
| 4603 |
3/4✓ Branch 0 taken 8635 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 8628 times.
|
13735 | if (std::any_of(pktUUID.begin(), pktUUID.end(), [](unsigned char c) { return !isgraph(c); })) { |
| 4604 | 7 | pktUUID.clear(); | |
| 4605 | } | ||
| 4606 | } | ||
| 4607 | |||
| 4608 |
2/2✓ Branch 0 taken 8882 times.
✓ Branch 1 taken 7 times.
|
8889 | if (isInstance) { |
| 4609 |
9/14✓ Branch 0 taken 4237 times.
✓ Branch 1 taken 4645 times.
✓ Branch 2 taken 4237 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4559 times.
✓ Branch 5 taken 86 times.
✓ Branch 6 taken 4559 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 86 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4559 times.
✓ Branch 11 taken 4323 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
|
13441 | std::string uuid = isLocal ? getSetting("SystemUUID") : (pktUUID.empty() ? "Unknown" : pktUUID); |
| 4610 | 8882 | multiSync->UpdateSystem(type, majorVersion, minorVersion, | |
| 4611 | systemMode, address, hostname, version, | ||
| 4612 | typeStr, ranges, uuid, true, | ||
| 4613 |
1/2✓ Branch 0 taken 8882 times.
✗ Branch 1 not taken.
|
8882 | systemMode & 0x04 ? true : false); |
| 4614 | |||
| 4615 | // A discover ping is what an instance sends as its fppd starts (fppd.cpp | ||
| 4616 | // calls Discover() during startup), and a restart is exactly when most | ||
| 4617 | // of what we cache about a peer changes -- channel inputs/outputs, host | ||
| 4618 | // description, background color, anything else that needed a restart to | ||
| 4619 | // take effect. Clearing the fetch timestamp makes the next sweep re-ask | ||
| 4620 | // within INFO_SCAN_INTERVAL instead of serving up to | ||
| 4621 | // INFO_REFRESH_INTERVAL of stale detail. The cached values themselves | ||
| 4622 | // are left in place so the UI doesn't blank out mid-refetch. | ||
| 4623 | // | ||
| 4624 | // This cannot amplify into a fetch storm: a discover ping is only ever | ||
| 4625 | // answered with plain (non-discover) pings, so one restart invalidates | ||
| 4626 | // one system on each peer. xLights' discovery ping is also a discover | ||
| 4627 | // ping, but it carries no address and so never reaches this branch. | ||
| 4628 |
4/4✓ Branch 0 taken 2040 times.
✓ Branch 1 taken 6842 times.
✓ Branch 2 taken 64 times.
✓ Branch 3 taken 1976 times.
|
8882 | if (discover && !isLocal) { |
| 4629 |
1/2✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
|
64 | multiSync->InvalidateSystemInfo(address); |
| 4630 | } | ||
| 4631 | 8882 | } | |
| 4632 |
2/2✓ Branch 0 taken 2047 times.
✓ Branch 1 taken 6842 times.
|
8889 | if (discover) { |
| 4633 |
6/6✓ Branch 0 taken 71 times.
✓ Branch 1 taken 1976 times.
✓ Branch 2 taken 70 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 70 times.
✓ Branch 5 taken 1977 times.
|
2047 | if ((hostname != m_hostname) && !isLocal) { |
| 4634 |
1/2✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
|
70 | QueueDiscoverReply(address, srcIp); |
| 4635 | } | ||
| 4636 | } | ||
| 4637 | |||
| 4638 | 8889 | BumpStat(stats->pktPing); | |
| 4639 | 8889 | } | |
| 4640 | |||
| 4641 | 28 | void MultiSync::ProcessPluginPacket(ControlPkt* pkt, int plen, MultiSyncStats* stats) { | |
| 4642 | 28 | LogDebug(VB_SYNC, "ProcessPluginPacket()\n"); | |
| 4643 | 28 | CommandPkt* cpkt = (CommandPkt*)(((char*)pkt) + sizeof(ControlPkt)); | |
| 4644 | 28 | int len = pkt->extraDataLen; | |
| 4645 | 28 | char* pn = &cpkt->command[0]; | |
| 4646 | // The plugin name must be NUL-terminated within the extra data. An | ||
| 4647 | // unterminated name would otherwise run strlen() out to the terminator the | ||
| 4648 | // receive loop writes one byte past the datagram, making nlen larger than | ||
| 4649 | // the data actually present and leaving a negative len for the plugins -- | ||
| 4650 | // which they take as a size_t and read far past the buffer. | ||
| 4651 | 28 | int nlen = (int)strnlen(pn, (size_t)len); | |
| 4652 |
2/2✓ Branch 0 taken 24 times.
✓ Branch 1 taken 4 times.
|
28 | if (nlen >= len) { |
| 4653 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (ShouldLogBadPacket()) { |
| 4654 | ✗ | LogErr(VB_SYNC, "Error: plugin packet name is not terminated within %d bytes of data\n", len); | |
| 4655 | } | ||
| 4656 | 24 | BumpStat(stats->pktError); | |
| 4657 | 24 | return; | |
| 4658 | } | ||
| 4659 | 4 | nlen++; // include the terminator | |
| 4660 | 4 | len -= nlen; | |
| 4661 | 4 | uint8_t* data = (uint8_t*)&cpkt->command[nlen]; | |
| 4662 | |||
| 4663 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | for (auto a : getPluginsCopy()) { |
| 4664 | ✗ | a->ReceivedPluginData(pn, data, len); | |
| 4665 | 4 | } | |
| 4666 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | PluginManager::INSTANCE.multiSyncData(pn, data, len); |
| 4667 | |||
| 4668 | 4 | BumpStat(stats->pktPlugin); | |
| 4669 | } | ||
| 4670 | |||
| 4671 | 5 | static bool MyHostMatches(const std::string& host, const std::string& hostName, std::vector<MultiSyncSystem>& localSystems) { | |
| 4672 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | std::vector<std::string> names = split(host, ','); |
| 4673 |
3/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3 times.
|
5 | if (std::find(names.begin(), names.end(), hostName) != names.end()) { |
| 4674 | 2 | return true; | |
| 4675 | } | ||
| 4676 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | for (auto& h : names) { |
| 4677 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | for (auto& ls : localSystems) { |
| 4678 |
3/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
|
3 | if (h == ls.address || h == ls.hostname) { |
| 4679 | ✗ | return true; | |
| 4680 | } | ||
| 4681 | } | ||
| 4682 | } | ||
| 4683 | 3 | return false; | |
| 4684 | 5 | } | |
| 4685 | |||
| 4686 | 59 | void MultiSync::ProcessFPPCommandPacket(ControlPkt* pkt, int len, MultiSyncStats* stats) { | |
| 4687 | // The caller has already checked len == sizeof(ControlPkt) + extraDataLen, | ||
| 4688 | // so the packet's own data ends at `end`. Every field below has to be | ||
| 4689 | // parsed strictly against that bound: a truncated or crafted packet whose | ||
| 4690 | // strings are not terminated inside it would otherwise be read out of the | ||
| 4691 | // stale contents of this reusable receive buffer -- and, for the last row of | ||
| 4692 | // that array, past the end of it -- and the result handed to CommandManager. | ||
| 4693 | 59 | const char* b = (const char*)pkt; | |
| 4694 | 59 | const char* end = b + sizeof(ControlPkt) + pkt->extraDataLen; | |
| 4695 | 59 | const char* pos = b + sizeof(ControlPkt); | |
| 4696 | |||
| 4697 | 1815 | auto readString = [&pos, end](std::string& out) { | |
| 4698 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1803 times.
|
1815 | if (pos >= end) { |
| 4699 | 12 | return false; | |
| 4700 | } | ||
| 4701 | 1803 | size_t avail = (size_t)(end - pos); | |
| 4702 | 1803 | size_t slen = strnlen(pos, avail); | |
| 4703 |
2/2✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1779 times.
|
1803 | if (slen == avail) { |
| 4704 | // ran to the end of the packet without finding a terminator | ||
| 4705 | 24 | return false; | |
| 4706 | } | ||
| 4707 | 1779 | out.assign(pos, slen); | |
| 4708 | 1779 | pos += slen + 1; | |
| 4709 | 1779 | return true; | |
| 4710 | 59 | }; | |
| 4711 | |||
| 4712 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 53 times.
|
59 | if (pos >= end) { |
| 4713 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
6 | if (ShouldLogBadPacket()) { |
| 4714 | ✗ | LogErr(VB_SYNC, "Error: truncated FPP Command packet\n"); | |
| 4715 | } | ||
| 4716 | 6 | BumpStat(stats->pktError); | |
| 4717 | 42 | return; | |
| 4718 | } | ||
| 4719 | // Unsigned: as a signed char an argument count above 127 reads as negative | ||
| 4720 | // and silently skips the whole argument list. | ||
| 4721 | 53 | uint8_t numArgs = (uint8_t)*pos++; | |
| 4722 | |||
| 4723 | 53 | std::string host; | |
| 4724 | 53 | std::string cmd; | |
| 4725 |
8/10✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
✓ Branch 3 taken 18 times.
✓ Branch 4 taken 35 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 29 times.
✓ Branch 8 taken 24 times.
✓ Branch 9 taken 29 times.
|
53 | if (!readString(host) || !readString(cmd)) { |
| 4726 |
2/4✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
|
24 | if (ShouldLogBadPacket()) { |
| 4727 | ✗ | LogErr(VB_SYNC, "Error: truncated FPP Command packet\n"); | |
| 4728 | } | ||
| 4729 | 24 | BumpStat(stats->pktError); | |
| 4730 | 24 | return; | |
| 4731 | } | ||
| 4732 | |||
| 4733 | 29 | std::vector<std::string> args; | |
| 4734 |
2/2✓ Branch 0 taken 1727 times.
✓ Branch 1 taken 17 times.
|
1744 | for (uint8_t x = 0; x < numArgs; x++) { |
| 4735 | 1727 | std::string arg; | |
| 4736 |
3/4✓ Branch 0 taken 1727 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 1715 times.
|
1727 | if (!readString(arg)) { |
| 4737 |
2/4✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
|
12 | if (ShouldLogBadPacket()) { |
| 4738 | ✗ | LogErr(VB_SYNC, "Error: FPP Command packet claims %d arguments but only holds %d\n", | |
| 4739 | (int)numArgs, (int)args.size()); | ||
| 4740 | } | ||
| 4741 | 12 | BumpStat(stats->pktError); | |
| 4742 | 12 | return; | |
| 4743 | } | ||
| 4744 |
1/2✓ Branch 0 taken 1715 times.
✗ Branch 1 not taken.
|
1715 | args.push_back(std::move(arg)); |
| 4745 |
2/2✓ Branch 0 taken 1715 times.
✓ Branch 1 taken 12 times.
|
1727 | } |
| 4746 | bool matches; | ||
| 4747 |
3/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 4 times.
|
17 | if (host == "") { |
| 4748 | 13 | matches = true; | |
| 4749 | } else { | ||
| 4750 | // Copy m_localSystems under m_systemsLock and release it before | ||
| 4751 | // evaluating/running -- MyHostMatches only needs a snapshot, and this | ||
| 4752 | // function must not hold m_systemsLock across CommandManager::run() or | ||
| 4753 | // the plugin callbacks below. | ||
| 4754 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 4755 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::vector<MultiSyncSystem> localSystems = m_localSystems; |
| 4756 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | lock.unlock(); |
| 4757 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | matches = MyHostMatches(host, m_hostname, localSystems); |
| 4758 | 4 | } | |
| 4759 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2 times.
|
17 | if (matches) { |
| 4760 | 15 | BumpStat(stats->pktFPPCommand); | |
| 4761 | // Remote-supplied and unbounded - truncate. (The args are covered by | ||
| 4762 | // CommandManager::run()'s own log line below.) | ||
| 4763 |
2/4✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
|
15 | LogDebug(VB_COMMAND, "Command \"%s\" received from remote host %s (%s)\n", |
| 4764 | TruncateForLog(cmd).c_str(), stats->hostname.c_str(), stats->sourceIP.c_str()); | ||
| 4765 |
2/4✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
|
15 | for (auto a : getPluginsCopy()) { |
| 4766 | ✗ | a->ReceivedFPPCommandPacket(cmd, args); | |
| 4767 | 15 | } | |
| 4768 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | CommandManager::INSTANCE.run(cmd, args); |
| 4769 | } | ||
| 4770 |
6/6✓ Branch 0 taken 17 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 36 times.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 36 times.
|
101 | } |
| 4771 | |||
| 4772 | 8 | void MultiSync::SendFPPCommandPacket(const std::string& host, const std::string& cmd, const std::vector<std::string>& args) { | |
| 4773 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (m_controlSock < 0) { |
| 4774 | ✗ | OpenControlSockets(); | |
| 4775 | } | ||
| 4776 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (m_controlSock < 0) { |
| 4777 | ✗ | OpenControlSockets(); | |
| 4778 | ✗ | LogErr(VB_SYNC, "ERROR: Tried to send FPP Command packet but sync socket is not open.\n"); | |
| 4779 | 1 | return; | |
| 4780 | } | ||
| 4781 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | LogDebug(VB_SYNC, "SendFPPCommandPacket\n"); |
| 4782 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | for (auto a : getPluginsCopy()) { |
| 4783 | ✗ | a->SendFPPCommandPacket(host, cmd, args); | |
| 4784 | 8 | } | |
| 4785 | // The argument count goes out as one byte, so more than 255 would wrap and | ||
| 4786 | // the remote would read a different command than this host runs. | ||
| 4787 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
|
8 | if (args.size() > 255) { |
| 4788 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | LogErr(VB_SYNC, "ERROR: FPP Command \"%s\" has %d arguments, more than a MultiSync packet can carry\n", |
| 4789 | TruncateForLog(cmd).c_str(), (int)args.size()); | ||
| 4790 | 1 | return; | |
| 4791 | } | ||
| 4792 | // Payload: arg count, then host, cmd and each arg NUL-terminated. Sized | ||
| 4793 | // up front so a command too large for one packet is refused as a whole | ||
| 4794 | // rather than sent truncated (or dropped by every receiver) while this | ||
| 4795 | // host runs it in full. | ||
| 4796 | 7 | size_t payloadLen = 1 + host.length() + 1 + cmd.length() + 1; | |
| 4797 |
2/2✓ Branch 0 taken 37 times.
✓ Branch 1 taken 7 times.
|
44 | for (auto& a : args) { |
| 4798 | 37 | payloadLen += a.length() + 1; | |
| 4799 | } | ||
| 4800 | 7 | const bool fits = payloadLen <= MAX_MS_PAYLOAD; | |
| 4801 | |||
| 4802 | char outBuf[2048]; | ||
| 4803 | static_assert(sizeof(ControlPkt) + MAX_MS_PAYLOAD <= sizeof(outBuf), "outBuf must hold the largest packet"); | ||
| 4804 | 7 | bzero(outBuf, sizeof(outBuf)); | |
| 4805 | |||
| 4806 | 7 | ControlPkt* cpkt = (ControlPkt*)outBuf; | |
| 4807 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | InitControlPacket(cpkt); |
| 4808 | 7 | cpkt->pktType = CTRL_PKT_FPPCOMMAND; | |
| 4809 | |||
| 4810 | 7 | int pos = sizeof(ControlPkt); | |
| 4811 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
|
7 | if (fits) { |
| 4812 | 42 | auto appendField = [&](const std::string& v) { | |
| 4813 | 42 | memcpy(&outBuf[pos], v.c_str(), v.length() + 1); | |
| 4814 | 42 | pos += v.length() + 1; | |
| 4815 | 48 | }; | |
| 4816 | 6 | outBuf[pos++] = (unsigned char)args.size(); | |
| 4817 | 6 | appendField(host); | |
| 4818 | 6 | appendField(cmd); | |
| 4819 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 6 times.
|
36 | for (auto& a : args) { |
| 4820 | 30 | appendField(a); | |
| 4821 | } | ||
| 4822 | } | ||
| 4823 | 7 | cpkt->extraDataLen = pos - sizeof(ControlPkt); | |
| 4824 | |||
| 4825 | // SendFPPCommandPacket can run on API/command threads, so m_localSystems | ||
| 4826 | // must not be read without m_systemsLock; take a snapshot for both | ||
| 4827 | // MyHostMatches() calls below rather than locking per-call. | ||
| 4828 | 7 | std::vector<MultiSyncSystem> localSystems; | |
| 4829 | { | ||
| 4830 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | std::unique_lock<std::recursive_mutex> lock(m_systemsLock); |
| 4831 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | localSystems = m_localSystems; |
| 4832 | 7 | } | |
| 4833 | |||
| 4834 |
6/8✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 6 times.
|
7 | if (host != "" && host.find(",") == std::string::npos) { |
| 4835 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
1 | if (MyHostMatches(host, m_hostname, localSystems)) { |
| 4836 | // only targetting myself, just run and don't send the packet | ||
| 4837 | ✗ | LogDebug(VB_COMMAND, "Command \"%s\" self-targeted, running locally without sending\n", cmd.c_str()); | |
| 4838 | ✗ | CommandManager::INSTANCE.run(cmd, args); | |
| 4839 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | } else if (!fits) { |
| 4840 | ✗ | LogErr(VB_SYNC, "ERROR: FPP Command \"%s\" (%d bytes) is too large for a MultiSync packet, not sent\n", | |
| 4841 | TruncateForLog(cmd).c_str(), (int)payloadLen); | ||
| 4842 | } else { | ||
| 4843 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | SendUnicastPacket(host, outBuf, pos); |
| 4844 | } | ||
| 4845 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | } else if (!fits) { |
| 4846 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | LogErr(VB_SYNC, "ERROR: FPP Command \"%s\" (%d bytes) is too large for a MultiSync packet, not sent or run\n", |
| 4847 | TruncateForLog(cmd).c_str(), (int)payloadLen); | ||
| 4848 | } else { | ||
| 4849 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | SendControlPacket(outBuf, pos); |
| 4850 | // Run it here directly: multicast and unicast don't loop back, and the | ||
| 4851 | // broadcast copies that do are dropped as our own (IsOwnEcho). | ||
| 4852 |
3/10✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
|
5 | if (host == "" || MyHostMatches(host, m_hostname, localSystems)) { |
| 4853 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | LogDebug(VB_COMMAND, "Command \"%s\" broadcast includes this host, running locally too\n", cmd.c_str()); |
| 4854 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | CommandManager::INSTANCE.run(cmd, args); |
| 4855 | } | ||
| 4856 | } | ||
| 4857 | 7 | } | |
| 4858 | |||
| 4859 | 19274 | MultiSyncStats::MultiSyncStats(std::string ip, std::string host) : | |
| 4860 | 19274 | sourceIP(ip), | |
| 4861 |
1/2✓ Branch 0 taken 19274 times.
✗ Branch 1 not taken.
|
19274 | hostname(host), |
| 4862 | 19274 | pktCommand(0), | |
| 4863 | 19274 | pktSyncSeqOpen(0), | |
| 4864 | 19274 | pktSyncSeqStart(0), | |
| 4865 | 19274 | pktSyncSeqStop(0), | |
| 4866 | 19274 | pktSyncSeqSync(0), | |
| 4867 | 19274 | pktSyncMedOpen(0), | |
| 4868 | 19274 | pktSyncMedStart(0), | |
| 4869 | 19274 | pktSyncMedStop(0), | |
| 4870 | 19274 | pktSyncMedSync(0), | |
| 4871 | 19274 | pktBlank(0), | |
| 4872 | 19274 | pktPing(0), | |
| 4873 | 19274 | pktPlugin(0), | |
| 4874 | 19274 | pktFPPCommand(0), | |
| 4875 | 19274 | pktError(0) { | |
| 4876 | 19274 | lastReceiveTime = time(NULL); | |
| 4877 | 19274 | } | |
| 4878 | |||
| 4879 | 2130 | Json::Value MultiSyncStats::toJSON() { | |
| 4880 |
1/2✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
|
2130 | Json::Value result; |
| 4881 | |||
| 4882 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["sourceIP"] = sourceIP; |
| 4883 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["hostname"] = hostname; |
| 4884 | |||
| 4885 | char timeStr[34]; | ||
| 4886 | 2130 | memset(timeStr, 0, sizeof(timeStr)); | |
| 4887 | struct tm tm; | ||
| 4888 | 2130 | localtime_r(&lastReceiveTime, &tm); | |
| 4889 | 2130 | snprintf(timeStr, sizeof(timeStr), "%4d-%.2d-%.2d %.2d:%.2d:%.2d", | |
| 4890 | 2130 | 1900 + tm.tm_year, tm.tm_mon + 1, tm.tm_mday, | |
| 4891 | tm.tm_hour, tm.tm_min, tm.tm_sec); | ||
| 4892 | |||
| 4893 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["lastReceiveTime"] = timeStr; |
| 4894 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktCommand"] = __atomic_load_n(&pktCommand, __ATOMIC_RELAXED); |
| 4895 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktSyncSeqOpen"] = __atomic_load_n(&pktSyncSeqOpen, __ATOMIC_RELAXED); |
| 4896 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktSyncSeqStart"] = __atomic_load_n(&pktSyncSeqStart, __ATOMIC_RELAXED); |
| 4897 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktSyncSeqStop"] = __atomic_load_n(&pktSyncSeqStop, __ATOMIC_RELAXED); |
| 4898 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktSyncSeqSync"] = __atomic_load_n(&pktSyncSeqSync, __ATOMIC_RELAXED); |
| 4899 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktSyncMedOpen"] = __atomic_load_n(&pktSyncMedOpen, __ATOMIC_RELAXED); |
| 4900 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktSyncMedStart"] = __atomic_load_n(&pktSyncMedStart, __ATOMIC_RELAXED); |
| 4901 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktSyncMedStop"] = __atomic_load_n(&pktSyncMedStop, __ATOMIC_RELAXED); |
| 4902 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktSyncMedSync"] = __atomic_load_n(&pktSyncMedSync, __ATOMIC_RELAXED); |
| 4903 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktBlank"] = __atomic_load_n(&pktBlank, __ATOMIC_RELAXED); |
| 4904 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktPing"] = __atomic_load_n(&pktPing, __ATOMIC_RELAXED); |
| 4905 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktPlugin"] = __atomic_load_n(&pktPlugin, __ATOMIC_RELAXED); |
| 4906 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktFPPCommand"] = __atomic_load_n(&pktFPPCommand, __ATOMIC_RELAXED); |
| 4907 |
2/4✓ Branch 0 taken 2130 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2130 times.
✗ Branch 3 not taken.
|
2130 | result["pktError"] = __atomic_load_n(&pktError, __ATOMIC_RELAXED); |
| 4908 | |||
| 4909 | 2130 | return result; | |
| 4910 | ✗ | } | |
| 4911 |