From 8c372c3cb097193b392ccafccaa35e6a309684aa Mon Sep 17 00:00:00 2001 From: Dr Jones Date: Sun, 3 May 2026 23:20:16 -0700 Subject: [PATCH] Initial commit: project docs and ignore rules --- .gitignore | 3 + README.md | 23 ++++ error_handler.cpp | 302 +++++++++++++++++++++++++++++++++++++++++ error_handler.h | 104 ++++++++++++++ memory_manager.cpp | 327 +++++++++++++++++++++++++++++++++++++++++++++ memory_manager.h | 282 ++++++++++++++++++++++++++++++++++++++ nfc_protocol.cpp | 69 ++++++++++ nfc_protocol.h | 67 ++++++++++ 8 files changed, 1177 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 error_handler.cpp create mode 100644 error_handler.h create mode 100644 memory_manager.cpp create mode 100644 memory_manager.h create mode 100644 nfc_protocol.cpp create mode 100644 nfc_protocol.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81db064 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +*.o +*.a diff --git a/README.md b/README.md new file mode 100644 index 0000000..53726cc --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# common (shared NFC helpers) + +Shared C++ sources for the **dual PN532 ESP-NOW** system described in the [parent README](../README.md): protocol definitions, error handling, memory helpers, and other code consumed by `listener_firmware/` and `emulator_firmware/`. + +## Usage + +Firmware sketches include headers via paths such as: + +```cpp +#include "../../common/nfc_protocol.h" +``` + +Keep this folder versioned **together** with: + +- `listener_firmware/` +- `emulator_firmware/` +- Parent `platformio.ini` (workspace root) + +Splitting into a separate remote is easiest if these three become submodules of a single meta-repo, or you refactor include paths. + +## Tests + +Unit tests that exercise parts of this tree live in [`tests/`](../tests/). diff --git a/error_handler.cpp b/error_handler.cpp new file mode 100644 index 0000000..81afc66 --- /dev/null +++ b/error_handler.cpp @@ -0,0 +1,302 @@ +#include "error_handler.h" +#include + +#ifdef ARDUINO +#include +#define SERIAL_PRINT(x) Serial.print(x) +#define SERIAL_PRINTLN(x) Serial.println(x) +#define GET_MILLIS() millis() +#define DELAY_MS(x) delay(x) +#else +#include +#include +#include +#include +#define SERIAL_PRINT(x) printf("%s", (x)) +#define SERIAL_PRINTLN(x) printf("%s\n", (x)) +#define GET_MILLIS() (static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count())) +#define DELAY_MS(x) std::this_thread::sleep_for(std::chrono::milliseconds(x)) +#endif + +// Global error handler instance +ErrorHandler errorHandler; + +ErrorHandler::ErrorHandler() : error_index(0), logging_enabled(true) { + memset(&stats, 0, sizeof(stats)); + memset(error_history, 0, sizeof(error_history)); +} + +void ErrorHandler::reportError(ErrorCode code, ErrorSeverity severity, const char* description) { + // Update statistics + stats.total_errors++; + stats.last_error_time = GET_MILLIS(); + stats.last_error_code = code; + + if (severity == SEVERITY_CRITICAL) { + stats.critical_errors++; + } + + // Categorize errors + switch (code) { + case ERR_NFC_INIT_FAILED: + case ERR_NFC_READ_FAILED: + stats.nfc_errors++; + break; + case ERR_COMMUNICATION_TIMEOUT: + case ERR_PAIRING_FAILED: + case ERR_INVALID_MESSAGE: + stats.communication_errors++; + break; + case ERR_SD_INIT_FAILED: + case ERR_SD_WRITE_FAILED: + stats.sd_errors++; + break; + default: + break; + } + + // Store in history + ErrorEntry& entry = error_history[error_index]; + entry.code = code; + entry.severity = severity; + entry.timestamp = GET_MILLIS(); + + if (description) { + strncpy(entry.description, description, sizeof(entry.description) - 1); + entry.description[sizeof(entry.description) - 1] = '\0'; + } else { + entry.description[0] = '\0'; + } + + error_index = (error_index + 1) % MAX_ERROR_HISTORY; + + // Log the error + if (logging_enabled) { +#ifdef ARDUINO + Serial.print("[ERROR] Code: "); + Serial.print(static_cast(code)); + Serial.print(", Severity: "); + Serial.print(static_cast(severity)); + Serial.print(", Time: "); + Serial.print(entry.timestamp); + if (description) { + Serial.print(", Desc: "); + Serial.print(description); + } + Serial.println(); +#else + printf("[ERROR] Code: %d, Severity: %d, Time: %lu", + static_cast(code), static_cast(severity), entry.timestamp); + if (description) { + printf(", Desc: %s", description); + } + printf("\n"); +#endif + } + + // Attempt recovery for non-critical errors + if (severity < SEVERITY_CRITICAL) { + handleError(code); + } +} + +void ErrorHandler::reportNFCError(const char* description) { + reportError(ERR_NFC_READ_FAILED, SEVERITY_ERROR, description); +} + +void ErrorHandler::reportCommunicationError(const char* description) { + reportError(ERR_COMMUNICATION_TIMEOUT, SEVERITY_WARNING, description); +} + +void ErrorHandler::reportSDError(const char* description) { + reportError(ERR_SD_WRITE_FAILED, SEVERITY_WARNING, description); +} + +bool ErrorHandler::handleError(ErrorCode code) { + switch (code) { + case ERR_NFC_READ_FAILED: + return attemptNFCRecovery(); + case ERR_COMMUNICATION_TIMEOUT: + case ERR_PAIRING_FAILED: + return attemptCommunicationRecovery(); + case ERR_SD_WRITE_FAILED: + return attemptSDRecovery(); + default: + return false; + } +} + +bool ErrorHandler::attemptNFCRecovery() { + if (logging_enabled) { +#ifdef ARDUINO + Serial.println("[RECOVERY] Attempting NFC recovery..."); +#else + printf("[RECOVERY] Attempting NFC recovery...\n"); +#endif + } + + DELAY_MS(100); + + // TODO: Implement actual NFC recovery logic + return true; // Assume success for now +} + +bool ErrorHandler::attemptCommunicationRecovery() { + if (logging_enabled) { +#ifdef ARDUINO + Serial.println("[RECOVERY] Attempting communication recovery..."); +#else + printf("[RECOVERY] Attempting communication recovery...\n"); +#endif + } + + DELAY_MS(500); + + return true; // Assume success for now +} + +bool ErrorHandler::attemptSDRecovery() { + if (logging_enabled) { +#ifdef ARDUINO + Serial.println("[RECOVERY] Attempting SD card recovery..."); +#else + printf("[RECOVERY] Attempting SD card recovery...\n"); +#endif + } + + DELAY_MS(200); + + return true; // Assume success for now +} + +void ErrorHandler::clearErrors() { + memset(&stats, 0, sizeof(stats)); + memset(error_history, 0, sizeof(error_history)); + error_index = 0; + + if (logging_enabled) { +#ifdef ARDUINO + Serial.println("[INFO] Error history cleared"); +#else + printf("[INFO] Error history cleared\n"); +#endif + } +} + +ErrorStats ErrorHandler::getStats() const { + return stats; +} + +bool ErrorHandler::hasRecentErrors(unsigned long time_window_ms) const { + unsigned long current_time = GET_MILLIS(); + return (current_time - stats.last_error_time) < time_window_ms; +} + +bool ErrorHandler::hasCriticalErrors() const { + return stats.critical_errors > 0; +} + +void ErrorHandler::printErrorHistory() const { + if (!logging_enabled) return; + +#ifdef ARDUINO + Serial.println("=== Error History ==="); + for (int i = 0; i < MAX_ERROR_HISTORY; i++) { + int idx = (error_index + i) % MAX_ERROR_HISTORY; + const ErrorEntry& entry = error_history[idx]; + + if (entry.timestamp == 0) continue; // Empty entry + + Serial.print("["); + Serial.print(i + 1); + Serial.print("] Code: "); + Serial.print(static_cast(entry.code)); + Serial.print(", Severity: "); + Serial.print(static_cast(entry.severity)); + Serial.print(", Time: "); + Serial.print(entry.timestamp); + if (entry.description[0] != '\0') { + Serial.print(", Desc: "); + Serial.print(entry.description); + } + Serial.println(); + } + Serial.println("==================="); +#else + printf("=== Error History ===\n"); + for (int i = 0; i < MAX_ERROR_HISTORY; i++) { + int idx = (error_index + i) % MAX_ERROR_HISTORY; + const ErrorEntry& entry = error_history[idx]; + + if (entry.timestamp == 0) continue; // Empty entry + + printf("[%d] Code: %d, Severity: %d, Time: %lu", + i + 1, static_cast(entry.code), static_cast(entry.severity), entry.timestamp); + if (entry.description[0] != '\0') { + printf(", Desc: %s", entry.description); + } + printf("\n"); + } + printf("===================\n"); +#endif +} + +void ErrorHandler::printStats() const { + if (!logging_enabled) return; + +#ifdef ARDUINO + Serial.println("=== Error Statistics ==="); + Serial.print("Total Errors: "); + Serial.println(stats.total_errors); + Serial.print("Critical Errors: "); + Serial.println(stats.critical_errors); + Serial.print("NFC Errors: "); + Serial.println(stats.nfc_errors); + Serial.print("Communication Errors: "); + Serial.println(stats.communication_errors); + Serial.print("SD Errors: "); + Serial.println(stats.sd_errors); + Serial.print("Last Error Time: "); + Serial.println(stats.last_error_time); + Serial.print("Last Error Code: "); + Serial.println(static_cast(stats.last_error_code)); + Serial.println("========================"); +#else + printf("=== Error Statistics ===\n"); + printf("Total Errors: %u\n", stats.total_errors); + printf("Critical Errors: %u\n", stats.critical_errors); + printf("NFC Errors: %u\n", stats.nfc_errors); + printf("Communication Errors: %u\n", stats.communication_errors); + printf("SD Errors: %u\n", stats.sd_errors); + printf("Last Error Time: %u\n", stats.last_error_time); + printf("Last Error Code: %d\n", static_cast(stats.last_error_code)); + printf("========================\n"); +#endif +} + +void ErrorHandler::enableLogging(bool enable) { + logging_enabled = enable; +} + +void ErrorHandler::feedWatchdog() { + // TODO: Implement watchdog feeding logic +} + +bool ErrorHandler::isSystemHealthy() const { + // Check for critical errors + if (hasCriticalErrors()) { + return false; + } + + // Check for recent errors (within 30 seconds) + if (hasRecentErrors(30000)) { + return false; + } + + // Check total error count threshold + if (stats.total_errors > 10) { + return false; + } + + return true; +} \ No newline at end of file diff --git a/error_handler.h b/error_handler.h new file mode 100644 index 0000000..6ef8c04 --- /dev/null +++ b/error_handler.h @@ -0,0 +1,104 @@ +#ifndef ERROR_HANDLER_H +#define ERROR_HANDLER_H + +#include +#ifdef ARDUINO +#include +#else +#include +#endif + +// Error codes +enum ErrorCode { + ERR_NONE = 0, + ERR_NFC_INIT_FAILED = 1, + ERR_ESPNOW_INIT_FAILED = 2, + ERR_SD_INIT_FAILED = 3, + ERR_COMMUNICATION_TIMEOUT = 4, + ERR_INVALID_MESSAGE = 5, + ERR_PAIRING_FAILED = 6, + ERR_NFC_READ_FAILED = 7, + ERR_SD_WRITE_FAILED = 8, + ERR_MEMORY_ALLOCATION = 9, + ERR_HARDWARE_FAULT = 10 +}; + +// Error severity levels +enum ErrorSeverity { + SEVERITY_INFO = 0, + SEVERITY_WARNING = 1, + SEVERITY_ERROR = 2, + SEVERITY_CRITICAL = 3 +}; + +// Error entry structure +struct ErrorEntry { + ErrorCode code; + ErrorSeverity severity; + unsigned long timestamp; + char description[64]; +}; + +// Error statistics +struct ErrorStats { + uint32_t total_errors; + uint32_t critical_errors; + uint32_t last_error_time; + ErrorCode last_error_code; + uint32_t nfc_errors; + uint32_t communication_errors; + uint32_t sd_errors; +}; + +class ErrorHandler { +private: + static const int MAX_ERROR_HISTORY = 10; + ErrorEntry error_history[MAX_ERROR_HISTORY]; + int error_index; + ErrorStats stats; + bool logging_enabled; + +public: + ErrorHandler(); + + // Error reporting + void reportError(ErrorCode code, ErrorSeverity severity, const char* description = nullptr); + void reportNFCError(const char* description = nullptr); + void reportCommunicationError(const char* description = nullptr); + void reportSDError(const char* description = nullptr); + + // Error handling + bool handleError(ErrorCode code); + void clearErrors(); + + // Recovery mechanisms + bool attemptNFCRecovery(); + bool attemptCommunicationRecovery(); + bool attemptSDRecovery(); + + // Status and statistics + ErrorStats getStats() const; + bool hasRecentErrors(unsigned long time_window_ms = 5000) const; + bool hasCriticalErrors() const; + void printErrorHistory() const; + void printStats() const; + + // Configuration + void enableLogging(bool enable); + void setMaxRetries(int retries); + + // Watchdog functionality + void feedWatchdog(); + bool isSystemHealthy() const; +}; + +// Global error handler instance +extern ErrorHandler errorHandler; + +// Convenience macros +#define REPORT_ERROR(code, severity, desc) errorHandler.reportError(code, severity, desc) +#define REPORT_NFC_ERROR(desc) errorHandler.reportNFCError(desc) +#define REPORT_COMM_ERROR(desc) errorHandler.reportCommunicationError(desc) +#define REPORT_SD_ERROR(desc) errorHandler.reportSDError(desc) + +#endif // ERROR_HANDLER_H \ No newline at end of file diff --git a/memory_manager.cpp b/memory_manager.cpp new file mode 100644 index 0000000..90d7ce7 --- /dev/null +++ b/memory_manager.cpp @@ -0,0 +1,327 @@ +#include "memory_manager.h" +#include + +#ifdef ARDUINO +#include +#define DEBUG_PRINT(x) Serial.print(x) +#define DEBUG_PRINTLN(x) Serial.println(x) +#else +#include +#define DEBUG_PRINT(x) printf("%s", (x)) +#define DEBUG_PRINTLN(x) printf("%s\n", (x)) +#endif + +// Global memory pool instance +MemoryPool globalMemoryPool; + +MemoryPool::MemoryPool() : next_free_offset(0), allocation_count(0), deallocation_count(0) { + // Initialize all blocks as unused + for (size_t i = 0; i < MAX_BLOCKS; ++i) { + blocks[i].ptr = nullptr; + blocks[i].size = 0; + blocks[i].in_use = false; + blocks[i].magic = 0; + } + + // Clear the memory pool + memset(pool_memory, 0, POOL_SIZE); +} + +MemoryPool::~MemoryPool() { + // Check for memory leaks + uint32_t active_blocks = 0; + for (size_t i = 0; i < MAX_BLOCKS; ++i) { + if (blocks[i].in_use) { + active_blocks++; + } + } + + if (active_blocks > 0) { +#ifdef DEBUG_MEMORY + DEBUG_PRINT("WARNING: Memory pool destroyed with "); + DEBUG_PRINT(active_blocks); + DEBUG_PRINTLN(" active blocks!"); +#endif + } +} + +void* MemoryPool::allocate(size_t size) { + if (size == 0 || size > POOL_SIZE) { + return nullptr; + } + + // Align size to 4-byte boundary for better performance + size = (size + 3) & ~3; + + // Check if we have enough space + if (next_free_offset + size > POOL_SIZE) { + // Try to find a free block that was previously deallocated + for (size_t i = 0; i < MAX_BLOCKS; ++i) { + if (!blocks[i].in_use && blocks[i].ptr != nullptr && blocks[i].size >= size) { + blocks[i].in_use = true; + blocks[i].magic = MAGIC_NUMBER; + allocation_count++; + return blocks[i].ptr; + } + } + return nullptr; // Out of memory + } + + // Find a free block descriptor + size_t block_index = MAX_BLOCKS; + for (size_t i = 0; i < MAX_BLOCKS; ++i) { + if (!blocks[i].in_use && blocks[i].ptr == nullptr) { + block_index = i; + break; + } + } + + if (block_index == MAX_BLOCKS) { + return nullptr; // No free block descriptors + } + + // Allocate from the pool + void* ptr = &pool_memory[next_free_offset]; + + // Set up the block descriptor + blocks[block_index].ptr = ptr; + blocks[block_index].size = size; + blocks[block_index].in_use = true; + blocks[block_index].magic = MAGIC_NUMBER; + + next_free_offset += size; + allocation_count++; + + return ptr; +} + +bool MemoryPool::deallocate(void* ptr) { + if (ptr == nullptr) { + return false; + } + + // Find the block + for (size_t i = 0; i < MAX_BLOCKS; ++i) { + if (blocks[i].ptr == ptr && blocks[i].in_use) { + // Check magic number for corruption + if (blocks[i].magic != MAGIC_NUMBER) { +#ifdef DEBUG_MEMORY + DEBUG_PRINTLN("ERROR: Memory corruption detected during deallocation!"); +#endif + return false; + } + + blocks[i].in_use = false; + blocks[i].magic = 0; + deallocation_count++; + + // Clear the memory for security + memset(ptr, 0, blocks[i].size); + + return true; + } + } + + return false; // Pointer not found +} + +MemoryPool::MemoryStats MemoryPool::getStats() const { + MemoryStats stats; + stats.total_size = POOL_SIZE; + stats.allocations = allocation_count; + stats.deallocations = deallocation_count; + stats.active_blocks = 0; + stats.used_size = 0; + + for (size_t i = 0; i < MAX_BLOCKS; ++i) { + if (blocks[i].in_use) { + stats.active_blocks++; + stats.used_size += blocks[i].size; + } + } + + stats.free_size = POOL_SIZE - stats.used_size; + + return stats; +} + +bool MemoryPool::checkIntegrity() const { + for (size_t i = 0; i < MAX_BLOCKS; ++i) { + if (blocks[i].in_use && blocks[i].magic != MAGIC_NUMBER) { + return false; + } + } + return true; +} + +void MemoryPool::reset() { + // Clear all blocks + for (size_t i = 0; i < MAX_BLOCKS; ++i) { + blocks[i].ptr = nullptr; + blocks[i].size = 0; + blocks[i].in_use = false; + blocks[i].magic = 0; + } + + next_free_offset = 0; + allocation_count = 0; + deallocation_count = 0; + + // Clear the memory pool + memset(pool_memory, 0, POOL_SIZE); +} + +void MemoryPool::printStats() const { + MemoryStats stats = getStats(); + +#ifdef ARDUINO + Serial.println("=== Memory Pool Statistics ==="); + Serial.print("Total Size: "); + Serial.print(stats.total_size); + Serial.println(" bytes"); + Serial.print("Used Size: "); + Serial.print(stats.used_size); + Serial.println(" bytes"); + Serial.print("Free Size: "); + Serial.print(stats.free_size); + Serial.println(" bytes"); + Serial.print("Active Blocks: "); + Serial.println(stats.active_blocks); + Serial.print("Total Allocations: "); + Serial.println(stats.allocations); + Serial.print("Total Deallocations: "); + Serial.println(stats.deallocations); + Serial.print("Memory Integrity: "); + Serial.println(checkIntegrity() ? "OK" : "CORRUPTED"); + Serial.println("=============================="); +#else + printf("=== Memory Pool Statistics ===\n"); + printf("Total Size: %zu bytes\n", stats.total_size); + printf("Used Size: %zu bytes\n", stats.used_size); + printf("Free Size: %zu bytes\n", stats.free_size); + printf("Active Blocks: %u\n", stats.active_blocks); + printf("Total Allocations: %u\n", stats.allocations); + printf("Total Deallocations: %u\n", stats.deallocations); + printf("Memory Integrity: %s\n", checkIntegrity() ? "OK" : "CORRUPTED"); + printf("==============================\n"); +#endif +} + +// SafeBuffer implementation +SafeBuffer::SafeBuffer(size_t capacity, MemoryPool* pool) + : data_(nullptr), size_(0), capacity_(capacity), pool_(pool), owns_memory_(false) { + + if (capacity > 0) { + if (pool_) { + data_ = static_cast(pool_->allocate(capacity)); + } else { +#ifdef ARDUINO + data_ = static_cast(malloc(capacity)); +#else + data_ = new uint8_t[capacity]; +#endif + } + + if (data_) { + owns_memory_ = true; + memset(data_, 0, capacity); + } + } +} + +SafeBuffer::~SafeBuffer() { + if (data_ && owns_memory_) { + if (pool_) { + pool_->deallocate(data_); + } else { +#ifdef ARDUINO + free(data_); +#else + delete[] data_; +#endif + } + } +} + +SafeBuffer::SafeBuffer(SafeBuffer&& other) noexcept + : data_(other.data_), size_(other.size_), capacity_(other.capacity_), + pool_(other.pool_), owns_memory_(other.owns_memory_) { + other.data_ = nullptr; + other.size_ = 0; + other.capacity_ = 0; + other.owns_memory_ = false; +} + +SafeBuffer& SafeBuffer::operator=(SafeBuffer&& other) noexcept { + if (this != &other) { + // Clean up current resources + if (data_ && owns_memory_) { + if (pool_) { + pool_->deallocate(data_); + } else { +#ifdef ARDUINO + free(data_); +#else + delete[] data_; +#endif + } + } + + // Move from other + data_ = other.data_; + size_ = other.size_; + capacity_ = other.capacity_; + pool_ = other.pool_; + owns_memory_ = other.owns_memory_; + + // Reset other + other.data_ = nullptr; + other.size_ = 0; + other.capacity_ = 0; + other.owns_memory_ = false; + } + return *this; +} + +bool SafeBuffer::write(const void* data, size_t size, size_t offset) { + if (!data_ || !data || size == 0) { + return false; + } + + if (offset + size > capacity_) { + return false; // Would exceed buffer capacity + } + + memcpy(data_ + offset, data, size); + + // Update size if we wrote beyond current size + if (offset + size > size_) { + size_ = offset + size; + } + + return true; +} + +bool SafeBuffer::read(void* data, size_t size, size_t offset) const { + if (!data_ || !data || size == 0) { + return false; + } + + if (offset + size > size_) { + return false; // Would read beyond valid data + } + + memcpy(data, data_ + offset, size); + return true; +} + +bool SafeBuffer::append(const void* data, size_t size) { + return write(data, size, size_); +} + +void SafeBuffer::clear() { + if (data_) { + memset(data_, 0, capacity_); + size_ = 0; + } +} \ No newline at end of file diff --git a/memory_manager.h b/memory_manager.h new file mode 100644 index 0000000..f7636f6 --- /dev/null +++ b/memory_manager.h @@ -0,0 +1,282 @@ +#ifndef MEMORY_MANAGER_H +#define MEMORY_MANAGER_H + +#include +#include + +#ifdef ARDUINO +#include +#else +#include +#include +#endif + +/** + * @brief Memory pool for embedded systems with limited heap + * + * This class provides a simple memory pool implementation to avoid + * heap fragmentation and provide predictable memory allocation. + */ +class MemoryPool { +private: + static const size_t POOL_SIZE = 4096; // 4KB pool + static const size_t MAX_BLOCKS = 32; + + struct Block { + void* ptr; + size_t size; + bool in_use; + uint32_t magic; // For corruption detection + }; + + uint8_t pool_memory[POOL_SIZE]; + Block blocks[MAX_BLOCKS]; + size_t next_free_offset; + uint32_t allocation_count; + uint32_t deallocation_count; + + static const uint32_t MAGIC_NUMBER = 0xDEADBEEF; + +public: + MemoryPool(); + ~MemoryPool(); + + /** + * @brief Allocate memory from the pool + * @param size Size in bytes to allocate + * @return Pointer to allocated memory or nullptr if failed + */ + void* allocate(size_t size); + + /** + * @brief Deallocate memory back to the pool + * @param ptr Pointer to memory to deallocate + * @return true if successful, false if invalid pointer + */ + bool deallocate(void* ptr); + + /** + * @brief Get memory usage statistics + */ + struct MemoryStats { + size_t total_size; + size_t used_size; + size_t free_size; + uint32_t allocations; + uint32_t deallocations; + uint32_t active_blocks; + }; + + MemoryStats getStats() const; + + /** + * @brief Check for memory corruption + * @return true if memory is intact, false if corruption detected + */ + bool checkIntegrity() const; + + /** + * @brief Reset the entire pool (use with caution!) + */ + void reset(); + + /** + * @brief Print memory usage information + */ + void printStats() const; +}; + +/** + * @brief RAII wrapper for automatic memory management + * + * This template class provides automatic memory management + * using RAII principles for embedded systems. + */ +template +class SmartPtr { +private: + T* ptr_; + MemoryPool* pool_; + bool owns_memory_; + +public: + /** + * @brief Constructor for pool-allocated memory + */ + explicit SmartPtr(MemoryPool* pool = nullptr) + : ptr_(nullptr), pool_(pool), owns_memory_(false) { + if (pool_) { + ptr_ = static_cast(pool_->allocate(sizeof(T))); + if (ptr_) { + new(ptr_) T(); // Placement new + owns_memory_ = true; + } + } + } + + /** + * @brief Constructor taking ownership of existing pointer + */ + SmartPtr(T* ptr, MemoryPool* pool, bool owns = true) + : ptr_(ptr), pool_(pool), owns_memory_(owns) {} + + /** + * @brief Move constructor + */ + SmartPtr(SmartPtr&& other) noexcept + : ptr_(other.ptr_), pool_(other.pool_), owns_memory_(other.owns_memory_) { + other.ptr_ = nullptr; + other.owns_memory_ = false; + } + + /** + * @brief Move assignment operator + */ + SmartPtr& operator=(SmartPtr&& other) noexcept { + if (this != &other) { + reset(); + ptr_ = other.ptr_; + pool_ = other.pool_; + owns_memory_ = other.owns_memory_; + other.ptr_ = nullptr; + other.owns_memory_ = false; + } + return *this; + } + + /** + * @brief Destructor - automatically cleans up + */ + ~SmartPtr() { + reset(); + } + + /** + * @brief Delete copy constructor and assignment (no copying) + */ + SmartPtr(const SmartPtr&) = delete; + SmartPtr& operator=(const SmartPtr&) = delete; + + /** + * @brief Access the managed object + */ + T* get() const { return ptr_; } + T& operator*() const { return *ptr_; } + T* operator->() const { return ptr_; } + + /** + * @brief Check if the pointer is valid + */ + bool isValid() const { return ptr_ != nullptr; } + explicit operator bool() const { return isValid(); } + + /** + * @brief Release ownership without destroying + */ + T* release() { + T* temp = ptr_; + ptr_ = nullptr; + owns_memory_ = false; + return temp; + } + + /** + * @brief Reset the pointer, destroying current object if owned + */ + void reset() { + if (ptr_ && owns_memory_) { + ptr_->~T(); // Explicit destructor call + if (pool_) { + pool_->deallocate(ptr_); + } + } + ptr_ = nullptr; + owns_memory_ = false; + } +}; + +/** + * @brief Buffer management class with bounds checking + */ +class SafeBuffer { +private: + uint8_t* data_; + size_t size_; + size_t capacity_; + MemoryPool* pool_; + bool owns_memory_; + +public: + SafeBuffer(size_t capacity, MemoryPool* pool = nullptr); + ~SafeBuffer(); + + // Delete copy constructor and assignment + SafeBuffer(const SafeBuffer&) = delete; + SafeBuffer& operator=(const SafeBuffer&) = delete; + + // Move constructor and assignment + SafeBuffer(SafeBuffer&& other) noexcept; + SafeBuffer& operator=(SafeBuffer&& other) noexcept; + + /** + * @brief Write data to buffer with bounds checking + */ + bool write(const void* data, size_t size, size_t offset = 0); + + /** + * @brief Read data from buffer with bounds checking + */ + bool read(void* data, size_t size, size_t offset = 0) const; + + /** + * @brief Append data to buffer + */ + bool append(const void* data, size_t size); + + /** + * @brief Clear buffer contents + */ + void clear(); + + /** + * @brief Get buffer information + */ + uint8_t* data() { return data_; } + const uint8_t* data() const { return data_; } + size_t size() const { return size_; } + size_t capacity() const { return capacity_; } + size_t available() const { return capacity_ - size_; } + + /** + * @brief Check if buffer is valid + */ + bool isValid() const { return data_ != nullptr; } +}; + +// Global memory pool instance +extern MemoryPool globalMemoryPool; + +/** + * @brief Convenience function to create smart pointers + */ +template +SmartPtr makeSmartPtr(MemoryPool* pool = &globalMemoryPool) { + return SmartPtr(pool); +} + +/** + * @brief Memory debugging macros (only active in debug builds) + */ +#ifdef DEBUG_MEMORY +#define MEM_ALLOC(size) globalMemoryPool.allocate(size) +#define MEM_FREE(ptr) globalMemoryPool.deallocate(ptr) +#define MEM_CHECK() globalMemoryPool.checkIntegrity() +#define MEM_STATS() globalMemoryPool.printStats() +#else +#define MEM_ALLOC(size) globalMemoryPool.allocate(size) +#define MEM_FREE(ptr) globalMemoryPool.deallocate(ptr) +#define MEM_CHECK() true +#define MEM_STATS() do {} while(0) +#endif + +#endif // MEMORY_MANAGER_H \ No newline at end of file diff --git a/nfc_protocol.cpp b/nfc_protocol.cpp new file mode 100644 index 0000000..de1abfe --- /dev/null +++ b/nfc_protocol.cpp @@ -0,0 +1,69 @@ +#include "nfc_protocol.h" + +uint16_t calculateChecksum(const uint8_t* data, size_t length) { + uint16_t checksum = 0; + for (size_t i = 0; i < length; i++) { + checksum += data[i]; + } + return checksum; +} + +bool validateMessage(const CommMessage* msg) { + if (msg == nullptr) return false; + + // Calculate checksum excluding the checksum field itself + size_t data_size = sizeof(CommMessage) - sizeof(msg->checksum); + uint16_t calculated_checksum = calculateChecksum((const uint8_t*)msg, data_size); + + return calculated_checksum == msg->checksum; +} + +void printNFCData(const NFCData* data) { + if (data == nullptr) return; + + Serial.println("=== NFC Data ==="); + Serial.print("UID: "); + for (int i = 0; i < data->uid_length; i++) { + if (data->uid[i] < 0x10) Serial.print("0"); + Serial.print(data->uid[i], HEX); + if (i < data->uid_length - 1) Serial.print(" "); + } + Serial.println(); + + Serial.print("UID Length: "); + Serial.println(data->uid_length); + + Serial.print("SAK: 0x"); + if (data->sak < 0x10) Serial.print("0"); + Serial.println(data->sak, HEX); + + Serial.print("ATQA: 0x"); + if (data->atqa[0] < 0x10) Serial.print("0"); + Serial.print(data->atqa[0], HEX); + Serial.print(" 0x"); + if (data->atqa[1] < 0x10) Serial.print("0"); + Serial.println(data->atqa[1], HEX); + + Serial.print("Tag Type: "); + Serial.println(data->tag_type); + + Serial.print("Data Length: "); + Serial.println(data->data_length); + + Serial.print("Timestamp: "); + Serial.println(data->timestamp); + + if (data->data_length > 0) { + Serial.print("Raw Data: "); + for (int i = 0; i < data->data_length && i < 32; i++) { + if (data->raw_data[i] < 0x10) Serial.print("0"); + Serial.print(data->raw_data[i], HEX); + Serial.print(" "); + } + if (data->data_length > 32) { + Serial.print("... (truncated)"); + } + Serial.println(); + } + Serial.println("================"); +} \ No newline at end of file diff --git a/nfc_protocol.h b/nfc_protocol.h new file mode 100644 index 0000000..b55b534 --- /dev/null +++ b/nfc_protocol.h @@ -0,0 +1,67 @@ +#ifndef NFC_PROTOCOL_H +#define NFC_PROTOCOL_H + +#include + +// Communication protocol definitions +#define MAX_NFC_DATA_SIZE 256 +#define ESPNOW_CHANNEL 1 +#define PAIRING_TIMEOUT_MS 30000 +#define HEARTBEAT_INTERVAL_MS 1000 +#define MAX_RETRY_ATTEMPTS 3 + +// Message types +enum MessageType { + MSG_PAIRING_REQUEST = 0x01, + MSG_PAIRING_RESPONSE = 0x02, + MSG_NFC_DATA = 0x03, + MSG_HEARTBEAT = 0x04, + MSG_ERROR = 0x05, + MSG_ACK = 0x06 +}; + +// NFC data structure +struct NFCData { + uint8_t uid[10]; // NFC UID (max 10 bytes) + uint8_t uid_length; // Actual UID length + uint8_t sak; // Select Acknowledge + uint8_t atqa[2]; // Answer to Request Type A + uint8_t raw_data[MAX_NFC_DATA_SIZE]; // Raw NFC data + uint16_t data_length; // Actual data length + uint32_t timestamp; // Capture timestamp + uint8_t tag_type; // NFC tag type +}; + +// Communication message structure +struct CommMessage { + uint8_t type; // Message type + uint8_t sequence; // Sequence number + uint16_t payload_size; // Payload size + union { + NFCData nfc_data; + uint8_t raw_payload[sizeof(NFCData)]; + }; + uint16_t checksum; // Message integrity check +}; + +// Device roles +enum DeviceRole { + ROLE_LISTENER = 1, + ROLE_EMULATOR = 2 +}; + +// Status codes +enum StatusCode { + STATUS_OK = 0, + STATUS_ERROR = 1, + STATUS_TIMEOUT = 2, + STATUS_INVALID_DATA = 3, + STATUS_NOT_PAIRED = 4 +}; + +// Function prototypes +uint16_t calculateChecksum(const uint8_t* data, size_t length); +bool validateMessage(const CommMessage* msg); +void printNFCData(const NFCData* data); + +#endif // NFC_PROTOCOL_H \ No newline at end of file