18#include <LiquidCrystal_AIP31068_I2C.h>
159 lcd.print(macOk ?
" OK" :
" MAC!");
172 while (Serial.available() > 0) {
334 packetNonce[10] ^=
static_cast<uint8_t
>((
seqNum >> 8u) & 0xFFu);
335 packetNonce[11] ^=
static_cast<uint8_t
>(
seqNum & 0xFFu);
339 const uint8_t aad[3] = {
341 static_cast<uint8_t
>(
seqNum & 0xFFu),
342 static_cast<uint8_t
>((
seqNum >> 8u) & 0xFFu)
349 s_cipher.addAuthData(aad,
sizeof(aad));
352 uint16_t plaintext[2];
354 reinterpret_cast<uint8_t*
>(plaintext),
355 reinterpret_cast<const uint8_t*
>(pkt->
payload),
361 uint8_t expectedMac[16];
362 s_cipher.computeTag(expectedMac, 16u);
371 lcd.print(
"MAC FAIL! NACK");
384 const uint16_t noteIndex = plaintext[0];
385 const uint16_t durationMs = plaintext[1];
422 packetNonce[10] ^=
static_cast<uint8_t
>((
seqNum >> 8u) & 0xFFu);
423 packetNonce[11] ^=
static_cast<uint8_t
>(
seqNum & 0xFFu);
426 const uint8_t aad[3] = {
428 static_cast<uint8_t
>(
seqNum & 0xFFu),
429 static_cast<uint8_t
>((
seqNum >> 8u) & 0xFFu)
436 s_cipher.addAuthData(aad,
sizeof(aad));
444 reinterpret_cast<const uint8_t*
>(pkt->
payload),
449 uint8_t expectedMac[16];
450 s_cipher.computeTag(expectedMac, 16u);
458 lcd.print(
"FIN MAC FAIL!");
460 lcd.print(
"NACK sent");
471 lcd.print(
"SESSION CLOSED");
494void startNote(uint16_t frequencyHz, uint16_t durationMs) {
524static inline uint32_t
mixEntropy(uint32_t pool, uint32_t bits) {
525 return ((pool << 1u) | (pool >> 31u)) ^ bits;
548 const uint8_t* sramBase =
reinterpret_cast<const uint8_t*
>(0x0100);
550 for (uint8_t wordIndex = 0; wordIndex < 8u; ++wordIndex) {
562 for (uint8_t i = 0; i < 8u; ++i) {
563 pool =
mixEntropy(pool, sramBase[wordIndex * 8u + i]);
568 const uint8_t savedAdmux = ADMUX;
569 ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3);
572 ADCSRA |= _BV(ADSC);
while (ADCSRA & _BV(ADSC)) {}
573 uint8_t adcEntropy = 0;
574 for (uint8_t i = 0; i < 8u; ++i) {
575 ADCSRA |= _BV(ADSC);
while (ADCSRA & _BV(ADSC)) {}
576 adcEntropy =
static_cast<uint8_t
>((adcEntropy << 1u) | (ADCL & 0x01u));
583 pool =
mixEntropy(pool,
static_cast<uint32_t
>(TCNT1));
587 uint8_t a0Entropy = 0;
588 for (uint8_t i = 0; i < 8u; ++i) {
589 a0Entropy =
static_cast<uint8_t
>(
590 (a0Entropy << 1u) | (static_cast<uint8_t>(analogRead(A0)) & 0x01u)
599 pool =
mixEntropy(pool,
static_cast<uint32_t
>(random()));
602 outputSeed[wordIndex * 4u + 0u] =
static_cast<uint8_t
>(pool);
603 outputSeed[wordIndex * 4u + 1u] =
static_cast<uint8_t
>(pool >> 8u);
604 outputSeed[wordIndex * 4u + 2u] =
static_cast<uint8_t
>(pool >> 16u);
605 outputSeed[wordIndex * 4u + 3u] =
static_cast<uint8_t
>(pool >> 24u);
637 uint8_t entropySeed[32];
640 memset(entropySeed, 0,
sizeof(entropySeed));
695 lcd.print(
"TIMEOUT! DROP");
697 lcd.print(
"Session reset");
705 while (Serial.available() > 0) {
706 const uint8_t inByte =
static_cast<uint8_t
>(Serial.read());
static volatile uint32_t s_ringOscPulses
Pulse counter incremented by the ring oscillator ISR on INT0 (pin 2).
void authenticateAndPlay(const DataPacket *pkt)
Run the full ChaCha20-Poly1305 pipeline; play the note only on MAC success.
void processFinPacket(const DataPacket *pkt)
Authenticate and process a FLAG_FIN session-teardown packet.
static void onRingOscPulse()
Ring oscillator ISR — increments pulse counter on each RISING edge.
static uint32_t noteStartMs
Timestamp (millis()) when the current note started playing.
static LiquidCrystal_AIP31068_I2C lcd(RX_LCD_ADDR, RX_LCD_COLS, RX_LCD_ROWS)
I2C LCD display object (Aip31068, 16×2, address 0x3E).
static bool isShowingError
True when the LCD is currently showing a MAC-error message.
static RxState currentState
Tracks the current RxState for LCD display updates; independent of the byte-parser FSM.
ParseState
Four-state byte-level frame assembly FSM states.
void rx_loop()
Execute one non-blocking C2P-ARQ FSM tick for the Receiver node.
static uint32_t errorDisplayStart
Timestamp (millis()) when the MAC-error display was last activated.
void processReceivedByte(uint8_t inByte)
Consume one incoming UART byte and advance the frame-assembly FSM.
const uint16_t RX_PARSER_TIMEOUT_MS
Stale byte accumulation timeout for the byte-level frame parser.
void startNote(uint16_t frequencyHz, uint16_t durationMs)
Start non-blocking note playback on the piezo buzzer.
static uint16_t noteLengthMs
Duration in milliseconds that the current note should sound.
static uint32_t mixEntropy(uint32_t pool, uint32_t bits)
Single-step cryptographic entropy mixer — rotate-left XOR fold.
void setup()
Arduino entry point — delegates to rx_setup().
void processHelloBody()
Store the received session nonce and install MASTER_PSK into the cipher.
static uint32_t current_timeout_limit
Current Dynamic Watchdog timeout in milliseconds.
static uint8_t expected_length
Total expected bytes for the current frame; set in READ_TYPE state.
static uint8_t s_sessionNonce[HELLO_NONCE_SIZE]
12-byte session nonce delivered by the last accepted HelloPacket (FLAG_SYN).
static bool s_sessionActive
Session active flag — set after a valid HelloPacket handshake; cleared on FIN or watchdog timeout.
static uint8_t rx_buffer[sizeof(DataPacket)]
Raw receive buffer sized to hold the largest frame body (DataPacket, 15 bytes).
static const uint16_t universal_notes[NOTE_DICT_SIZE]
Universal note frequency dictionary for the C2P-ARQ Receiver.
void generateEntropyPool(uint8_t *outputSeed)
Harvest 256 bits of hardware entropy and fill outputSeed (RX variant).
void stopNote()
Stop the currently playing note and clear the playback flag.
static void resetParser()
Flush the UART RX FIFO and reset the byte-parser FSM to WAIT_AA.
static uint8_t rx_index
Number of bytes written to rx_buffer for the frame currently being collected.
static uint32_t last_valid_packet_time
Timestamp (millis()) of the most recently successfully authenticated DataPacket.
static ParseState parseState
Current byte-parser FSM state; reset to WAIT_AA by resetParser().
static ChaChaPoly s_cipher
ChaCha20-Poly1305 cipher instance; re-initialised per packet via clear().
void updateRxDisplay(RxState state, uint16_t seqNum, bool macOk)
Refresh the RX LCD with the current FSM state and last packet result.
const uint32_t NETWORK_GRACE_PERIOD_MS
Network grace period added to the last note's duration for the Dynamic Smart Watchdog.
static uint32_t rxLastByteMs
Timestamp (millis()) of the most recently received UART byte.
const uint16_t CHK_ERR_DISPLAY_MS
Duration of the MAC-error message shown on the LCD before returning to idle display.
static bool isPlayingNote
True while a note is actively sounding; cleared by stopNote().
void rx_setup()
Initialise RX hardware, seed the CSPRNG, and display the idle screen.
void loop()
Arduino main loop — delegates to rx_loop().
void initCSPRNG(const uint8_t *hardwareSeed)
Seed the ChaCha20 CSPRNG with 256 bits of hardware entropy.
static uint16_t seqNum
Packet sequence number (0–65535, wraps).
const uint8_t FLAG_FIN
Session close — instructs RX to teardown the current session.
const uint8_t FLAG_DAT
Data frame — DataPacket carrying an encrypted note (index + duration).
const uint8_t FLAG_SYN
Session open — HelloPacket carrying the 12-byte CSPRNG nonce.
const uint8_t HELLO_NONCE_SIZE
ChaCha20 IV length in bytes (IETF 96-bit nonce format).
const uint8_t DATA_PAYLOAD_SIZE
Byte size of the encrypted payload in a DataPacket.
const uint8_t TRUNCATED_MAC_SIZE
Bytes of Poly1305 MAC actually placed on the wire (first 8 of 16).
const uint8_t REST_INDEX
Special note index value representing a rest (silence).
const uint8_t MASTER_PSK[32]
256-bit (32-byte) Pre-Shared Master Key for ChaCha20-Poly1305 AEAD.
const uint8_t RX_BUZZER_PIN
PWM-capable pin connected to the piezo buzzer.
const uint8_t RX_LCD_ADDR
I2C address of the Aip31068 16x2 LCD display.
const uint8_t RX_LCD_ROWS
Number of rows on the RX LCD.
const uint8_t RX_LCD_COLS
Number of columns on the RX LCD.
const uint8_t NACK_BYTE
Negative acknowledgement — MAC mismatch; TX must retransmit.
const uint8_t ACK_BYTE
Positive acknowledgement — MAC verified, note played (or FIN accepted).
const uint8_t SYNC_BYTE_2
Second synchronisation byte (preamble marker).
const uint8_t SYNC_BYTE_1
First synchronisation byte (preamble marker).
const uint8_t ENTROPY_RING_OSC_PIN
Hardware ring oscillator entropy source (INT0).
const uint16_t BAUD_RATE
UART baud rate in bps (8N1). Chosen for clear bit-width visibility on SimulIDE oscilloscope.
SH2SC-EDT — Receiver Node B ("The Synthesizer") public interface.
const uint8_t NOTE_DICT_SIZE
Size of the universal note frequency dictionary on RX.
RxState
Byte-level parser Finite State Machine states for the C2P-ARQ Receiver (Node B).
@ 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).
Authenticated data packet (FLAG_DAT) and session-close packet (FLAG_FIN).
uint16_t payload[2]
ChaCha20-encrypted payload: [0] = note_index, [1] = duration_ms.
uint8_t flags
Frame type — FLAG_DAT (0x02) for data; FLAG_FIN (0x04) for teardown. Part of plaintext AAD.
uint16_t seq_num
16-bit packet sequence number (little-endian). Part of plaintext AAD.
uint8_t mac[TRUNCATED_MAC_SIZE]
First 8 bytes of the Poly1305 authentication tag.
Session-open handshake packet (FLAG_SYN).