SH2SC-EDT
Self-Healing Hardware and Software Complex for Encrypted Data Transmission
Loading...
Searching...
No Matches
receiver.h File Reference

SH2SC-EDT — Receiver Node B ("The Synthesizer") public interface. More...

#include <Arduino.h>
#include "protocol.h"
#include "csprng.h"
#include <ChaChaPoly.h>
+ 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.
 

Detailed Description

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.

Enumeration Type Documentation

◆ RxState

enum class RxState : uint8_t
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:

^ |
|______ resetParser() ________________|
(MAC fail | parser timeout)
void resetParser()
Reset the byte-parser FSM to WAITING_SYNC_1 and drain the UART RX FIFO.
@ WAITING_SYNC_2
SYNC_BYTE_1 confirmed — waiting for SYNC_BYTE_2 (0x55).
@ GOT_DATA
Full DataPacket buffered; authenticateAndPlay() will be called by rx_loop().
@ READING_DATA
Accumulating the 14-byte body (seq_num + payload + mac) of a DataPacket.
@ READING_HELLO
Accumulating the 12-byte nonce body of a HelloPacket into the RX buffer.
@ WAITING_FOR_TYPE
Sync preamble complete — waiting for the frame flags byte.
@ GOT_HELLO
Full HelloPacket buffered; processHelloBody() will be called by rx_loop().
@ EXECUTING_ACTION
MAC verified; note is sounding — non-blocking timer wait for note end.
@ WAITING_SYNC_1
Initial/reset state — scanning the UART stream for SYNC_BYTE_1 (0xAA).
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 flags byte.

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.

Function Documentation

◆ authenticateAndPlay()

void authenticateAndPlay ( const DataPacket pkt)

Authenticate and decrypt a DataPacket (FLAG_DAT); play the note on MAC success.

Full ChaCha20-Poly1305 AEAD pipeline:

  1. Read seq_num directly from pkt->seq_num (uint16_t, packed struct).
  2. Derive per-packet nonce: local copy of s_sessionNonce, then packetNonce[10] ^= (seq_num >> 8), packetNonce[11] ^= seq_num.
  3. clear() -> setKey(MASTER_PSK, 32) -> setIV(packetNonce, 12).
  4. addAuthData({flags, seq_lo, seq_hi}, 3) — 3-byte plaintext AAD.
  5. decrypt(pkt->payload, plaintext, DATA_PAYLOAD_SIZE).
  6. 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.

Parameters
pktPointer 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()):

  1. Read seq_num from the packed struct field directly.
  2. Derive per-packet nonce: local copy of s_sessionNonce, then packetNonce[10] ^= (seqNum >> 8), packetNonce[11] ^= seqNum.
  3. clear() -> setKey(MASTER_PSK, 32) -> setIV(packetNonce, 12).
  4. addAuthData({flags, seq_lo, seq_hi}, 3) — 3-byte plaintext AAD.
  5. decrypt(pkt->payload, plaintext, DATA_PAYLOAD_SIZE).
  6. 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.
    Parameters
    pktPointer 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:

◆ generateEntropyPool()

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):

  1. Ring oscillator on INT0 (pin 2): pulse count over a 2 ms gate window.
  2. Eight uninitialised SRAM bytes at 0x0100 + wordIndex*8.
  3. On-die temperature ADC (channel 8, 1.1 V reference): 8 LSBs per word.
  4. TCNT1 free-running timer snapshot (2 bytes).
  5. A0 white-noise ADC: 8 LSBs per word.
  6. Arduino software PRNG random() (obfuscation layer).

Total harvest time: ~8 × 2 ms gate = ~16 ms (one-shot cost in setup()).

Parameters
outputSeedPointer to a 32-byte buffer that will receive the entropy pool. Pass directly to initCSPRNG(); scrub afterwards if desired.
Note
Invoke once during rx_setup(), before the UART receive loop starts.

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.

Parameters
outputSeedPointer to a 32-byte output buffer. Must be valid and writable. Pass directly to initCSPRNG(); scrub afterwards if desired.
Note
Must be called once per button press, AFTER readButtonPress() returns 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):

  1. Ring oscillator on INT0 (pin 2): pulse count over a 2 ms gate.
  2. Uninitialised SRAM bytes at 0x0100 + wordIndex*8 (8 bytes per word).
  3. On-die temperature ADC (channel 8, 1.1 V reference): 8 LSBs per word.
  4. TCNT1 free-running timer snapshot (2 bytes).
  5. A0 white-noise ADC: 8 LSBs per word.
  6. Arduino software PRNG random() (obfuscation layer).

Total execution time: 8 × 2 ms gate ≈ 16 ms (one-shot in rx_setup()).

Parameters
outputSeedPointer to a 32-byte buffer for the entropy pool output. Pass directly to initCSPRNG(); scrub with memset() afterwards.
Note
Invoke once during rx_setup(), before the UART receive loop starts.

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:

◆ processFinPacket()

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.

Parameters
pktPointer to the fully buffered DataPacket (with flags == FLAG_FIN).
Note
Key erasure (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.

Parameters
pktPointer 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:

◆ processHelloBody()

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.

Note
Called from rx_loop() when the state is 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:

◆ processReceivedByte()

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.

Parameters
inByteByte 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.

Parameters
inByteByte 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:

◆ resetParser()

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.

◆ rx_loop()

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.

Note
Must be called repeatedly from Arduino 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:

◆ rx_setup()

void rx_setup ( )

Initialise RX hardware and seed the CSPRNG. Called once from setup().

Note
Must be called once from Arduino 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:

◆ startNote()

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.

Parameters
frequencyHzFrequency in Hz for tone() (e.g. 262 for middle C). Pass 0 to produce a rest (silence).
durationMsDuration 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.

Parameters
frequencyHzFrequency in Hz to pass to tone(); 0 = silence.
durationMsDuration 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:

◆ stopNote()

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:

◆ updateRxDisplay()

void updateRxDisplay ( RxState  state,
uint16_t  seqNum,
bool  macOk 
)

Refresh the RX LCD with the current FSM state and last packet result.

  • Row 0: Human-readable RxState label.
  • Row 1: SEQ: <seqNum> OK (MAC passed) or SEQ: <seqNum> MAC! (failed). Must only be called on FSM state transitions — NOT in a tight loop.
    Parameters
    stateCurrent RxState to display on row 0.
    seqNumLast received sequence number to display on row 1.
    macOktrue if the last packet's MAC verification passed; false otherwise.
    Row 0 shows a human-readable RxState label; Row 1 shows SEQ: <seqNum> OK or SEQ: <seqNum> MAC! depending on macOk.
    Parameters
    stateCurrent RxState to display on LCD row 0.
    seqNumLast received sequence number for LCD row 1.
    macOktrue 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:

Variable Documentation

◆ NOTE_DICT_SIZE

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().