| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2022 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 10 | * included LICENSE.LGPL file. | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include "fpp-pch.h" | ||
| 14 | |||
| 15 | #include "fpp-json.h" | ||
| 16 | |||
| 17 | #ifndef _GNU_SOURCE | ||
| 18 | #define _GNU_SOURCE | ||
| 19 | #endif | ||
| 20 | |||
| 21 | #if defined(__APPLE__) | ||
| 22 | #include <copyfile.h> | ||
| 23 | #else | ||
| 24 | #include <sys/sendfile.h> | ||
| 25 | #endif | ||
| 26 | |||
| 27 | #include <arpa/inet.h> | ||
| 28 | #include <curl/curl.h> | ||
| 29 | #include <net/if.h> | ||
| 30 | #include <netinet/in.h> | ||
| 31 | #include <sys/file.h> | ||
| 32 | #include <sys/ioctl.h> | ||
| 33 | #include <sys/socket.h> | ||
| 34 | #include <sys/stat.h> | ||
| 35 | #include <sys/time.h> | ||
| 36 | #include <atomic> | ||
| 37 | #include <cstdint> | ||
| 38 | #include <ctime> | ||
| 39 | #include <ctype.h> | ||
| 40 | #include <cxxabi.h> | ||
| 41 | #include <dirent.h> | ||
| 42 | #include <errno.h> | ||
| 43 | #include <fcntl.h> | ||
| 44 | #include <fstream> | ||
| 45 | #include <ifaddrs.h> | ||
| 46 | #include <iomanip> | ||
| 47 | #include <iterator> | ||
| 48 | #include <list> | ||
| 49 | #include <map> | ||
| 50 | #include <mutex> | ||
| 51 | #include <netdb.h> | ||
| 52 | #include <pwd.h> | ||
| 53 | #include <sstream> | ||
| 54 | #include <stdio.h> | ||
| 55 | #include <stdlib.h> | ||
| 56 | #include <string.h> | ||
| 57 | #include <string> | ||
| 58 | #include <unistd.h> | ||
| 59 | #include <utility> | ||
| 60 | #include <vector> | ||
| 61 | |||
| 62 | #include "common.h" | ||
| 63 | #include "CurlSocket.h" | ||
| 64 | #include "fppversion.h" | ||
| 65 | #include "log.h" | ||
| 66 | |||
| 67 | /* | ||
| 68 | * Dump memory block in hex and human-readable formats | ||
| 69 | */ | ||
| 70 | 3061 | void HexDump(const char* title, const void* data, int len, FPPLoggerInstance& facility, int perLine) { | |
| 71 | 3061 | int l = 0; | |
| 72 | 3061 | int i = 0; | |
| 73 | 3061 | int x = 0; | |
| 74 | 3061 | int y = 0; | |
| 75 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3061 times.
|
3061 | if (perLine <= 0) { |
| 76 | ✗ | perLine = 16; | |
| 77 | } | ||
| 78 | 3061 | unsigned char* ch = (unsigned char*)data; | |
| 79 | 3061 | unsigned char* str = new unsigned char[perLine + 1]; | |
| 80 | |||
| 81 | 3061 | int maxLen = perLine * 7 + 20; | |
| 82 | 3061 | char* tmpStr = new char[maxLen]; | |
| 83 | 3061 | tmpStr[0] = 0; | |
| 84 | |||
| 85 | // The title is data, not a format string. | ||
| 86 |
2/4✓ Branch 0 taken 3061 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3061 times.
✗ Branch 3 not taken.
|
3061 | if (title && strlen(title)) { |
| 87 | 3061 | snprintf(tmpStr, maxLen, "%s: (%d bytes)\n", title, len); | |
| 88 | 3061 | LogInfo(facility, "%s", tmpStr); | |
| 89 | } | ||
| 90 | // Nothing to dump. The tail below appends to tmpStr, which was never | ||
| 91 | // started with an offset (left uninitialized with no title) and would | ||
| 92 | // be logged again. | ||
| 93 |
2/4✓ Branch 0 taken 3061 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3061 times.
|
3061 | if (len <= 0 || !data) { |
| 94 | ✗ | delete[] tmpStr; | |
| 95 | ✗ | delete[] str; | |
| 96 | ✗ | return; | |
| 97 | } | ||
| 98 | |||
| 99 |
2/2✓ Branch 0 taken 26347 times.
✓ Branch 1 taken 3061 times.
|
29408 | while (l < len) { |
| 100 |
2/2✓ Branch 0 taken 3061 times.
✓ Branch 1 taken 23286 times.
|
26347 | if (x == 0) { |
| 101 | 3061 | snprintf(tmpStr, maxLen, "%06x: ", i); | |
| 102 | } | ||
| 103 | |||
| 104 |
2/2✓ Branch 0 taken 26241 times.
✓ Branch 1 taken 106 times.
|
26347 | if (x < perLine) { |
| 105 | 26241 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), "%02x ", *ch & 0xFF); | |
| 106 | 26241 | str[x] = *ch; | |
| 107 | 26241 | x++; | |
| 108 | 26241 | i++; | |
| 109 | } else { | ||
| 110 | 106 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), " "); | |
| 111 |
2/2✓ Branch 0 taken 1696 times.
✓ Branch 1 taken 106 times.
|
1802 | for (; x > 0; x--) { |
| 112 |
2/4✓ Branch 0 taken 1696 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1696 times.
|
1696 | if (str[perLine - x] == '%' || str[perLine - x] == '\\') { |
| 113 | // these are escapes for the Log call, so don't display them | ||
| 114 | ✗ | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), "."); | |
| 115 |
4/4✓ Branch 0 taken 1593 times.
✓ Branch 1 taken 103 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 1584 times.
|
1696 | } else if (isgraph(str[perLine - x]) || str[perLine - x] == ' ') { |
| 116 | 112 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), "%c", str[perLine - x]); | |
| 117 | } else { | ||
| 118 | 1584 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), "."); | |
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | 106 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), "\n"); | |
| 123 | 106 | LogInfo(facility, "%s", tmpStr); | |
| 124 | 106 | x = 0; | |
| 125 | |||
| 126 | 106 | snprintf(tmpStr, maxLen, "%06x: ", i); | |
| 127 | 106 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), "%02x ", *ch & 0xFF); | |
| 128 | 106 | str[x] = *ch; | |
| 129 | 106 | x++; | |
| 130 | 106 | i++; | |
| 131 | } | ||
| 132 | |||
| 133 | 26347 | l++; | |
| 134 | 26347 | ch++; | |
| 135 | } | ||
| 136 |
2/2✓ Branch 0 taken 24325 times.
✓ Branch 1 taken 3061 times.
|
27386 | for (y = x; y < perLine; y++) { |
| 137 | 24325 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), " "); | |
| 138 | } | ||
| 139 | 3061 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), " "); | |
| 140 |
2/2✓ Branch 0 taken 24651 times.
✓ Branch 1 taken 3061 times.
|
27712 | for (y = 0; y < x; y++) { |
| 141 |
4/4✓ Branch 0 taken 24579 times.
✓ Branch 1 taken 72 times.
✓ Branch 2 taken 74 times.
✓ Branch 3 taken 24505 times.
|
24651 | if (str[y] == '%' || str[y] == '\\') { |
| 142 | // these are escapes for the Log call, so don't display them | ||
| 143 | 146 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), "."); | |
| 144 |
4/4✓ Branch 0 taken 17707 times.
✓ Branch 1 taken 6798 times.
✓ Branch 2 taken 72 times.
✓ Branch 3 taken 17635 times.
|
24505 | } else if (isgraph(str[y]) || str[y] == ' ') { |
| 145 | 6870 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), "%c", str[y]); | |
| 146 | } else { | ||
| 147 | 17635 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), "."); | |
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | 3061 | snprintf(tmpStr + strlen(tmpStr), maxLen - strlen(tmpStr), "\n"); | |
| 152 | 3061 | LogInfo(facility, "%s", tmpStr); | |
| 153 |
1/2✓ Branch 0 taken 3061 times.
✗ Branch 1 not taken.
|
3061 | delete[] tmpStr; |
| 154 |
1/2✓ Branch 0 taken 3061 times.
✗ Branch 1 not taken.
|
3061 | delete[] str; |
| 155 | } | ||
| 156 | |||
| 157 | 76 | int CheckForHostSpecificFile(const std::string& hostname, std::string& filename) { | |
| 158 |
1/2✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
|
76 | std::string localFilename = filename; |
| 159 | |||
| 160 | 76 | int len = (int)localFilename.length(); | |
| 161 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 76 times.
|
76 | if (len < 4) |
| 162 | ✗ | return 0; | |
| 163 | 76 | int extIdx = 0; | |
| 164 | |||
| 165 | // Check for 3 or 4-digit extension | ||
| 166 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 76 times.
|
76 | if (localFilename[len - 4] == '.') { |
| 167 | ✗ | extIdx = len - 4; | |
| 168 |
5/6✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 67 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 67 times.
✓ Branch 5 taken 9 times.
|
76 | } else if (len >= 5 && localFilename[len - 5] == '.') { |
| 169 | 67 | extIdx = len - 5; | |
| 170 | } | ||
| 171 | |||
| 172 |
2/2✓ Branch 0 taken 67 times.
✓ Branch 1 taken 9 times.
|
76 | if (extIdx) { |
| 173 | // Preserve the extension including the dot | ||
| 174 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | std::string ext = localFilename.substr(extIdx); |
| 175 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | localFilename = localFilename.substr(0, extIdx); |
| 176 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | localFilename += "-"; |
| 177 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | localFilename += hostname; |
| 178 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | localFilename += ext; |
| 179 | |||
| 180 |
2/4✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
|
67 | if (FileExists(localFilename)) { |
| 181 | ✗ | LogDebug(VB_SEQUENCE, "Found %s to use instead of %s\n", | |
| 182 | localFilename.c_str(), filename.c_str()); | ||
| 183 | ✗ | filename = localFilename; | |
| 184 | ✗ | return 1; | |
| 185 | } else { | ||
| 186 | // Replace hyphen with an underscore and recheck | ||
| 187 | 67 | localFilename[extIdx] = '_'; | |
| 188 |
2/4✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
|
67 | if (FileExists(localFilename)) { |
| 189 | ✗ | LogDebug(VB_SEQUENCE, "Found %s to use instead of %s\n", | |
| 190 | localFilename.c_str(), filename.c_str()); | ||
| 191 | ✗ | filename = localFilename; | |
| 192 | ✗ | return 1; | |
| 193 | } | ||
| 194 | } | ||
| 195 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | } |
| 196 | |||
| 197 | 76 | return 0; | |
| 198 | 76 | } | |
| 199 | 39 | int CheckForHostSpecificFile(const char* hostname, char* filename) { | |
| 200 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
78 | std::string f = filename; |
| 201 |
3/6✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 39 times.
|
78 | if (CheckForHostSpecificFile(hostname, f)) { |
| 202 | // In-tree callers pass a 2048-byte tmpFilename (Sequence.cpp). Clamp to | ||
| 203 | // that size to avoid overflowing a smaller plugin-provided buffer while | ||
| 204 | // preserving the original strcpy semantics for fitting names. Truncation | ||
| 205 | // is treated as "no host file" so the caller keeps the original name. | ||
| 206 | ✗ | constexpr size_t kSafeLimit = 2048; | |
| 207 | ✗ | if (f.size() >= kSafeLimit) { | |
| 208 | ✗ | LogErr(VB_SEQUENCE, "Host-specific filename too long (%zu >= %zu), keeping %s\n", | |
| 209 | f.size(), kSafeLimit, filename); | ||
| 210 | ✗ | return 0; | |
| 211 | } | ||
| 212 | ✗ | snprintf(filename, kSafeLimit, "%s", f.c_str()); | |
| 213 | ✗ | return 1; | |
| 214 | } | ||
| 215 | 39 | return 0; | |
| 216 | 39 | } | |
| 217 | ✗ | char* FindInterfaceForIP(char* ip) { | |
| 218 | struct ifaddrs *ifaddr, *ifa; | ||
| 219 | int family, s, n; | ||
| 220 | char host[NI_MAXHOST]; | ||
| 221 | char interfaceIP[16]; | ||
| 222 | // Per thread, and cleared on every call: a lookup that found nothing | ||
| 223 | // returned the previous call's interface. | ||
| 224 | static thread_local char interface[IFNAMSIZ] = ""; | ||
| 225 | ✗ | interface[0] = 0; | |
| 226 | |||
| 227 | ✗ | if (getifaddrs(&ifaddr) == -1) { | |
| 228 | ✗ | LogErr(VB_SETTING, "Error getting interfaces list: %s\n", | |
| 229 | FPPstrerror(errno)); | ||
| 230 | ✗ | return interface; | |
| 231 | } | ||
| 232 | |||
| 233 | ✗ | for (ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) { | |
| 234 | ✗ | if (ifa->ifa_addr == NULL) | |
| 235 | ✗ | continue; | |
| 236 | |||
| 237 | ✗ | if (ifa->ifa_addr->sa_family == AF_INET) { | |
| 238 | ✗ | s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), | |
| 239 | host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); | ||
| 240 | ✗ | if (s != 0) { | |
| 241 | ✗ | LogErr(VB_SETTING, "getnameinfo failed"); | |
| 242 | ✗ | freeifaddrs(ifaddr); | |
| 243 | ✗ | return interface; | |
| 244 | } | ||
| 245 | |||
| 246 | ✗ | if (!strcmp(host, ip)) { | |
| 247 | ✗ | snprintf(interface, sizeof(interface), "%s", ifa->ifa_name); | |
| 248 | ✗ | freeifaddrs(ifaddr); | |
| 249 | ✗ | return interface; | |
| 250 | } | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | ✗ | freeifaddrs(ifaddr); | |
| 255 | ✗ | return interface; | |
| 256 | } | ||
| 257 | |||
| 258 | 3726 | static void ReplaceString(std::string& str, std::string pattern, std::string replacement) { | |
| 259 | 3726 | std::size_t found = str.find(pattern); | |
| 260 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3726 times.
|
3734 | while (found != std::string::npos) { |
| 261 | 8 | str.replace(found, pattern.length(), replacement); | |
| 262 | |||
| 263 | // Resume after what was inserted, not from the start: a value that | ||
| 264 | // contains its own keyword (a playlist named "%PLAYLIST_NAME%") was | ||
| 265 | // found again inside the replacement, forever, hanging whichever | ||
| 266 | // thread fired the preset. | ||
| 267 | 8 | found = str.find(pattern, found + replacement.length()); | |
| 268 | } | ||
| 269 | 3726 | } | |
| 270 | 666 | std::string ReplaceKeywords(std::string str, std::map<std::string, std::string>& keywords) { | |
| 271 | 666 | std::string key; | |
| 272 | |||
| 273 |
2/2✓ Branch 0 taken 396 times.
✓ Branch 1 taken 666 times.
|
1062 | for (auto& k : keywords) { |
| 274 |
1/2✓ Branch 0 taken 396 times.
✗ Branch 1 not taken.
|
396 | key = std::string("%"); |
| 275 |
1/2✓ Branch 0 taken 396 times.
✗ Branch 1 not taken.
|
396 | key += k.first; |
| 276 |
2/4✓ Branch 0 taken 396 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 396 times.
✗ Branch 3 not taken.
|
396 | key += std::string("%"); |
| 277 | |||
| 278 |
3/6✓ Branch 0 taken 396 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 396 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 396 times.
✗ Branch 5 not taken.
|
396 | ReplaceString(str, key, k.second); |
| 279 | } | ||
| 280 | |||
| 281 | 666 | std::time_t currTime = std::time(NULL); | |
| 282 | struct tm now; | ||
| 283 | 666 | localtime_r(&currTime, &now); | |
| 284 | char tmpStr[20]; | ||
| 285 | |||
| 286 | 666 | snprintf(tmpStr, sizeof(tmpStr), "%02d:%02d:%02d", now.tm_hour, now.tm_min, now.tm_sec); | |
| 287 |
3/6✓ Branch 0 taken 666 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 666 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 666 times.
✗ Branch 5 not taken.
|
1998 | ReplaceString(str, "%TIME%", tmpStr); |
| 288 | |||
| 289 | 666 | snprintf(tmpStr, sizeof(tmpStr), "%04d-%02d-%02d", now.tm_year + 1900, now.tm_mon + 1, now.tm_mday); | |
| 290 |
3/6✓ Branch 0 taken 666 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 666 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 666 times.
✗ Branch 5 not taken.
|
2664 | ReplaceString(str, "%DATE%", tmpStr); |
| 291 | |||
| 292 |
4/8✓ Branch 0 taken 666 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 666 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 666 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 666 times.
✗ Branch 7 not taken.
|
2664 | ReplaceString(str, "%FPP_VERSION%", getFPPVersionTriplet()); |
| 293 |
4/8✓ Branch 0 taken 666 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 666 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 666 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 666 times.
✗ Branch 7 not taken.
|
2664 | ReplaceString(str, "%FPP_SOURCE_VERSION%", getFPPVersion()); |
| 294 |
4/8✓ Branch 0 taken 666 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 666 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 666 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 666 times.
✗ Branch 7 not taken.
|
1998 | ReplaceString(str, "%FPP_BRANCH%", getFPPBranch()); |
| 295 | |||
| 296 | 1332 | return str; | |
| 297 | 666 | } | |
| 298 | |||
| 299 | ///////////////////////////////////////////////////////////////////////////// | ||
| 300 | /* | ||
| 301 | * Merge the contens of Json::Value b into Json::Value a | ||
| 302 | */ | ||
| 303 | ✗ | void MergeJsonValues(Json::Value& a, Json::Value& b) { | |
| 304 | ✗ | if (!a.isObject() || !b.isObject()) | |
| 305 | ✗ | return; | |
| 306 | |||
| 307 | ✗ | Json::Value::Members memberNames = b.getMemberNames(); | |
| 308 | |||
| 309 | ✗ | for (unsigned int i = 0; i < memberNames.size(); i++) { | |
| 310 | ✗ | std::string key = memberNames[i]; | |
| 311 | |||
| 312 | ✗ | if ((a[key].type() == Json::objectValue) && | |
| 313 | ✗ | (b[key].type() == Json::objectValue)) { | |
| 314 | ✗ | MergeJsonValues(a[key], b[key]); | |
| 315 | } else { | ||
| 316 | ✗ | a[key] = b[key]; | |
| 317 | } | ||
| 318 | ✗ | } | |
| 319 | ✗ | } | |
| 320 | |||
| 321 | /* | ||
| 322 | * | ||
| 323 | */ | ||
| 324 | ✗ | Json::Value LoadJsonFromFile(const std::string& filename) { | |
| 325 | ✗ | Json::Value root; | |
| 326 | |||
| 327 | ✗ | LoadJsonFromFile(filename, root); | |
| 328 | |||
| 329 | ✗ | return root; | |
| 330 | ✗ | } | |
| 331 | |||
| 332 | /* | ||
| 333 | * | ||
| 334 | */ | ||
| 335 | 190067 | Json::Value LoadJsonFromString(const std::string& str) { | |
| 336 | 190067 | Json::Value root; | |
| 337 |
2/2✓ Branch 0 taken 190065 times.
✓ Branch 1 taken 2 times.
|
190067 | bool result = LoadJsonFromString(str, root); |
| 338 | |||
| 339 | 190065 | return root; | |
| 340 | 2 | } | |
| 341 | |||
| 342 | /* | ||
| 343 | * | ||
| 344 | */ | ||
| 345 | 429000 | bool LoadJsonFromString(const std::string& str, Json::Value& root) { | |
| 346 |
1/2✓ Branch 0 taken 429000 times.
✗ Branch 1 not taken.
|
429000 | Json::CharReaderBuilder builder; |
| 347 |
1/2✓ Branch 0 taken 429000 times.
✗ Branch 1 not taken.
|
429000 | Json::CharReader* reader = builder.newCharReader(); |
| 348 | 429000 | std::string errors; | |
| 349 | |||
| 350 |
3/6✓ Branch 0 taken 429000 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 429000 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 429000 times.
✗ Branch 5 not taken.
|
1287000 | builder["collectComments"] = false; |
| 351 | |||
| 352 |
2/2✓ Branch 0 taken 428899 times.
✓ Branch 1 taken 101 times.
|
429000 | bool success = reader->parse(str.c_str(), str.c_str() + str.size(), &root, &errors); |
| 353 |
1/2✓ Branch 0 taken 428899 times.
✗ Branch 1 not taken.
|
428899 | delete reader; |
| 354 | |||
| 355 |
2/2✓ Branch 0 taken 462 times.
✓ Branch 1 taken 428437 times.
|
428899 | if (!success) { |
| 356 | // Bodies parsed here include HTTP requests and config files that carry | ||
| 357 | // credentials; log only the start, which is enough to see what it was. | ||
| 358 |
2/4✓ Branch 0 taken 462 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 462 times.
✗ Branch 3 not taken.
|
462 | LogErr(VB_GENERAL, "Error parsing JSON string in LoadJsonFromString(): '%s'\n", TruncateForLog(str).c_str()); |
| 359 |
1/2✓ Branch 0 taken 462 times.
✗ Branch 1 not taken.
|
462 | Json::Value empty; |
| 360 |
1/2✓ Branch 0 taken 462 times.
✗ Branch 1 not taken.
|
462 | root = empty; |
| 361 | 462 | return false; | |
| 362 | 462 | } | |
| 363 | |||
| 364 |
2/4✓ Branch 0 taken 428437 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 428437 times.
✗ Branch 3 not taken.
|
428437 | LogDebug(VB_GENERAL, "LoadJsonFromString() loaded: '%s'\n", TruncateForLog(str).c_str()); |
| 365 | |||
| 366 | 428437 | return true; | |
| 367 | 429101 | } | |
| 368 | |||
| 369 | 19072 | bool LoadJsonFromFile(const std::string& filename, Json::Value& root) { | |
| 370 |
3/4✓ Branch 0 taken 19072 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9189 times.
✓ Branch 3 taken 9883 times.
|
19072 | if (!FileExists(filename)) { |
| 371 |
1/2✓ Branch 0 taken 9189 times.
✗ Branch 1 not taken.
|
9189 | LogErr(VB_GENERAL, "JSON File %s does not exist\n", filename.c_str()); |
| 372 | 9189 | return false; | |
| 373 | } | ||
| 374 | |||
| 375 |
1/2✓ Branch 0 taken 9883 times.
✗ Branch 1 not taken.
|
9883 | std::string jsonStr = GetFileContents(filename); |
| 376 | |||
| 377 |
1/2✓ Branch 0 taken 9883 times.
✗ Branch 1 not taken.
|
9883 | return LoadJsonFromString(jsonStr, root); |
| 378 | 9883 | } | |
| 379 | |||
| 380 | ✗ | bool LoadJsonFromFile(const char* filename, Json::Value& root) { | |
| 381 | ✗ | std::string filenameStr = filename; | |
| 382 | |||
| 383 | ✗ | return LoadJsonFromFile(filenameStr, root); | |
| 384 | ✗ | } | |
| 385 | |||
| 386 | 184 | static const char* JsonTypeName(Json::ValueType t) { | |
| 387 |
4/9✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 59 times.
✓ Branch 7 taken 92 times.
✗ Branch 8 not taken.
|
184 | switch (t) { |
| 388 | ✗ | case Json::nullValue: | |
| 389 | ✗ | return "null"; | |
| 390 | 17 | case Json::intValue: | |
| 391 | 17 | return "int"; | |
| 392 | ✗ | case Json::uintValue: | |
| 393 | ✗ | return "uint"; | |
| 394 | ✗ | case Json::realValue: | |
| 395 | ✗ | return "real"; | |
| 396 | 16 | case Json::stringValue: | |
| 397 | 16 | return "string"; | |
| 398 | ✗ | case Json::booleanValue: | |
| 399 | ✗ | return "boolean"; | |
| 400 | 59 | case Json::arrayValue: | |
| 401 | 59 | return "array"; | |
| 402 | 92 | case Json::objectValue: | |
| 403 | 92 | return "object"; | |
| 404 | } | ||
| 405 | ✗ | return "unknown"; | |
| 406 | } | ||
| 407 | |||
| 408 | 19051 | bool LoadJsonFromFile(const std::string& filename, Json::Value& root, JsonRoot expected) { | |
| 409 |
2/2✓ Branch 0 taken 212 times.
✓ Branch 1 taken 18839 times.
|
19051 | Json::ValueType expectedType = (expected == JsonRoot::Array) ? Json::arrayValue : Json::objectValue; |
| 410 | |||
| 411 |
2/2✓ Branch 0 taken 9272 times.
✓ Branch 1 taken 9779 times.
|
19051 | if (!LoadJsonFromFile(filename, root)) { |
| 412 |
1/2✓ Branch 0 taken 9272 times.
✗ Branch 1 not taken.
|
9272 | root = Json::Value(expectedType); |
| 413 | 9272 | return false; | |
| 414 | } | ||
| 415 | |||
| 416 | // A null root is not a mismatch. jsoncpp treats null as an empty value of | ||
| 417 | // whatever type it is first used as and never throws on it, and FPP writes | ||
| 418 | // it itself - Variables::save() stores a default-constructed Json::Value, | ||
| 419 | // so a stock variables.json with nothing persisted is literally "null". | ||
| 420 | // Normalize it and report success, exactly as before this check existed. | ||
| 421 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 9762 times.
|
9779 | if (root.isNull()) { |
| 422 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | root = Json::Value(expectedType); |
| 423 | 17 | return true; | |
| 424 | } | ||
| 425 | |||
| 426 |
2/2✓ Branch 0 taken 25 times.
✓ Branch 1 taken 9737 times.
|
9762 | if (root.type() != expectedType) { |
| 427 | 25 | LogErr(VB_GENERAL, "JSON File %s has a %s at its root, expected %s - treating as empty\n", | |
| 428 | filename.c_str(), JsonTypeName(root.type()), JsonTypeName(expectedType)); | ||
| 429 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | root = Json::Value(expectedType); |
| 430 | 25 | return false; | |
| 431 | } | ||
| 432 | |||
| 433 | 9737 | return true; | |
| 434 | } | ||
| 435 | |||
| 436 | 3916 | Json::Value LoadJsonFromFile(const std::string& filename, JsonRoot expected) { | |
| 437 | 3916 | Json::Value root; | |
| 438 | |||
| 439 |
1/2✓ Branch 0 taken 3916 times.
✗ Branch 1 not taken.
|
3916 | LoadJsonFromFile(filename, root, expected); |
| 440 | |||
| 441 | 3916 | return root; | |
| 442 | ✗ | } | |
| 443 | |||
| 444 | 3056 | bool LoadJsonFromString(const std::string& str, Json::Value& root, JsonRoot expected) { | |
| 445 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3056 times.
|
3056 | Json::ValueType expectedType = (expected == JsonRoot::Array) ? Json::arrayValue : Json::objectValue; |
| 446 | |||
| 447 |
2/2✓ Branch 0 taken 92 times.
✓ Branch 1 taken 2948 times.
|
3056 | if (!LoadJsonFromString(str, root)) { |
| 448 |
1/2✓ Branch 0 taken 92 times.
✗ Branch 1 not taken.
|
92 | root = Json::Value(expectedType); |
| 449 | 92 | return false; | |
| 450 | } | ||
| 451 | |||
| 452 | // See LoadJsonFromFile() above - a null root is not a mismatch, it's | ||
| 453 | // normalized to an empty value of the expected shape and reported as | ||
| 454 | // success. | ||
| 455 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2932 times.
|
2948 | if (root.isNull()) { |
| 456 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | root = Json::Value(expectedType); |
| 457 | 16 | return true; | |
| 458 | } | ||
| 459 | |||
| 460 |
2/2✓ Branch 0 taken 67 times.
✓ Branch 1 taken 2865 times.
|
2932 | if (root.type() != expectedType) { |
| 461 | 67 | LogErr(VB_GENERAL, "JSON string has a %s at its root, expected %s - treating as empty\n", | |
| 462 | JsonTypeName(root.type()), JsonTypeName(expectedType)); | ||
| 463 |
1/2✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
|
67 | root = Json::Value(expectedType); |
| 464 | 67 | return false; | |
| 465 | } | ||
| 466 | |||
| 467 | 2865 | return true; | |
| 468 | } | ||
| 469 | |||
| 470 | ✗ | Json::Value LoadJsonFromString(const std::string& str, JsonRoot expected) { | |
| 471 | ✗ | Json::Value root; | |
| 472 | |||
| 473 | ✗ | LoadJsonFromString(str, root, expected); | |
| 474 | |||
| 475 | ✗ | return root; | |
| 476 | ✗ | } | |
| 477 | |||
| 478 | 1473301 | std::string SaveJsonToString(const Json::Value& root, const std::string& indentation) { | |
| 479 |
1/2✓ Branch 0 taken 1473301 times.
✗ Branch 1 not taken.
|
1473301 | Json::StreamWriterBuilder wbuilder; |
| 480 |
3/6✓ Branch 0 taken 1473301 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1473301 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1473301 times.
✗ Branch 5 not taken.
|
4419903 | wbuilder["indentation"] = indentation; |
| 481 | |||
| 482 |
1/2✓ Branch 0 taken 1473301 times.
✗ Branch 1 not taken.
|
1473301 | std::string result = Json::writeString(wbuilder, root); |
| 483 | |||
| 484 | 1473301 | return result; | |
| 485 | 1473301 | } | |
| 486 | |||
| 487 | 16446 | bool SaveJsonToString(const Json::Value& root, std::string& str, const std::string& indentation) { | |
| 488 |
1/2✓ Branch 0 taken 16446 times.
✗ Branch 1 not taken.
|
16446 | str = SaveJsonToString(root, indentation); |
| 489 | |||
| 490 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16446 times.
|
16446 | if (str.empty()) |
| 491 | ✗ | return false; | |
| 492 | |||
| 493 | 16446 | return true; | |
| 494 | } | ||
| 495 | |||
| 496 | 16446 | bool SaveJsonToFile(const Json::Value& root, const std::string& filename, const std::string& indentation) { | |
| 497 | 16446 | std::string str; | |
| 498 | |||
| 499 |
1/2✓ Branch 0 taken 16446 times.
✗ Branch 1 not taken.
|
16446 | bool result = SaveJsonToString(root, str, indentation); |
| 500 | |||
| 501 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16446 times.
|
16446 | if (!result) { |
| 502 | ✗ | LogErr(VB_GENERAL, "Error converting Json::Value to std::string()\n"); | |
| 503 | ✗ | return false; | |
| 504 | } | ||
| 505 | |||
| 506 |
1/2✓ Branch 0 taken 16446 times.
✗ Branch 1 not taken.
|
16446 | result = PutFileContents(filename, str); |
| 507 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16446 times.
|
16446 | if (!result) { |
| 508 | ✗ | return false; | |
| 509 | } | ||
| 510 | |||
| 511 | 16446 | return true; | |
| 512 | 16446 | } | |
| 513 | |||
| 514 | ✗ | bool SaveJsonToFile(const Json::Value& root, const char* filename, const char* indentation) { | |
| 515 | ✗ | std::string filenameStr = filename; | |
| 516 | ✗ | std::string indentationStr = indentation; | |
| 517 | |||
| 518 | ✗ | return SaveJsonToFile(root, filenameStr, indentationStr); | |
| 519 | ✗ | } | |
| 520 | |||
| 521 | ✗ | std::string getSimpleHTMLTTag(const std::string& html, const std::string& searchStr, const std::string& skipStr, const std::string& endStr) { | |
| 522 | ✗ | std::string value; | |
| 523 | ✗ | std::string tStr; | |
| 524 | ✗ | std::size_t fStart = html.find(searchStr); | |
| 525 | std::size_t fEnd; | ||
| 526 | |||
| 527 | ✗ | if (fStart != std::string::npos) { | |
| 528 | ✗ | tStr = html.substr(fStart + searchStr.size()); | |
| 529 | ✗ | fStart = tStr.find(skipStr); | |
| 530 | ✗ | if (fStart != std::string::npos) { | |
| 531 | ✗ | fStart += skipStr.size(); | |
| 532 | ✗ | fEnd = tStr.substr(fStart).find(endStr); | |
| 533 | ✗ | fEnd += fStart; | |
| 534 | ✗ | if (fEnd > fStart) { | |
| 535 | ✗ | value = tStr.substr(fStart, fEnd - fStart); | |
| 536 | ✗ | TrimWhiteSpace(value); | |
| 537 | ✗ | replaceAll(value, std::string(" "), std::string(" ")); | |
| 538 | } | ||
| 539 | } | ||
| 540 | } | ||
| 541 | |||
| 542 | ✗ | return value; | |
| 543 | ✗ | } | |
| 544 | |||
| 545 | 4 | std::string getSimpleXMLTag(const std::string& xml, const std::string& tag) { | |
| 546 | 4 | std::string value; | |
| 547 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::string sSearch = "<"; |
| 548 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | sSearch += tag + ">"; |
| 549 | |||
| 550 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::string eSearch = "</"; |
| 551 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | eSearch += tag + ">"; |
| 552 | |||
| 553 | 4 | std::size_t fStart = xml.find(sSearch); | |
| 554 | 4 | std::size_t fEnd = xml.find(eSearch); | |
| 555 | |||
| 556 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | if ((fStart != std::string::npos) && |
| 557 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | (fEnd != std::string::npos) && |
| 558 | (fEnd > fStart)) { | ||
| 559 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | value = xml.substr(fStart + sSearch.length(), fEnd - fStart - sSearch.length()); |
| 560 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | TrimWhiteSpace(value); |
| 561 | } | ||
| 562 | |||
| 563 | 4 | return value; | |
| 564 | 4 | } | |
| 565 | |||
| 566 | // URL Helpers | ||
| 567 | 20 | size_t urlWriteData(void* buffer, size_t size, size_t nmemb, void* userp) { | |
| 568 | 20 | std::string* str = (std::string*)userp; | |
| 569 | |||
| 570 | 20 | str->append(static_cast<const char*>(buffer), size * nmemb); | |
| 571 | |||
| 572 | 20 | return size * nmemb; | |
| 573 | } | ||
| 574 | |||
| 575 | 219 | std::string NormalizeMacAddress(const std::string& mac) { | |
| 576 | 219 | std::string out; | |
| 577 |
2/2✓ Branch 0 taken 3560 times.
✓ Branch 1 taken 219 times.
|
3779 | for (char c : mac) { |
| 578 |
2/2✓ Branch 0 taken 2545 times.
✓ Branch 1 taken 1015 times.
|
3560 | if (isxdigit((unsigned char)c)) { |
| 579 |
1/2✓ Branch 0 taken 2545 times.
✗ Branch 1 not taken.
|
2545 | out += toupper((unsigned char)c); |
| 580 |
3/8✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1010 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
1015 | } else if (c != ':' && c != '-' && c != '.' && c != ' ') { |
| 581 | // Anything else means this was never a MAC. | ||
| 582 | ✗ | return ""; | |
| 583 | } | ||
| 584 | } | ||
| 585 |
6/8✓ Branch 0 taken 212 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 212 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 212 times.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 212 times.
|
219 | if (out.size() != 12 || out == "000000000000") { |
| 586 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
14 | return ""; |
| 587 | } | ||
| 588 | 212 | return out; | |
| 589 | 219 | } | |
| 590 | |||
| 591 | 4534 | std::string GetMacForAddress(const std::string& address) { | |
| 592 |
4/8✓ Branch 0 taken 4534 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4534 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4534 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 4534 times.
|
4534 | if (address.empty() || IsLoopbackAddress(address)) { |
| 593 | ✗ | return ""; | |
| 594 | } | ||
| 595 | // IPv6 neighbours are not exposed through procfs, so a v6-only peer gets no | ||
| 596 | // MAC here. That is harmless: a dual-stacked device is still identified | ||
| 597 | // through its IPv4 entry, and the UI merges a device's addresses by the | ||
| 598 | // UUID any one of them carries. | ||
| 599 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4534 times.
|
4534 | if (address.find(':') != std::string::npos) { |
| 600 | ✗ | return ""; | |
| 601 | } | ||
| 602 | |||
| 603 |
1/2✓ Branch 0 taken 4534 times.
✗ Branch 1 not taken.
|
4534 | std::ifstream arp("/proc/net/arp"); |
| 604 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4534 times.
|
4534 | if (!arp.is_open()) { |
| 605 | ✗ | return ""; | |
| 606 | } | ||
| 607 | 4534 | std::string line; | |
| 608 |
1/2✓ Branch 0 taken 4534 times.
✗ Branch 1 not taken.
|
4534 | std::getline(arp, line); // header |
| 609 |
4/6✓ Branch 0 taken 13693 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13693 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9351 times.
✓ Branch 5 taken 4342 times.
|
13693 | while (std::getline(arp, line)) { |
| 610 |
1/2✓ Branch 0 taken 9351 times.
✗ Branch 1 not taken.
|
9351 | std::stringstream ss(line); |
| 611 | 9351 | std::string ip, hwType, flags, hwAddr; | |
| 612 |
6/12✓ Branch 0 taken 9351 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9351 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9351 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 9351 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 9351 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 9351 times.
|
9351 | if (!(ss >> ip >> hwType >> flags >> hwAddr)) { |
| 613 | ✗ | continue; | |
| 614 | } | ||
| 615 |
2/2✓ Branch 0 taken 9159 times.
✓ Branch 1 taken 192 times.
|
9351 | if (ip != address) { |
| 616 | 9159 | continue; | |
| 617 | } | ||
| 618 | // Flag bit 0x2 (ATF_COM) means the entry is complete. An incomplete | ||
| 619 | // entry carries a zeroed address that would otherwise look like a MAC. | ||
| 620 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 192 times.
|
192 | if ((strtol(flags.c_str(), nullptr, 0) & 0x2) == 0) { |
| 621 | ✗ | return ""; | |
| 622 | } | ||
| 623 |
1/2✓ Branch 0 taken 192 times.
✗ Branch 1 not taken.
|
192 | return NormalizeMacAddress(hwAddr); |
| 624 |
10/10✓ Branch 0 taken 9159 times.
✓ Branch 1 taken 192 times.
✓ Branch 2 taken 9159 times.
✓ Branch 3 taken 192 times.
✓ Branch 4 taken 9159 times.
✓ Branch 5 taken 192 times.
✓ Branch 6 taken 9159 times.
✓ Branch 7 taken 192 times.
✓ Branch 8 taken 9159 times.
✓ Branch 9 taken 192 times.
|
46755 | } |
| 625 |
1/2✓ Branch 0 taken 4342 times.
✗ Branch 1 not taken.
|
8684 | return ""; |
| 626 | 4534 | } | |
| 627 | |||
| 628 | 22749 | bool IsLoopbackAddress(const std::string& address) { | |
| 629 |
4/6✓ Branch 0 taken 22749 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22749 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 22740 times.
|
45498 | if (startsWith(address, "127.")) { |
| 630 | 9 | return true; | |
| 631 | } | ||
| 632 | // "::1" is the usual spelling; the fully expanded form is legal too and | ||
| 633 | // shows up from some resolvers. | ||
| 634 |
2/4✓ Branch 0 taken 22740 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22740 times.
|
22740 | return address == "::1" || address == "0:0:0:0:0:0:0:1"; |
| 635 | } | ||
| 636 | |||
| 637 | // See the comment on the declarations in common.h. | ||
| 638 | namespace { | ||
| 639 | struct ResolveCacheEntry { | ||
| 640 | uint32_t addr = 0; | ||
| 641 | bool valid = false; | ||
| 642 | uint64_t expiresMS = 0; | ||
| 643 | }; | ||
| 644 | std::mutex resolveCacheLock; | ||
| 645 | std::map<std::string, ResolveCacheEntry> resolveCache; | ||
| 646 | |||
| 647 | // Short enough that a peer coming online is noticed on the next poll of | ||
| 648 | // whatever is watching it, long enough to collapse a startup burst. | ||
| 649 | constexpr uint64_t RESOLVE_TTL_OK_MS = 60000; | ||
| 650 | constexpr uint64_t RESOLVE_TTL_FAIL_MS = 30000; | ||
| 651 | // Nothing should be resolving thousands of distinct names; if it is, drop the | ||
| 652 | // expired entries rather than growing without bound. | ||
| 653 | constexpr size_t RESOLVE_CACHE_MAX = 256; | ||
| 654 | } // namespace | ||
| 655 | |||
| 656 | 11009 | bool ResolveHostToIPv4(const std::string& host, uint32_t& addr) { | |
| 657 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11009 times.
|
11009 | if (host.empty()) { |
| 658 | ✗ | return false; | |
| 659 | } | ||
| 660 | // A literal needs no resolver and must not take a cache slot -- the HTTP | ||
| 661 | // discovery scan walks whole /24s of them. | ||
| 662 | struct in_addr numeric; | ||
| 663 |
2/2✓ Branch 0 taken 10970 times.
✓ Branch 1 taken 39 times.
|
11009 | if (inet_pton(AF_INET, host.c_str(), &numeric) == 1) { |
| 664 | 10970 | addr = numeric.s_addr; | |
| 665 | 10970 | return true; | |
| 666 | } | ||
| 667 | |||
| 668 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | uint64_t now = (uint64_t)GetMonotonicTimeMS(); |
| 669 | { | ||
| 670 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | std::unique_lock<std::mutex> lock(resolveCacheLock); |
| 671 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | auto it = resolveCache.find(host); |
| 672 |
5/6✓ Branch 0 taken 23 times.
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 23 times.
✓ Branch 5 taken 16 times.
|
39 | if (it != resolveCache.end() && it->second.expiresMS > now) { |
| 673 | 23 | addr = it->second.addr; | |
| 674 | 23 | return it->second.valid; | |
| 675 | } | ||
| 676 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 23 times.
|
39 | } |
| 677 | |||
| 678 | // Resolve with the lock released: this blocks for seconds on a name that | ||
| 679 | // does not exist, and holding the lock across it would hand that stall to | ||
| 680 | // every other thread resolving anything at all. Two threads racing on the | ||
| 681 | // same new name can both pay for it; that is cheaper than serialising all | ||
| 682 | // lookups behind one. | ||
| 683 | 16 | struct addrinfo hints{}; | |
| 684 | 16 | hints.ai_family = AF_INET; // IPv4 only -- callers want a dotted-quad | |
| 685 | 16 | hints.ai_socktype = SOCK_DGRAM; // one result per address, not per socktype | |
| 686 | 16 | struct addrinfo* res = nullptr; | |
| 687 | 16 | bool valid = false; | |
| 688 | 16 | uint32_t resolved = 0; | |
| 689 |
6/8✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 13 times.
|
16 | if (getaddrinfo(host.c_str(), nullptr, &hints, &res) == 0 && res != nullptr) { |
| 690 | 3 | resolved = ((struct sockaddr_in*)res->ai_addr)->sin_addr.s_addr; | |
| 691 | 3 | valid = true; | |
| 692 | } | ||
| 693 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 13 times.
|
16 | if (res) { |
| 694 | 3 | freeaddrinfo(res); | |
| 695 | } | ||
| 696 | |||
| 697 | { | ||
| 698 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | std::unique_lock<std::mutex> lock(resolveCacheLock); |
| 699 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (resolveCache.size() >= RESOLVE_CACHE_MAX) { |
| 700 | ✗ | uint64_t cutoff = (uint64_t)GetMonotonicTimeMS(); | |
| 701 | ✗ | for (auto it = resolveCache.begin(); it != resolveCache.end();) { | |
| 702 | ✗ | it = (it->second.expiresMS <= cutoff) ? resolveCache.erase(it) : std::next(it); | |
| 703 | } | ||
| 704 | } | ||
| 705 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | ResolveCacheEntry& e = resolveCache[host]; |
| 706 | 16 | e.addr = resolved; | |
| 707 | 16 | e.valid = valid; | |
| 708 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 13 times.
|
16 | e.expiresMS = now + (valid ? RESOLVE_TTL_OK_MS : RESOLVE_TTL_FAIL_MS); |
| 709 | 16 | } | |
| 710 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 13 times.
|
16 | if (valid) { |
| 711 | 3 | addr = resolved; | |
| 712 | } | ||
| 713 | 16 | return valid; | |
| 714 | } | ||
| 715 | |||
| 716 | 10995 | std::string ResolveHostToIPv4(const std::string& host) { | |
| 717 | 10995 | uint32_t addr = 0; | |
| 718 |
3/4✓ Branch 0 taken 10995 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 10974 times.
|
10995 | if (!ResolveHostToIPv4(host, addr)) { |
| 719 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
42 | return ""; |
| 720 | } | ||
| 721 | struct in_addr ia; | ||
| 722 | 10974 | ia.s_addr = addr; | |
| 723 | 10974 | char buf[INET_ADDRSTRLEN] = { 0 }; | |
| 724 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10974 times.
|
10974 | if (!inet_ntop(AF_INET, &ia, buf, sizeof(buf))) { |
| 725 | ✗ | return ""; | |
| 726 | } | ||
| 727 |
1/2✓ Branch 0 taken 10974 times.
✗ Branch 1 not taken.
|
21948 | return buf; |
| 728 | } | ||
| 729 | |||
| 730 | 1673 | void FlushHostResolveCache() { | |
| 731 |
1/2✓ Branch 0 taken 1673 times.
✗ Branch 1 not taken.
|
1673 | std::unique_lock<std::mutex> lock(resolveCacheLock); |
| 732 | 1673 | resolveCache.clear(); | |
| 733 | 1673 | } | |
| 734 | |||
| 735 | 663 | std::string buildHttpURL(const std::string& address, const std::string& path) { | |
| 736 |
1/2✓ Branch 0 taken 663 times.
✗ Branch 1 not taken.
|
663 | std::string host = address; |
| 737 | // An IPv6 literal contains ':' (hostnames and IPv4 never do). curl requires | ||
| 738 | // it bracketed, and a link-local zone id ("fe80::1%eth0") must have its '%' | ||
| 739 | // percent-encoded as "%25" inside a URL. | ||
| 740 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 653 times.
|
663 | if (host.find(':') != std::string::npos) { |
| 741 | 10 | std::string::size_type pct = host.find('%'); | |
| 742 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
|
10 | if (pct != std::string::npos) { |
| 743 |
4/8✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
|
8 | host = host.substr(0, pct) + "%25" + host.substr(pct + 1); |
| 744 | } | ||
| 745 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
10 | host = "[" + host + "]"; |
| 746 | } | ||
| 747 |
2/4✓ Branch 0 taken 663 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 663 times.
✗ Branch 3 not taken.
|
1326 | return "http://" + host + path; |
| 748 | 663 | } | |
| 749 | |||
| 750 | // DEPRECATED -- see the comment on the declarations in common.h. Kept for | ||
| 751 | // external plugins and for the callers that have no main loop to complete a | ||
| 752 | // CurlManager request against. | ||
| 753 | 39 | bool urlHelper(const std::string method, const std::string& url, const std::string& data, std::string& resp, const unsigned int timeout) { | |
| 754 |
2/4✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
|
39 | return urlHelper(method, url, data, resp, std::list<std::string>(), timeout); |
| 755 | } | ||
| 756 | 39 | bool urlHelper(const std::string method, const std::string& url, const std::string& data, std::string& resp, const std::list<std::string>& extraHeaders, const unsigned int timeout) { | |
| 757 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | CURL* curl = curl_easy_init(); |
| 758 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | std::string userAgent = "FPP/"; |
| 759 |
2/4✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
|
39 | userAgent += getFPPVersionTriplet(); |
| 760 | |||
| 761 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | resp = ""; |
| 762 | |||
| 763 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
|
39 | if (!curl) { |
| 764 | ✗ | LogDebug(VB_GENERAL, "Unable to create curl instance in urlHelper()\n"); | |
| 765 | ✗ | return false; | |
| 766 | } | ||
| 767 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | SetCurlCloseOnExec(curl); |
| 768 | |||
| 769 | CURLcode status; | ||
| 770 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | status = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, urlWriteData); |
| 771 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
|
39 | if (status != CURLE_OK) { |
| 772 | ✗ | LogErr(VB_GENERAL, "curl_easy_setopt() Error setting write callback function: %s\n", curl_easy_strerror(status)); | |
| 773 | ✗ | curl_easy_cleanup(curl); | |
| 774 | ✗ | return false; | |
| 775 | } | ||
| 776 | |||
| 777 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | status = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp); |
| 778 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
|
39 | if (status != CURLE_OK) { |
| 779 | ✗ | LogErr(VB_GENERAL, "curl_easy_setopt() Error setting class pointer: %s\n", curl_easy_strerror(status)); | |
| 780 | ✗ | curl_easy_cleanup(curl); | |
| 781 | ✗ | return false; | |
| 782 | } | ||
| 783 | |||
| 784 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | status = curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); |
| 785 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
|
39 | if (status != CURLE_OK) { |
| 786 | ✗ | LogErr(VB_GENERAL, "curl_easy_setopt() Error setting URL: %s\n", curl_easy_strerror(status)); | |
| 787 | ✗ | curl_easy_cleanup(curl); | |
| 788 | ✗ | return false; | |
| 789 | } | ||
| 790 | |||
| 791 | 39 | struct curl_slist* headers = NULL; | |
| 792 |
15/30✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 33 times.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 6 times.
✓ Branch 13 taken 33 times.
✓ Branch 14 taken 6 times.
✓ Branch 15 taken 33 times.
✓ Branch 16 taken 39 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 39 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 6 times.
✓ Branch 21 taken 33 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
|
129 | if (startsWith(data, "{") && endsWith(data, "}")) { |
| 793 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | headers = curl_slist_append(headers, "Accept: application/json"); |
| 794 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | headers = curl_slist_append(headers, "Content-Type: application/json"); |
| 795 | } | ||
| 796 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
|
39 | for (auto& h : extraHeaders) { |
| 797 | ✗ | headers = curl_slist_append(headers, h.c_str()); | |
| 798 | } | ||
| 799 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 33 times.
|
39 | if (headers) { |
| 800 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); |
| 801 | } | ||
| 802 | |||
| 803 |
8/10✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 36 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 28 times.
✓ Branch 7 taken 8 times.
✓ Branch 8 taken 31 times.
✓ Branch 9 taken 8 times.
|
39 | if ((method == "POST") || (method == "PUT")) { |
| 804 | // The size first: without it curl takes strlen() of the body, which | ||
| 805 | // cuts a binary body short at its first zero byte. | ||
| 806 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)data.size()); |
| 807 |
1/2✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
|
31 | status = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); |
| 808 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | if (status != CURLE_OK) { |
| 809 | ✗ | LogErr(VB_GENERAL, "curl_easy_setopt() Error setting postfields data: %s\n", curl_easy_strerror(status)); | |
| 810 | ✗ | curl_slist_free_all(headers); | |
| 811 | ✗ | curl_easy_cleanup(curl); | |
| 812 | ✗ | return false; | |
| 813 | } | ||
| 814 | } | ||
| 815 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)timeout); |
| 816 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 2); |
| 817 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); |
| 818 | // Accept (and transparently decode) whatever encodings this libcurl was built | ||
| 819 | // with. Some controllers gzip a response whether or not the client asked for | ||
| 820 | // it, and without this the caller is handed the compressed bytes and every | ||
| 821 | // find()/JSON parse on them quietly fails. CurlManager has always done this; | ||
| 822 | // this path had not, which is what made such a device undetectable. | ||
| 823 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); |
| 824 | |||
| 825 |
3/4✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 36 times.
|
39 | if (method == "POST") |
| 826 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | curl_easy_setopt(curl, CURLOPT_POST, 1); |
| 827 |
3/4✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 8 times.
|
36 | else if (method == "PUT") |
| 828 |
1/2✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
|
28 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); |
| 829 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | else if (method == "DELETE") |
| 830 | ✗ | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); | |
| 831 | |||
| 832 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str()); |
| 833 | |||
| 834 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | LogDebug(VB_GENERAL, "Calling %s %s\n", method.c_str(), url.c_str()); |
| 835 | |||
| 836 |
1/2✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
|
39 | status = curl_easy_perform(curl); |
| 837 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 18 times.
|
39 | if (status != CURLE_OK) { |
| 838 |
2/4✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
|
21 | LogErr(VB_GENERAL, "curl_easy_perform() failed (%s): %s\n", url.c_str(), curl_easy_strerror(status)); |
| 839 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | curl_slist_free_all(headers); |
| 840 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | curl_easy_cleanup(curl); |
| 841 | 21 | return false; | |
| 842 | } | ||
| 843 | |||
| 844 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | LogDebug(VB_GENERAL, "%s %s resp: %s\n", method.c_str(), url.c_str(), resp.c_str()); |
| 845 | |||
| 846 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | curl_slist_free_all(headers); |
| 847 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | curl_easy_cleanup(curl); |
| 848 | 18 | return true; | |
| 849 | 39 | } | |
| 850 | |||
| 851 | 8 | bool urlHelper(const std::string method, const std::string& url, std::string& resp, const unsigned int timeout) { | |
| 852 | 8 | std::string data; | |
| 853 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | return urlHelper(method, url, data, resp, timeout); |
| 854 | 8 | } | |
| 855 | |||
| 856 | 8 | bool urlGet(const std::string url, std::string& resp) { | |
| 857 | 8 | std::string data; | |
| 858 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
16 | return urlHelper("GET", url, resp); |
| 859 | 8 | } | |
| 860 | |||
| 861 | 3 | bool urlPost(const std::string url, const std::string data, std::string& resp) { | |
| 862 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
6 | return urlHelper("POST", url, data, resp); |
| 863 | } | ||
| 864 | |||
| 865 | 28 | bool urlPut(const std::string url, const std::string data, std::string& resp) { | |
| 866 |
2/4✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
|
56 | return urlHelper("PUT", url, data, resp); |
| 867 | } | ||
| 868 | |||
| 869 | ✗ | bool urlDelete(const std::string url, const std::string data, std::string& resp) { | |
| 870 | ✗ | return urlHelper("DELETE", url, data, resp); | |
| 871 | } | ||
| 872 | |||
| 873 | ✗ | bool urlDelete(const std::string url, std::string& resp) { | |
| 874 | ✗ | std::string data; | |
| 875 | ✗ | return urlHelper("DELETE", url, data, resp); | |
| 876 | ✗ | } | |
| 877 | |||
| 878 | static const std::string BASE64_CHARS = | ||
| 879 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
| 880 | "abcdefghijklmnopqrstuvwxyz" | ||
| 881 | "0123456789+/"; | ||
| 882 | |||
| 883 | 191 | static inline bool isBase64(uint8_t c) { | |
| 884 |
4/6✓ Branch 0 taken 1 times.
✓ Branch 1 taken 190 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
191 | return (isalnum(c) || (c == '+') || (c == '/')); |
| 885 | } | ||
| 886 | |||
| 887 | 14 | std::string base64Encode(uint8_t const* buf, unsigned int bufLen) { | |
| 888 | 14 | std::string ret; | |
| 889 | 14 | int i = 0; | |
| 890 | 14 | int j = 0; | |
| 891 | uint8_t char_array_3[3]; | ||
| 892 | uint8_t char_array_4[4]; | ||
| 893 | |||
| 894 |
2/2✓ Branch 0 taken 280 times.
✓ Branch 1 taken 14 times.
|
294 | while (bufLen--) { |
| 895 | 280 | char_array_3[i++] = *(buf++); | |
| 896 |
2/2✓ Branch 0 taken 84 times.
✓ Branch 1 taken 196 times.
|
280 | if (i == 3) { |
| 897 | 84 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; | |
| 898 | 84 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); | |
| 899 | 84 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); | |
| 900 | 84 | char_array_4[3] = char_array_3[2] & 0x3f; | |
| 901 | |||
| 902 |
2/2✓ Branch 0 taken 336 times.
✓ Branch 1 taken 84 times.
|
420 | for (i = 0; (i < 4); i++) { |
| 903 |
1/2✓ Branch 0 taken 336 times.
✗ Branch 1 not taken.
|
336 | ret += BASE64_CHARS[char_array_4[i]]; |
| 904 | } | ||
| 905 | 84 | i = 0; | |
| 906 | } | ||
| 907 | } | ||
| 908 | |||
| 909 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | if (i) { |
| 910 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 14 times.
|
28 | for (j = i; j < 3; j++) { |
| 911 | 14 | char_array_3[j] = '\0'; | |
| 912 | } | ||
| 913 | |||
| 914 | 14 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; | |
| 915 | 14 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); | |
| 916 | 14 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); | |
| 917 | 14 | char_array_4[3] = char_array_3[2] & 0x3f; | |
| 918 | |||
| 919 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 14 times.
|
56 | for (j = 0; (j < i + 1); j++) { |
| 920 |
1/2✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
|
42 | ret += BASE64_CHARS[char_array_4[j]]; |
| 921 | } | ||
| 922 | |||
| 923 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 14 times.
|
28 | while ((i++ < 3)) { |
| 924 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | ret += '='; |
| 925 | } | ||
| 926 | } | ||
| 927 | 14 | return ret; | |
| 928 | ✗ | } | |
| 929 | 18 | std::vector<uint8_t> base64Decode(std::string const& encodedString) { | |
| 930 | 18 | int in_len = encodedString.size(); | |
| 931 | 18 | int i = 0; | |
| 932 | 18 | int j = 0; | |
| 933 | 18 | int in_ = 0; | |
| 934 | uint8_t char_array_4[4], char_array_3[3]; | ||
| 935 | 18 | std::vector<uint8_t> ret; | |
| 936 | |||
| 937 |
7/8✓ Branch 0 taken 208 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 191 times.
✓ Branch 3 taken 17 times.
✓ Branch 4 taken 191 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 191 times.
✓ Branch 7 taken 18 times.
|
209 | while (in_len-- && (encodedString[in_] != '=') && isBase64(encodedString[in_])) { |
| 938 | 191 | char_array_4[i++] = encodedString[in_]; | |
| 939 | 191 | in_++; | |
| 940 |
2/2✓ Branch 0 taken 35 times.
✓ Branch 1 taken 156 times.
|
191 | if (i == 4) { |
| 941 |
2/2✓ Branch 0 taken 140 times.
✓ Branch 1 taken 35 times.
|
175 | for (i = 0; i < 4; i++) { |
| 942 | 140 | char_array_4[i] = BASE64_CHARS.find(char_array_4[i]); | |
| 943 | } | ||
| 944 | |||
| 945 | 35 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); | |
| 946 | 35 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); | |
| 947 | 35 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; | |
| 948 | |||
| 949 |
2/2✓ Branch 0 taken 105 times.
✓ Branch 1 taken 35 times.
|
140 | for (i = 0; (i < 3); i++) { |
| 950 |
1/2✓ Branch 0 taken 105 times.
✗ Branch 1 not taken.
|
105 | ret.push_back(char_array_3[i]); |
| 951 | } | ||
| 952 | 35 | i = 0; | |
| 953 | } | ||
| 954 | } | ||
| 955 | |||
| 956 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 times.
|
18 | if (i) { |
| 957 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 17 times.
|
34 | for (j = i; j < 4; j++) { |
| 958 | 17 | char_array_4[j] = 0; | |
| 959 | } | ||
| 960 |
2/2✓ Branch 0 taken 68 times.
✓ Branch 1 taken 17 times.
|
85 | for (j = 0; j < 4; j++) { |
| 961 | 68 | char_array_4[j] = BASE64_CHARS.find(char_array_4[j]); | |
| 962 | } | ||
| 963 | |||
| 964 | 17 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); | |
| 965 | 17 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); | |
| 966 | 17 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; | |
| 967 | |||
| 968 |
2/2✓ Branch 0 taken 34 times.
✓ Branch 1 taken 17 times.
|
51 | for (j = 0; (j < i - 1); j++) { |
| 969 |
1/2✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
|
34 | ret.push_back(char_array_3[j]); |
| 970 | } | ||
| 971 | } | ||
| 972 | 18 | return ret; | |
| 973 | ✗ | } | |
| 974 | |||
| 975 | static std::function<void(bool)> SHUTDOWN_HOOK; | ||
| 976 | static std::atomic<bool> RESTART_RESUME_PLAYLIST{ false }; | ||
| 977 | 13 | void ShutdownFPPD(bool restart) { | |
| 978 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | if (SHUTDOWN_HOOK) { |
| 979 | 13 | SHUTDOWN_HOOK(restart); | |
| 980 | } | ||
| 981 | 13 | } | |
| 982 | 4 | void RestartFPPDResumingPlaylist() { | |
| 983 | 4 | RESTART_RESUME_PLAYLIST = true; | |
| 984 | 4 | ShutdownFPPD(true); | |
| 985 | 4 | } | |
| 986 | 12 | bool RestartShouldResumePlaylist() { | |
| 987 | 12 | return RESTART_RESUME_PLAYLIST; | |
| 988 | } | ||
| 989 | 1887 | void RegisterShutdownHandler(const std::function<void(bool)> hook) { | |
| 990 | 1887 | SHUTDOWN_HOOK = hook; | |
| 991 | 1887 | } | |
| 992 | |||
| 993 | static std::function<std::string(std::string&)> MANUAL_CRASH_REPORT_HOOK; | ||
| 994 | 4 | std::string BuildManualCrashReport(std::string& error) { | |
| 995 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (!MANUAL_CRASH_REPORT_HOOK) { |
| 996 | ✗ | error = "unavailable"; | |
| 997 | ✗ | return ""; | |
| 998 | } | ||
| 999 | 4 | return MANUAL_CRASH_REPORT_HOOK(error); | |
| 1000 | } | ||
| 1001 | 1887 | void RegisterManualCrashReportHandler(const std::function<std::string(std::string&)> hook) { | |
| 1002 | 1887 | MANUAL_CRASH_REPORT_HOOK = hook; | |
| 1003 | 1887 | } | |
| 1004 | |||
| 1005 | static std::atomic<const char*> MAIN_LOOP_PHASE_STR{ "startup" }; | ||
| 1006 | 3717740 | void SetMainLoopPhase(const char* phase) { | |
| 1007 | 3717740 | MAIN_LOOP_PHASE_STR.store(phase, std::memory_order_relaxed); | |
| 1008 | 3717740 | } | |
| 1009 | 22 | const char* GetMainLoopPhase() { | |
| 1010 | 22 | const char* p = MAIN_LOOP_PHASE_STR.load(std::memory_order_relaxed); | |
| 1011 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | return p ? p : "?"; |
| 1012 | } | ||
| 1013 | |||
| 1014 | 5832 | std::string GetFileExtension(const std::string& filename) { | |
| 1015 |
2/2✓ Branch 0 taken 2476 times.
✓ Branch 1 taken 3356 times.
|
5832 | if (filename.find_last_of(".") != std::string::npos) |
| 1016 | 2476 | return filename.substr(filename.find_last_of(".") + 1); | |
| 1017 |
1/2✓ Branch 0 taken 3356 times.
✗ Branch 1 not taken.
|
6712 | return ""; |
| 1018 | } | ||
| 1019 | |||
| 1020 | 271250 | void SetThreadName(const std::string& name) { | |
| 1021 | #ifdef PLATFORM_OSX | ||
| 1022 | pthread_setname_np(name.c_str()); | ||
| 1023 | #else | ||
| 1024 | 271250 | pthread_setname_np(pthread_self(), name.c_str()); | |
| 1025 | #endif | ||
| 1026 | 271250 | } | |
| 1027 | |||
| 1028 | 2805 | bool SetThreadRealtimePriority(int priority) { | |
| 1029 | #ifndef PLATFORM_OSX | ||
| 1030 | // Kernel RT throttling (sched_rt_runtime_us, default 95%) still guarantees | ||
| 1031 | // normal-priority threads a CPU share even if an RT thread runs hot. | ||
| 1032 | // Returns false (typically EPERM in containers/non-root) so callers can | ||
| 1033 | // log which mode they're running in. | ||
| 1034 | struct sched_param param; | ||
| 1035 | 2805 | param.sched_priority = priority; | |
| 1036 | 2805 | return pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m) == 0; | |
| 1037 | #else | ||
| 1038 | return false; | ||
| 1039 | #endif | ||
| 1040 | } | ||
| 1041 | |||
| 1042 | 1885 | std::string getPlatform() { | |
| 1043 |
2/4✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1885 times.
✗ Branch 3 not taken.
|
1885 | std::string platform = GetFileContents("/etc/fpp/platform"); |
| 1044 |
1/2✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
|
1885 | TrimWhiteSpace(platform); |
| 1045 | 1885 | return platform; | |
| 1046 | ✗ | } | |
| 1047 | |||
| 1048 | |||
| 1049 | ///////////////////////////////////////////////////////////////////////////// | ||
| 1050 | // Unrolled 32x32 bitwise transform from Hacker's Delight | ||
| 1051 | // | ||
| 1052 | // Bit flips an array along the diagonal from MSbit on LS uint32_t to LS bit on MS uint32_t | ||
| 1053 | // | ||
| 1054 | // src[00] = 0b00000000000000000000000000000001 | ||
| 1055 | // src[01] = 0b00000000000000000000000000000010 | ||
| 1056 | // ... | ||
| 1057 | // src[30] = 0b00000000000000000000000000000000 | ||
| 1058 | // src[31] = 0b00000000000000000000000000000000 | ||
| 1059 | // becomes | ||
| 1060 | // dst[00] = 0b00000000000000000000000000000000 | ||
| 1061 | // dst[01] = 0b00000000000000000000000000000000 | ||
| 1062 | // ... | ||
| 1063 | // dst[30] = 0b01000000000000000000000000000000 | ||
| 1064 | // dst[31] = 0b10000000000000000000000000000000 | ||
| 1065 | |||
| 1066 | #define tr32swap(a0, a1, j, m) t = (a0 ^ (a1 >> j)) & m; \ | ||
| 1067 | a0 = a0 ^ t; \ | ||
| 1068 | a1 = a1 ^ (t << j); | ||
| 1069 | |||
| 1070 | 40088 | void TransposeBits32x32(uint32_t *dst, uint32_t *src) { | |
| 1071 | uint32_t m, t; | ||
| 1072 | uint32_t a0, a1, a2, a3, a4, a5, a6, a7, | ||
| 1073 | a8, a9, a10, a11, a12, a13, a14, a15, | ||
| 1074 | a16, a17, a18, a19, a20, a21, a22, a23, | ||
| 1075 | a24, a25, a26, a27, a28, a29, a30, a31; | ||
| 1076 | |||
| 1077 | 40088 | a0 = src[ 0]; a1 = src[ 1]; a2 = src[ 2]; a3 = src[ 3]; | |
| 1078 | 40088 | a4 = src[ 4]; a5 = src[ 5]; a6 = src[ 6]; a7 = src[ 7]; | |
| 1079 | 40088 | a8 = src[ 8]; a9 = src[ 9]; a10 = src[10]; a11 = src[11]; | |
| 1080 | 40088 | a12 = src[12]; a13 = src[13]; a14 = src[14]; a15 = src[15]; | |
| 1081 | 40088 | a16 = src[16]; a17 = src[17]; a18 = src[18]; a19 = src[19]; | |
| 1082 | 40088 | a20 = src[20]; a21 = src[21]; a22 = src[22]; a23 = src[23]; | |
| 1083 | 40088 | a24 = src[24]; a25 = src[25]; a26 = src[26]; a27 = src[27]; | |
| 1084 | 40088 | a28 = src[28]; a29 = src[29]; a30 = src[30]; a31 = src[31]; | |
| 1085 | |||
| 1086 | 40088 | m = 0x0000FFFF; | |
| 1087 | 40088 | tr32swap(a0, a16, 16, m) | |
| 1088 | 40088 | tr32swap(a1, a17, 16, m) | |
| 1089 | 40088 | tr32swap(a2, a18, 16, m) | |
| 1090 | 40088 | tr32swap(a3, a19, 16, m) | |
| 1091 | 40088 | tr32swap(a4, a20, 16, m) | |
| 1092 | 40088 | tr32swap(a5, a21, 16, m) | |
| 1093 | 40088 | tr32swap(a6, a22, 16, m) | |
| 1094 | 40088 | tr32swap(a7, a23, 16, m) | |
| 1095 | 40088 | tr32swap(a8, a24, 16, m) | |
| 1096 | 40088 | tr32swap(a9, a25, 16, m) | |
| 1097 | 40088 | tr32swap(a10, a26, 16, m) | |
| 1098 | 40088 | tr32swap(a11, a27, 16, m) | |
| 1099 | 40088 | tr32swap(a12, a28, 16, m) | |
| 1100 | 40088 | tr32swap(a13, a29, 16, m) | |
| 1101 | 40088 | tr32swap(a14, a30, 16, m) | |
| 1102 | 40088 | tr32swap(a15, a31, 16, m) | |
| 1103 | |||
| 1104 | 40088 | m = 0x00FF00FF; | |
| 1105 | 40088 | tr32swap(a0, a8, 8, m) | |
| 1106 | 40088 | tr32swap(a1, a9, 8, m) | |
| 1107 | 40088 | tr32swap(a2, a10, 8, m) | |
| 1108 | 40088 | tr32swap(a3, a11, 8, m) | |
| 1109 | 40088 | tr32swap(a4, a12, 8, m) | |
| 1110 | 40088 | tr32swap(a5, a13, 8, m) | |
| 1111 | 40088 | tr32swap(a6, a14, 8, m) | |
| 1112 | 40088 | tr32swap(a7, a15, 8, m) | |
| 1113 | 40088 | tr32swap(a16, a24, 8, m) | |
| 1114 | 40088 | tr32swap(a17, a25, 8, m) | |
| 1115 | 40088 | tr32swap(a18, a26, 8, m) | |
| 1116 | 40088 | tr32swap(a19, a27, 8, m) | |
| 1117 | 40088 | tr32swap(a20, a28, 8, m) | |
| 1118 | 40088 | tr32swap(a21, a29, 8, m) | |
| 1119 | 40088 | tr32swap(a22, a30, 8, m) | |
| 1120 | 40088 | tr32swap(a23, a31, 8, m) | |
| 1121 | |||
| 1122 | 40088 | m = 0x0F0F0F0F; | |
| 1123 | 40088 | tr32swap(a0, a4, 4, m) | |
| 1124 | 40088 | tr32swap(a1, a5, 4, m) | |
| 1125 | 40088 | tr32swap(a2, a6, 4, m) | |
| 1126 | 40088 | tr32swap(a3, a7, 4, m) | |
| 1127 | 40088 | tr32swap(a8, a12, 4, m) | |
| 1128 | 40088 | tr32swap(a9, a13, 4, m) | |
| 1129 | 40088 | tr32swap(a10, a14, 4, m) | |
| 1130 | 40088 | tr32swap(a11, a15, 4, m) | |
| 1131 | 40088 | tr32swap(a16, a20, 4, m) | |
| 1132 | 40088 | tr32swap(a17, a21, 4, m) | |
| 1133 | 40088 | tr32swap(a18, a22, 4, m) | |
| 1134 | 40088 | tr32swap(a19, a23, 4, m) | |
| 1135 | 40088 | tr32swap(a24, a28, 4, m) | |
| 1136 | 40088 | tr32swap(a25, a29, 4, m) | |
| 1137 | 40088 | tr32swap(a26, a30, 4, m) | |
| 1138 | 40088 | tr32swap(a27, a31, 4, m) | |
| 1139 | |||
| 1140 | 40088 | m = 0x33333333; | |
| 1141 | 40088 | tr32swap(a0, a2, 2, m) | |
| 1142 | 40088 | tr32swap(a1, a3, 2, m) | |
| 1143 | 40088 | tr32swap(a4, a6, 2, m) | |
| 1144 | 40088 | tr32swap(a5, a7, 2, m) | |
| 1145 | 40088 | tr32swap(a8, a10, 2, m) | |
| 1146 | 40088 | tr32swap(a9, a11, 2, m) | |
| 1147 | 40088 | tr32swap(a12, a14, 2, m) | |
| 1148 | 40088 | tr32swap(a13, a15, 2, m) | |
| 1149 | 40088 | tr32swap(a16, a18, 2, m) | |
| 1150 | 40088 | tr32swap(a17, a19, 2, m) | |
| 1151 | 40088 | tr32swap(a20, a22, 2, m) | |
| 1152 | 40088 | tr32swap(a21, a23, 2, m) | |
| 1153 | 40088 | tr32swap(a24, a26, 2, m) | |
| 1154 | 40088 | tr32swap(a25, a27, 2, m) | |
| 1155 | 40088 | tr32swap(a28, a30, 2, m) | |
| 1156 | 40088 | tr32swap(a29, a31, 2, m) | |
| 1157 | |||
| 1158 | 40088 | m = 0x55555555; | |
| 1159 | 40088 | tr32swap(a0, a1, 1, m) | |
| 1160 | 40088 | tr32swap(a2, a3, 1, m) | |
| 1161 | 40088 | tr32swap(a4, a5, 1, m) | |
| 1162 | 40088 | tr32swap(a6, a7, 1, m) | |
| 1163 | 40088 | tr32swap(a8, a9, 1, m) | |
| 1164 | 40088 | tr32swap(a10, a11, 1, m) | |
| 1165 | 40088 | tr32swap(a12, a13, 1, m) | |
| 1166 | 40088 | tr32swap(a14, a15, 1, m) | |
| 1167 | 40088 | tr32swap(a16, a17, 1, m) | |
| 1168 | 40088 | tr32swap(a18, a19, 1, m) | |
| 1169 | 40088 | tr32swap(a20, a21, 1, m) | |
| 1170 | 40088 | tr32swap(a22, a23, 1, m) | |
| 1171 | 40088 | tr32swap(a24, a25, 1, m) | |
| 1172 | 40088 | tr32swap(a26, a27, 1, m) | |
| 1173 | 40088 | tr32swap(a28, a29, 1, m) | |
| 1174 | 40088 | tr32swap(a30, a31, 1, m) | |
| 1175 | |||
| 1176 | 40088 | dst[ 0] = a0; dst[ 1] = a1; dst[ 2] = a2; dst[ 3] = a3; | |
| 1177 | 40088 | dst[ 4] = a4; dst[ 5] = a5; dst[ 6] = a6; dst[ 7] = a7; | |
| 1178 | 40088 | dst[ 8] = a8; dst[ 9] = a9; dst[10] = a10; dst[11] = a11; | |
| 1179 | 40088 | dst[12] = a12; dst[13] = a13; dst[14] = a14; dst[15] = a15; | |
| 1180 | 40088 | dst[16] = a16; dst[17] = a17; dst[18] = a18; dst[19] = a19; | |
| 1181 | 40088 | dst[20] = a20; dst[21] = a21; dst[22] = a22; dst[23] = a23; | |
| 1182 | 40088 | dst[24] = a24; dst[25] = a25; dst[26] = a26; dst[27] = a27; | |
| 1183 | 40088 | dst[28] = a28; dst[29] = a29; dst[30] = a30; dst[31] = a31; | |
| 1184 | 40088 | } | |
| 1185 | ///////////////////////////////////////////////////////////////////////////// | ||
| 1186 | |||
| 1187 |