Self-Healing Hardware and Software Complex for Encrypted Data Transmission
SH2SC-EDT is a fault-tolerant, cryptographically secured communication complex built on two Arduino Nano (ATmega328P) microcontrollers. It implements a custom transport/session-layer protocol — C2P-ARQ (ChaCha20-Poly1305 Automatic Repeat reQuest) — that provides authenticated encryption (ChaCha20-Poly1305 AEAD), guaranteed packet delivery via Stop-and-Wait ARQ, and a full three-phase session lifecycle (SYN / DAT / FIN).
The defining property of the complex is Self-Healing: upon link disruption, the transmitter node preserves its position in the data stream and autonomously re-establishes the session from the exact point of failure without operator intervention and without data loss. All logic on both nodes is implemented as non-blocking Finite State Machines running at 100% real-time in SimulIDE. Dynamic memory allocation is architecturally prohibited; the entire firmware operates within the 2 KB SRAM budget of the ATmega328P.
- Authenticated Encryption (AEAD): ChaCha20 stream cipher with a truncated 8-byte Poly1305
MAC per packet. No packet is acted upon before MAC verification passes
memcmp. - Full Session Lifecycle: Three-phase C2P-ARQ session —
FLAG_SYNhandshake (nonce delivery),FLAG_DATdata transfer (Stop-and-Wait ARQ),FLAG_FINteardown (authenticated session close). - Per-Packet Nonce Derivation: Unique IV per packet derived locally from the session nonce
via XOR with the 16-bit sequence number across bytes
[10..11]. The IV is never transmitted. - Stop-and-Wait ARQ: Every
DataPacketrequires an explicitACK_BYTE(0x06). OnNACK_BYTE(0x15) or timeout, the transmitter retransmits the identical ciphertext with the sameseqNum. Retry limit:MAX_RETRIES = 50, timeout window:ACK_TIMEOUT_MS = 50 ms. - Self-Healing (Auto-Resume): On retry exhaustion,
suspendSession()erases the session nonce from RAM, preservesmelodyIndex, and entersTxState::RECONNECTING. A freshHelloPacketis broadcast everyRECONNECT_INTERVAL_MS = 2000 msuntil the link is restored. Transmission resumes from the preserved index with zero data loss. - Dynamic Smart Watchdog (RX): The receiver session timeout adapts after each authenticated
packet:
current_timeout_limit = durationMs + NETWORK_GRACE_PERIOD_MS(3000 ms). Absorbs the worst-case retry storm (50 x 50 ms = 2500 ms) without false session teardown. - FSM-First Architecture: All TX and RX logic is driven exclusively by
enum classstate machines. No blockingdelay()calls exist in the main loop. All timers usemillis()deltas. - Hardware CSPRNG: Session nonce generated by a ChaCha20-based CSPRNG seeded with 256 bits of hardware entropy (ring oscillator, uninitialised SRAM, on-die ADC, TCNT1, white-noise ADC).
All frames are preceded by a two-byte sync preamble (0xAA 0x55). The flags byte uses
bitmask matching (&), providing noise resilience against single-bit errors.
| Field | Size | Value | Description |
|---|---|---|---|
| SYNC1 | 1 byte | 0xAA |
Preamble byte 1 |
| SYNC2 | 1 byte | 0x55 |
Preamble byte 2 |
flags |
1 byte | FLAG_SYN = 0x01 |
Session open identifier |
nonce[12] |
12 bytes | CSPRNG | 96-bit IETF ChaCha20 session nonce |
| Field | Size | Value | Description |
|---|---|---|---|
| SYNC1 | 1 byte | 0xAA |
Preamble byte 1 |
| SYNC2 | 1 byte | 0x55 |
Preamble byte 2 |
flags |
1 byte | 0x02 / 0x04 |
FLAG_DAT or FLAG_FIN — AAD, not encrypted |
seq_num |
2 bytes | uint16_t LE |
Packet sequence number — AAD, not encrypted |
payload[4] |
4 bytes | ChaCha20 ciphertext | Encrypted uint16_t note_index + uint16_t duration_ms |
mac[8] |
8 bytes | Poly1305 truncated | First 8 bytes of the full 16-byte Poly1305 tag |
The 3-byte AAD (flags + seq_lo + seq_hi) is authenticated but not encrypted. Any
bit-flip in these fields causes MAC verification to fail.
For FLAG_FIN packets, payload is encrypted zeros. The RX pipeline must call
decrypt() into a discardBuf before computeTag() to advance the Poly1305 accumulator
correctly; skipping decrypt() produces an incorrect expected tag and results in a false NACK.
| Constant | Value | Description |
|---|---|---|
ACK_TIMEOUT_MS |
50 ms | Maximum wait for ACK before retransmission |
MAX_RETRIES |
50 | Consecutive retransmissions before suspendSession() |
RECONNECT_INTERVAL_MS |
2000 ms | HelloPacket broadcast interval in RECONNECTING state |
NETWORK_GRACE_PERIOD_MS |
3000 ms | Added to note duration for Dynamic Watchdog timeout |
BAUD_RATE |
9600 | UART speed (8N1) |
drainRxFifo() is called on TX before every sendHelloPacket(), sendPacket(), and
sendFinPacket(). It discards all bytes in the 64-byte hardware UART RX FIFO, preventing
stale NACKs accumulated during a noise burst from poisoning the response to the next packet.
On RX, resetParser() performs the symmetric operation after every MAC failure or parser
timeout, draining the FIFO and resetting the byte-level FSM to WAIT_AA.
Both nodes use the ChaChaPoly library (Rhys Weatherley, Arduino Crypto). The pipeline per
packet is identical on TX and RX:
flowchart LR
A([clear]) --> B([setKey<br/>MASTER_PSK, 32])
B --> C([setIV<br/>packetNonce, 12])
C --> D([addAuthData<br/>aad, 3 bytes])
D --> E([encrypt / decrypt<br/>payload, 4 bytes])
E --> F([computeTag<br/>mac, 16 bytes])
F --> G{"memcmp<br/>mac[0..7]"}
G -->|match| H([ACK<br/>process data])
G -->|mismatch| I([NACK<br/>resetParser])
Only the first 8 bytes of the 16-byte Poly1305 tag are transmitted (TRUNCATED_MAC_SIZE = 8).
Verification uses memcmp(expectedMac, pkt->mac, 8). Tag truncation is a deliberate trade-off
between wire overhead and security margin, acceptable for a 16-bit AVR channel.
The per-packet IV is derived without transmission:
uint8_t packetNonce[12];
memcpy(packetNonce, s_sessionNonce, 12);
packetNonce[10] ^= (uint8_t)((seqNum >> 8u) & 0xFF);
packetNonce[11] ^= (uint8_t)(seqNum & 0xFF);All 65,536 seqNum values produce a distinct IV within a session. A new s_sessionNonce
is generated by CSPRNG on every button press or RECONNECTING attempt, rendering cross-session
replay attacks computationally infeasible.
MASTER_PSK is compiled into both nodes and never transmitted. s_sessionNonce is the only
runtime secret and is zeroed at every session boundary:
memset(s_sessionNonce, 0x00, HELLO_NONCE_SIZE); // HELLO_NONCE_SIZE = 12This call is made unconditionally in four locations:
| Location | Trigger |
|---|---|
TX suspendSession() |
MAX_RETRIES exhausted in any WAITING_* state |
TX WAITING_FIN_ACK success |
FLAG_FIN acknowledged by RX |
RX processFinPacket() MAC OK |
Authenticated session close |
| RX Dynamic Watchdog timeout | TX disappeared without sending FLAG_FIN |
Key material is never logged to Serial, LCD, or any output channel.
stateDiagram-v2
[*] --> IDLE
IDLE --> SENDING_HELLO : button press
SENDING_HELLO --> WAITING_HELLO_ACK : HelloPacket sent
WAITING_HELLO_ACK --> SENDING : ACK received
WAITING_HELLO_ACK --> RECONNECTING : NACK / timeout × MAX_RETRIES\nsuspendSession()
SENDING --> WAITING_ACK : DataPacket sent
WAITING_ACK --> WAIT_BETWEEN_NOTES : ACK received
WAITING_ACK --> RECONNECTING : NACK / timeout × MAX_RETRIES\nsuspendSession()
RECONNECTING --> SENDING_HELLO : HelloPacket ACK received\n(auto-reconnect every 2 s)
WAIT_BETWEEN_NOTES --> SENDING : gap elapsed, notes remain
WAIT_BETWEEN_NOTES --> SENDING_FIN : all notes sent
SENDING_FIN --> WAITING_FIN_ACK : FinPacket sent
WAITING_FIN_ACK --> IDLE : ACK received\nmemset(s_sessionNonce)
WAITING_FIN_ACK --> RECONNECTING : NACK / timeout × MAX_RETRIES\nsuspendSession()
stateDiagram-v2
[*] --> WAIT_AA
WAIT_AA --> WAIT_55 : byte == 0xAA
WAIT_AA --> WAIT_AA : byte != 0xAA
WAIT_55 --> READ_TYPE : byte == 0x55
WAIT_55 --> WAIT_AA : byte != 0x55
READ_TYPE --> READ_PAYLOAD : flags byte read\n(FLAG_SYN / FLAG_DAT / FLAG_FIN)
READ_TYPE --> WAIT_AA : invalid byte
READ_PAYLOAD --> WAIT_AA : MAC OK → ACK, process packet
READ_PAYLOAD --> WAIT_AA : MAC FAIL → resetParser() + NACK\n(drains UART FIFO)
stateDiagram-v2
[*] --> WAITING_SYNC_1
WAITING_SYNC_1 --> WAITING_SYNC_2 : byte = 0xAA
WAITING_SYNC_2 --> WAITING_FOR_TYPE : byte = 0x55
WAITING_SYNC_2 --> WAITING_SYNC_1 : byte ≠ 0x55
WAITING_FOR_TYPE --> READING_HELLO : FLAG_SYN (0x01)
WAITING_FOR_TYPE --> READING_DATA : FLAG_DAT / FLAG_FIN
READING_HELLO --> GOT_HELLO : nonce[12] complete
READING_DATA --> GOT_DATA : payload[4] + mac[8] complete
GOT_HELLO --> WAITING_SYNC_1 : processHelloBody() · ACK · Watchdog armed (5 s)
GOT_DATA --> EXECUTING_ACTION : MAC pass
GOT_DATA --> WAITING_SYNC_1 : MAC fail · NACK · resetParser()
EXECUTING_ACTION --> WAITING_SYNC_1 : ACK · startNote() · Watchdog = durationMs + 3 s
state "Dynamic Watchdog timeout\nmemset(s_sessionNonce)\ns_sessionActive = false" as WDT
WAITING_FOR_TYPE --> WDT
READING_DATA --> WDT
EXECUTING_ACTION --> WDT
WDT --> WAITING_SYNC_1
| Component | Specification |
|---|---|
| Microcontrollers | 2x Arduino Nano (ATmega328P, 16 MHz, 2 KB SRAM, 32 KB Flash) |
| Displays | 2x Aip31068 I2C LCD 16x2, address 0x3E, pins A4/A5 |
| Buzzer | 1x Piezo speaker on PWM pin 9 (Node B only) |
| CSPRNG source | Hardware ring oscillator on INT0 (pin 2, both nodes) |
| Noise injector | XOR gate + Pulse Generator (1 kHz, 15%) + AND gate + Switch |
| Monitoring | Multi-channel oscilloscope, Time/Div ~200 us |
| Library | Purpose | Source |
|---|---|---|
Arduino Cryptography Library (Crypto) |
ChaCha20-Poly1305 AEAD | Rhys Weatherley |
LiquidCrystal_AIP31068 |
I2C LCD driver for Aip31068 controller | Andriy Golovnya |
- Arduino IDE 2.x with AVR board support package.
- SimulIDE 1.1.0 (or later) for circuit simulation and real-time validation.
- Compiler:
avr-g++with C++11 (-std=gnu++11).
sh2sc-edt/
|
+-- TransmitterNode/
| +-- TransmitterNode.ino Entry point: setup(), loop(), button debounce
| +-- transmitter.h TxState FSM, sendHelloPacket(), sendPacket(),
| | sendFinPacket(), suspendSession(), drainRxFifo()
| +-- protocol.h Shared structs (HelloPacket, DataPacket), flag
| | constants, MASTER_PSK, timing constants
| +-- csprng.h ChaCha20-based CSPRNG, hardware entropy pool,
| | 64-byte keystream cache
| +-- melody.h PROGMEM melody arrays (note indices, durations)
|
+-- ReceiverNode/
| +-- ReceiverNode.ino Entry point: setup(), loop(), note timer, watchdog
| +-- receiver.h RxState FSM, processReceivedByte(), ParseState,
| | authenticateAndPlay(), processFinPacket(),
| | resetParser(), Dynamic Watchdog
| +-- protocol.h Shared structs and constants (identical to TX)
| +-- csprng.h CSPRNG (RX variant, no button entropy source)
|
+-- docs/
| +-- main.qd Quarkdown technical documentation source
|
+-- shsc-arq.sim1 SimulIDE circuit file (primary simulation)
+-- README.md This file
| Constant | Value | Defined in | Description |
|---|---|---|---|
FLAG_SYN |
0x01 |
protocol.h |
Session open frame flag |
FLAG_DAT |
0x02 |
protocol.h |
Data frame flag |
FLAG_FIN |
0x04 |
protocol.h |
Session close frame flag |
ACK_BYTE |
0x06 |
protocol.h |
Positive acknowledgement |
NACK_BYTE |
0x15 |
protocol.h |
Negative acknowledgement |
SYNC_BYTE_1 |
0xAA |
protocol.h |
Frame preamble byte 1 |
SYNC_BYTE_2 |
0x55 |
protocol.h |
Frame preamble byte 2 |
HELLO_NONCE_SIZE |
12 |
protocol.h |
ChaCha20 IETF nonce length (bytes) |
TRUNCATED_MAC_SIZE |
8 |
protocol.h |
Transmitted Poly1305 tag length |
DATA_PAYLOAD_SIZE |
4 |
protocol.h |
Encrypted payload length (bytes) |
REST_INDEX |
255 |
protocol.h |
Sentinel index for silence/pause |
NOTE_DICT_SIZE |
21 |
receiver.h |
Size of universal frequency table |
MAX_RETRIES |
50 |
protocol.h |
ARQ retransmission limit |
ACK_TIMEOUT_MS |
50 |
protocol.h |
ACK wait window (ms) |
RECONNECT_INTERVAL_MS |
2000 |
transmitter.h |
HelloPacket broadcast interval |
NETWORK_GRACE_PERIOD_MS |
3000 |
receiver.h |
Watchdog grace period added to note duration |
Developed as project for the discipline "Computer Systems Architecture". The system demonstrates that a resource-constrained 8-bit AVR microcontroller can implement industry-grade cryptographic authentication (AEAD), a stateful session protocol, and application-layer fault recovery within a 2 KB SRAM budget. The SimulIDE noise injector (XOR gate + 1 kHz pulse generator) provides reproducible channel corruption for validating all five fault-tolerance scenarios: single-burst corruption, sustained jamming (Self-Healing proof), stale FIFO poisoning, Dynamic Watchdog teardown, and FLAG_FIN corruption under noise.