Mirror of espurna firmware for wireless switches and more
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

436 lines
11 KiB

1 year ago
  1. /*
  2. UTILS MODULE
  3. Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "utils.h"
  6. #include <limits>
  7. // We can only return small values (max 'z' aka 122)
  8. static constexpr uint8_t InvalidByte { 255u };
  9. static uint8_t bin_char2byte(char c) {
  10. switch (c) {
  11. case '0'...'1':
  12. return (c - '0');
  13. }
  14. return InvalidByte;
  15. }
  16. static uint8_t oct_char2byte(char c) {
  17. switch (c) {
  18. case '0'...'7':
  19. return (c - '0');
  20. }
  21. return InvalidByte;
  22. }
  23. static uint8_t dec_char2byte(char c) {
  24. switch (c) {
  25. case '0'...'9':
  26. return (c - '0');
  27. }
  28. return InvalidByte;
  29. }
  30. static uint8_t hex_char2byte(char c) {
  31. switch (c) {
  32. case '0'...'9':
  33. return (c - '0');
  34. case 'a'...'f':
  35. return 10 + (c - 'a');
  36. case 'A'...'F':
  37. return 10 + (c - 'A');
  38. }
  39. return InvalidByte;
  40. }
  41. static ParseUnsignedResult parseUnsignedImpl(espurna::StringView value, int base) {
  42. auto out = ParseUnsignedResult{
  43. .ok = false,
  44. .value = 0,
  45. };
  46. using Char2Byte = uint8_t(*)(char);
  47. Char2Byte char2byte = nullptr;
  48. switch (base) {
  49. case 2:
  50. char2byte = bin_char2byte;
  51. break;
  52. case 8:
  53. char2byte = oct_char2byte;
  54. break;
  55. case 10:
  56. char2byte = dec_char2byte;
  57. break;
  58. case 16:
  59. char2byte = hex_char2byte;
  60. break;
  61. }
  62. if (!char2byte) {
  63. return out;
  64. }
  65. for (auto it = value.begin(); it != value.end(); ++it) {
  66. const auto digit = char2byte(*it);
  67. if (digit == InvalidByte) {
  68. out.ok = false;
  69. goto err;
  70. }
  71. const auto value = out.value;
  72. out.value = out.value * uint32_t(base);
  73. // TODO explicitly set the output bit width?
  74. if (value > out.value) {
  75. out.ok = false;
  76. goto err;
  77. }
  78. out.value += uint32_t(digit);
  79. out.ok = true;
  80. }
  81. err:
  82. return out;
  83. }
  84. bool tryParseId(espurna::StringView value, size_t limit, size_t& out) {
  85. using T = std::remove_cvref<decltype(out)>::type;
  86. static_assert(std::is_same<T, size_t>::value, "");
  87. if (value.length()) {
  88. const auto result = parseUnsignedImpl(value, 10);
  89. if (result.ok && (result.value < limit)) {
  90. out = result.value;
  91. return true;
  92. }
  93. }
  94. return false;
  95. }
  96. bool tryParseIdPath(espurna::StringView value, size_t limit, size_t& out) {
  97. if (value.length()) {
  98. const auto before_begin = value.begin() - 1;
  99. for (auto it = value.end() - 1; it != before_begin; --it) {
  100. if ((*it) == '/') {
  101. return tryParseId(
  102. espurna::StringView(it + 1, value.end()),
  103. limit, out);
  104. }
  105. }
  106. }
  107. return false;
  108. }
  109. String prettyDuration(espurna::duration::Seconds seconds) {
  110. time_t timestamp = static_cast<time_t>(seconds.count());
  111. tm spec;
  112. gmtime_r(&timestamp, &spec);
  113. char buffer[64];
  114. sprintf_P(buffer, PSTR("%02dy %02dd %02dh %02dm %02ds"),
  115. (spec.tm_year - 70), spec.tm_yday, spec.tm_hour,
  116. spec.tm_min, spec.tm_sec);
  117. return String(buffer);
  118. }
  119. // -----------------------------------------------------------------------------
  120. // SSL
  121. // -----------------------------------------------------------------------------
  122. bool sslCheckFingerPrint(const char * fingerprint) {
  123. return (strlen(fingerprint) == 59);
  124. }
  125. bool sslFingerPrintArray(const char * fingerprint, unsigned char * bytearray) {
  126. // check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
  127. if (!sslCheckFingerPrint(fingerprint)) return false;
  128. // walk the fingerprint
  129. for (unsigned int i=0; i<20; i++) {
  130. bytearray[i] = strtol(fingerprint + 3*i, NULL, 16);
  131. }
  132. return true;
  133. }
  134. bool sslFingerPrintChar(const char * fingerprint, char * destination) {
  135. // check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
  136. if (!sslCheckFingerPrint(fingerprint)) return false;
  137. // copy it
  138. strncpy(destination, fingerprint, 59);
  139. // walk the fingerprint replacing ':' for ' '
  140. for (unsigned char i = 0; i<59; i++) {
  141. if (destination[i] == ':') destination[i] = ' ';
  142. }
  143. return true;
  144. }
  145. // -----------------------------------------------------------------------------
  146. // Helper functions
  147. // -----------------------------------------------------------------------------
  148. double roundTo(double num, unsigned char positions) {
  149. double multiplier = 1;
  150. while (positions-- > 0) multiplier *= 10;
  151. return round(num * multiplier) / multiplier;
  152. }
  153. // ref. https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
  154. // the machine epsilon has to be scaled to the magnitude of the values used
  155. // and multiplied by the desired precision in ULPs (units in the last place)
  156. // unless the result is subnormal
  157. bool almostEqual(double lhs, double rhs, int ulp) {
  158. return __builtin_fabs(lhs - rhs) <= std::numeric_limits<double>::epsilon() * __builtin_fabs(lhs + rhs) * ulp
  159. || __builtin_fabs(lhs - rhs) < std::numeric_limits<double>::min();
  160. }
  161. bool almostEqual(double lhs, double rhs) {
  162. return almostEqual(lhs, rhs, 3);
  163. }
  164. espurna::StringView stripNewline(espurna::StringView value) {
  165. if ((value.length() >= 2)
  166. && (*(value.end() - 1) == '\n')
  167. && (*(value.end() - 2) == '\r')) {
  168. value = espurna::StringView(value.begin(), value.end() - 2);
  169. } else if ((value.length() >= 1) && (*(value.end() - 1) == '\n')) {
  170. value = espurna::StringView(value.begin(), value.end() - 1);
  171. }
  172. return value;
  173. }
  174. bool isNumber(espurna::StringView view) {
  175. bool dot { false };
  176. bool digit { false };
  177. for (auto ptr = view.begin(); ptr != view.end(); ++ptr) {
  178. switch (*ptr) {
  179. case '-':
  180. case '+':
  181. if (ptr != view.begin()) {
  182. return false;
  183. }
  184. break;
  185. case '.':
  186. if (dot) {
  187. return false;
  188. }
  189. dot = true;
  190. break;
  191. case '0' ... '9':
  192. digit = true;
  193. break;
  194. case 'a' ... 'z':
  195. case 'A' ... 'Z':
  196. return false;
  197. }
  198. }
  199. return digit;
  200. }
  201. // ref: lwip2 lwip_strnstr with strnlen
  202. char* strnstr(const char* buffer, const char* token, size_t n) {
  203. const auto token_len = strnlen_P(token, n);
  204. if (!token_len) {
  205. return const_cast<char*>(buffer);
  206. }
  207. const auto first = pgm_read_byte(token);
  208. for (const char* p = buffer; *p && (p + token_len <= buffer + n); p++) {
  209. if ((*p == first) && (strncmp_P(p, token, token_len) == 0)) {
  210. return const_cast<char*>(p);
  211. }
  212. }
  213. return nullptr;
  214. }
  215. ParseUnsignedResult parseUnsigned(espurna::StringView value, int base) {
  216. return parseUnsignedImpl(value, base);
  217. }
  218. static constexpr int base_from_char(char c) {
  219. return (c == 'b') ? 2 :
  220. (c == 'o') ? 8 :
  221. (c == 'x') ? 16 : 0;
  222. }
  223. ParseUnsignedResult parseUnsigned(espurna::StringView value) {
  224. int base = 10;
  225. if (value.length() && (value.length() > 2)) {
  226. const auto from_base = base_from_char(value[1]);
  227. if ((value[0] == '0') && (from_base != 0)) {
  228. base = from_base;
  229. value = espurna::StringView(
  230. value.begin() + 2, value.end());
  231. }
  232. }
  233. return parseUnsignedImpl(value, base);
  234. }
  235. String formatUnsigned(uint32_t value, int base) {
  236. constexpr size_t BufferSize { 8 * sizeof(decltype(value)) };
  237. String result;
  238. if (base == 2) {
  239. result += "0b";
  240. } else if (base == 8) {
  241. result += "0o";
  242. } else if (base == 16) {
  243. result += "0x";
  244. }
  245. char buffer[BufferSize + 1] = {0};
  246. ultoa(value, buffer, base);
  247. result += buffer;
  248. return result;
  249. }
  250. namespace {
  251. // From a byte array to an hexa char array ("A220EE...", double the size)
  252. template <typename T>
  253. const uint8_t* hexEncodeImpl(const uint8_t* in_begin, const uint8_t* in_end, T&& callback) {
  254. static const char base16[] = "0123456789ABCDEF";
  255. constexpr uint8_t Left { 0xf0 };
  256. constexpr uint8_t Right { 0xf };
  257. constexpr uint8_t Shift { 4 };
  258. auto* in_ptr = in_begin;
  259. for (; in_ptr != in_end; ++in_ptr) {
  260. const char buf[2] {
  261. base16[((*in_ptr) & Left) >> Shift],
  262. base16[(*in_ptr) & Right]
  263. };
  264. if (!callback(buf)) {
  265. break;
  266. }
  267. }
  268. return in_ptr;
  269. }
  270. } // namespace
  271. char* hexEncode(const uint8_t* in_begin, const uint8_t* in_end, char* out_begin, char* out_end) {
  272. char* out_ptr { out_begin };
  273. hexEncodeImpl(in_begin, in_end, [&](const char (&byte)[2]) {
  274. *(out_ptr) = byte[0];
  275. ++out_ptr;
  276. *(out_ptr) = byte[1];
  277. ++out_ptr;
  278. return out_ptr != out_end;
  279. });
  280. return out_ptr;
  281. }
  282. String hexEncode(const uint8_t* in_begin, const uint8_t* in_end) {
  283. String out;
  284. out.reserve(in_end - in_begin);
  285. hexEncodeImpl(in_begin, in_end, [&](const char (&byte)[2]) {
  286. out.concat(byte, 2);
  287. return true;
  288. });
  289. return out;
  290. }
  291. size_t hexEncode(const uint8_t* in, size_t in_size, char* out, size_t out_size) {
  292. if (out_size >= ((in_size * 2) + 1)) {
  293. char* out_ptr = hexEncode(in, in + in_size, out, out + out_size);
  294. *out_ptr = '\0';
  295. ++out_ptr;
  296. return out_ptr - out;
  297. }
  298. return 0;
  299. }
  300. // From an hexa char array ("A220EE...") to a byte array (half the size)
  301. uint8_t* hexDecode(const char* in_begin, const char* in_end, uint8_t* out_begin, uint8_t* out_end) {
  302. constexpr uint8_t Shift { 4 };
  303. const char* in_ptr { in_begin };
  304. uint8_t* out_ptr { out_begin };
  305. while ((in_ptr != in_end) && (out_ptr != out_end)) {
  306. uint8_t lhs = hex_char2byte(*in_ptr);
  307. if (lhs == InvalidByte) {
  308. break;
  309. }
  310. ++in_ptr;
  311. uint8_t rhs = hex_char2byte(*in_ptr);
  312. if (rhs == InvalidByte) {
  313. break;
  314. }
  315. ++in_ptr;
  316. (*out_ptr) = (lhs << Shift) | rhs;
  317. ++out_ptr;
  318. }
  319. return out_ptr;
  320. }
  321. size_t hexDecode(const char* in, size_t in_size, uint8_t* out, size_t out_size) {
  322. if ((in_size & 1) || (out_size < (in_size / 2))) {
  323. return 0;
  324. }
  325. uint8_t* out_ptr { hexDecode(in, in + in_size, out, out + out_size) };
  326. return out_ptr - out;
  327. }
  328. size_t consumeAvailable(Stream& stream) {
  329. const auto result = stream.available();
  330. if (result <= 0) {
  331. return 0;
  332. }
  333. const auto available = static_cast<size_t>(result);
  334. size_t size = 0;
  335. uint8_t buf[64];
  336. do {
  337. const auto chunk = std::min(available, std::size(buf));
  338. stream.readBytes(&buf[0], chunk);
  339. size += chunk;
  340. } while (size != available);
  341. return size;
  342. }