#include "FlashStore.h" #include #include #include "SmartResponseXE.h" void SPI_Init(void); namespace { const uint8_t FLASH_CS = 0xD3; } FlashStore::FlashStore() : _next_page(0), _count(0), _next_sequence(1), _sent_count(0), _recv_count(0), _latest_timestamp(0), _total_lines(0), _blank_pages(0), _invalid_pages(0), _physical_breaks(0) { memset(&_record_scratch, 0, sizeof(_record_scratch)); memset(_pages, 0, sizeof(_pages)); memset(_sequences, 0, sizeof(_sequences)); } void FlashStore::resetStats() { _next_page = 0; _count = 0; _next_sequence = 1; _sent_count = 0; _recv_count = 0; _latest_timestamp = 0; _total_lines = 0; _blank_pages = 0; _invalid_pages = 0; _physical_breaks = 0; memset(_pages, 0, sizeof(_pages)); memset(_sequences, 0, sizeof(_sequences)); } void FlashStore::begin() { SPI_Init(); mypinMode(FLASH_CS, OUTPUT); mydigitalWrite(FLASH_CS, HIGH); resetStats(); uint32_t max_sequence = 0; uint16_t max_page = 0; MessageRecord current; for (uint16_t page = 0; page < kPageCount; ++page) { if (!readRecord(page, current)) { continue; } if (isBlank((const uint8_t*)¤t, sizeof(current))) { ++_blank_pages; continue; } if (!isValidRecord(current)) { ++_invalid_pages; continue; } if (_count == 0 || current.sequence > max_sequence) { max_sequence = current.sequence; max_page = page; } insertPageSorted(page, current.sequence); if (current.timestamp > _latest_timestamp) { _latest_timestamp = current.timestamp; } _total_lines += current.line_count ? current.line_count : 1; if (current.flags & kFlagOutbound) { ++_sent_count; } else { ++_recv_count; } } if (_count > 0) { _next_page = findNextBlankPage((uint16_t)((max_page + 1) % kPageCount)); _next_sequence = max_sequence + 1; updatePhysicalBreaks(); } } uint16_t FlashStore::crc16(const uint8_t* data, size_t len) { uint16_t crc = 0xFFFFu; for (size_t i = 0; i < len; ++i) { crc ^= (uint16_t)data[i] << 8; for (uint8_t bit = 0; bit < 8; ++bit) { crc = (crc & 0x8000u) ? (uint16_t)((crc << 1) ^ 0x1021u) : (uint16_t)(crc << 1); } } return crc; } bool FlashStore::isBlank(const uint8_t* data, size_t len) { for (size_t i = 0; i < len; ++i) { if (data[i] != 0xFFu) { return false; } } return true; } bool FlashStore::isValidRecord(const MessageRecord& record) { if (isBlank((const uint8_t*)&record, sizeof(record))) { return false; } if (record.magic != kMagic || record.version != 2) { return false; } if (record.line_count == 0 || record.line_count > 8) { return false; } uint16_t expected = crc16((const uint8_t*)&record.sequence, sizeof(record) - offsetof(MessageRecord, sequence)); return expected == record.crc; } PublicMessage FlashStore::messageFrom(const MessageRecord& record) { PublicMessage message; memset(&message, 0, sizeof(message)); message.sequence = record.sequence; message.timestamp = record.timestamp; message.outbound = (record.flags & kFlagOutbound) != 0; memcpy(message.hash, record.hash, sizeof(message.hash)); strncpy(message.text, record.text, sizeof(message.text) - 1); message.text[sizeof(message.text) - 1] = 0; return message; } bool FlashStore::readRecord(uint16_t page, MessageRecord& record) const { if (page >= kPageCount) { return false; } SPI_Init(); int ok = SRXEFlashRead(kBaseAddr + ((uint32_t)page * kPageSize), (uint8_t*)&record, sizeof(record)); return ok == 1; } void FlashStore::insertPageSorted(uint16_t page, uint32_t sequence) { if (_count >= kPageCount) { return; } uint16_t pos = _count; while (pos > 0 && sequence < _sequences[pos - 1]) { _pages[pos] = _pages[pos - 1]; _sequences[pos] = _sequences[pos - 1]; --pos; } _pages[pos] = page; _sequences[pos] = sequence; ++_count; } void FlashStore::removePageAt(uint16_t idx) { if (idx >= _count) { return; } for (uint16_t i = idx; i + 1 < _count; ++i) { _pages[i] = _pages[i + 1]; _sequences[i] = _sequences[i + 1]; } --_count; _pages[_count] = 0; _sequences[_count] = 0; } void FlashStore::updatePhysicalBreaks() { _physical_breaks = 0; if (_count < 2) { return; } for (uint16_t i = 1; i < _count; ++i) { uint16_t expected = (uint16_t)((_pages[i - 1] + 1) % kPageCount); if (_pages[i] != expected) { ++_physical_breaks; } } } uint16_t FlashStore::findNextBlankPage(uint16_t start_page) const { MessageRecord record; for (uint16_t offset = 0; offset < kPageCount; ++offset) { uint16_t page = (uint16_t)((start_page + offset) % kPageCount); if (readRecord(page, record) && isBlank((const uint8_t*)&record, sizeof(record))) { return page; } } return start_page; } uint16_t FlashStore::pageForIndex(uint16_t idx) const { return (idx < _count) ? _pages[idx] : kPageCount; } bool FlashStore::pageInUse(uint16_t page) const { if (_count == 0 || page >= kPageCount) { return false; } for (uint16_t i = 0; i < _count; ++i) { if (_pages[i] == page) { return true; } } return false; } bool FlashStore::sectorHasActivePages(uint16_t sector) const { uint16_t sector_start = (uint16_t)(sector * kPagesPerSector); for (uint8_t i = 0; i < kPagesPerSector; ++i) { if (pageInUse((uint16_t)(sector_start + i))) { return true; } } return false; } bool FlashStore::writeRecord(uint16_t page, const MessageRecord& record) { SPI_Init(); int ok = SRXEFlashWritePage(kBaseAddr + ((uint32_t)page * kPageSize), (uint8_t*)(uintptr_t)&record); return ok == 1; } bool FlashStore::eraseSector(uint16_t sector) { SPI_Init(); int ok = SRXEFlashEraseSector(kBaseAddr + ((uint32_t)sector * kSectorSize), 1); return ok == 1; } bool FlashStore::prepareNextPageForWrite() { MessageRecord record; for (uint16_t offset = 0; offset < kPageCount; ++offset) { uint16_t page = (uint16_t)((_next_page + offset) % kPageCount); if (!readRecord(page, record)) { return false; } if (isBlank((const uint8_t*)&record, sizeof(record))) { _next_page = page; return true; } if ((page % kPagesPerSector) == 0) { uint16_t sector = (uint16_t)(page / kPagesPerSector); bool overlaps_active = sectorHasActivePages(sector); if (overlaps_active) { dropSectorFromStats(sector); } if (!eraseSector(sector)) { return false; } _blank_pages = (uint16_t)(_blank_pages + kPagesPerSector); _next_page = page; return true; } } return false; } void FlashStore::dropSectorFromStats(uint16_t sector) { MessageRecord record; uint16_t sector_start = (uint16_t)(sector * kPagesPerSector); uint16_t idx = 0; while (idx < _count) { uint16_t page = _pages[idx]; if (page < sector_start || page >= sector_start + kPagesPerSector) { ++idx; continue; } if (!readRecord(page, record) || !isValidRecord(record)) { removePageAt(idx); updatePhysicalBreaks(); continue; } if (_total_lines >= record.line_count) { _total_lines -= record.line_count; } else { _total_lines = 0; } if (record.flags & kFlagOutbound) { if (_sent_count > 0) { --_sent_count; } } else if (_recv_count > 0) { --_recv_count; } removePageAt(idx); updatePhysicalBreaks(); } } bool FlashStore::getMessage(uint16_t idx, PublicMessage& message) const { if (idx >= _count) { return false; } MessageRecord record; if (!readRecord(pageForIndex(idx), record) || !isValidRecord(record)) { return false; } message = messageFrom(record); return true; } bool FlashStore::appendMessage(uint32_t timestamp, const char* text, bool outbound, const uint8_t* hash, bool rebroadcasted) { (void)rebroadcasted; if (!prepareNextPageForWrite()) { return false; } MessageRecord& record = _record_scratch; memset(&record, 0xFF, sizeof(record)); record.magic = kMagic; record.version = 2; record.sequence = _next_sequence; record.timestamp = timestamp; record.flags = outbound ? kFlagOutbound : 0; record.line_count = wrappedLineCount(text); if (hash) { memcpy(record.hash, hash, sizeof(record.hash)); } else { memset(record.hash, 0, sizeof(record.hash)); } strncpy(record.text, text ? text : "", sizeof(record.text) - 1); record.text[sizeof(record.text) - 1] = 0; record.crc = crc16((const uint8_t*)&record.sequence, sizeof(record) - offsetof(MessageRecord, sequence)); if (!writeRecord(_next_page, record)) { return false; } insertPageSorted(_next_page, record.sequence); if (_blank_pages > 0) { --_blank_pages; } updatePhysicalBreaks(); if (record.timestamp > _latest_timestamp) { _latest_timestamp = record.timestamp; } _total_lines += record.line_count; if (outbound) { ++_sent_count; } else { ++_recv_count; } _next_page = (uint16_t)((_next_page + 1) % kPageCount); ++_next_sequence; return true; }