|
SH2SC-EDT
Self-Healing Hardware and Software Complex for Encrypted Data Transmission
|
SH2SC-EDT — Receiver Node B ("The Synthesizer") public interface. More...
Include dependency graph for receiver.h:
This graph shows which files directly or indirectly include this file:Go to the source code of this file.
Enumerations | |
| enum class | RxState : uint8_t { WAITING_SYNC_1 , WAITING_SYNC_2 , WAITING_FOR_TYPE , READING_HELLO , READING_DATA , GOT_HELLO , GOT_DATA , EXECUTING_ACTION } |
| Byte-level parser Finite State Machine states for the C2P-ARQ Receiver (Node B). More... | |
Functions | |
| void | rx_setup () |
| Initialise RX hardware and seed the CSPRNG. Called once from setup(). | |
| void | rx_loop () |
| Execute one non-blocking C2P-ARQ FSM tick for the Receiver node. | |
| void | processReceivedByte (uint8_t inByte) |
| Consume one incoming UART byte and advance the frame-assembly FSM. | |
| void | processHelloBody () |
| Handle a fully buffered HelloPacket (FLAG_SYN) from the Transmitter. | |
| void | authenticateAndPlay (const DataPacket *pkt) |
| Authenticate and decrypt a DataPacket (FLAG_DAT); play the note on MAC success. | |
| void | processFinPacket (const DataPacket *pkt) |
| Authenticate a FLAG_FIN teardown packet and close the session on MAC success. | |
| void | resetParser () |
| Reset the byte-parser FSM to WAITING_SYNC_1 and drain the UART RX FIFO. | |
| void | startNote (uint16_t frequencyHz, uint16_t durationMs) |
| Start a non-blocking note playback on the piezo buzzer. | |
| void | stopNote () |
Stop the currently playing note by calling noTone(). | |
| void | updateRxDisplay (RxState state, uint16_t seqNum, bool macOk) |
| Refresh the RX LCD with the current FSM state and last packet result. | |
| void | generateEntropyPool (uint8_t *outputSeed) |
Harvest 256 bits of hardware entropy and write them to outputSeed (RX variant). | |
Variables | |
| const uint8_t | RX_BUZZER_PIN = 9 |
| PWM-capable pin connected to the piezo buzzer. | |
| const uint8_t | ENTROPY_RING_OSC_PIN = 2 |
| Hardware ring oscillator entropy source (INT0). | |
| const uint8_t | RX_LCD_ADDR = 0x3E |
| I2C address of the Aip31068 16x2 LCD display. | |
| const uint8_t | RX_LCD_COLS = 16 |
| Number of columns on the RX LCD. | |
| const uint8_t | RX_LCD_ROWS = 2 |
| Number of rows on the RX LCD. | |
| const uint8_t | NOTE_DICT_SIZE = 21 |
| Size of the universal note frequency dictionary on RX. | |
SH2SC-EDT — Receiver Node B ("The Synthesizer") public interface.
Part of the SH2SC-EDT project. Implements the C2P-ARQ protocol. Declares the RX FSM states, hardware pin constants, the universal note frequency dictionary, and all public functions used by ReceiverNode.ino. Node B has no knowledge of the melody structure — it holds only a universal frequency dictionary (universal_notes[21]) and authenticates and plays one note at a time as directed by Transmitter Node A.
Definition in file receiver.h.
|
strong |
Byte-level parser Finite State Machine states for the C2P-ARQ Receiver (Node B).
The entire RX logic is driven by this FSM. No blocking delays are permitted; each state transition is handled in a single non-blocking pass through rx_loop(). A MAC failure or parser timeout triggers resetParser() which returns the FSM to WAITING_SYNC_1.
Canonical byte-parser lifecycle:
| Enumerator | |
|---|---|
| WAITING_SYNC_1 | Initial/reset state — scanning the UART stream for SYNC_BYTE_1 (0xAA). |
| WAITING_SYNC_2 | SYNC_BYTE_1 confirmed — waiting for SYNC_BYTE_2 (0x55). |
| WAITING_FOR_TYPE | Sync preamble complete — waiting for the frame |
| READING_HELLO | Accumulating the 12-byte nonce body of a HelloPacket into the RX buffer. |
| READING_DATA | Accumulating the 14-byte body (seq_num + payload + mac) of a DataPacket. |
| GOT_HELLO | Full HelloPacket buffered; processHelloBody() will be called by rx_loop(). |
| GOT_DATA | Full DataPacket buffered; authenticateAndPlay() will be called by rx_loop(). |
| EXECUTING_ACTION | MAC verified; note is sounding — non-blocking timer wait for note end. |
Definition at line 55 of file receiver.h.
| void authenticateAndPlay | ( | const DataPacket * | pkt | ) |
Authenticate and decrypt a DataPacket (FLAG_DAT); play the note on MAC success.
Full ChaCha20-Poly1305 AEAD pipeline:
seq_num directly from pkt->seq_num (uint16_t, packed struct).s_sessionNonce, then packetNonce[10] ^= (seq_num >> 8), packetNonce[11] ^= seq_num.clear() -> setKey(MASTER_PSK, 32) -> setIV(packetNonce, 12).addAuthData({flags, seq_lo, seq_hi}, 3) — 3-byte plaintext AAD.decrypt(pkt->payload, plaintext, DATA_PAYLOAD_SIZE).computeTag(expectedMac, 16), then memcmp(expectedMac, pkt->mac, TRUNCATED_MAC_SIZE).Security invariant: startNote() is called ONLY after memcmp returns 0. A corrupted, replayed, or forged packet is always discarded at the MAC gate. Also validates noteIndex < NOTE_DICT_SIZE before accessing universal_notes[] to prevent out-of-bounds reads on ATmega328P.
| pkt | Pointer to the fully buffered DataPacket in the RX buffer. |
Authenticate and decrypt a DataPacket (FLAG_DAT); play the note on MAC success.
Pipeline (mirrors TX sendPacket()):
seq_num from the packed struct field directly.s_sessionNonce, then packetNonce[10] ^= (seqNum >> 8), packetNonce[11] ^= seqNum.clear() -> setKey(MASTER_PSK, 32) -> setIV(packetNonce, 12).addAuthData({flags, seq_lo, seq_hi}, 3) — 3-byte plaintext AAD.decrypt(pkt->payload, plaintext, DATA_PAYLOAD_SIZE).computeTag(expectedMac, 16), then memcmp with truncated 8-byte tag. Security invariant: startNote() is called ONLY after memcmp returns 0. Also bounds-checks noteIndex < NOTE_DICT_SIZE before indexing universal_notes[] to prevent out-of-bounds reads on ATmega328P. | pkt | Pointer to the fully buffered DataPacket (flags == FLAG_DAT). |
Definition at line 327 of file ReceiverNode.ino.
References ACK_BYTE, current_timeout_limit, currentState, DATA_PAYLOAD_SIZE, errorDisplayStart, EXECUTING_ACTION, DataPacket::flags, HELLO_NONCE_SIZE, isShowingError, last_valid_packet_time, lcd(), DataPacket::mac, MASTER_PSK, NACK_BYTE, NETWORK_GRACE_PERIOD_MS, NOTE_DICT_SIZE, DataPacket::payload, resetParser(), REST_INDEX, s_cipher, s_sessionNonce, DataPacket::seq_num, seqNum, startNote(), stopNote(), TRUNCATED_MAC_SIZE, universal_notes, and updateRxDisplay().
Referenced by processReceivedByte().
Here is the call graph for this function:
Here is the caller graph for this function:| void generateEntropyPool | ( | uint8_t * | outputSeed | ) |
Harvest 256 bits of hardware entropy and write them to outputSeed (RX variant).
Fills outputSeed[32] as 8 independent 32-bit words from six hardware sources (no human-timing jitter source since RX has no button):
0x0100 + wordIndex*8.random() (obfuscation layer).Total harvest time: ~8 × 2 ms gate = ~16 ms (one-shot cost in setup()).
| outputSeed | Pointer to a 32-byte buffer that will receive the entropy pool. Pass directly to initCSPRNG(); scrub afterwards if desired. |
Harvest 256 bits of hardware entropy and write them to outputSeed (RX variant).
Harvest 256 bits of hardware entropy and write them to outputSeed.
Fills outputSeed[32] as 8 independent 32-bit words. Each word is produced by a fresh pass over all seven entropy sources so that a biased source in one iteration is compensated by the others through the rotate-XOR mixing chain. Total execution time: 8 iterations x 2 ms gate = ~16 ms (one-shot cost). This is the TX variant — it includes micros() human-timing jitter from the button press as a seventh source not present on the RX node.
| outputSeed | Pointer to a 32-byte output buffer. Must be valid and writable. Pass directly to initCSPRNG(); scrub afterwards if desired. |
true.Harvest 256 bits of hardware entropy and write them to outputSeed (RX variant).
Harvest 256 bits of hardware entropy and write them to outputSeed.
Fills outputSeed[32] as 8 independent 32-bit words, each gathered from six hardware sources (micros() human-timing jitter is omitted — RX has no button):
Total execution time: 8 × 2 ms gate ≈ 16 ms (one-shot in rx_setup()).
| outputSeed | Pointer to a 32-byte buffer for the entropy pool output. Pass directly to initCSPRNG(); scrub with memset() afterwards. |
Definition at line 345 of file TransmitterNode.ino.
References ENTROPY_RING_OSC_PIN, mixEntropy(), onRingOscPulse(), and s_ringOscPulses.
Referenced by rx_setup(), and tx_setup().
Here is the call graph for this function:
Here is the caller graph for this function:| void processFinPacket | ( | const DataPacket * | pkt | ) |
Authenticate a FLAG_FIN teardown packet and close the session on MAC success.
Runs the same full ChaChaPoly pipeline as authenticateAndPlay() using a discardBuf to receive the zero plaintext — the decrypt() call MUST execute before computeTag() to correctly advance the Poly1305 accumulator. On MAC success: sends ACK, erases s_sessionNonce via memset(), clears s_sessionActive, and resets the parser to WAITING_SYNC_1. On MAC failure: sends NACK and leaves the session active for FIN retransmission.
| pkt | Pointer to the fully buffered DataPacket (with flags == FLAG_FIN). |
memset) on MAC success is mandatory for forward secrecy.Authenticate a FLAG_FIN teardown packet and close the session on MAC success.
Cryptographic pipeline is identical to authenticateAndPlay() to guarantee that only the legitimate TX (holding MASTER_PSK and s_sessionNonce) can produce a verifiable FIN. A noise-induced FLAG_FIN byte will fail MAC and trigger NACK without touching the session state. Uses discardBuf to receive the zero plaintext — decrypt() MUST run before computeTag() to correctly advance the Poly1305 accumulator. On MAC success: ACK sent, s_sessionNonce zeroed (key erasure), parser reset. On MAC failure: NACK sent, session left active for FIN retransmission.
| pkt | Pointer to the fully buffered DataPacket (flags == FLAG_FIN). |
Definition at line 416 of file ReceiverNode.ino.
References ACK_BYTE, DATA_PAYLOAD_SIZE, FLAG_FIN, HELLO_NONCE_SIZE, lcd(), DataPacket::mac, MASTER_PSK, NACK_BYTE, DataPacket::payload, resetParser(), s_cipher, s_sessionActive, s_sessionNonce, DataPacket::seq_num, seqNum, and TRUNCATED_MAC_SIZE.
Referenced by processReceivedByte().
Here is the call graph for this function:
Here is the caller graph for this function:| void processHelloBody | ( | ) |
Handle a fully buffered HelloPacket (FLAG_SYN) from the Transmitter.
Copies the received 12-byte nonce into s_sessionNonce, calls s_cipher.setKey(MASTER_PSK, 32) to pre-install the key, and sends ACK_BYTE on the feedback channel. Also arms the Dynamic Smart Watchdog with the default current_timeout_limit = 5000 ms.
RxState::GOT_HELLO.Handle a fully buffered HelloPacket (FLAG_SYN) from the Transmitter.
Copies rx_buffer[1..12] into s_sessionNonce, calls s_cipher.setKey(MASTER_PSK, 32) to pre-install the key, sets s_sessionActive = true, sends ACK_BYTE, and arms the Dynamic Smart Watchdog with current_timeout_limit = 5000 ms.
Definition at line 290 of file ReceiverNode.ino.
References ACK_BYTE, current_timeout_limit, HELLO_NONCE_SIZE, last_valid_packet_time, MASTER_PSK, rx_buffer, s_cipher, s_sessionActive, and s_sessionNonce.
Referenced by processReceivedByte().
Here is the caller graph for this function:| void processReceivedByte | ( | uint8_t | inByte | ) |
Consume one incoming UART byte and advance the frame-assembly FSM.
Implements the byte-level parser FSM: WAITING_SYNC_1 -> WAITING_SYNC_2 -> WAITING_FOR_TYPE -> READING_HELLO/READING_DATA. On buffer completion transitions to GOT_HELLO or GOT_DATA for processing in the next rx_loop() pass.
| inByte | Byte just received from the UART hardware buffer. |
Implements the WAIT_AA -> WAIT_55 -> READ_TYPE -> READ_PAYLOAD pipeline. Both HELLO and DATA frames must pass the 0xAA 0x55 preamble gate. On buffer completion, dispatches to processHelloBody(), authenticateAndPlay(), or processFinPacket() as appropriate, then returns to WAIT_AA.
| inByte | Byte just read from the UART hardware buffer. |
Definition at line 188 of file ReceiverNode.ino.
References authenticateAndPlay(), currentState, expected_length, FLAG_DAT, FLAG_FIN, FLAG_SYN, NACK_BYTE, parseState, processFinPacket(), processHelloBody(), READ_PAYLOAD, READ_TYPE, READING_DATA, READING_HELLO, resetParser(), rx_buffer, rx_index, s_sessionActive, SYNC_BYTE_1, SYNC_BYTE_2, updateRxDisplay(), WAIT_55, WAIT_AA, and WAITING_SYNC_1.
Referenced by rx_loop().
Here is the call graph for this function:
Here is the caller graph for this function:| void resetParser | ( | ) |
Reset the byte-parser FSM to WAITING_SYNC_1 and drain the UART RX FIFO.
Called on MAC failure or parser timeout to prevent a corrupted frame from also poisoning the boundary detection of the next frame. Drains any lingering bytes from the 64-byte hardware UART FIFO so the next successful 0xAA 0x55 preamble is genuinely the start of a new frame.
| void rx_loop | ( | ) |
Execute one non-blocking C2P-ARQ FSM tick for the Receiver node.
Reads bytes from the UART, drives processReceivedByte(), dispatches packet handlers (processHelloBody(), authenticateAndPlay(), processFinPacket()), and manages the Dynamic Smart Watchdog timer. Each invocation performs at most one FSM transition and returns immediately — no delay() calls are permitted.
loop().Handles the MAC-error display timer, note-duration timer, Dynamic Smart Watchdog tear-down, non-blocking UART byte reading, and parser timeout. At most one FSM transition per invocation; no delay() calls.
Definition at line 672 of file ReceiverNode.ino.
References CHK_ERR_DISPLAY_MS, current_timeout_limit, currentState, errorDisplayStart, EXECUTING_ACTION, HELLO_NONCE_SIZE, isPlayingNote, isShowingError, last_valid_packet_time, lcd(), noteLengthMs, noteStartMs, parseState, processReceivedByte(), resetParser(), rx_index, RX_PARSER_TIMEOUT_MS, rxLastByteMs, s_sessionActive, s_sessionNonce, stopNote(), updateRxDisplay(), WAIT_AA, and WAITING_SYNC_1.
Referenced by loop().
Here is the call graph for this function:
Here is the caller graph for this function:| void rx_setup | ( | ) |
Initialise RX hardware and seed the CSPRNG. Called once from setup().
setup().Initialise RX hardware and seed the CSPRNG. Called once from setup().
Configures UART @ 9600 bps, buzzer pin, I2C LCD, and byte-parser FSM. Harvests 256-bit hardware entropy (~16 ms) and seeds the ChaCha20 CSPRNG. The entropy buffer is scrubbed from the stack immediately after use.
Definition at line 615 of file ReceiverNode.ino.
References BAUD_RATE, currentState, generateEntropyPool(), initCSPRNG(), lcd(), parseState, RX_BUZZER_PIN, rx_index, updateRxDisplay(), WAIT_AA, and WAITING_SYNC_1.
Referenced by setup().
Here is the call graph for this function:
Here is the caller graph for this function:| void startNote | ( | uint16_t | frequencyHz, |
| uint16_t | durationMs | ||
| ) |
Start a non-blocking note playback on the piezo buzzer.
Calls tone(RX_BUZZER_PIN, frequencyHz), records the start timestamp, and stores durationMs so that rx_loop() can call stopNote() when the required time has elapsed. For REST_INDEX, noTone() is called immediately instead.
| frequencyHz | Frequency in Hz for tone() (e.g. 262 for middle C). Pass 0 to produce a rest (silence). |
| durationMs | Duration in milliseconds to play the note. |
Start a non-blocking note playback on the piezo buzzer.
Calls tone(RX_BUZZER_PIN, frequencyHz), records noteStartMs = millis(), and sets isPlayingNote = true. rx_loop() will call stopNote() when (millis() - noteStartMs) >= noteLengthMs. For frequencyHz == 0 (REST), no tone is started and isPlayingNote is still set to handle timing.
| frequencyHz | Frequency in Hz to pass to tone(); 0 = silence. |
| durationMs | Duration in milliseconds to hold the note before calling noTone(). |
Definition at line 494 of file ReceiverNode.ino.
References isPlayingNote, noteLengthMs, noteStartMs, and RX_BUZZER_PIN.
Referenced by authenticateAndPlay().
Here is the caller graph for this function:| void stopNote | ( | ) |
Stop the currently playing note by calling noTone().
Called by rx_loop() when (millis() - noteStartTime) >= noteDurationMs. Also updates the Dynamic Smart Watchdog timeout limit for the next note.
Stop the currently playing note by calling noTone().
Calls noTone(RX_BUZZER_PIN) and sets isPlayingNote = false. Called by rx_loop() when (millis() - noteStartMs) >= noteLengthMs.
Definition at line 506 of file ReceiverNode.ino.
References isPlayingNote, and RX_BUZZER_PIN.
Referenced by authenticateAndPlay(), and rx_loop().
Here is the caller graph for this function:| void updateRxDisplay | ( | RxState | state, |
| uint16_t | seqNum, | ||
| bool | macOk | ||
| ) |
Refresh the RX LCD with the current FSM state and last packet result.
SEQ: <seqNum> OK (MAC passed) or SEQ: <seqNum> MAC! (failed). Must only be called on FSM state transitions — NOT in a tight loop. | state | Current RxState to display on row 0. |
| seqNum | Last received sequence number to display on row 1. |
| macOk | true if the last packet's MAC verification passed; false otherwise. |
SEQ: <seqNum> OK or SEQ: <seqNum> MAC! depending on macOk. | state | Current RxState to display on LCD row 0. |
| seqNum | Last received sequence number for LCD row 1. |
| macOk | true if MAC verification passed; false on failure. |
Definition at line 138 of file ReceiverNode.ino.
References EXECUTING_ACTION, GOT_DATA, GOT_HELLO, lcd(), READING_DATA, READING_HELLO, s_sessionActive, seqNum, WAITING_FOR_TYPE, WAITING_SYNC_1, and WAITING_SYNC_2.
Referenced by authenticateAndPlay(), processReceivedByte(), rx_loop(), and rx_setup().
Here is the call graph for this function:
Here is the caller graph for this function:| const uint8_t NOTE_DICT_SIZE = 21 |
Size of the universal note frequency dictionary on RX.
Indices 0–20 cover the full chromatic range (C4…G#5) used by any melody TX may send. Index 255 (REST_INDEX) means silence.
Definition at line 36 of file receiver.h.
Referenced by authenticateAndPlay().