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

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

#include <Arduino.h>
#include "protocol.h"
#include "csprng.h"
#include <ChaChaPoly.h>
+ Include dependency graph for transmitter.h:
+ This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Enumerations

enum class  TxState : uint8_t {
  IDLE , RECONNECTING , SENDING_HELLO , WAITING_HELLO_ACK ,
  SENDING , WAITING_ACK , WAIT_BETWEEN_NOTES , SENDING_FIN ,
  WAITING_FIN_ACK
}
 Finite State Machine states for the C2P-ARQ Transmitter (Node A). More...
 

Functions

void tx_setup ()
 Initialise TX hardware: UART, I2C LCD, button pin, ring-oscillator entropy.
 
void tx_loop ()
 Execute one non-blocking FSM tick for the Transmitter node.
 
void suspendSession ()
 Suspend the current C2P-ARQ session and enter the Self-Healing reconnect loop.
 
bool readButtonPress ()
 Read and debounce the start button using a millis()-based filter.
 
void sendHelloPacket ()
 Generate a fresh 12-byte CSPRNG session nonce and transmit it as a HelloPacket.
 
void sendPacket (uint8_t noteIndex, uint16_t noteDurationMs, uint8_t seqNum)
 Encrypt and transmit a single DataPacket for the given note.
 
void formAndSendPacket (uint8_t note_idx, uint16_t duration_ms)
 High-level wrapper called by the SENDING FSM state.
 
void sendFinPacket ()
 Construct and transmit the FLAG_FIN session-close packet.
 
void updateTxDisplay (TxState state, uint16_t seqNum, uint8_t retries)
 Refresh the TX LCD with the current ARQ status and FSM state label.
 
void generateEntropyPool (uint8_t *outputSeed)
 Harvest 256 bits of hardware entropy and write them to outputSeed.
 

Variables

const uint8_t TX_BUTTON_PIN = 3
 Tactile start button (INPUT_PULLUP, active LOW).
 
const uint8_t ENTROPY_RING_OSC_PIN = 2
 Hardware ring oscillator entropy source (INT0).
 
const uint8_t TX_LCD_ADDR = 0x3E
 I2C address of the Aip31068 16x2 LCD display.
 
const uint8_t TX_LCD_COLS = 16
 Number of columns on the TX LCD.
 
const uint8_t TX_LCD_ROWS = 2
 Number of rows on the TX LCD.
 
const uint16_t DEBOUNCE_DELAY_MS = 50
 
const uint32_t RECONNECT_INTERVAL_MS = 2000
 Auto-reconnect HelloPacket broadcast interval (ms) while in RECONNECTING state.
 

Detailed Description

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

Part of the SH2SC-EDT project. Implements the C2P-ARQ protocol. Declares the TX FSM states, hardware pin constants, ARQ timing constants, and all public functions used by TransmitterNode.ino. Stores the melody in PROGMEM, encrypts packets via ChaCha20-Poly1305, and drives the full C2P-ARQ session lifecycle (SYN -> DAT -> FIN).

Definition in file transmitter.h.

Enumeration Type Documentation

◆ TxState

enum class TxState : uint8_t
strong

Finite State Machine states for the C2P-ARQ Transmitter (Node A).

The entire TX logic is driven by this FSM. No blocking delays are permitted; each state transition is handled in a single non-blocking pass through tx_loop().

Canonical lifecycle:

(any WAITING_* + MAX_RETRIES exhausted) -> suspendSession() -> RECONNECTING
RECONNECTING -> (HelloPacket ACK) -> SENDING [Auto-Resume]
const uint8_t MAX_RETRIES
Retransmission limit per packet; exhausting this triggers suspendSession().
Definition protocol.h:158
Session-open handshake packet (FLAG_SYN).
Definition protocol.h:93
void suspendSession()
Suspend the current C2P-ARQ session and enter the Self-Healing reconnect loop.
@ SENDING_FIN
All notes delivered; transmitting the FLAG_FIN teardown packet.
@ WAITING_ACK
DataPacket sent; listening on the feedback line for ACK or NACK.
@ SENDING_HELLO
Generating a CSPRNG nonce and transmitting the SYN handshake packet.
@ IDLE
Waiting for a button press to begin melody playback.
@ SENDING
Constructing, encrypting, and transmitting the current DataPacket.
@ WAITING_HELLO_ACK
HelloPacket sent; awaiting RX nonce-acceptance ACK.
@ WAIT_BETWEEN_NOTES
ACK received; holding the inter-note gap before advancing melodyIndex.
@ RECONNECTING
Link lost mid-melody; broadcasting HelloPackets every RECONNECT_INTERVAL_MS.
@ WAITING_FIN_ACK
FIN packet sent; awaiting RX acknowledgement before erasing the session nonce.

stateDiagram-v2 direction LR [*] --> IDLE IDLE --> SENDING_HELLO : button press SENDING_HELLO --> WAITING_HELLO_ACK : HelloPacket sent WAITING_HELLO_ACK --> SENDING : ACK received WAITING_HELLO_ACK --> RECONNECTING : MAX_RETRIES exhausted SENDING --> WAITING_ACK : DataPacket sent WAITING_ACK --> WAIT_BETWEEN_NOTES : ACK received WAITING_ACK --> RECONNECTING : MAX_RETRIES exhausted WAIT_BETWEEN_NOTES --> SENDING : gap elapsed WAIT_BETWEEN_NOTES --> SENDING_FIN : all notes done SENDING_FIN --> WAITING_FIN_ACK : FIN packet sent WAITING_FIN_ACK --> IDLE : ACK + nonce erased WAITING_FIN_ACK --> RECONNECTING : MAX_RETRIES exhausted RECONNECTING --> SENDING_HELLO : auto-ping every 2s (melodyIndex preserved)

Enumerator
IDLE 

Waiting for a button press to begin melody playback.

RECONNECTING 

Link lost mid-melody; broadcasting HelloPackets every RECONNECT_INTERVAL_MS.

SENDING_HELLO 

Generating a CSPRNG nonce and transmitting the SYN handshake packet.

WAITING_HELLO_ACK 

HelloPacket sent; awaiting RX nonce-acceptance ACK.

SENDING 

Constructing, encrypting, and transmitting the current DataPacket.

WAITING_ACK 

DataPacket sent; listening on the feedback line for ACK or NACK.

WAIT_BETWEEN_NOTES 

ACK received; holding the inter-note gap before advancing melodyIndex.

SENDING_FIN 

All notes delivered; transmitting the FLAG_FIN teardown packet.

WAITING_FIN_ACK 

FIN packet sent; awaiting RX acknowledgement before erasing the session nonce.

Definition at line 72 of file transmitter.h.

Function Documentation

◆ formAndSendPacket()

void formAndSendPacket ( uint8_t  note_idx,
uint16_t  duration_ms 
)

High-level wrapper called by the SENDING FSM state.

Reads noteIndex and duration_ms from PROGMEM at the current melodyIndex, snapshots them into module-level retransmit variables, then delegates to sendPacket(). On NACK or timeout the FSM calls sendPacket() again directly (retransmit path reuses the snapshot).

Parameters
note_idxNote index loaded from the PROGMEM melody table.
duration_msDuration in milliseconds loaded from the PROGMEM melody table.

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.

Fills outputSeed[32] (8 × 32-bit words) by XOR-combining seven independent entropy sources per word:

  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. micros() at button-press time (human-timing jitter, TX-only source).
  7. Arduino software PRNG random() (obfuscation layer).

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

Parameters
outputSeedPointer to a 32-byte buffer that will receive the entropy pool. Must be valid and writable. Caller should pass the buffer directly to initCSPRNG() and then scrub it if desired.
Note
Invoke once per button press, AFTER readButtonPress() returns true.

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.

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.

+ Here is the call graph for this function:

◆ readButtonPress()

bool readButtonPress ( )

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

Returns true exactly once per physical button press (falling edge on INPUT_PULLUP). Debounce window is DEBOUNCE_DELAY_MS milliseconds.

Returns
true on confirmed button press; false otherwise.

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 the FLAG_FIN session-close packet.

Cryptographically identical to sendPacket(): same per-packet nonce derivation (XOR bytes 10–11 with seqNum), same 3-byte AAD (flags + seq_num), same truncated Poly1305 MAC. The plaintext payload is all-zeros ({0, 0}) — the AEAD pipeline still runs in full. Called from SENDING_FIN state; retransmitted on NACK or timeout.

Note
On successful FIN ACK, the caller must memset(s_sessionNonce, 0, 12) to complete the forward-secrecy teardown.

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 session nonce and transmit it as a HelloPacket.

Calls getSecureRandom32() three times to fill s_sessionNonce[12], then broadcasts it in a FLAG_SYN HelloPacket preceded by the sync preamble (0xAA 0x55). Also calls drainRxFifo() before transmitting to discard any stale ACK/NACK bytes accumulated from prior noise bursts. The nonce is stored internally for use by subsequent sendPacket() calls.

Note
Must be called ONCE per button press or per RECONNECTING attempt, before any sendPacket() call in the new session.

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,
uint8_t  seqNum 
)

Encrypt and transmit a single DataPacket for the given note.

Derives the per-packet IV by copying s_sessionNonce and XOR-ing bytes 10–11 with the 16-bit seqNum, then runs the full ChaChaPoly pipeline:

clear() -> setKey(MASTER_PSK, 32) -> setIV(packetNonce, 12)
-> addAuthData({flags, seq_lo, seq_hi}, 3)
-> encrypt(plaintext, ciphertext, DATA_PAYLOAD_SIZE)
-> computeTag(fullMac, AUTH_TAG_SIZE)
const uint8_t AUTH_TAG_SIZE
Full Poly1305 tag length in bytes (computed internally, not fully transmitted).
Definition protocol.h:55
const uint8_t DATA_PAYLOAD_SIZE
Byte size of the encrypted payload in a DataPacket.
Definition protocol.h:63
const uint8_t MASTER_PSK[32]
256-bit (32-byte) Pre-Shared Master Key for ChaCha20-Poly1305 AEAD.
Definition protocol.h:27

Only the first TRUNCATED_MAC_SIZE bytes of the tag are transmitted. Sends sync preamble (0xAA 0x55) + 15-byte DataPacket body (17 bytes total).

Parameters
noteIndexNote index to encrypt (0–20 for a pitch, REST_INDEX=255 for silence).
noteDurationMsDuration of the note in milliseconds (stored directly as uint16_t).
seqNum16-bit packet sequence number; used for per-packet nonce derivation.

◆ suspendSession()

void suspendSession ( )

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

Called automatically when retryCount reaches MAX_RETRIES in any WAITING_* FSM state. Performs the following operations:

  1. memset(s_sessionNonce, 0x00, HELLO_NONCE_SIZE) — erases the session nonce from RAM (forward secrecy; prevents key material leakage).
  2. Resets retryCount to 0.
  3. Preserves melodyIndex — the critical Self-Healing state that allows transmission to resume from the exact note of failure.
  4. Transitions the FSM to TxState::RECONNECTING.
    Note
    The preserved melodyIndex is the defining "self-healing" property of SH2SC-EDT.
    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 FSM tick for the Transmitter node.

Dispatches to the handler for the current TxState. Each invocation performs at most one state transition and returns immediately — no delay() calls are permitted anywhere in this function or its callees.

Note
Must be called repeatedly from Arduino 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: UART, I2C LCD, button pin, ring-oscillator entropy.

Note
Must be called once from Arduino 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: <seqNum> RTY:<retries>
  • Row 1: Human-readable FSM state label. Must be called only on FSM state transitions — NOT in a tight loop — to avoid I2C bus saturation.
    Parameters
    stateCurrent TxState to display on row 1.
    seqNumCurrent packet sequence number to display on row 0.
    retriesCurrent consecutive retry count to display on row 0.
    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: