| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2026 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 <arpa/inet.h> | ||
| 18 | #include <dirent.h> | ||
| 19 | #include <errno.h> | ||
| 20 | #include <fcntl.h> | ||
| 21 | #include <ifaddrs.h> | ||
| 22 | #include <net/if.h> | ||
| 23 | #include <netinet/in.h> | ||
| 24 | #include <openssl/sha.h> | ||
| 25 | #include <poll.h> | ||
| 26 | #include <strings.h> | ||
| 27 | #include <sys/socket.h> | ||
| 28 | #include <sys/time.h> | ||
| 29 | #include <unistd.h> | ||
| 30 | |||
| 31 | #include <algorithm> | ||
| 32 | #include <chrono> | ||
| 33 | #include <cstring> | ||
| 34 | #include <sstream> | ||
| 35 | |||
| 36 | #include "WLEDAPIResponder.h" | ||
| 37 | |||
| 38 | #include "common.h" | ||
| 39 | #include "fppversion.h" | ||
| 40 | #include "log.h" | ||
| 41 | #include "settings.h" | ||
| 42 | |||
| 43 | #include "commands/Commands.h" | ||
| 44 | #include "overlays/PixelOverlay.h" | ||
| 45 | #include "overlays/PixelOverlayEffects.h" | ||
| 46 | #include "overlays/PixelOverlayModel.h" | ||
| 47 | #include "overlays/WLEDEffects.h" | ||
| 48 | #include "SysSocket.h" | ||
| 49 | |||
| 50 | WLEDAPIResponder WLEDAPIResponder::INSTANCE; | ||
| 51 | |||
| 52 | namespace { | ||
| 53 | |||
| 54 | // RFC 6455 magic GUID, appended to the client's Sec-WebSocket-Key before | ||
| 55 | // SHA1 + base64 to produce the Sec-WebSocket-Accept response. | ||
| 56 | constexpr const char* WS_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | ||
| 57 | |||
| 58 | 745262 | std::string headerValue(const std::string& headers, const std::string& name) { | |
| 59 | // Case-insensitive header lookup. | ||
| 60 |
1/2✓ Branch 0 taken 745262 times.
✗ Branch 1 not taken.
|
745262 | std::string lname = name; |
| 61 | 745262 | std::transform(lname.begin(), lname.end(), lname.begin(), ::tolower); | |
| 62 |
1/2✓ Branch 0 taken 745262 times.
✗ Branch 1 not taken.
|
745262 | std::istringstream is(headers); |
| 63 | 745262 | std::string line; | |
| 64 |
4/6✓ Branch 0 taken 1987377 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1987377 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1490555 times.
✓ Branch 5 taken 496822 times.
|
1987377 | while (std::getline(is, line)) { |
| 65 | 1490555 | auto colon = line.find(':'); | |
| 66 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1490555 times.
|
2732670 | if (colon == std::string::npos) continue; |
| 67 |
1/2✓ Branch 0 taken 1490555 times.
✗ Branch 1 not taken.
|
1490555 | std::string h = line.substr(0, colon); |
| 68 | 1490555 | std::transform(h.begin(), h.end(), h.begin(), ::tolower); | |
| 69 |
2/2✓ Branch 0 taken 1242115 times.
✓ Branch 1 taken 248440 times.
|
1490555 | if (h != lname) continue; |
| 70 |
1/2✓ Branch 0 taken 248440 times.
✗ Branch 1 not taken.
|
248440 | std::string v = line.substr(colon + 1); |
| 71 | // Trim leading spaces and trailing CR/whitespace. | ||
| 72 | 248440 | size_t s = v.find_first_not_of(" \t"); | |
| 73 | 248440 | size_t e = v.find_last_not_of(" \t\r\n"); | |
| 74 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 248440 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
248440 | if (s == std::string::npos) return ""; |
| 75 |
1/2✓ Branch 0 taken 248440 times.
✗ Branch 1 not taken.
|
248440 | return v.substr(s, e - s + 1); |
| 76 |
2/2✓ Branch 0 taken 1242115 times.
✓ Branch 1 taken 248440 times.
|
1738995 | } |
| 77 |
1/2✓ Branch 0 taken 496822 times.
✗ Branch 1 not taken.
|
993644 | return ""; |
| 78 | 745262 | } | |
| 79 | |||
| 80 | // Read until "\r\n\r\n", which separates HTTP headers from body. | ||
| 81 | // Returns the full headers-and-line string, or empty on error. | ||
| 82 | 248447 | bool readUntilHeadersEnd(int fd, std::string& out, size_t maxBytes = 16384) { | |
| 83 | char buf[1024]; | ||
| 84 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | while (out.size() < maxBytes) { |
| 85 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | ssize_t n = ::recv(fd, buf, sizeof(buf), 0); |
| 86 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 248425 times.
|
248447 | if (n <= 0) return false; |
| 87 |
1/2✓ Branch 0 taken 248425 times.
✗ Branch 1 not taken.
|
248425 | out.append(buf, n); |
| 88 |
1/2✓ Branch 0 taken 248425 times.
✗ Branch 1 not taken.
|
248425 | if (out.find("\r\n\r\n") != std::string::npos) return true; |
| 89 | } | ||
| 90 | ✗ | return false; | |
| 91 | } | ||
| 92 | |||
| 93 | 87303 | bool readExact(int fd, void* dst, size_t n) { | |
| 94 | 87303 | auto* p = static_cast<uint8_t*>(dst); | |
| 95 |
2/2✓ Branch 0 taken 87303 times.
✓ Branch 1 taken 87291 times.
|
174594 | while (n) { |
| 96 | 87303 | ssize_t r = ::recv(fd, p, n, 0); | |
| 97 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 87291 times.
|
87303 | if (r <= 0) return false; |
| 98 | 87291 | p += r; | |
| 99 | 87291 | n -= r; | |
| 100 | } | ||
| 101 | 87291 | return true; | |
| 102 | } | ||
| 103 | |||
| 104 | 311260 | bool writeAll(int fd, const void* src, size_t n) { | |
| 105 | 311260 | auto* p = static_cast<const uint8_t*>(src); | |
| 106 |
2/2✓ Branch 0 taken 311262 times.
✓ Branch 1 taken 311257 times.
|
622519 | while (n) { |
| 107 | 311262 | ssize_t w = ::send(fd, p, n, MSG_NOSIGNAL); | |
| 108 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 311259 times.
|
311262 | if (w <= 0) return false; |
| 109 | 311259 | p += w; | |
| 110 | 311259 | n -= w; | |
| 111 | } | ||
| 112 | 311257 | return true; | |
| 113 | } | ||
| 114 | |||
| 115 | 14 | std::string sha1Base64(const std::string& in) { | |
| 116 | unsigned char hash[SHA_DIGEST_LENGTH]; | ||
| 117 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | SHA1(reinterpret_cast<const unsigned char*>(in.data()), in.size(), hash); |
| 118 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
28 | return base64Encode(hash, SHA_DIGEST_LENGTH); |
| 119 | } | ||
| 120 | |||
| 121 | // Readers for JSON that came off the network: a field of the wrong type is | ||
| 122 | // treated as absent rather than thrown on (jsoncpp's as*() throw, and an | ||
| 123 | // exception on a client thread terminates fppd). | ||
| 124 | 127 | int jsonInt(const Json::Value& v, int dflt) { | |
| 125 |
6/6✓ Branch 0 taken 106 times.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 104 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 104 times.
✓ Branch 5 taken 23 times.
|
127 | if (v.isNumeric() && v.isConvertibleTo(Json::intValue)) { |
| 126 | 104 | return v.asInt(); | |
| 127 | } | ||
| 128 | 23 | return dflt; | |
| 129 | } | ||
| 130 | |||
| 131 | 96506 | bool jsonBool(const Json::Value& v, bool dflt) { | |
| 132 |
1/2✓ Branch 0 taken 96506 times.
✗ Branch 1 not taken.
|
96506 | if (v.isBool()) { |
| 133 | 96506 | return v.asBool(); | |
| 134 | } | ||
| 135 | ✗ | if (v.isNumeric()) { | |
| 136 | ✗ | return v.asDouble() != 0; | |
| 137 | } | ||
| 138 | ✗ | return dflt; | |
| 139 | } | ||
| 140 | |||
| 141 | // The status line's reason phrase: every response used to say "OK", even | ||
| 142 | // a 404. | ||
| 143 | 248410 | const char* reasonPhrase(int code) { | |
| 144 |
1/6✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
248410 | switch (code) { |
| 145 | 248410 | case 200: | |
| 146 | 248410 | return "OK"; | |
| 147 | ✗ | case 400: | |
| 148 | ✗ | return "Bad Request"; | |
| 149 | ✗ | case 404: | |
| 150 | ✗ | return "Not Found"; | |
| 151 | ✗ | case 405: | |
| 152 | ✗ | return "Method Not Allowed"; | |
| 153 | ✗ | case 413: | |
| 154 | ✗ | return "Payload Too Large"; | |
| 155 | ✗ | default: | |
| 156 | ✗ | return code < 400 ? "OK" : "Error"; | |
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | 254048 | void closeFd(int& fd) { | |
| 161 |
1/2✓ Branch 0 taken 254048 times.
✗ Branch 1 not taken.
|
254048 | if (fd >= 0) { |
| 162 | 254048 | ::close(fd); | |
| 163 | 254048 | fd = -1; | |
| 164 | } | ||
| 165 | 254048 | } | |
| 166 | |||
| 167 | // Pick the first non-loopback interface and return its MAC (lowercase | ||
| 168 | // hex, no colons — the form WLED uses) along with its primary IPv4. | ||
| 169 | 209520 | void primaryNetIdentity(std::string& macFlat, std::string& ipv4) { | |
| 170 | 209520 | macFlat.clear(); | |
| 171 | 209520 | ipv4.clear(); | |
| 172 | 209520 | std::string ifname; | |
| 173 | |||
| 174 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | if (DIR* d = opendir("/sys/class/net")) { |
| 175 | struct dirent* e; | ||
| 176 |
2/4✓ Branch 0 taken 838080 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 838080 times.
✗ Branch 3 not taken.
|
838080 | while ((e = readdir(d)) != nullptr) { |
| 177 |
12/18✓ Branch 0 taken 419040 times.
✓ Branch 1 taken 419040 times.
✓ Branch 2 taken 419040 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 419040 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 209520 times.
✓ Branch 7 taken 209520 times.
✓ Branch 8 taken 419040 times.
✓ Branch 9 taken 419040 times.
✓ Branch 10 taken 419040 times.
✓ Branch 11 taken 419040 times.
✓ Branch 12 taken 628560 times.
✓ Branch 13 taken 209520 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
1676160 | if (e->d_name[0] == '.' || std::string(e->d_name) == "lo") continue; |
| 178 | char path[256]; | ||
| 179 | 209520 | snprintf(path, sizeof(path), "/sys/class/net/%s/address", e->d_name); | |
| 180 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | FILE* f = fopen(path, "r"); |
| 181 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 209520 times.
|
209520 | if (!f) continue; |
| 182 | 209520 | char buf[32] = {}; | |
| 183 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | if (fgets(buf, sizeof(buf), f)) { |
| 184 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | std::string s(buf); |
| 185 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | s.erase(s.find_last_not_of(" \t\r\n") + 1); |
| 186 |
4/8✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 209520 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 209520 times.
✗ Branch 7 not taken.
|
209520 | if (!s.empty() && s != "00:00:00:00:00:00") { |
| 187 |
5/6✓ Branch 0 taken 2514240 times.
✓ Branch 1 taken 1047600 times.
✓ Branch 2 taken 2514240 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3561840 times.
✓ Branch 5 taken 209520 times.
|
3771360 | for (char c : s) if (c != ':') macFlat.push_back(std::tolower(c)); |
| 188 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | ifname = e->d_name; |
| 189 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | fclose(f); |
| 190 | 209520 | break; | |
| 191 | } | ||
| 192 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 209520 times.
|
209520 | } |
| 193 | ✗ | fclose(f); | |
| 194 | } | ||
| 195 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | closedir(d); |
| 196 | } | ||
| 197 | |||
| 198 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | if (!ifname.empty()) { |
| 199 | 209520 | ifaddrs* ifa = nullptr; | |
| 200 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | if (getifaddrs(&ifa) == 0) { |
| 201 |
1/2✓ Branch 0 taken 838080 times.
✗ Branch 1 not taken.
|
838080 | for (auto* p = ifa; p; p = p->ifa_next) { |
| 202 |
3/4✓ Branch 0 taken 838080 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 419040 times.
✓ Branch 3 taken 419040 times.
|
1047600 | if (!p->ifa_addr || p->ifa_addr->sa_family != AF_INET) continue; |
| 203 |
3/4✓ Branch 0 taken 419040 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✓ Branch 3 taken 209520 times.
|
419040 | if (ifname != p->ifa_name) continue; |
| 204 | char buf[INET_ADDRSTRLEN]; | ||
| 205 | 209520 | auto* in = reinterpret_cast<sockaddr_in*>(p->ifa_addr); | |
| 206 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | if (inet_ntop(AF_INET, &in->sin_addr, buf, sizeof(buf))) { |
| 207 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | ipv4 = buf; |
| 208 | 209520 | break; | |
| 209 | } | ||
| 210 | } | ||
| 211 | 209520 | freeifaddrs(ifa); | |
| 212 | } | ||
| 213 | } | ||
| 214 | 209520 | } | |
| 215 | |||
| 216 | } // namespace | ||
| 217 | |||
| 218 | 2346 | WLEDAPIResponder::WLEDAPIResponder() = default; | |
| 219 | |||
| 220 | 458 | WLEDAPIResponder::~WLEDAPIResponder() { | |
| 221 | 458 | Cleanup(); | |
| 222 | 458 | } | |
| 223 | |||
| 224 | 1887 | void WLEDAPIResponder::Initialize() { | |
| 225 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
|
1887 | if (m_running) return; |
| 226 | |||
| 227 | // Allow disabling via /media/settings. | ||
| 228 |
1/2✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
|
1887 | std::string en = getSetting("WLEDAPIResponder"); |
| 229 |
7/14✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1887 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1887 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1887 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1887 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 1887 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 1887 times.
|
1887 | if (en == "0" || en == "false" || en == "off") { |
| 230 | ✗ | LogInfo(VB_GENERAL, "WLEDAPIResponder disabled by setting\n"); | |
| 231 | ✗ | return; | |
| 232 | } | ||
| 233 | |||
| 234 |
1/2✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
|
1887 | int p = getSettingInt("WLEDAPIPort", 0); |
| 235 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1887 | if (p > 0 && p < 65536) m_port = p; |
| 236 | |||
| 237 | 1887 | m_socket = OpenSocket(AF_INET, SOCK_STREAM, 0); | |
| 238 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
|
1887 | if (m_socket < 0) { |
| 239 | ✗ | LogErr(VB_GENERAL, "WLEDAPIResponder: socket() failed: %s\n", FPPstrerror(errno)); | |
| 240 | ✗ | return; | |
| 241 | } | ||
| 242 | |||
| 243 | 1887 | int opt = 1; | |
| 244 | 1887 | ::setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | |
| 245 | 1887 | ::setsockopt(m_socket, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); | |
| 246 | |||
| 247 | 1887 | sockaddr_in addr{}; | |
| 248 | 1887 | addr.sin_family = AF_INET; | |
| 249 | 1887 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // proxied through Apache | |
| 250 | 1887 | addr.sin_port = htons(m_port); | |
| 251 | |||
| 252 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
|
1887 | if (::bind(m_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) { |
| 253 | ✗ | LogErr(VB_GENERAL, "WLEDAPIResponder: bind(%d) failed: %s\n", m_port, FPPstrerror(errno)); | |
| 254 | ✗ | closeFd(m_socket); | |
| 255 | ✗ | return; | |
| 256 | } | ||
| 257 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
|
1887 | if (::listen(m_socket, 8) < 0) { |
| 258 | ✗ | LogErr(VB_GENERAL, "WLEDAPIResponder: listen() failed: %s\n", FPPstrerror(errno)); | |
| 259 | ✗ | closeFd(m_socket); | |
| 260 | ✗ | return; | |
| 261 | } | ||
| 262 | // The accept thread waits in poll() on the socket and this pipe, and | ||
| 263 | // Cleanup() wakes it through the pipe. It used to wait in accept() and be | ||
| 264 | // woken by shutdown() on the listening socket, which only Linux honours: | ||
| 265 | // on macOS accept() went on waiting and fppd hung on every stop. The | ||
| 266 | // socket is non-blocking so a connection that goes away between poll() | ||
| 267 | // and accept() can't leave the thread in accept() either. | ||
| 268 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
|
1887 | if (::pipe(m_wakePipe) < 0) { |
| 269 | ✗ | LogErr(VB_GENERAL, "WLEDAPIResponder: pipe() failed: %s\n", FPPstrerror(errno)); | |
| 270 | ✗ | closeFd(m_socket); | |
| 271 | ✗ | return; | |
| 272 | } | ||
| 273 |
2/2✓ Branch 0 taken 3774 times.
✓ Branch 1 taken 1887 times.
|
5661 | for (int fd : m_wakePipe) { |
| 274 |
1/2✓ Branch 0 taken 3710 times.
✗ Branch 1 not taken.
|
3774 | fcntl(fd, F_SETFD, FD_CLOEXEC); |
| 275 | } | ||
| 276 |
2/4✓ Branch 0 taken 1855 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1855 times.
✗ Branch 3 not taken.
|
1887 | fcntl(m_wakePipe[1], F_SETFL, fcntl(m_wakePipe[1], F_GETFL) | O_NONBLOCK); |
| 277 |
2/4✓ Branch 0 taken 1855 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1855 times.
✗ Branch 3 not taken.
|
1887 | fcntl(m_socket, F_SETFL, fcntl(m_socket, F_GETFL) | O_NONBLOCK); |
| 278 | |||
| 279 | 1887 | m_running = true; | |
| 280 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
3774 | m_acceptThread = new std::thread([this]() { |
| 281 |
2/4✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1887 times.
✗ Branch 3 not taken.
|
1887 | SetThreadName("FPP-WLEDAPI"); |
| 282 | 1887 | AcceptLoop(); | |
| 283 |
2/4✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1887 times.
✗ Branch 3 not taken.
|
3754 | }); |
| 284 | |||
| 285 |
1/2✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
|
1887 | LogInfo(VB_GENERAL, "WLEDAPIResponder: listening on 127.0.0.1:%d\n", m_port); |
| 286 |
1/2✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
|
1887 | } |
| 287 | |||
| 288 | 2325 | void WLEDAPIResponder::Cleanup() { | |
| 289 |
5/6✓ Branch 0 taken 458 times.
✓ Branch 1 taken 1867 times.
✓ Branch 2 taken 458 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 458 times.
✓ Branch 5 taken 1867 times.
|
2325 | if (!m_running && m_socket < 0) return; |
| 290 | 1867 | m_running = false; | |
| 291 | |||
| 292 | // Wake the accept thread out of its poll(). The descriptors are closed | ||
| 293 | // only after it has been joined below: closed while that thread could | ||
| 294 | // still loop back into poll() and accept(), they raced it, and accept() | ||
| 295 | // could land on a closed descriptor or one already reused elsewhere. | ||
| 296 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | if (m_wakePipe[1] >= 0) { |
| 297 | 1867 | char c = 0; | |
| 298 |
2/4✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1867 times.
|
1867 | if (::write(m_wakePipe[1], &c, 1) < 0) { |
| 299 | ✗ | LogWarn(VB_GENERAL, "WLEDAPIResponder: could not wake the accept thread: %s\n", FPPstrerror(errno)); | |
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | // Shut down every per-connection socket too — accept-thread might | ||
| 304 | // have handed off a connection that's now blocked in recv(). | ||
| 305 | { | ||
| 306 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | std::unique_lock<std::mutex> lock(m_clientsLock); |
| 307 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1867 times.
|
1879 | for (int fd : m_clientFds) ::shutdown(fd, SHUT_RDWR); |
| 308 | 1867 | m_wsClients.clear(); | |
| 309 | 1867 | } | |
| 310 | |||
| 311 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | if (m_acceptThread) { |
| 312 | 1867 | m_acceptThread->join(); | |
| 313 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | delete m_acceptThread; |
| 314 | 1867 | m_acceptThread = nullptr; | |
| 315 | } | ||
| 316 | 1867 | closeFd(m_socket); | |
| 317 | 1867 | closeFd(m_wakePipe[0]); | |
| 318 | 1867 | closeFd(m_wakePipe[1]); | |
| 319 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 1867 times.
|
1897 | for (auto& t : m_clientThreads) { |
| 320 |
2/4✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
|
30 | if (t->thread.joinable()) t->thread.join(); |
| 321 | } | ||
| 322 | 1867 | m_clientThreads.clear(); | |
| 323 | { | ||
| 324 |
1/2✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
|
1867 | std::unique_lock<std::mutex> lock(m_clientsLock); |
| 325 | 1867 | m_clientFds.clear(); | |
| 326 | 1867 | } | |
| 327 | } | ||
| 328 | |||
| 329 | 1887 | void WLEDAPIResponder::AcceptLoop() { | |
| 330 | 1887 | std::chrono::steady_clock::time_point lastAcceptWarn{}; | |
| 331 | 1887 | int acceptFailures = 0; | |
| 332 |
2/2✓ Branch 0 taken 250368 times.
✓ Branch 1 taken 1 times.
|
250369 | while (m_running) { |
| 333 | 250368 | struct pollfd fds[2] = { { m_socket, POLLIN, 0 }, { m_wakePipe[0], POLLIN, 0 } }; | |
| 334 |
3/8✓ Branch 0 taken 250348 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 250348 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 250348 times.
|
250368 | if (::poll(fds, 2, -1) < 0 && errno != EINTR) { |
| 335 | ✗ | LogErr(VB_GENERAL, "WLEDAPIResponder: poll() failed: %s\n", FPPstrerror(errno)); | |
| 336 | 1866 | break; | |
| 337 | } | ||
| 338 |
5/6✓ Branch 0 taken 248482 times.
✓ Branch 1 taken 1866 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 248482 times.
✓ Branch 4 taken 1866 times.
✓ Branch 5 taken 248482 times.
|
250348 | if (!m_running || fds[1].revents) { |
| 339 | 1866 | break; | |
| 340 | } | ||
| 341 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 248482 times.
|
248482 | if (!fds[0].revents) { |
| 342 | 35 | continue; | |
| 343 | } | ||
| 344 |
1/2✓ Branch 0 taken 248482 times.
✗ Branch 1 not taken.
|
248482 | int client = AcceptSocket(m_socket, nullptr, nullptr); |
| 345 |
2/2✓ Branch 0 taken 35 times.
✓ Branch 1 taken 248447 times.
|
248482 | if (client < 0) { |
| 346 | 35 | int err = errno; | |
| 347 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 35 times.
|
35 | if (!m_running) { |
| 348 | ✗ | break; | |
| 349 | } | ||
| 350 |
4/8✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 35 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 35 times.
|
35 | if (err == EINTR || err == ECONNABORTED || err == EAGAIN || err == EWOULDBLOCK) { |
| 351 | 35 | continue; | |
| 352 | } | ||
| 353 | // Out of descriptors (EMFILE/ENFILE) or memory, the pending | ||
| 354 | // connection stays queued and accept() fails again at once: this | ||
| 355 | // loop spun a core and wrote a log line per spin. Back off, and | ||
| 356 | // log once per interval with a count. | ||
| 357 | 35 | ++acceptFailures; | |
| 358 | 35 | auto now = std::chrono::steady_clock::now(); | |
| 359 |
4/6✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 34 times.
|
35 | if (now - lastAcceptWarn >= std::chrono::seconds(10)) { |
| 360 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | LogWarn(VB_GENERAL, "WLEDAPIResponder: accept failed: %s (%d failures)\n", FPPstrerror(err), acceptFailures); |
| 361 | 1 | lastAcceptWarn = now; | |
| 362 | 1 | acceptFailures = 0; | |
| 363 | } | ||
| 364 |
1/2✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
|
35 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
| 365 | 35 | continue; | |
| 366 | 35 | } | |
| 367 | // The client thread reads with blocking calls; on macOS an accepted | ||
| 368 | // socket inherits the listening socket's O_NONBLOCK. | ||
| 369 |
2/4✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248447 times.
✗ Branch 3 not taken.
|
248447 | fcntl(client, F_SETFL, fcntl(client, F_GETFL) & ~O_NONBLOCK); |
| 370 | // Writes to a client happen under m_clientsLock (the state | ||
| 371 | // broadcast), so one that stops reading must not block them for | ||
| 372 | // good: give up on it after a couple of seconds. | ||
| 373 | 248447 | struct timeval tv = { 2, 0 }; | |
| 374 | 248447 | ::setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); | |
| 375 | |||
| 376 | // Hand off to a per-connection thread. WebSocket streams are | ||
| 377 | // long-lived, but there will only ever be a handful of clients | ||
| 378 | // (one mobile app at a time, maybe a couple), so a dedicated | ||
| 379 | // thread per connection is simpler than juggling select/epoll. | ||
| 380 | 248447 | std::vector<std::unique_ptr<ClientThread>> finished; | |
| 381 | { | ||
| 382 | // Track every connection fd so Cleanup() can shutdown | ||
| 383 | // them all. The client thread removes its fd, and then | ||
| 384 | // closes it, when the connection ends. | ||
| 385 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | std::unique_lock<std::mutex> lock(m_clientsLock); |
| 386 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | m_clientFds.push_back(client); |
| 387 |
2/2✓ Branch 0 taken 949655 times.
✓ Branch 1 taken 248447 times.
|
1198102 | for (auto it = m_clientThreads.begin(); it != m_clientThreads.end();) { |
| 388 |
2/2✓ Branch 0 taken 248417 times.
✓ Branch 1 taken 701238 times.
|
949655 | if ((*it)->done) { |
| 389 |
1/2✓ Branch 0 taken 248417 times.
✗ Branch 1 not taken.
|
248417 | finished.push_back(std::move(*it)); |
| 390 |
1/2✓ Branch 0 taken 248417 times.
✗ Branch 1 not taken.
|
248417 | it = m_clientThreads.erase(it); |
| 391 | } else { | ||
| 392 | 701238 | ++it; | |
| 393 | } | ||
| 394 | } | ||
| 395 | 248447 | } | |
| 396 |
2/2✓ Branch 0 taken 248417 times.
✓ Branch 1 taken 248447 times.
|
496864 | for (auto& f : finished) { |
| 397 |
1/2✓ Branch 0 taken 248417 times.
✗ Branch 1 not taken.
|
248417 | f->thread.join(); // done is its last act, so this is immediate |
| 398 | } | ||
| 399 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | auto ct = std::make_unique<ClientThread>(); |
| 400 | 248447 | ClientThread* self = ct.get(); | |
| 401 | 745341 | ct->thread = std::thread([this, client, self]() { | |
| 402 |
2/4✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248447 times.
✗ Branch 3 not taken.
|
248447 | SetThreadName("FPP-WLEDAPI-cli"); |
| 403 | try { | ||
| 404 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | ClientLoop(client); |
| 405 | ✗ | } catch (const std::exception& ex) { | |
| 406 | // A request is never worth the daemon; ClientLoop's own | ||
| 407 | // checks should keep anything from getting here. | ||
| 408 | ✗ | LogWarn(VB_GENERAL, "WLEDAPIResponder: dropped a request: %s\n", ex.what()); | |
| 409 | ✗ | } | |
| 410 | { | ||
| 411 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | std::unique_lock<std::mutex> lock(m_clientsLock); |
| 412 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
496894 | m_clientFds.erase( |
| 413 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | std::remove(m_clientFds.begin(), m_clientFds.end(), client), |
| 414 | 248447 | m_clientFds.end()); | |
| 415 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
496894 | m_wsClients.erase( |
| 416 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | std::remove(m_wsClients.begin(), m_wsClients.end(), client), |
| 417 | 248447 | m_wsClients.end()); | |
| 418 | 248447 | } | |
| 419 | // Closed only here, after it has left both lists. ClientLoop | ||
| 420 | // used to close it itself: accept() could then hand the same | ||
| 421 | // number to a new connection, which the erase above removed | ||
| 422 | // along with this one, so Cleanup() never woke it. | ||
| 423 | 248447 | int fd = client; | |
| 424 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | closeFd(fd); |
| 425 | 248447 | self->done = true; | |
| 426 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
745341 | }); |
| 427 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | std::unique_lock<std::mutex> lock(m_clientsLock); |
| 428 |
1/2✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
|
248447 | m_clientThreads.push_back(std::move(ct)); |
| 429 | 248447 | } | |
| 430 | 1867 | } | |
| 431 | |||
| 432 | // Never closes fd: the thread that runs it closes it once it is untracked. | ||
| 433 | 248447 | void WLEDAPIResponder::ClientLoop(int fd) { | |
| 434 | 248447 | std::string headerBlock; | |
| 435 |
3/4✓ Branch 0 taken 248447 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 248425 times.
|
248447 | if (!readUntilHeadersEnd(fd, headerBlock)) { |
| 436 | 22 | return; | |
| 437 | } | ||
| 438 | |||
| 439 | 248425 | auto headerEnd = headerBlock.find("\r\n\r\n"); | |
| 440 | 248425 | std::string requestLine; | |
| 441 | 248425 | std::string headers; | |
| 442 |
1/2✓ Branch 0 taken 248425 times.
✗ Branch 1 not taken.
|
248425 | std::string body = headerBlock.substr(headerEnd + 4); |
| 443 | |||
| 444 | 248425 | auto firstLineEnd = headerBlock.find("\r\n"); | |
| 445 |
1/2✓ Branch 0 taken 248425 times.
✗ Branch 1 not taken.
|
248425 | if (firstLineEnd != std::string::npos) { |
| 446 |
1/2✓ Branch 0 taken 248425 times.
✗ Branch 1 not taken.
|
248425 | requestLine = headerBlock.substr(0, firstLineEnd); |
| 447 |
1/2✓ Branch 0 taken 248425 times.
✗ Branch 1 not taken.
|
248425 | headers = headerBlock.substr(firstLineEnd + 2, headerEnd - firstLineEnd - 2); |
| 448 | } | ||
| 449 | |||
| 450 | // If the request is a WebSocket upgrade, do the handshake and then | ||
| 451 | // keep the connection open in WS mode. | ||
| 452 |
2/4✓ Branch 0 taken 248425 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248425 times.
✗ Branch 3 not taken.
|
248425 | std::string upgrade = headerValue(headers, "Upgrade"); |
| 453 | 248425 | std::transform(upgrade.begin(), upgrade.end(), upgrade.begin(), ::tolower); | |
| 454 |
2/4✓ Branch 0 taken 248425 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248425 times.
✗ Branch 3 not taken.
|
248425 | std::string wsKey = headerValue(headers, "Sec-WebSocket-Key"); |
| 455 | |||
| 456 |
6/8✓ Branch 0 taken 248425 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 248411 times.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 14 times.
✓ Branch 7 taken 248411 times.
|
248425 | if (upgrade == "websocket" && !wsKey.empty()) { |
| 457 |
2/4✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
|
14 | if (!DoWebSocketHandshake(fd, wsKey)) { |
| 458 | ✗ | return; | |
| 459 | } | ||
| 460 | // Send initial state snapshot. | ||
| 461 |
2/4✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
|
14 | SendTextFrame(fd, BuildCombinedJson()); |
| 462 | { | ||
| 463 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | std::unique_lock<std::mutex> lock(m_clientsLock); |
| 464 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | m_wsClients.push_back(fd); |
| 465 | 14 | } | |
| 466 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | RunWebSocket(fd); |
| 467 | { | ||
| 468 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | std::unique_lock<std::mutex> lock(m_clientsLock); |
| 469 |
2/4✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
|
14 | m_wsClients.erase(std::remove(m_wsClients.begin(), m_wsClients.end(), fd), |
| 470 | 14 | m_wsClients.end()); | |
| 471 | 14 | } | |
| 472 | 14 | return; | |
| 473 | } | ||
| 474 | |||
| 475 | // Otherwise treat as plain HTTP. We need the body if Content-Length | ||
| 476 | // says so; recv any remainder. Apache proxies /ws here without the UI | ||
| 477 | // password, so the length is a stranger's claim: cap it (a WLED state | ||
| 478 | // post is a few hundred bytes; frames are capped the same in ReadFrame) | ||
| 479 | // and don't let a stalled sender hold this thread forever. | ||
| 480 | static constexpr long MAX_HTTP_BODY = 64 * 1024; | ||
| 481 |
2/4✓ Branch 0 taken 248411 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248411 times.
✗ Branch 3 not taken.
|
248411 | long contentLen = std::strtol(headerValue(headers, "Content-Length").c_str(), nullptr, 10); |
| 482 |
3/4✓ Branch 0 taken 248411 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 248410 times.
|
248411 | if (contentLen < 0 || contentLen > MAX_HTTP_BODY) { |
| 483 | static const char tooLarge[] = "HTTP/1.1 413 Payload Too Large\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"; | ||
| 484 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | writeAll(fd, tooLarge, sizeof(tooLarge) - 1); |
| 485 | 1 | return; | |
| 486 | } | ||
| 487 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 248409 times.
|
248410 | if (static_cast<long>(body.size()) < contentLen) { |
| 488 | // curl and others wait for this before sending the body; without it | ||
| 489 | // Apache and this thread wait on each other until the proxy timeout. | ||
| 490 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | std::string expect = headerValue(headers, "Expect"); |
| 491 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (strcasecmp(expect.c_str(), "100-continue") == 0) { |
| 492 | static const char cont[] = "HTTP/1.1 100 Continue\r\n\r\n"; | ||
| 493 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | writeAll(fd, cont, sizeof(cont) - 1); |
| 494 | } | ||
| 495 | 1 | struct timeval tv = { 5, 0 }; | |
| 496 | 1 | ::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); | |
| 497 | 1 | } | |
| 498 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 248410 times.
|
248411 | while (static_cast<long>(body.size()) < contentLen) { |
| 499 | char buf[1024]; | ||
| 500 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | ssize_t n = ::recv(fd, buf, sizeof(buf), 0); |
| 501 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (n <= 0) break; |
| 502 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | body.append(buf, n); |
| 503 | } | ||
| 504 | |||
| 505 |
1/2✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
|
248410 | HandleHTTPRequest(fd, requestLine, headers, body); |
| 506 |
12/12✓ Branch 0 taken 248410 times.
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 248410 times.
✓ Branch 3 taken 15 times.
✓ Branch 4 taken 248410 times.
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 248410 times.
✓ Branch 7 taken 15 times.
✓ Branch 8 taken 248410 times.
✓ Branch 9 taken 15 times.
✓ Branch 10 taken 248410 times.
✓ Branch 11 taken 37 times.
|
248522 | } |
| 507 | |||
| 508 | 14 | bool WLEDAPIResponder::DoWebSocketHandshake(int fd, const std::string& key) { | |
| 509 |
2/4✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
|
14 | std::string accept = sha1Base64(key + WS_MAGIC); |
| 510 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | std::ostringstream resp; |
| 511 | resp << "HTTP/1.1 101 Switching Protocols\r\n" | ||
| 512 | << "Upgrade: websocket\r\n" | ||
| 513 | << "Connection: Upgrade\r\n" | ||
| 514 | << "Sec-WebSocket-Accept: " << accept << "\r\n" | ||
| 515 | << "Server: fppd/" << getFPPVersion() << "\r\n" | ||
| 516 |
11/22✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 14 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 14 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 14 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 14 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 14 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 14 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 14 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 14 times.
✗ Branch 21 not taken.
|
14 | << "\r\n"; |
| 517 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | std::string s = resp.str(); |
| 518 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
28 | return writeAll(fd, s.data(), s.size()); |
| 519 | 14 | } | |
| 520 | |||
| 521 | 62834 | bool WLEDAPIResponder::SendTextFrame(int fd, const std::string& payload) { | |
| 522 | 62834 | size_t len = payload.size(); | |
| 523 | uint8_t header[10]; | ||
| 524 | 62834 | size_t headerLen = 0; | |
| 525 | 62834 | header[headerLen++] = 0x81; // FIN + text opcode | |
| 526 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 62834 times.
|
62834 | if (len < 126) { |
| 527 | ✗ | header[headerLen++] = static_cast<uint8_t>(len); | |
| 528 |
1/2✓ Branch 0 taken 62834 times.
✗ Branch 1 not taken.
|
62834 | } else if (len <= 0xffff) { |
| 529 | 62834 | header[headerLen++] = 126; | |
| 530 | 62834 | header[headerLen++] = (len >> 8) & 0xff; | |
| 531 | 62834 | header[headerLen++] = len & 0xff; | |
| 532 | } else { | ||
| 533 | ✗ | header[headerLen++] = 127; | |
| 534 | ✗ | for (int i = 7; i >= 0; --i) header[headerLen++] = (len >> (i * 8)) & 0xff; | |
| 535 | } | ||
| 536 | 62834 | std::vector<uint8_t> frame; | |
| 537 |
1/2✓ Branch 0 taken 62834 times.
✗ Branch 1 not taken.
|
62834 | frame.reserve(headerLen + len); |
| 538 |
1/2✓ Branch 0 taken 62834 times.
✗ Branch 1 not taken.
|
62834 | frame.insert(frame.end(), header, header + headerLen); |
| 539 |
1/2✓ Branch 0 taken 62834 times.
✗ Branch 1 not taken.
|
62834 | frame.insert(frame.end(), payload.begin(), payload.end()); |
| 540 |
1/2✓ Branch 0 taken 62834 times.
✗ Branch 1 not taken.
|
125668 | return writeAll(fd, frame.data(), frame.size()); |
| 541 | 62834 | } | |
| 542 | |||
| 543 | ✗ | bool WLEDAPIResponder::SendCloseFrame(int fd) { | |
| 544 | ✗ | uint8_t close[2] = { 0x88, 0x00 }; | |
| 545 | ✗ | return writeAll(fd, close, sizeof(close)); | |
| 546 | } | ||
| 547 | |||
| 548 | 29109 | int WLEDAPIResponder::ReadFrame(int fd, std::string& out) { | |
| 549 | uint8_t hdr[2]; | ||
| 550 |
3/4✓ Branch 0 taken 29109 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 29097 times.
|
29109 | if (!readExact(fd, hdr, 2)) return -1; |
| 551 | |||
| 552 | 29097 | uint8_t opcode = hdr[0] & 0x0f; | |
| 553 | 29097 | bool masked = (hdr[1] & 0x80) != 0; | |
| 554 | 29097 | uint64_t len = hdr[1] & 0x7f; | |
| 555 | |||
| 556 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 29096 times.
|
29097 | if (len == 126) { |
| 557 | uint8_t e[2]; | ||
| 558 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
1 | if (!readExact(fd, e, 2)) return -1; |
| 559 | 1 | len = (uint64_t(e[0]) << 8) | e[1]; | |
| 560 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 29096 times.
|
29096 | } else if (len == 127) { |
| 561 | uint8_t e[8]; | ||
| 562 | ✗ | if (!readExact(fd, e, 8)) return -1; | |
| 563 | ✗ | len = 0; | |
| 564 | ✗ | for (int i = 0; i < 8; ++i) len = (len << 8) | e[i]; | |
| 565 | } | ||
| 566 | |||
| 567 | 29097 | uint8_t mask[4] = {}; | |
| 568 |
4/8✓ Branch 0 taken 29097 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29097 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 29097 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 29097 times.
|
29097 | if (masked && !readExact(fd, mask, 4)) return -1; |
| 569 | |||
| 570 | // Sanity cap — the WLED iOS app sends very small messages. | ||
| 571 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 29097 times.
|
29097 | if (len > 65536) return -1; |
| 572 | // RFC 6455 5.5: a control frame (close, ping, pong) carries at most 125 | ||
| 573 | // bytes. A longer ping was echoed with its length in a single byte, | ||
| 574 | // whose top bit is the MASK flag, so the pong was unparseable. | ||
| 575 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 29096 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
29097 | if ((opcode & 0x8) && len > 125) return -1; |
| 576 | |||
| 577 | 29096 | std::string payload; | |
| 578 |
1/2✓ Branch 0 taken 29096 times.
✗ Branch 1 not taken.
|
29096 | payload.resize(len); |
| 579 |
4/8✓ Branch 0 taken 29096 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29096 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 29096 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 29096 times.
|
29096 | if (len && !readExact(fd, payload.data(), len)) return -1; |
| 580 |
1/2✓ Branch 0 taken 29096 times.
✗ Branch 1 not taken.
|
29096 | if (masked) { |
| 581 |
2/2✓ Branch 0 taken 349325 times.
✓ Branch 1 taken 29096 times.
|
378421 | for (uint64_t i = 0; i < len; ++i) payload[i] ^= mask[i & 3]; |
| 582 | } | ||
| 583 | |||
| 584 |
1/5✓ Branch 0 taken 29096 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
29096 | switch (opcode) { |
| 585 | 29096 | case 0x1: // text | |
| 586 | 29096 | out = std::move(payload); | |
| 587 | 29096 | return 1; | |
| 588 | ✗ | case 0x8: { // close | |
| 589 | // Under m_clientsLock, as every other write to a WebSocket is: a | ||
| 590 | // frame written alongside a broadcast would interleave with it. | ||
| 591 | ✗ | std::unique_lock<std::mutex> lock(m_clientsLock); | |
| 592 | ✗ | SendCloseFrame(fd); | |
| 593 | ✗ | return -1; | |
| 594 | ✗ | } | |
| 595 | ✗ | case 0x9: { // ping → pong with same payload (at most 125 bytes, above) | |
| 596 | ✗ | std::vector<uint8_t> pong; | |
| 597 | ✗ | pong.push_back(0x8a); | |
| 598 | ✗ | pong.push_back(static_cast<uint8_t>(payload.size())); | |
| 599 | ✗ | pong.insert(pong.end(), payload.begin(), payload.end()); | |
| 600 | ✗ | std::unique_lock<std::mutex> lock(m_clientsLock); | |
| 601 | // A pong only partly written leaves the stream mid-frame. | ||
| 602 | ✗ | return writeAll(fd, pong.data(), pong.size()) ? 0 : -1; | |
| 603 | ✗ | } | |
| 604 | ✗ | case 0xa: // pong | |
| 605 | ✗ | return 0; | |
| 606 | ✗ | default: | |
| 607 | // Binary, continuation, or anything else — ignore for now. | ||
| 608 | ✗ | return 0; | |
| 609 | } | ||
| 610 | 29096 | } | |
| 611 | |||
| 612 | 14 | void WLEDAPIResponder::RunWebSocket(int fd) { | |
| 613 | 14 | std::string msg; | |
| 614 |
1/2✓ Branch 0 taken 29109 times.
✗ Branch 1 not taken.
|
29109 | while (m_running) { |
| 615 |
1/2✓ Branch 0 taken 29109 times.
✗ Branch 1 not taken.
|
29109 | int r = ReadFrame(fd, msg); |
| 616 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 29096 times.
|
29110 | if (r < 0) break; |
| 617 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 29096 times.
|
29096 | if (r == 0) continue; |
| 618 | |||
| 619 | // Apply incoming WledState JSON, then echo back the new combined | ||
| 620 | // {state, info} just like the real WLED node does. | ||
| 621 |
1/2✓ Branch 0 taken 29096 times.
✗ Branch 1 not taken.
|
29096 | ApplyStatePost(msg); |
| 622 | // Under m_clientsLock, as NotifyStateChanged() writes: two writers on | ||
| 623 | // one socket would interleave their frames. | ||
| 624 |
1/2✓ Branch 0 taken 29096 times.
✗ Branch 1 not taken.
|
29096 | std::string reply = BuildCombinedJson(); |
| 625 |
1/2✓ Branch 0 taken 29096 times.
✗ Branch 1 not taken.
|
29096 | std::unique_lock<std::mutex> lock(m_clientsLock); |
| 626 |
3/4✓ Branch 0 taken 29096 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 29095 times.
|
29096 | if (!SendTextFrame(fd, reply)) break; |
| 627 |
4/4✓ Branch 0 taken 29095 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 29095 times.
✓ Branch 3 taken 1 times.
|
29097 | } |
| 628 | 14 | } | |
| 629 | |||
| 630 | 96542 | void WLEDAPIResponder::NotifyStateChanged() { | |
| 631 |
1/2✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
|
96542 | std::string snapshot = BuildCombinedJson(); |
| 632 |
1/2✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
|
96542 | std::unique_lock<std::mutex> lock(m_clientsLock); |
| 633 |
2/2✓ Branch 0 taken 33724 times.
✓ Branch 1 taken 96542 times.
|
130266 | for (auto it = m_wsClients.begin(); it != m_wsClients.end();) { |
| 634 |
3/4✓ Branch 0 taken 33724 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 33722 times.
✓ Branch 3 taken 2 times.
|
33724 | if (SendTextFrame(*it, snapshot)) { |
| 635 | 33722 | ++it; | |
| 636 | 33722 | continue; | |
| 637 | } | ||
| 638 | // A send that failed or timed out (SO_SNDTIMEO) may have written | ||
| 639 | // part of the frame, so whatever followed on this connection would | ||
| 640 | // be read from the middle of it; and a client that stopped reading | ||
| 641 | // would cost every later broadcast the full timeout again, all of | ||
| 642 | // it under this lock. Drop it: shutdown() ends its ClientLoop's | ||
| 643 | // read, and that thread untracks and closes the fd. | ||
| 644 | 2 | ::shutdown(*it, SHUT_RDWR); | |
| 645 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | it = m_wsClients.erase(it); |
| 646 | } | ||
| 647 | 96542 | } | |
| 648 | |||
| 649 | 248410 | bool WLEDAPIResponder::HandleHTTPRequest(int fd, const std::string& reqLine, | |
| 650 | const std::string& /*headers*/, | ||
| 651 | const std::string& body) { | ||
| 652 | 248410 | std::string method, path; | |
| 653 | { | ||
| 654 |
1/2✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
|
248410 | std::istringstream is(reqLine); |
| 655 |
2/4✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248410 times.
✗ Branch 3 not taken.
|
248410 | is >> method >> path; |
| 656 | 248410 | } | |
| 657 | |||
| 658 | 248410 | auto respond = [&](int code, const std::string& ctype, const std::string& payload) { | |
| 659 |
1/2✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
|
248410 | std::ostringstream r; |
| 660 |
2/4✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248410 times.
✗ Branch 3 not taken.
|
248410 | r << "HTTP/1.1 " << code << " " << reasonPhrase(code) << "\r\n" |
| 661 | << "Content-Type: " << ctype << "\r\n" | ||
| 662 | << "Access-Control-Allow-Origin: *\r\n" | ||
| 663 | << "Cache-Control: no-store\r\n" | ||
| 664 |
10/20✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248410 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 248410 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 248410 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 248410 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 248410 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 248410 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 248410 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 248410 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 248410 times.
✗ Branch 19 not taken.
|
248410 | << "Content-Length: " << payload.size() << "\r\n" |
| 665 | << "Connection: close\r\n\r\n" | ||
| 666 |
3/6✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248410 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 248410 times.
✗ Branch 5 not taken.
|
248410 | << payload; |
| 667 |
1/2✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
|
248410 | std::string s = r.str(); |
| 668 |
1/2✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
|
248410 | writeAll(fd, s.data(), s.size()); |
| 669 | 248410 | }; | |
| 670 | |||
| 671 |
7/10✓ Branch 0 taken 248410 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 164562 times.
✓ Branch 3 taken 83848 times.
✓ Branch 4 taken 164562 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 164562 times.
✓ Branch 8 taken 83848 times.
✓ Branch 9 taken 164562 times.
|
248410 | if (path == "/json/info" || path == "/json/info/") { |
| 672 |
3/6✓ Branch 0 taken 83848 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 83848 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 83848 times.
✗ Branch 5 not taken.
|
251544 | respond(200, "application/json", BuildInfoJson()); |
| 673 |
7/10✓ Branch 0 taken 164562 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 164537 times.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 25 times.
✓ Branch 8 taken 164537 times.
✓ Branch 9 taken 25 times.
|
164562 | } else if (path == "/json/state" || path == "/json/state/") { |
| 674 |
8/12✓ Branch 0 taken 164537 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 97091 times.
✓ Branch 3 taken 67446 times.
✓ Branch 4 taken 97091 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 97091 times.
✓ Branch 8 taken 67446 times.
✓ Branch 9 taken 97091 times.
✓ Branch 10 taken 67446 times.
✗ Branch 11 not taken.
|
164537 | if (method == "POST" || method == "PUT") ApplyStatePost(body); |
| 675 |
3/6✓ Branch 0 taken 164537 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 164537 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 164537 times.
✗ Branch 5 not taken.
|
493611 | respond(200, "application/json", BuildStateJson()); |
| 676 |
7/10✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 20 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 20 times.
✓ Branch 8 taken 5 times.
✓ Branch 9 taken 20 times.
|
25 | } else if (path == "/json/eff" || path == "/json/eff/") { |
| 677 |
3/6✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
|
15 | respond(200, "application/json", BuildEffectsJson()); |
| 678 |
3/10✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 20 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 20 times.
✗ Branch 9 not taken.
|
20 | } else if (path == "/json" || path == "/json/") { |
| 679 |
3/6✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 20 times.
✗ Branch 5 not taken.
|
60 | respond(200, "application/json", BuildCombinedJson()); |
| 680 | } else { | ||
| 681 | ✗ | respond(404, "text/plain", "not found\n"); | |
| 682 | } | ||
| 683 | 248410 | return true; | |
| 684 | 248410 | } | |
| 685 | |||
| 686 | // ----- JSON builders ------------------------------------------------------ | ||
| 687 | |||
| 688 | 209520 | std::string WLEDAPIResponder::BuildInfoJson() { | |
| 689 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | Json::Value info; |
| 690 |
3/6✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 209520 times.
✗ Branch 5 not taken.
|
209520 | info["ver"] = getFPPVersion(); |
| 691 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["vid"] = 0; |
| 692 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["cn"] = "FPP"; |
| 693 |
3/6✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 209520 times.
✗ Branch 5 not taken.
|
209520 | info["release"] = getFPPVersion(); |
| 694 | // Prefer the actual hostname for the display name. HostDescription | ||
| 695 | // is an optional free-text label; if it's empty or still set to the | ||
| 696 | // default placeholder "FPP", fall back to HostName so the WLED | ||
| 697 | // client shows the same identifier the user sees on the FPP UI and | ||
| 698 | // on the network (matching the mDNS service name). | ||
| 699 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | std::string hn = getSetting("HostName"); |
| 700 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | std::string hd = getSetting("HostDescription"); |
| 701 | 209520 | std::string nm; | |
| 702 |
4/8✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 209520 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 209520 times.
|
209520 | if (!hd.empty() && hd != "FPP") { |
| 703 | ✗ | nm = hd; | |
| 704 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | } else if (!hn.empty()) { |
| 705 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | nm = hn; |
| 706 | } else { | ||
| 707 | ✗ | nm = "FPP"; | |
| 708 | } | ||
| 709 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["name"] = nm; |
| 710 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["brand"] = "FPP"; |
| 711 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["product"] = "FalconPlayer"; |
| 712 |
3/6✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 209520 times.
✗ Branch 5 not taken.
|
209520 | info["arch"] = getSetting("Platform"); |
| 713 | |||
| 714 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | Json::Value leds(Json::objectValue); |
| 715 | 209520 | int totalPixels = 0; | |
| 716 | 209520 | int segCount = 0; | |
| 717 |
3/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 140887 times.
✓ Branch 3 taken 209520 times.
|
350407 | for (const auto& name : PixelOverlayManager::INSTANCE.getModelNamesSnapshot()) { |
| 718 |
1/2✓ Branch 0 taken 140887 times.
✗ Branch 1 not taken.
|
140887 | auto m = PixelOverlayManager::INSTANCE.getModelShared(name); |
| 719 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 140887 times.
|
140887 | if (!m) continue; |
| 720 | 140887 | int w = m->getWidth(); | |
| 721 | 140887 | int h = m->getHeight(); | |
| 722 | 140887 | totalPixels += std::max(1, w) * std::max(1, h); | |
| 723 | 140887 | ++segCount; | |
| 724 |
1/2✓ Branch 0 taken 140887 times.
✗ Branch 1 not taken.
|
350407 | } |
| 725 |
4/6✓ Branch 0 taken 140887 times.
✓ Branch 1 taken 68633 times.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 209520 times.
✗ Branch 5 not taken.
|
209520 | leds["count"] = totalPixels ? totalPixels : 1; |
| 726 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | leds["pwr"] = 0; |
| 727 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | leds["fps"] = 40; |
| 728 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | leds["maxpwr"] = 0; |
| 729 |
4/6✓ Branch 0 taken 140887 times.
✓ Branch 1 taken 68633 times.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 209520 times.
✗ Branch 5 not taken.
|
209520 | leds["maxseg"] = segCount ? segCount : 1; |
| 730 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | leds["bootps"] = 0; |
| 731 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | leds["rgbw"] = false; |
| 732 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | leds["wv"] = 0; |
| 733 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | leds["cct"] = false; |
| 734 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | leds["lc"] = 1; |
| 735 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["leds"] = leds; |
| 736 | |||
| 737 |
3/6✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 209520 times.
✗ Branch 5 not taken.
|
209520 | info["fxcount"] = static_cast<int>(WLEDEffect::getWLEDAPIEffectNames().size()); |
| 738 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["palcount"] = 1; |
| 739 | { | ||
| 740 | 209520 | std::string mac, ip; | |
| 741 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | primaryNetIdentity(mac, ip); |
| 742 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["mac"] = mac; |
| 743 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["ip"] = ip; |
| 744 | 209520 | } | |
| 745 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["udpport"] = 21324; |
| 746 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["live"] = false; |
| 747 | { | ||
| 748 | // Client threads add and remove themselves while others build this; | ||
| 749 | // every caller builds it before taking m_clientsLock. | ||
| 750 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | std::unique_lock<std::mutex> lock(m_clientsLock); |
| 751 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["ws"] = static_cast<int>(m_wsClients.size()); |
| 752 | 209520 | } | |
| 753 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["str"] = false; |
| 754 | |||
| 755 | // The WLED Codable expects a `wifi` object even on wired devices. | ||
| 756 | // Reporting a fake-good signal keeps clients from rendering us as | ||
| 757 | // a degraded node; for an Ethernet-only FPP this is conventional. | ||
| 758 |
1/2✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
|
209520 | Json::Value wifi(Json::objectValue); |
| 759 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | wifi["bssid"] = "00:00:00:00:00:00"; |
| 760 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | wifi["rssi"] = -50; |
| 761 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | wifi["signal"] = 100; |
| 762 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | wifi["channel"] = 0; |
| 763 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
209520 | info["wifi"] = wifi; |
| 764 | |||
| 765 |
2/4✓ Branch 0 taken 209520 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 209520 times.
✗ Branch 3 not taken.
|
419040 | return SaveJsonToString(info, ""); |
| 766 | 209520 | } | |
| 767 | |||
| 768 | 290209 | std::string WLEDAPIResponder::BuildStateJson() { | |
| 769 |
1/2✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
|
290209 | Json::Value state; |
| 770 |
1/2✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
|
290209 | Json::Value segs(Json::arrayValue); |
| 771 | |||
| 772 | 290209 | bool anyOn = false; | |
| 773 | 290209 | int idx = 0; | |
| 774 |
1/2✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
|
290209 | const auto& effects = WLEDEffect::getWLEDAPIEffectNames(); |
| 775 |
3/4✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✓ Branch 3 taken 290209 times.
|
473645 | for (const auto& name : PixelOverlayManager::INSTANCE.getModelNamesSnapshot()) { |
| 776 |
1/2✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
|
183436 | auto m = PixelOverlayManager::INSTANCE.getModelShared(name); |
| 777 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 183436 times.
|
183436 | if (!m) continue; |
| 778 | |||
| 779 | 183436 | int w = m->getWidth(); | |
| 780 | 183436 | int h = m->getHeight(); | |
| 781 | 183436 | int len = std::max(1, w) * std::max(1, h); | |
| 782 | |||
| 783 | 183436 | bool on = false; | |
| 784 | 183436 | std::string fxName; | |
| 785 | { | ||
| 786 |
1/2✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
|
183436 | std::unique_lock<std::recursive_mutex> lock(m->getRunningEffectMutex()); |
| 787 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 183394 times.
|
183436 | if (m->getRunningEffect()) { |
| 788 | 42 | on = true; | |
| 789 |
2/4✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 42 times.
✗ Branch 3 not taken.
|
42 | fxName = m->getRunningEffect()->name(); |
| 790 | } | ||
| 791 | 183436 | } | |
| 792 |
3/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183327 times.
✓ Branch 3 taken 109 times.
|
183436 | if ((int)m->getState().getState() > 0) on = true; |
| 793 |
2/2✓ Branch 0 taken 183327 times.
✓ Branch 1 taken 109 times.
|
183436 | if (on) anyOn = true; |
| 794 | |||
| 795 | 183436 | int fxIdx = 0; | |
| 796 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 183394 times.
|
183436 | if (!fxName.empty()) { |
| 797 | 42 | int i = 0; | |
| 798 |
1/2✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
|
1008 | for (const auto& en : effects) { |
| 799 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 966 times.
|
1008 | if (en == fxName) { fxIdx = i; break; } |
| 800 | 966 | ++i; | |
| 801 | } | ||
| 802 | } | ||
| 803 | |||
| 804 |
1/2✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
|
183436 | Json::Value s; |
| 805 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["id"] = idx; |
| 806 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["start"] = 0; |
| 807 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["stop"] = len; |
| 808 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["len"] = len; |
| 809 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["grp"] = 1; |
| 810 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["spc"] = 0; |
| 811 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["of"] = 0; |
| 812 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["on"] = on; |
| 813 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["frz"] = false; |
| 814 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["bri"] = 255; |
| 815 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["cct"] = 127; |
| 816 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["set"] = 0; |
| 817 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["n"] = name; |
| 818 |
1/2✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
|
183436 | Json::Value cols(Json::arrayValue); |
| 819 |
2/2✓ Branch 0 taken 550308 times.
✓ Branch 1 taken 183436 times.
|
733744 | for (int c = 0; c < 3; ++c) { |
| 820 |
1/2✓ Branch 0 taken 550308 times.
✗ Branch 1 not taken.
|
550308 | Json::Value rgbw(Json::arrayValue); |
| 821 |
8/10✓ Branch 0 taken 733744 times.
✓ Branch 1 taken 1467488 times.
✓ Branch 2 taken 550308 times.
✓ Branch 3 taken 183436 times.
✓ Branch 4 taken 2201232 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2201232 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2201232 times.
✓ Branch 9 taken 550308 times.
|
2751540 | for (int b = 0; b < 4; ++b) rgbw.append(c == 0 && b < 3 ? 255 : 0); |
| 822 |
1/2✓ Branch 0 taken 550308 times.
✗ Branch 1 not taken.
|
550308 | cols.append(rgbw); |
| 823 | 550308 | } | |
| 824 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["col"] = cols; |
| 825 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["fx"] = fxIdx; |
| 826 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["sx"] = 128; |
| 827 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["ix"] = 128; |
| 828 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["pal"] = 0; |
| 829 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["sel"] = idx == 0; |
| 830 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["rev"] = false; |
| 831 |
2/4✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 183436 times.
✗ Branch 3 not taken.
|
183436 | s["mi"] = false; |
| 832 |
1/2✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
|
183436 | segs.append(s); |
| 833 | 183436 | ++idx; | |
| 834 |
1/2✓ Branch 0 taken 183436 times.
✗ Branch 1 not taken.
|
473645 | } |
| 835 |
3/4✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 106773 times.
✓ Branch 3 taken 183436 times.
|
290209 | if (segs.empty()) { |
| 836 |
1/2✓ Branch 0 taken 106773 times.
✗ Branch 1 not taken.
|
106773 | Json::Value s; |
| 837 |
8/16✓ Branch 0 taken 106773 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 106773 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 106773 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 106773 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 106773 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 106773 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 106773 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 106773 times.
✗ Branch 15 not taken.
|
106773 | s["id"] = 0; s["start"] = 0; s["stop"] = 1; s["len"] = 1; |
| 838 |
8/16✓ Branch 0 taken 106773 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 106773 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 106773 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 106773 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 106773 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 106773 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 106773 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 106773 times.
✗ Branch 15 not taken.
|
106773 | s["on"] = false; s["bri"] = 255; s["fx"] = 0; s["sx"] = 128; |
| 839 |
6/12✓ Branch 0 taken 106773 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 106773 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 106773 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 106773 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 106773 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 106773 times.
✗ Branch 11 not taken.
|
106773 | s["ix"] = 128; s["pal"] = 0; s["sel"] = true; |
| 840 |
2/4✓ Branch 0 taken 106773 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 106773 times.
✗ Branch 3 not taken.
|
106773 | s["n"] = "Placeholder"; |
| 841 |
1/2✓ Branch 0 taken 106773 times.
✗ Branch 1 not taken.
|
106773 | segs.append(s); |
| 842 | 106773 | } | |
| 843 | |||
| 844 |
2/4✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 290209 times.
✗ Branch 3 not taken.
|
290209 | state["on"] = anyOn; |
| 845 |
2/4✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 290209 times.
✗ Branch 3 not taken.
|
290209 | state["bri"] = 255; |
| 846 |
2/4✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 290209 times.
✗ Branch 3 not taken.
|
290209 | state["transition"] = 7; |
| 847 |
2/4✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 290209 times.
✗ Branch 3 not taken.
|
290209 | state["ps"] = -1; |
| 848 |
2/4✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 290209 times.
✗ Branch 3 not taken.
|
290209 | state["pl"] = -1; |
| 849 |
2/4✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 290209 times.
✗ Branch 3 not taken.
|
290209 | state["mainseg"] = 0; |
| 850 |
2/4✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 290209 times.
✗ Branch 3 not taken.
|
290209 | state["seg"] = segs; |
| 851 |
2/4✓ Branch 0 taken 290209 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 290209 times.
✗ Branch 3 not taken.
|
580418 | return SaveJsonToString(state, ""); |
| 852 | 290209 | } | |
| 853 | |||
| 854 | 125672 | std::string WLEDAPIResponder::BuildCombinedJson() { | |
| 855 |
1/2✓ Branch 0 taken 125672 times.
✗ Branch 1 not taken.
|
125672 | Json::Value root; |
| 856 |
1/2✓ Branch 0 taken 125672 times.
✗ Branch 1 not taken.
|
125672 | Json::Reader r; |
| 857 |
2/4✓ Branch 0 taken 125672 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125672 times.
✗ Branch 3 not taken.
|
125672 | Json::Value s, i; |
| 858 |
2/4✓ Branch 0 taken 125672 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125672 times.
✗ Branch 3 not taken.
|
125672 | r.parse(BuildStateJson(), s); |
| 859 |
2/4✓ Branch 0 taken 125672 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125672 times.
✗ Branch 3 not taken.
|
125672 | r.parse(BuildInfoJson(), i); |
| 860 |
2/4✓ Branch 0 taken 125672 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125672 times.
✗ Branch 3 not taken.
|
125672 | root["state"] = s; |
| 861 |
2/4✓ Branch 0 taken 125672 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125672 times.
✗ Branch 3 not taken.
|
125672 | root["info"] = i; |
| 862 |
2/4✓ Branch 0 taken 125672 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125672 times.
✗ Branch 3 not taken.
|
251344 | return SaveJsonToString(root, ""); |
| 863 | 125672 | } | |
| 864 | |||
| 865 | 5 | std::string WLEDAPIResponder::BuildEffectsJson() { | |
| 866 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | Json::Value arr(Json::arrayValue); |
| 867 |
5/8✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1095 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1095 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1095 times.
✓ Branch 7 taken 5 times.
|
1100 | for (auto& n : WLEDEffect::getWLEDAPIEffectNames()) arr.append(n); |
| 868 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
10 | return SaveJsonToString(arr, ""); |
| 869 | 5 | } | |
| 870 | |||
| 871 | 96542 | void WLEDAPIResponder::ApplyStatePost(const std::string& jsonBody) { | |
| 872 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 96542 times.
|
96542 | if (jsonBody.empty()) return; |
| 873 |
1/2✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
|
96542 | Json::Value root; |
| 874 |
1/2✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
|
96542 | Json::Reader rdr; |
| 875 |
2/4✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 96542 times.
|
96542 | if (!rdr.parse(jsonBody, root)) { |
| 876 | ✗ | LogDebug(VB_GENERAL, "WLEDAPIResponder: ignoring malformed JSON body\n"); | |
| 877 | ✗ | return; | |
| 878 | } | ||
| 879 | |||
| 880 | // Snapshot the current model name list as a vector so we can index it. | ||
| 881 |
1/2✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
|
96542 | std::vector<std::string> modelNames = PixelOverlayManager::INSTANCE.getModelNamesSnapshot(); |
| 882 | |||
| 883 |
2/4✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 96542 times.
|
96542 | if (!root.isObject()) { |
| 884 | ✗ | return; | |
| 885 | } | ||
| 886 | |||
| 887 | 65529 | auto applyOnOff = [&](const std::string& modelName, bool on) { | |
| 888 |
1/2✓ Branch 0 taken 65529 times.
✗ Branch 1 not taken.
|
65529 | auto m = PixelOverlayManager::INSTANCE.getModelShared(modelName); |
| 889 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 65529 times.
|
65529 | if (!m) return; |
| 890 |
1/2✓ Branch 0 taken 65529 times.
✗ Branch 1 not taken.
|
131058 | m->setState(PixelOverlayState(on ? PixelOverlayState::Enabled |
| 891 |
1/2✓ Branch 0 taken 65529 times.
✗ Branch 1 not taken.
|
65529 | : PixelOverlayState::Disabled)); |
| 892 |
1/2✓ Branch 0 taken 65529 times.
✗ Branch 1 not taken.
|
65529 | }; |
| 893 | |||
| 894 | // Read only through a const reference: a non-const operator[] inserts a | ||
| 895 | // null member, and in jsonInt(seg["fx"], seg.isMember("fx") ? ...) | ||
| 896 | // whether that happened before the isMember() test depended on the | ||
| 897 | // compiler's argument evaluation order. | ||
| 898 | 96542 | const Json::Value& req = root; | |
| 899 | |||
| 900 |
8/10✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 96511 times.
✓ Branch 3 taken 31 times.
✓ Branch 4 taken 96511 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 96510 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 96506 times.
✓ Branch 9 taken 36 times.
|
193052 | if (req.isMember("on") && !req.isMember("seg") && |
| 901 |
7/12✓ Branch 0 taken 96510 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 96510 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 96506 times.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 4 times.
|
96510 | (req["on"].isBool() || req["on"].isNumeric())) { |
| 902 |
2/4✓ Branch 0 taken 96506 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 96506 times.
✗ Branch 3 not taken.
|
96506 | bool on = jsonBool(req["on"], false); |
| 903 |
3/4✓ Branch 0 taken 65529 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 65529 times.
✓ Branch 3 taken 96506 times.
|
162035 | for (auto& n : modelNames) applyOnOff(n, on); |
| 904 | } | ||
| 905 | |||
| 906 | // WLED takes "seg" as one segment object as well as an array of them. | ||
| 907 |
1/2✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
|
96542 | Json::Value segList(Json::arrayValue); |
| 908 |
4/6✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 96542 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✓ Branch 5 taken 96511 times.
|
96542 | if (req["seg"].isArray()) { |
| 909 |
2/4✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
|
31 | segList = req["seg"]; |
| 910 |
4/6✓ Branch 0 taken 96511 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 96511 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 96510 times.
|
96511 | } else if (req["seg"].isObject()) { |
| 911 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | segList.append(req["seg"]); |
| 912 | } | ||
| 913 | |||
| 914 |
3/4✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 96510 times.
|
96542 | if (!segList.empty()) { |
| 915 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | const auto& effList = WLEDEffect::getWLEDAPIEffectNames(); |
| 916 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | std::vector<std::string> effects(effList.begin(), effList.end()); |
| 917 | |||
| 918 |
7/12✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 32 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 32 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 64 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 32 times.
✓ Branch 11 taken 32 times.
|
64 | for (const auto& seg : segList) { |
| 919 |
3/4✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 30 times.
|
32 | if (!seg.isObject()) continue; |
| 920 | // No id means the main segment (0); one that isn't a valid | ||
| 921 | // index means none. | ||
| 922 |
4/8✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 30 times.
✗ Branch 7 not taken.
|
30 | int id = seg.isMember("id") ? jsonInt(seg["id"], -1) : 0; |
| 923 |
5/6✓ Branch 0 taken 26 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 26 times.
|
30 | if (id < 0 || id >= (int)modelNames.size()) continue; |
| 924 | 26 | const std::string& modelName = modelNames[id]; | |
| 925 | |||
| 926 |
7/20✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 26 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 26 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 26 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 26 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
|
26 | if (seg["on"].isBool() || seg["on"].isNumeric()) applyOnOff(modelName, jsonBool(seg["on"], false)); |
| 927 | |||
| 928 | // Effect change via WLED `fx` index → look up name. We also | ||
| 929 | // rebuild the args from the rest of the segment fields any | ||
| 930 | // time a fx index, sx/ix/bri, or col[] is provided so live | ||
| 931 | // slider drags update the running effect. | ||
| 932 |
4/6✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
|
29 | bool hasParamUpdate = seg.isMember("fx") || seg.isMember("sx") || |
| 933 |
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.
|
2 | seg.isMember("ix") || seg.isMember("bri") || |
| 934 |
6/10✓ Branch 0 taken 3 times.
✓ Branch 1 taken 23 times.
✓ 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 not taken.
✓ Branch 9 taken 2 times.
|
29 | seg.isMember("col") || seg.isMember("pal"); |
| 935 |
2/2✓ Branch 0 taken 24 times.
✓ Branch 1 taken 2 times.
|
26 | if (hasParamUpdate) { |
| 936 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | auto m = PixelOverlayManager::INSTANCE.getModelShared(modelName); |
| 937 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (!m) continue; |
| 938 | 24 | std::string fxName; | |
| 939 |
3/4✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✓ Branch 3 taken 1 times.
|
24 | if (seg.isMember("fx")) { |
| 940 |
2/4✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
|
23 | int fxIdx = jsonInt(seg["fx"], -1); |
| 941 |
5/6✓ Branch 0 taken 21 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 21 times.
|
23 | if (fxIdx < 0 || fxIdx >= (int)effects.size()) continue; |
| 942 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | fxName = effects[fxIdx]; |
| 943 |
3/4✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 19 times.
|
21 | if (fxName == "RSVD") { |
| 944 | // A retired effect's number, kept so the ones after | ||
| 945 | // it don't move. | ||
| 946 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | LogWarn(VB_GENERAL, "WLEDAPIResponder: effect %d is no longer offered -- ignoring\n", fxIdx); |
| 947 | 2 | continue; | |
| 948 | } | ||
| 949 | } else { | ||
| 950 | // A slider drag or colour pick without "fx" adjusts the | ||
| 951 | // effect that is running. It used to fall back to index | ||
| 952 | // 0, which is "Stop Effects". | ||
| 953 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | std::unique_lock<std::recursive_mutex> lock(m->getRunningEffectMutex()); |
| 954 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (m->getRunningEffect()) { |
| 955 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | fxName = m->getRunningEffect()->name(); |
| 956 | } | ||
| 957 | 1 | } | |
| 958 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
|
20 | if (fxName.empty()) continue; |
| 959 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | auto* eff = PixelOverlayEffect::GetPixelOverlayEffect(fxName); |
| 960 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
|
20 | if (!eff) continue; |
| 961 | |||
| 962 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | std::vector<std::string> args = BuildEffectArgs(eff, seg); |
| 963 | // Through applyEffect(), not eff->apply(): it checks the | ||
| 964 | // argument count every effect relies on. | ||
| 965 |
2/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
40 | m->applyEffect("Enabled", fxName, args); |
| 966 |
4/4✓ Branch 0 taken 20 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 4 times.
|
28 | } |
| 967 | } | ||
| 968 | 32 | } | |
| 969 | |||
| 970 |
1/2✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
|
96542 | NotifyStateChanged(); |
| 971 |
3/6✓ Branch 0 taken 96542 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 96542 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 96542 times.
✗ Branch 5 not taken.
|
96542 | } |
| 972 | |||
| 973 | // Translate a WLED segment object into the std::vector<std::string> | ||
| 974 | // argument list that PixelOverlayEffect::apply() takes. We walk the | ||
| 975 | // effect's declared CommandArg list and fill each slot by matching the | ||
| 976 | // arg's name to the relevant WLED segment field. Unrecognized args fall | ||
| 977 | // back to the CommandArg's default value, which keeps WLED-ported and | ||
| 978 | // FPP-native effects equally happy. | ||
| 979 | 20 | std::vector<std::string> WLEDAPIResponder::BuildEffectArgs(PixelOverlayEffect* eff, | |
| 980 | const Json::Value& seg) { | ||
| 981 | 20 | std::vector<std::string> out; | |
| 982 | 27 | auto colHex = [&](int idx) -> std::string { | |
| 983 |
11/18✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 15 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 15 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 15 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 15 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 15 times.
✓ Branch 16 taken 12 times.
✓ Branch 17 taken 15 times.
|
27 | if (!seg.isMember("col") || !seg["col"].isArray() || (int)seg["col"].size() <= idx) { |
| 984 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
24 | return ""; |
| 985 | } | ||
| 986 |
2/4✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
|
15 | const auto& c = seg["col"][idx]; |
| 987 |
5/12✓ 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 not taken.
✓ Branch 7 taken 15 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 15 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
15 | if (!c.isArray() || c.size() < 3) return ""; |
| 988 | char buf[16]; | ||
| 989 | 45 | snprintf(buf, sizeof(buf), "#%02X%02X%02X", | |
| 990 |
6/12✓ 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.
✓ Branch 8 taken 15 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 15 times.
✗ Branch 11 not taken.
|
15 | jsonInt(c[0], 0) & 0xFF, jsonInt(c[1], 0) & 0xFF, jsonInt(c[2], 0) & 0xFF); |
| 991 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
30 | return buf; |
| 992 | 20 | }; | |
| 993 | |||
| 994 | 29 | auto rangeScaled = [&](int wledVal, int min, int max, int wledMax = 255) -> std::string { | |
| 995 | // WLED slider values are 0-255 (or 0-31 for c3). FPP effect args | ||
| 996 | // declare their own min/max; if the FPP arg has the same range we | ||
| 997 | // pass through, otherwise we linearly rescale. The value is a | ||
| 998 | // network peer's: clamp it to the slider's range first, as an | ||
| 999 | // out-of-range one overflowed the int conversion below. | ||
| 1000 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | wledVal = std::clamp(wledVal, 0, wledMax); |
| 1001 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
|
29 | if (max <= 0) return std::to_string(wledVal); |
| 1002 | 29 | int v = wledVal; | |
| 1003 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 24 times.
|
29 | if (max != wledMax) { |
| 1004 | // e.g. Blink declares Speed range 1-5000. | ||
| 1005 | 5 | double f = (double)wledVal / wledMax; | |
| 1006 | 5 | v = min + (int)(f * ((double)max - min)); | |
| 1007 | } | ||
| 1008 |
2/4✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
|
29 | if (min <= max) v = std::clamp(v, min, max); |
| 1009 | 29 | return std::to_string(v); | |
| 1010 | }; | ||
| 1011 | |||
| 1012 | // RawWLEDEffect (every WLED-ported FX) declares an explicit role | ||
| 1013 | // per arg via getArgRoles(). Use that when available; fall back to | ||
| 1014 | // matching by arg name for the hand-written effects (Blink, Bars, | ||
| 1015 | // ColorFade, Text, and the FPP-native ones) whose arg names match | ||
| 1016 | // the WLED slider naming convention. | ||
| 1017 | // Only the WLED effects are WLEDEffects: the FPP-native ones (Bars, | ||
| 1018 | // Text, Color Fade, Image, Stop Effects) are not, and calling | ||
| 1019 | // getArgRoles() through a static_cast on one of those jumped through a | ||
| 1020 | // vtable slot it doesn't have. | ||
| 1021 | 20 | std::vector<WLEDArgRole> roles; | |
| 1022 |
3/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 11 times.
|
20 | if (auto* we = dynamic_cast<WLEDEffect*>(eff)) { |
| 1023 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | roles = we->getArgRoles(); |
| 1024 | } | ||
| 1025 | |||
| 1026 | 20 | int idx = 0; | |
| 1027 |
2/2✓ Branch 0 taken 122 times.
✓ Branch 1 taken 20 times.
|
142 | for (const auto& a : eff->args) { |
| 1028 | 122 | WLEDArgRole role = WLEDArgRole::Unknown; | |
| 1029 |
2/2✓ Branch 0 taken 65 times.
✓ Branch 1 taken 57 times.
|
122 | if (idx < (int)roles.size()) role = roles[idx]; |
| 1030 | |||
| 1031 |
2/2✓ Branch 0 taken 57 times.
✓ Branch 1 taken 65 times.
|
122 | if (role == WLEDArgRole::Unknown) { |
| 1032 |
1/2✓ Branch 0 taken 57 times.
✗ Branch 1 not taken.
|
57 | std::string lname = a.name; |
| 1033 | 57 | std::transform(lname.begin(), lname.end(), lname.begin(), ::tolower); | |
| 1034 |
3/4✓ Branch 0 taken 57 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 56 times.
|
57 | if (lname == "brightness") role = WLEDArgRole::Brightness; |
| 1035 |
7/10✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 53 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 53 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 53 times.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 53 times.
|
56 | else if (lname == "speed" || lname == "sx") role = WLEDArgRole::Speed; |
| 1036 |
7/10✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 52 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 52 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 52 times.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 52 times.
|
53 | else if (lname == "intensity" || lname == "ix") role = WLEDArgRole::Intensity; |
| 1037 |
3/4✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 50 times.
|
52 | else if (lname == "color1") role = WLEDArgRole::Color1; |
| 1038 |
3/4✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 47 times.
|
50 | else if (lname == "color2") role = WLEDArgRole::Color2; |
| 1039 |
3/4✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 45 times.
|
47 | else if (lname == "color3") role = WLEDArgRole::Color3; |
| 1040 |
3/4✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 44 times.
|
45 | else if (lname == "palette") role = WLEDArgRole::Palette; |
| 1041 | 57 | } | |
| 1042 | |||
| 1043 |
1/2✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
|
122 | std::string v = a.defaultValue; |
| 1044 |
13/14✓ Branch 0 taken 9 times.
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 10 times.
✓ Branch 10 taken 10 times.
✓ Branch 11 taken 7 times.
✓ Branch 12 taken 9 times.
✓ Branch 13 taken 52 times.
|
122 | switch (role) { |
| 1045 | 9 | case WLEDArgRole::Brightness: | |
| 1046 |
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 | v = rangeScaled(jsonInt(seg["bri"], 255), a.min, a.max); |
| 1047 | 9 | break; | |
| 1048 | 11 | case WLEDArgRole::Speed: | |
| 1049 |
3/6✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
|
11 | v = rangeScaled(jsonInt(seg["sx"], 128), a.min, a.max); |
| 1050 | 11 | break; | |
| 1051 | 9 | case WLEDArgRole::Intensity: | |
| 1052 |
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 | v = rangeScaled(jsonInt(seg["ix"], 128), a.min, a.max); |
| 1053 | 9 | break; | |
| 1054 | 1 | case WLEDArgRole::Custom1: | |
| 1055 |
2/10✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
1 | if (seg.isMember("c1")) v = rangeScaled(jsonInt(seg["c1"], 0), a.min, a.max); |
| 1056 | 1 | break; | |
| 1057 | 1 | case WLEDArgRole::Custom2: | |
| 1058 |
2/10✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
1 | if (seg.isMember("c2")) v = rangeScaled(jsonInt(seg["c2"], 0), a.min, a.max); |
| 1059 | 1 | break; | |
| 1060 | 1 | case WLEDArgRole::Custom3: | |
| 1061 |
2/10✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
|
1 | if (seg.isMember("c3")) v = rangeScaled(jsonInt(seg["c3"], 0), a.min, a.max, 31); |
| 1062 | 1 | break; | |
| 1063 | 1 | case WLEDArgRole::Check1: | |
| 1064 |
2/12✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ 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.
|
1 | if (seg.isMember("o1")) v = jsonBool(seg["o1"], false) ? "1" : "0"; |
| 1065 | 1 | break; | |
| 1066 | ✗ | case WLEDArgRole::Check2: | |
| 1067 | ✗ | if (seg.isMember("o2")) v = jsonBool(seg["o2"], false) ? "1" : "0"; | |
| 1068 | ✗ | break; | |
| 1069 | 1 | case WLEDArgRole::Check3: | |
| 1070 |
2/12✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ 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.
|
1 | if (seg.isMember("o3")) v = jsonBool(seg["o3"], false) ? "1" : "0"; |
| 1071 | 1 | break; | |
| 1072 |
4/6✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
10 | case WLEDArgRole::Color1: { auto c = colHex(0); if (!c.empty()) v = c; break; } |
| 1073 |
4/6✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
10 | case WLEDArgRole::Color2: { auto c = colHex(1); if (!c.empty()) v = c; break; } |
| 1074 |
4/6✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
7 | case WLEDArgRole::Color3: { auto c = colHex(2); if (!c.empty()) v = c; break; } |
| 1075 | 9 | case WLEDArgRole::Palette: | |
| 1076 | // WLED sends a numeric palette index; the WLED-ported effects | ||
| 1077 | // here want the palette name. Without a full palette catalog | ||
| 1078 | // round-trip we leave the default in place. | ||
| 1079 | 9 | break; | |
| 1080 | 52 | default: | |
| 1081 | 52 | break; | |
| 1082 | } | ||
| 1083 |
1/2✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
|
122 | out.push_back(v); |
| 1084 | 122 | ++idx; | |
| 1085 | 122 | } | |
| 1086 | 20 | return out; | |
| 1087 | 20 | } | |
| 1088 |