Fork of the espurna firmware for `mhsw` switches
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.

674 lines
20 KiB

Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
  1. /*
  2. Part of the SETTINGS MODULE
  3. Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
  4. Reimplementation of the Embedis storage format:
  5. - https://github.com/thingSoC/embedis
  6. */
  7. #pragma once
  8. #include <Arduino.h>
  9. #include <algorithm>
  10. #include <memory>
  11. #include <vector>
  12. #include "libs/TypeChecks.h"
  13. namespace settings {
  14. namespace embedis {
  15. // 'optional' type for byte range
  16. struct ValueResult {
  17. operator bool() {
  18. return result;
  19. }
  20. bool result { false };
  21. String value;
  22. };
  23. // We can't save empty keys but can save empty values as 0x00 0x00 0x00 0x00
  24. // total sum is:
  25. // - 2 bytes gap at the end (will be re-used by the next value length byte)
  26. // - 4 bytes to store length of 2 values (stored as big-endian)
  27. // - N bytes of values themselves
  28. inline size_t estimate(const String& key, const String& value) {
  29. if (!key.length()) {
  30. return 0;
  31. }
  32. const auto key_len = key.length();
  33. const auto value_len = value.length();
  34. return (4 + key_len + ((value_len > 0) ? value_len : 2));
  35. }
  36. // Note: KeyValueStore is templated to avoid having to provide RawStorageBase via virtual inheritance.
  37. template <typename RawStorageBase>
  38. class KeyValueStore {
  39. // -----------------------------------------------------------------------------------
  40. // Notice: we can only use sfinae checks with the current compiler version
  41. // TODO: provide actual benchmark comparison with 'lambda'-list-as-vtable (old Embedis style)
  42. // and vtable approach (write(), read() and commit() as pure virtual)
  43. // TODO: consider overrides for bulk operations like move (see ::del method)
  44. template <typename T>
  45. using storage_can_write_t = decltype(std::declval<T>().write(
  46. std::declval<uint16_t>(), std::declval<uint8_t>()));
  47. template <typename T>
  48. using storage_can_write = is_detected<storage_can_write_t, T>;
  49. template <typename T>
  50. using storage_can_read_t = decltype(std::declval<T>().read(std::declval<uint16_t>()));
  51. template <typename T>
  52. using storage_can_read = is_detected<storage_can_read_t, T>;
  53. template <typename T>
  54. using storage_can_commit_t = decltype(std::declval<T>().commit());
  55. template <typename T>
  56. using storage_can_commit = is_detected<storage_can_commit_t, T>;
  57. static_assert(
  58. (storage_can_commit<RawStorageBase>{} &&
  59. storage_can_read<RawStorageBase>{} &&
  60. storage_can_write<RawStorageBase>{}),
  61. "Storage class must implement read(index), write(index, byte) and commit()"
  62. );
  63. // -----------------------------------------------------------------------------------
  64. protected:
  65. // Tracking state of the parser inside of _raw_read()
  66. enum class State {
  67. Begin,
  68. End,
  69. LenByte1,
  70. LenByte2,
  71. EmptyValue,
  72. Value
  73. };
  74. // Pointer to the region of data that we are using
  75. //
  76. // XXX: It does not matter right now, but we **will** overflow position when using sizes >= (2^16) - 1
  77. // Note: Implementation is also in the header b/c c++ won't allow us
  78. // to have a plain member (not a ptr or ref) of unknown size.
  79. // Note: There was a considiration to implement this as 'stashing iterator' to be compatible with stl algorithms.
  80. // In such implementation, we would store intermediate index and allow the user to receive a `value_proxy`,
  81. // temporary returned by `value_proxy& operator*()' that is bound to Cursor instance.
  82. // This **will** cause problems with 'reverse_iterator' or anything like it, as it expects reference to
  83. // outlive the iterator object (specifically, result of `return *--tmp`, where `tmp` is created inside of a function block)
  84. struct Cursor {
  85. Cursor(RawStorageBase& storage, uint16_t position_, uint16_t begin_, uint16_t end_) :
  86. position(position_),
  87. begin(begin_),
  88. end(end_),
  89. _storage(storage)
  90. {}
  91. Cursor(RawStorageBase& storage, uint16_t begin_, uint16_t end_) :
  92. Cursor(storage, 0, begin_, end_)
  93. {}
  94. explicit Cursor(RawStorageBase& storage) :
  95. Cursor(storage, 0, 0, 0)
  96. {}
  97. static Cursor merge(RawStorageBase& storage, const Cursor& key, const Cursor& value) {
  98. return Cursor(storage, key.begin, value.end);
  99. }
  100. static Cursor fromEnd(RawStorageBase& storage, uint16_t begin, uint16_t end) {
  101. return Cursor(storage, end - begin, begin, end);
  102. }
  103. Cursor() = delete;
  104. void reset(uint16_t begin_, uint16_t end_) {
  105. position = 0;
  106. begin = begin_;
  107. end = end_;
  108. }
  109. uint8_t read() {
  110. return _storage.read(begin + position);
  111. }
  112. void write(uint8_t value) {
  113. _storage.write(begin + position, value);
  114. }
  115. void resetBeginning() {
  116. position = 0;
  117. }
  118. void resetEnd() {
  119. position = end - begin;
  120. }
  121. size_t size() {
  122. return (end - begin);
  123. }
  124. bool inRange(uint16_t position_) {
  125. return (position_ < (end - begin));
  126. }
  127. operator bool() {
  128. return inRange(position);
  129. }
  130. uint8_t operator[](size_t position_) const {
  131. return _storage.read(begin + position_);
  132. }
  133. bool operator ==(const Cursor& other) {
  134. return (begin == other.begin) && (end == other.end);
  135. }
  136. bool operator !=(const Cursor& other) {
  137. return !(*this == other);
  138. }
  139. Cursor& operator++() {
  140. ++position;
  141. return *this;
  142. }
  143. Cursor operator++(int) {
  144. Cursor other(*this);
  145. ++*this;
  146. return other;
  147. }
  148. Cursor& operator--() {
  149. --position;
  150. return *this;
  151. }
  152. Cursor operator--(int) {
  153. Cursor other(*this);
  154. --*this;
  155. return other;
  156. }
  157. uint16_t position;
  158. uint16_t begin;
  159. uint16_t end;
  160. private:
  161. RawStorageBase& _storage;
  162. };
  163. public:
  164. // Store value location in a more reasonable forward-iterator-style manner
  165. // Allows us to skip string creation when just searching for specific values
  166. // XXX: be cautious that cursor positions **will** break when underlying storage changes
  167. struct ReadResult {
  168. friend class KeyValueStore<RawStorageBase>;
  169. ReadResult(const Cursor& cursor_) :
  170. length(0),
  171. cursor(cursor_),
  172. result(false)
  173. {}
  174. ReadResult(RawStorageBase& storage) :
  175. length(0),
  176. cursor(storage),
  177. result(false)
  178. {}
  179. operator bool() {
  180. return result;
  181. }
  182. String read() {
  183. String out;
  184. out.reserve(length);
  185. if (!length) {
  186. return out;
  187. }
  188. decltype(length) index = 0;
  189. cursor.resetBeginning();
  190. while (index < length) {
  191. out += static_cast<char>(cursor.read());
  192. ++cursor;
  193. ++index;
  194. }
  195. return out;
  196. }
  197. uint16_t length;
  198. private:
  199. Cursor cursor;
  200. bool result;
  201. };
  202. // Internal storage consists of sequences of <byte-range><length>
  203. struct KeyValueResult {
  204. operator bool() {
  205. return (key) && (value) && (key.length > 0);
  206. }
  207. bool operator !() {
  208. return !(static_cast<bool>(*this));
  209. }
  210. template <typename T = ReadResult>
  211. KeyValueResult(T&& key_, T&& value_) :
  212. key(std::forward<T>(key_)),
  213. value(std::forward<T>(value_))
  214. {}
  215. KeyValueResult(RawStorageBase& storage) :
  216. key(storage),
  217. value(storage)
  218. {}
  219. ReadResult key;
  220. ReadResult value;
  221. };
  222. // one and only possible constructor, simply move the class object into the
  223. // member variable to avoid forcing the user of the API to keep 2 objects alive.
  224. KeyValueStore(RawStorageBase&& storage, uint16_t begin, uint16_t end) :
  225. _storage(std::move(storage)),
  226. _cursor(_storage, begin, end),
  227. _state(State::Begin)
  228. {}
  229. // Try to find the matching key. Datastructure that we use does not specify
  230. // any value 'type' inside of it. We expect 'key' to be the first non-empty string,
  231. // 'value' can be empty.
  232. ValueResult get(const String& key) {
  233. return _get(key, true);
  234. }
  235. bool has(const String& key) {
  236. return static_cast<bool>(_get(key, false));
  237. }
  238. // We going be using this pattern all the time here, because we need 2 consecutive **valid** ranges
  239. // TODO: expose _read_kv() and _cursor_reset_end() so we can have 'break' here?
  240. // perhaps as a wrapper object, allow something like next() and seekBegin()
  241. template <typename CallbackType>
  242. void foreach(CallbackType callback) {
  243. _cursor_reset_end();
  244. do {
  245. auto kv = _read_kv();
  246. if (!kv) {
  247. break;
  248. }
  249. callback(std::move(kv));
  250. } while (_state != State::End);
  251. }
  252. // read every key into a vector
  253. std::vector<String> keys() {
  254. std::vector<String> out;
  255. out.reserve(count());
  256. foreach([&](KeyValueResult&& kv) {
  257. out.push_back(kv.key.read());
  258. });
  259. return out;
  260. }
  261. // set or update key with value contents. ensure 'key' isn't empty, 'value' can be empty
  262. bool set(const String& key, const String& value) {
  263. // ref. 'estimate()' implementation in regards to the storage calculation
  264. auto need = estimate(key, value);
  265. if (!need) {
  266. return false;
  267. }
  268. auto key_len = key.length();
  269. auto value_len = value.length();
  270. Cursor to_erase(_storage);
  271. bool need_erase = false;
  272. auto start_pos = _cursor_reset_end();
  273. do {
  274. auto kv = _read_kv();
  275. if (!kv) {
  276. break;
  277. }
  278. start_pos = kv.value.cursor.begin;
  279. // in the very special case we can match the existing key
  280. if ((kv.key.length == key_len) && (kv.key.read() == key)) {
  281. if (kv.value.length == value.length()) {
  282. if (kv.value.read() == value) {
  283. return true;
  284. }
  285. start_pos = kv.key.cursor.end;
  286. break;
  287. }
  288. // but we may need to write over it, when contents are different
  289. to_erase.reset(kv.value.cursor.begin, kv.key.cursor.end);
  290. need_erase = true;
  291. }
  292. } while (_state != State::End);
  293. if (need_erase) {
  294. _raw_erase(start_pos, to_erase);
  295. start_pos += to_erase.size();
  296. }
  297. // we should only insert when possition is still within possible size
  298. if (start_pos && (start_pos >= need)) {
  299. auto writer = Cursor::fromEnd(_storage, start_pos - need, start_pos);
  300. // put the length of the value as 2 bytes and then write the data
  301. (--writer).write(key_len & 0xff);
  302. (--writer).write((key_len >> 8) & 0xff);
  303. while (key_len--) {
  304. (--writer).write(key[key_len]);
  305. }
  306. (--writer).write(value_len & 0xff);
  307. (--writer).write((value_len >> 8) & 0xff);
  308. if (value_len) {
  309. while (value_len--) {
  310. (--writer).write(value[value_len]);
  311. }
  312. } else {
  313. (--writer).write(0);
  314. (--writer).write(0);
  315. }
  316. // we also need to pad the space *after* the value
  317. // but, only when we still have some space left
  318. if ((start_pos - need) >= 2) {
  319. _cursor_set_position(writer.begin - _cursor.begin);
  320. auto next_kv = _read_kv();
  321. if (!next_kv) {
  322. auto padding = Cursor::fromEnd(_storage, writer.begin - 2, writer.begin);
  323. (--padding).write(0);
  324. (--padding).write(0);
  325. }
  326. }
  327. _storage.commit();
  328. return true;
  329. }
  330. return false;
  331. }
  332. // remove key from the storage. will check that 'key' argument isn't empty
  333. bool del(const String& key) {
  334. size_t key_len = key.length();
  335. if (!key_len) {
  336. return false;
  337. }
  338. // Removes key from the storage by overwriting the key with left-most data
  339. size_t start_pos = _cursor_reset_end() - 1;
  340. auto to_erase = Cursor::fromEnd(_storage, _cursor.begin, _cursor.end);
  341. // we should only compare strings of equal length.
  342. // when matching, record { value ... key } range + 4 bytes for length
  343. // continue searching for the leftmost boundary
  344. foreach([&](KeyValueResult&& kv) {
  345. start_pos = kv.value.cursor.begin;
  346. if (!to_erase && (kv.key.length == key_len) && (kv.key.read() == key)) {
  347. to_erase.reset(kv.value.cursor.begin, kv.key.cursor.end);
  348. }
  349. });
  350. if (!to_erase) {
  351. return false;
  352. }
  353. _raw_erase(start_pos, to_erase);
  354. return true;
  355. }
  356. // Simply count key-value pairs that we could parse
  357. size_t count() {
  358. size_t result = 0;
  359. foreach([&result](KeyValueResult&&) {
  360. ++result;
  361. });
  362. return result;
  363. }
  364. // Do exactly the same thing as 'keys' does, but return the amount
  365. // of bytes to the left of the last kv
  366. size_t available() {
  367. size_t result = _cursor.size();
  368. foreach([&result](KeyValueResult&& kv) {
  369. result -= kv.key.cursor.size();
  370. result -= kv.value.cursor.size();
  371. });
  372. return result;
  373. }
  374. // How much bytes can be used is directly read from the cursor, based on begin and end values
  375. size_t size() {
  376. return _cursor.size();
  377. }
  378. protected:
  379. // Try to find the matching key. Datastructure that we use does not specify
  380. // any value 'type' inside of it. We expect 'key' to be the first non-empty string,
  381. // 'value' can be empty.
  382. // To implement has(), allow to skip reading the value
  383. ValueResult _get(const String& key, bool read_value) {
  384. ValueResult out;
  385. auto len = key.length();
  386. _cursor_reset_end();
  387. do {
  388. auto kv = _read_kv();
  389. if (!kv) {
  390. break;
  391. }
  392. // no point in comparing keys when length does not match
  393. // (and we also don't want to allocate the string)
  394. if (kv.key.length != len) {
  395. continue;
  396. }
  397. auto key_result = kv.key.read();
  398. if (key_result == key) {
  399. if (read_value) {
  400. out.value = kv.value.read();
  401. }
  402. out.result = true;
  403. break;
  404. }
  405. } while (_state != State::End);
  406. return out;
  407. }
  408. // Place cursor at the `end` and resets the parser to expect length byte
  409. uint16_t _cursor_reset_end() {
  410. _cursor.resetEnd();
  411. _state = State::Begin;
  412. return _cursor.end;
  413. }
  414. uint16_t _cursor_set_position(uint16_t position) {
  415. _state = State::Begin;
  416. _cursor.position = position;
  417. return _cursor.position;
  418. }
  419. // implementation quirk is that `Cursor::operator=` won't work because of the `RawStorageBase&` member
  420. // right now, just construct in place and assume that compiler will inline things
  421. // on one hand, we can implement it. but, we can't specifically
  422. KeyValueResult _read_kv() {
  423. auto key = _raw_read();
  424. if (!key || !key.length) {
  425. return {_storage};
  426. }
  427. auto value = _raw_read();
  428. return {key, value};
  429. };
  430. void _raw_erase(size_t start_pos, Cursor& to_erase) {
  431. // we either end up to the left or to the right of the boundary
  432. if (start_pos < to_erase.begin) {
  433. auto from = Cursor::fromEnd(_storage, start_pos, to_erase.begin);
  434. auto to = Cursor::fromEnd(_storage, start_pos + to_erase.size(), to_erase.end);
  435. while (--from && --to) {
  436. to.write(from.read());
  437. from.write(0xff);
  438. };
  439. } else {
  440. // just null the length bytes, since we at the last key
  441. to_erase.resetEnd();
  442. (--to_erase).write(0);
  443. (--to_erase).write(0);
  444. }
  445. _storage.commit();
  446. }
  447. // Returns Cursor to the region that holds the data
  448. // Result object does not hold any data, we need to explicitly request read()
  449. //
  450. // Cursor object is always expected to point to something, e.g. minimum:
  451. // 0x01 0x00 0x01
  452. // data len2 len1
  453. // Position will be 0, end will be 4. Total length is 3, data length is 1
  454. //
  455. // Note the distinction between real length and data length. For example,
  456. // special-case for empty 'value' (as 'key' can never be empty and will be rejected):
  457. // 0x00 0x00 0x00 0x00
  458. // data data len2 len1
  459. // Position will be 0, end will be 5. Total length is 4, data length is 0
  460. ReadResult _raw_read() {
  461. uint16_t len = 0;
  462. ReadResult out(_storage);
  463. do {
  464. // storage is written right-to-left, cursor is always decreasing
  465. switch (_state) {
  466. case State::Begin:
  467. if (_cursor.position > 2) {
  468. --_cursor;
  469. _state = State::LenByte1;
  470. } else {
  471. _state = State::End;
  472. }
  473. continue;
  474. // len is 16 bit uint (bigendian)
  475. // special case is 0, which is valid and should be returned when encountered
  476. // another special case is 0xffff, meaning we just hit an empty space
  477. case State::LenByte1:
  478. len = _cursor.read();
  479. --_cursor;
  480. _state = State::LenByte2;
  481. break;
  482. case State::LenByte2:
  483. {
  484. uint8_t len2 = _cursor.read();
  485. --_cursor;
  486. if ((0 == len) && (0 == len2)) {
  487. len = 2;
  488. _state = State::EmptyValue;
  489. } else if ((0xff == len) && (0xff == len2)) {
  490. _state = State::End;
  491. } else {
  492. len |= len2 << 8;
  493. _state = State::Value;
  494. }
  495. break;
  496. }
  497. case State::EmptyValue:
  498. case State::Value: {
  499. uint16_t left = len;
  500. // ensure we don't go out-of-bounds
  501. switch (_state) {
  502. case State::Value:
  503. while (_cursor && --left) {
  504. --_cursor;
  505. }
  506. break;
  507. // ...and only read 0's
  508. case State::EmptyValue:
  509. while (_cursor && (_cursor.read() == 0) && --left) {
  510. --_cursor;
  511. }
  512. break;
  513. default:
  514. break;
  515. }
  516. if (left) {
  517. _state = State::End;
  518. break;
  519. }
  520. // set the resulting cursor as [pos:len+2)
  521. out.result = true;
  522. out.length = (_state == State::EmptyValue) ? 0 : len;
  523. out.cursor.reset(
  524. _cursor.begin + _cursor.position,
  525. _cursor.begin + _cursor.position + len + 2
  526. );
  527. _state = State::Begin;
  528. goto return_result;
  529. }
  530. case State::End:
  531. default:
  532. break;
  533. }
  534. } while (_state != State::End);
  535. return_result:
  536. return out;
  537. }
  538. RawStorageBase _storage;
  539. Cursor _cursor;
  540. State _state { State::Begin };
  541. };
  542. } // namespace embedis
  543. } // namespace settings