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

SH2SC-EDT — Transmitter Node A ("The Conductor") firmware. More...

#include "transmitter.h"
#include "melody.h"
#include <LiquidCrystal_AIP31068_I2C.h>
+ Include dependency graph for TransmitterNode.ino:

Go to the source code of this file.

Functions

static LiquidCrystal_AIP31068_I2C lcd (TX_LCD_ADDR, TX_LCD_COLS, TX_LCD_ROWS)
 I2C LCD — 16 columns x 2 rows, Aip31068-compatible controller.
 
bool readButtonPress ()
 Read and debounce the start button (millis-based falling-edge detector).
 
static void drainRxFifo ()
 Discard all bytes currently waiting in the hardware UART RX FIFO.
 
void sendHelloPacket ()
 Generate a fresh 12-byte CSPRNG nonce and broadcast it as a FLAG_SYN HelloPacket.
 
void sendPacket (uint8_t noteIndex, uint16_t noteDurationMs, uint16_t seqNumber)
 
void formAndSendPacket (uint8_t note_idx, uint16_t duration_ms)
 Snapshot the note payload and delegate to sendPacket() for encryption and transmission.
 
void updateTxDisplay (TxState state, uint16_t seqNumber, uint8_t retries)
 Refresh the TX LCD with the current ARQ status and FSM state label.
 
void sendFinPacket ()
 Construct and transmit a FLAG_FIN session-teardown packet.
 
static void onRingOscPulse ()
 INT0 interrupt service routine — counts ring oscillator rising edges.
 
static uint32_t mixEntropy (uint32_t pool, uint32_t bits)
 One mixing step of the entropy accumulation sponge.
 
void generateEntropyPool (uint8_t *outputSeed)
 Harvest 256 bits of hardware entropy and write them to outputSeed (TX variant).
 
void suspendSession ()
 Perform an unclean session teardown and enter the Self-Healing reconnect loop.
 
void tx_setup ()
 Initialise TX hardware and seed the CSPRNG. Called once from setup().
 
void tx_loop ()
 Execute one non-blocking C2P-ARQ FSM tick. Called repeatedly from loop().
 
void setup ()
 Arduino sketch entry point — delegates to tx_setup().
 
void loop ()
 Arduino sketch main loop — delegates to tx_loop() on every iteration.
 

Variables

static TxState currentState = TxState::IDLE
 Active FSM state.
 
static uint16_t melodyIndex = 0
 Current position in melody[][] (preserved across RECONNECTING).
 
static uint16_t seqNum = 0
 Packet sequence number (0–65535, wraps).
 
static uint8_t retryCount = 0
 Consecutive retransmission counter (displayed on LCD).
 
static uint32_t ackWaitStart = 0
 millis() timestamp when the current ACK-wait window opened.
 
static uint32_t lastReconnectAttempt = 0
 Auto-reconnect countdown; a new HelloPacket is broadcast when (millis() - lastReconnectAttempt) >= RECONNECT_INTERVAL_MS.
 
static uint8_t s_sessionNonce [HELLO_NONCE_SIZE]
 96-bit session nonce generated once per button press by sendHelloPacket().
 
static ChaChaPoly s_cipher
 ChaCha20-Poly1305 cipher instance — re-initialised per packet via clear().
 
static uint8_t pendingNoteIndex = 0
 Snapshot of the last transmitted note index — allows retransmission without re-reading PROGMEM.
 
static uint16_t pendingNoteDuration = 0
 Snapshot of the last transmitted note duration (ms) — allows retransmission without re-reading PROGMEM.
 
static uint32_t noteWaitStart = 0
 millis() timestamp marking the start of the current WAIT_BETWEEN_NOTES pause.
 
static bool btnLastRawState = HIGH
 Raw digitalRead() result from the previous call.
 
static bool btnStableState = HIGH
 Debounce-confirmed stable button state.
 
static uint32_t btnLastChangeMs = 0
 millis() timestamp of the last raw state transition.
 
static volatile uint32_t s_ringOscPulses = 0
 Ring oscillator pulse counter — incremented by the INT0 ISR on pin 2.
 

Detailed Description

SH2SC-EDT — Transmitter Node A ("The Conductor") firmware.

Part of the SH2SC-EDT project. Implements the C2P-ARQ protocol. Flash this sketch onto the TX Arduino Nano.

Responsibilities:

  • Read the start button (millis-based debounce on pin 3).
  • Walk through the Imperial March melody array packet by packet.
  • sendHelloPacket(): generate a CSPRNG nonce and broadcast a HelloPacket.
  • sendPacket(): full ChaCha20-Poly1305 pipeline (nonce derivation -> encrypt -> authenticate -> transmit DataPacket).
  • Stop-and-Wait ARQ: wait up to ACK_TIMEOUT_MS (50 ms) for ACK after each SEND. ACK -> advance melody (melodyIndex++, seqNum++). NACK or timeout -> retransmit the SAME packet with the SAME seqNum.
  • Self-Healing: after MAX_RETRIES, suspendSession() preserves melodyIndex and enters RECONNECTING; resumes transmission from the failure point on reconnect.

Definition in file TransmitterNode.ino.

Function Documentation

◆ drainRxFifo()

static void drainRxFifo ( )
inlinestatic

Discard all bytes currently waiting in the hardware UART RX FIFO.

Prevents stale NACK bytes — accumulated from noise bursts before the session started — from being misread as responses to the packet that is about to be transmitted. Called at the start of every send function (sendHelloPacket(), sendPacket(), sendFinPacket()) to guarantee that the first byte read after a send is always a fresh response.

Definition at line 104 of file TransmitterNode.ino.

Referenced by sendFinPacket(), sendHelloPacket(), and sendPacket().

+ Here is the caller graph for this function:

◆ formAndSendPacket()

void formAndSendPacket ( uint8_t  note_idx,
uint16_t  duration_ms 
)

Snapshot the note payload and delegate to sendPacket() for encryption and transmission.

High-level wrapper called by the SENDING FSM state.

Stores note_idx and duration_ms as the pending retransmit snapshot so that on NACK or timeout, the FSM can call sendPacket() directly without re-reading PROGMEM.

Parameters
note_idxNote index read from the PROGMEM melody table (0–20 or REST_INDEX=255).
duration_msNote duration in milliseconds read from the PROGMEM melody table.

Definition at line 209 of file TransmitterNode.ino.

References pendingNoteDuration, pendingNoteIndex, sendPacket(), and seqNum.

Referenced by tx_loop().

+ 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 (TX variant).

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.

Definition at line 345 of file TransmitterNode.ino.

Referenced by tx_setup().

+ Here is the caller graph for this function:

◆ lcd()

static LiquidCrystal_AIP31068_I2C lcd ( TX_LCD_ADDR  ,
TX_LCD_COLS  ,
TX_LCD_ROWS   
)
static

I2C LCD — 16 columns x 2 rows, Aip31068-compatible controller.

Referenced by suspendSession(), tx_setup(), and updateTxDisplay().

+ Here is the caller graph for this function:

◆ loop()

void loop ( )

Arduino sketch main loop — delegates to tx_loop() on every iteration.

Definition at line 714 of file TransmitterNode.ino.

References tx_loop().

+ Here is the call graph for this function:

◆ mixEntropy()

static uint32_t mixEntropy ( uint32_t  pool,
uint32_t  bits 
)
inlinestatic

One mixing step of the entropy accumulation sponge.

Left-rotates pool by 1 bit, then XOR-folds in bits. Left-rotation ensures each bit of the pool eventually influences all others, preventing entropy accumulation from being purely commutative.

Parameters
poolAccumulated entropy pool value from previous iterations.
bitsNew entropy bits to fold in.
Returns
Updated pool value after the rotation-XOR mix.

Definition at line 329 of file TransmitterNode.ino.

Referenced by generateEntropyPool().

+ Here is the caller graph for this function:

◆ onRingOscPulse()

static void onRingOscPulse ( )
static

INT0 interrupt service routine — counts ring oscillator rising edges.

Definition at line 318 of file TransmitterNode.ino.

References s_ringOscPulses.

Referenced by generateEntropyPool().

+ Here is the caller graph for this function:

◆ readButtonPress()

bool readButtonPress ( )

Read and debounce the start button (millis-based falling-edge detector).

Read and debounce the start button using a millis()-based filter.

Must be called every iteration of loop() so the debounce timer continues accumulating regardless of the active FSM state.

Returns
true exactly once per confirmed physical button press; false otherwise.

Definition at line 71 of file TransmitterNode.ino.

References btnLastChangeMs, btnLastRawState, btnStableState, DEBOUNCE_DELAY_MS, and TX_BUTTON_PIN.

Referenced by tx_loop().

+ Here is the caller graph for this function:

◆ sendFinPacket()

void sendFinPacket ( )

Construct and transmit a FLAG_FIN session-teardown packet.

Construct and transmit the FLAG_FIN session-close packet.

Architecture mirrors sendPacket() exactly: same per-packet nonce derivation (XOR bytes 10–11 with seqNum), same 3-byte AAD (FLAG_FIN + seq_num), same truncated 8-byte Poly1305 MAC. The plaintext payload is all-zeros; the flags byte in the AAD binds the MAC to FLAG_FIN, preventing a bit-flip attack from turning a data packet into a teardown signal. Called from SENDING_FIN; retransmitted on NACK or timeout until MAX_RETRIES is exhausted (which triggers suspendSession()).

Note
After receiving ACK for this packet, the caller MUST erase s_sessionNonce via memset() to complete forward-secrecy teardown.

Definition at line 266 of file TransmitterNode.ino.

References DATA_PAYLOAD_SIZE, drainRxFifo(), FLAG_FIN, DataPacket::flags, HELLO_NONCE_SIZE, DataPacket::mac, MASTER_PSK, DataPacket::payload, s_cipher, s_sessionNonce, DataPacket::seq_num, seqNum, SYNC_BYTE_1, SYNC_BYTE_2, and TRUNCATED_MAC_SIZE.

Referenced by tx_loop().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ sendHelloPacket()

void sendHelloPacket ( )

Generate a fresh 12-byte CSPRNG nonce and broadcast it as a FLAG_SYN HelloPacket.

Generate a fresh 12-byte CSPRNG session nonce and transmit it as a HelloPacket.

Called once per button press (from IDLE state) and once per reconnect attempt (from RECONNECTING state) — always before any DataPacket is transmitted. Calls drainRxFifo() before writing to the UART to purge stale NACKs. Stores the generated nonce in s_sessionNonce for subsequent per-packet IV derivation by sendPacket() and sendFinPacket().

Definition at line 118 of file TransmitterNode.ino.

References drainRxFifo(), FLAG_SYN, HelloPacket::flags, getSecureRandom32(), HELLO_NONCE_SIZE, HelloPacket::nonce, s_sessionNonce, SYNC_BYTE_1, and SYNC_BYTE_2.

Referenced by tx_loop().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ sendPacket()

void sendPacket ( uint8_t  noteIndex,
uint16_t  noteDurationMs,
uint16_t  seqNumber 
)

Definition at line 142 of file TransmitterNode.ino.

References DATA_PAYLOAD_SIZE, drainRxFifo(), FLAG_DAT, DataPacket::flags, HELLO_NONCE_SIZE, DataPacket::mac, MASTER_PSK, DataPacket::payload, s_cipher, s_sessionNonce, DataPacket::seq_num, SYNC_BYTE_1, SYNC_BYTE_2, and TRUNCATED_MAC_SIZE.

Referenced by formAndSendPacket().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ setup()

void setup ( )

Arduino sketch entry point — delegates to tx_setup().

Definition at line 712 of file TransmitterNode.ino.

References tx_setup().

+ Here is the call graph for this function:

◆ suspendSession()

void suspendSession ( )

Perform an unclean session teardown and enter the Self-Healing reconnect loop.

Suspend the current C2P-ARQ session and enter the Self-Healing reconnect loop.

Called when retryCount reaches MAX_RETRIES in any WAITING_* FSM state. Unlike a clean FIN close, this is an unclean abort — the receiver vanished without sending FLAG_FIN. Actions performed:

  1. Erases s_sessionNonce via memset() (forward secrecy — the old nonce must not be reused after an unclean close).
  2. Resets seqNum and retryCount to 0.
  3. Preserves melodyIndex — the defining Self-Healing property of SH2SC-EDT; transmission will resume from the exact note of failure on reconnect.
  4. Transitions the FSM to TxState::RECONNECTING.
  5. Pre-arms lastReconnectAttempt so the first auto-ping fires after a full RECONNECT_INTERVAL_MS, giving the receiver time to reboot.
    Note
    The delay(2000) inside this function is the only permitted blocking call outside of setup() — it holds the LCD error message visible to the operator.

Definition at line 426 of file TransmitterNode.ino.

References currentState, HELLO_NONCE_SIZE, lastReconnectAttempt, lcd(), RECONNECTING, retryCount, s_sessionNonce, and seqNum.

Referenced by tx_loop().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ tx_loop()

void tx_loop ( )

Execute one non-blocking C2P-ARQ FSM tick. Called repeatedly from loop().

Execute one non-blocking FSM tick for the Transmitter node.

Dispatches to the handler for the active TxState. Each invocation performs at most one FSM transition and returns immediately. No delay() calls are permitted in this function or any callee (except the intentional 2-second error display in suspendSession()).

Definition at line 486 of file TransmitterNode.ino.

References ACK_BYTE, ACK_TIMEOUT_MS, ackWaitStart, currentState, formAndSendPacket(), HELLO_NONCE_SIZE, IDLE, lastReconnectAttempt, MAX_RETRIES, melody, MELODY_LENGTH, melodyIndex, NACK_BYTE, noteWaitStart, pendingNoteDuration, pendingNoteIndex, readButtonPress(), RECONNECT_INTERVAL_MS, RECONNECTING, retryCount, s_sessionNonce, sendFinPacket(), sendHelloPacket(), SENDING, SENDING_FIN, SENDING_HELLO, seqNum, suspendSession(), updateTxDisplay(), WAIT_BETWEEN_NOTES, WAITING_ACK, WAITING_FIN_ACK, and WAITING_HELLO_ACK.

Referenced by loop().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ tx_setup()

void tx_setup ( )

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

Initialise TX hardware: UART, I2C LCD, button pin, ring-oscillator entropy.

Initialises UART at BAUD_RATE (9600), configures TX_BUTTON_PIN as INPUT_PULLUP, initialises the I2C LCD, harvests 256-bit hardware entropy with generateEntropyPool(), seeds the ChaCha20 CSPRNG with initCSPRNG(), scrubs the entropy seed buffer from the stack, and sets the initial FSM state to IDLE.

Definition at line 454 of file TransmitterNode.ino.

References BAUD_RATE, currentState, generateEntropyPool(), IDLE, initCSPRNG(), lcd(), retryCount, seqNum, TX_BUTTON_PIN, and updateTxDisplay().

Referenced by setup().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ updateTxDisplay()

void updateTxDisplay ( TxState  state,
uint16_t  seqNumber,
uint8_t  retries 
)

Refresh the TX LCD with the current ARQ status and FSM state label.

Row 0: PKT: <seqNumber> RTY:<retries> (live ARQ visibility for the operator). Row 1: Human-readable FSM state name. Must only be called on FSM state transitions — NOT in a tight loop — to avoid I2C bus saturation.

Parameters
stateCurrent TxState to display on row 1.
seqNumberCurrent packet sequence number to display on row 0.
retriesCurrent consecutive retry count to display on row 0.

Definition at line 228 of file TransmitterNode.ino.

References IDLE, lcd(), RECONNECTING, SENDING, SENDING_FIN, SENDING_HELLO, WAIT_BETWEEN_NOTES, WAITING_ACK, WAITING_FIN_ACK, and WAITING_HELLO_ACK.

Referenced by tx_loop(), and tx_setup().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ ackWaitStart

uint32_t ackWaitStart = 0
static

millis() timestamp when the current ACK-wait window opened.

Definition at line 32 of file TransmitterNode.ino.

Referenced by tx_loop().

◆ btnLastChangeMs

uint32_t btnLastChangeMs = 0
static

millis() timestamp of the last raw state transition.

Definition at line 63 of file TransmitterNode.ino.

Referenced by readButtonPress().

◆ btnLastRawState

bool btnLastRawState = HIGH
static

Raw digitalRead() result from the previous call.

Definition at line 61 of file TransmitterNode.ino.

Referenced by readButtonPress().

◆ btnStableState

bool btnStableState = HIGH
static

Debounce-confirmed stable button state.

Definition at line 62 of file TransmitterNode.ino.

Referenced by readButtonPress().

◆ currentState

TxState currentState = TxState::IDLE
static

Active FSM state.

Definition at line 28 of file TransmitterNode.ino.

Referenced by suspendSession(), tx_loop(), and tx_setup().

◆ lastReconnectAttempt

uint32_t lastReconnectAttempt = 0
static

Auto-reconnect countdown; a new HelloPacket is broadcast when (millis() - lastReconnectAttempt) >= RECONNECT_INTERVAL_MS.

Definition at line 35 of file TransmitterNode.ino.

Referenced by suspendSession(), and tx_loop().

◆ melodyIndex

uint16_t melodyIndex = 0
static

Current position in melody[][] (preserved across RECONNECTING).

Definition at line 29 of file TransmitterNode.ino.

Referenced by tx_loop().

◆ noteWaitStart

uint32_t noteWaitStart = 0
static

millis() timestamp marking the start of the current WAIT_BETWEEN_NOTES pause.

Definition at line 58 of file TransmitterNode.ino.

Referenced by tx_loop().

◆ pendingNoteDuration

uint16_t pendingNoteDuration = 0
static

Snapshot of the last transmitted note duration (ms) — allows retransmission without re-reading PROGMEM.

Definition at line 55 of file TransmitterNode.ino.

Referenced by formAndSendPacket(), and tx_loop().

◆ pendingNoteIndex

uint8_t pendingNoteIndex = 0
static

Snapshot of the last transmitted note index — allows retransmission without re-reading PROGMEM.

Definition at line 53 of file TransmitterNode.ino.

Referenced by formAndSendPacket(), and tx_loop().

◆ retryCount

uint8_t retryCount = 0
static

Consecutive retransmission counter (displayed on LCD).

Definition at line 31 of file TransmitterNode.ino.

Referenced by suspendSession(), tx_loop(), and tx_setup().

◆ s_cipher

ChaChaPoly s_cipher
static

ChaCha20-Poly1305 cipher instance — re-initialised per packet via clear().

Definition at line 50 of file TransmitterNode.ino.

Referenced by sendFinPacket(), and sendPacket().

◆ s_ringOscPulses

volatile uint32_t s_ringOscPulses = 0
static

Ring oscillator pulse counter — incremented by the INT0 ISR on pin 2.

Note
Declared volatile to prevent the compiler from caching the value in a register.

Definition at line 315 of file TransmitterNode.ino.

Referenced by generateEntropyPool(), and onRingOscPulse().

◆ s_sessionNonce

uint8_t s_sessionNonce[HELLO_NONCE_SIZE]
static

96-bit session nonce generated once per button press by sendHelloPacket().

Per-packet IV derivation:

packetNonce[0..11] = s_sessionNonce[0..11]
packetNonce[10] ^= (seqNum >> 8) & 0xFF
packetNonce[11] ^= seqNum & 0xFF
static uint16_t seqNum
Packet sequence number (0–65535, wraps).
static uint8_t s_sessionNonce[HELLO_NONCE_SIZE]
96-bit session nonce generated once per button press by sendHelloPacket().

Erased with memset() in suspendSession() and on WAITING_FIN_ACK success (forward secrecy).

Definition at line 47 of file TransmitterNode.ino.

Referenced by sendFinPacket(), sendHelloPacket(), sendPacket(), suspendSession(), and tx_loop().

◆ seqNum

uint16_t seqNum = 0
static

Packet sequence number (0–65535, wraps).

Definition at line 30 of file TransmitterNode.ino.

Referenced by authenticateAndPlay(), formAndSendPacket(), processFinPacket(), sendFinPacket(), suspendSession(), tx_loop(), tx_setup(), and updateRxDisplay().