Reliable, secure, serial link protocol added.
This commit is contained in:
parent
ab69652a72
commit
0fcaae54c3
15 changed files with 2838 additions and 4 deletions
38
packet/Makefile
Normal file
38
packet/Makefile
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Packet Serial Transport Makefile for DJGPP cross-compilation
|
||||
|
||||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
AR = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ar
|
||||
RANLIB = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ranlib
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586
|
||||
|
||||
OBJDIR = ../obj/packet
|
||||
LIBDIR = ../lib
|
||||
|
||||
SRCS = packet.c
|
||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||
TARGET = $(LIBDIR)/libpacket.a
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) | $(LIBDIR)
|
||||
$(AR) rcs $@ $(OBJS)
|
||||
$(RANLIB) $@
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(LIBDIR):
|
||||
mkdir -p $(LIBDIR)
|
||||
|
||||
# Dependencies
|
||||
$(OBJDIR)/packet.o: packet.c packet.h ../rs232/rs232.h
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
215
packet/README.md
Normal file
215
packet/README.md
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
# Packet — Reliable Serial Transport
|
||||
|
||||
Packetized serial transport with HDLC-style framing, CRC-16 error
|
||||
detection, and a Go-Back-N sliding window protocol for reliable,
|
||||
ordered delivery over an unreliable serial link.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Application
|
||||
|
|
||||
[packet] framing, CRC, retransmit, ordering
|
||||
|
|
||||
[rs232] raw byte I/O
|
||||
```
|
||||
|
||||
The packet layer sits on top of an already-open rs232 COM port. It
|
||||
does not open or close the serial port itself.
|
||||
|
||||
## Frame Format
|
||||
|
||||
Before byte stuffing:
|
||||
|
||||
```
|
||||
[0x7E] [SEQ] [TYPE] [LEN_LO] [LEN_HI] [PAYLOAD...] [CRC_LO] [CRC_HI]
|
||||
```
|
||||
|
||||
| Field | Size | Description |
|
||||
|---------|---------|--------------------------------------|
|
||||
| `0x7E` | 1 byte | Frame delimiter (flag byte) |
|
||||
| `SEQ` | 1 byte | Sequence number (wrapping uint8) |
|
||||
| `TYPE` | 1 byte | Frame type (see below) |
|
||||
| `LEN` | 2 bytes | Payload length, little-endian |
|
||||
| Payload | 0-256 | Application data |
|
||||
| `CRC` | 2 bytes | CRC-16-CCITT over SEQ+TYPE+LEN+payload |
|
||||
|
||||
### Frame Types
|
||||
|
||||
| Type | Value | Description |
|
||||
|--------|-------|----------------------------------------------|
|
||||
| `DATA` | 0x00 | Data frame carrying application payload |
|
||||
| `ACK` | 0x01 | Cumulative acknowledgment (next expected seq) |
|
||||
| `NAK` | 0x02 | Negative ack (request retransmit from seq) |
|
||||
| `RST` | 0x03 | Connection reset |
|
||||
|
||||
### Byte Stuffing
|
||||
|
||||
The flag byte (`0x7E`) and escape byte (`0x7D`) are escaped within
|
||||
frame data:
|
||||
|
||||
- `0x7E` becomes `0x7D 0x5E`
|
||||
- `0x7D` becomes `0x7D 0x5D`
|
||||
|
||||
## Reliability
|
||||
|
||||
The protocol uses Go-Back-N with a configurable sliding window
|
||||
(1-8 slots, default 4):
|
||||
|
||||
- **Sender** assigns sequential numbers to each DATA frame and retains
|
||||
a copy in the retransmit buffer until acknowledged.
|
||||
- **Receiver** delivers frames in order. Out-of-order frames trigger a
|
||||
NAK for the expected sequence number.
|
||||
- **ACK** carries the next expected sequence number (cumulative).
|
||||
- **NAK** triggers retransmission of the requested frame and all
|
||||
subsequent unacknowledged frames.
|
||||
- **Timer-based retransmit** fires after 500 poll cycles if no ACK or
|
||||
NAK has been received.
|
||||
|
||||
## API Reference
|
||||
|
||||
### Types
|
||||
|
||||
```c
|
||||
// Receive callback — called for each verified, in-order packet
|
||||
typedef void (*PktRecvCallbackT)(void *ctx, const uint8_t *data, int len);
|
||||
|
||||
// Opaque connection handle
|
||||
typedef struct PktConnS PktConnT;
|
||||
```
|
||||
|
||||
### Constants
|
||||
|
||||
| Name | Value | Description |
|
||||
|-----------------------|-------|-------------------------------------|
|
||||
| `PKT_MAX_PAYLOAD` | 256 | Max payload bytes per packet |
|
||||
| `PKT_DEFAULT_WINDOW` | 4 | Default sliding window size |
|
||||
| `PKT_MAX_WINDOW` | 8 | Maximum sliding window size |
|
||||
| `PKT_SUCCESS` | 0 | Success |
|
||||
| `PKT_ERR_INVALID_PORT`| -1 | Invalid COM port |
|
||||
| `PKT_ERR_NOT_OPEN` | -2 | Connection not open |
|
||||
| `PKT_ERR_ALREADY_OPEN`| -3 | Connection already open |
|
||||
| `PKT_ERR_WOULD_BLOCK` | -4 | Operation would block |
|
||||
| `PKT_ERR_OVERFLOW` | -5 | Buffer overflow |
|
||||
| `PKT_ERR_INVALID_PARAM`| -6 | Invalid parameter |
|
||||
| `PKT_ERR_TX_FULL` | -7 | Transmit window full |
|
||||
| `PKT_ERR_NO_DATA` | -8 | No data available |
|
||||
|
||||
### Functions
|
||||
|
||||
#### pktOpen
|
||||
|
||||
```c
|
||||
PktConnT *pktOpen(int com, int windowSize,
|
||||
PktRecvCallbackT callback, void *callbackCtx);
|
||||
```
|
||||
|
||||
Creates a packetized connection over an already-open COM port.
|
||||
|
||||
- `com` — RS232 port index (`RS232_COM1`..`RS232_COM4`)
|
||||
- `windowSize` — sliding window size (1-8), 0 for default (4)
|
||||
- `callback` — called from `pktPoll()` for each received packet
|
||||
- `callbackCtx` — user pointer passed to callback
|
||||
|
||||
Returns a connection handle, or `NULL` on failure.
|
||||
|
||||
#### pktClose
|
||||
|
||||
```c
|
||||
void pktClose(PktConnT *conn);
|
||||
```
|
||||
|
||||
Frees the connection state. Does **not** close the underlying COM port.
|
||||
|
||||
#### pktSend
|
||||
|
||||
```c
|
||||
int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block);
|
||||
```
|
||||
|
||||
Sends a packet. `len` must be 1..`PKT_MAX_PAYLOAD`.
|
||||
|
||||
- `block = true` — waits for window space, polling for ACKs internally
|
||||
- `block = false` — returns `PKT_ERR_TX_FULL` if the window is full
|
||||
|
||||
The packet is stored in the retransmit buffer until acknowledged.
|
||||
|
||||
#### pktPoll
|
||||
|
||||
```c
|
||||
int pktPoll(PktConnT *conn);
|
||||
```
|
||||
|
||||
Reads available serial data, processes received frames, sends ACKs and
|
||||
NAKs, and checks retransmit timers. Returns the number of DATA packets
|
||||
delivered to the callback.
|
||||
|
||||
Must be called frequently (e.g. in your main loop).
|
||||
|
||||
#### pktReset
|
||||
|
||||
```c
|
||||
int pktReset(PktConnT *conn);
|
||||
```
|
||||
|
||||
Resets all sequence numbers and buffers to zero. Sends a RST frame to
|
||||
the remote side so it resets as well.
|
||||
|
||||
#### pktGetPending
|
||||
|
||||
```c
|
||||
int pktGetPending(PktConnT *conn);
|
||||
```
|
||||
|
||||
Returns the number of unacknowledged packets currently in the transmit
|
||||
window. Useful for throttling sends in non-blocking mode.
|
||||
|
||||
## Example
|
||||
|
||||
```c
|
||||
#include "packet.h"
|
||||
#include "../rs232/rs232.h"
|
||||
|
||||
void onPacket(void *ctx, const uint8_t *data, int len) {
|
||||
// process received packet
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// Open serial port first
|
||||
rs232Open(RS232_COM1, 115200, 8, 'N', 1, RS232_HANDSHAKE_NONE);
|
||||
|
||||
// Create packet connection with default window size
|
||||
PktConnT *conn = pktOpen(RS232_COM1, 0, onPacket, NULL);
|
||||
|
||||
// Send a packet (blocking)
|
||||
uint8_t msg[] = "Hello, packets!";
|
||||
pktSend(conn, msg, sizeof(msg), true);
|
||||
|
||||
// Main loop
|
||||
while (1) {
|
||||
int delivered = pktPoll(conn);
|
||||
// delivered = number of packets received this iteration
|
||||
}
|
||||
|
||||
pktClose(conn);
|
||||
rs232Close(RS232_COM1);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## CRC
|
||||
|
||||
CRC-16-CCITT (polynomial 0x1021, init 0xFFFF) computed via a 256-entry
|
||||
lookup table (512 bytes). The CRC covers the SEQ, TYPE, LEN, and
|
||||
payload fields.
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
make # builds ../lib/libpacket.a
|
||||
make clean # removes objects and library
|
||||
```
|
||||
|
||||
Requires `librs232.a` at link time.
|
||||
|
||||
Target: DJGPP cross-compiler, 486+ CPU.
|
||||
523
packet/packet.c
Normal file
523
packet/packet.c
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
// Packetized serial transport with HDLC-style framing and sliding window
|
||||
//
|
||||
// Frame format (before byte stuffing):
|
||||
// [0x7E] [SEQ] [TYPE] [LEN_LO] [LEN_HI] [PAYLOAD...] [CRC_LO] [CRC_HI]
|
||||
//
|
||||
// Byte stuffing:
|
||||
// 0x7E -> 0x7D 0x5E
|
||||
// 0x7D -> 0x7D 0x5D
|
||||
//
|
||||
// CRC-16-CCITT over SEQ+TYPE+LEN+PAYLOAD
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pc.h>
|
||||
#include "packet.h"
|
||||
#include "../rs232/rs232.h"
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Internal defines
|
||||
// ========================================================================
|
||||
|
||||
#define FLAG_BYTE 0x7E
|
||||
#define ESC_BYTE 0x7D
|
||||
#define ESC_XOR 0x20
|
||||
|
||||
// Frame types
|
||||
#define FRAME_DATA 0x00
|
||||
#define FRAME_ACK 0x01
|
||||
#define FRAME_NAK 0x02
|
||||
#define FRAME_RST 0x03
|
||||
|
||||
// Header size: SEQ + TYPE + LEN_LO + LEN_HI
|
||||
#define HEADER_SIZE 4
|
||||
// CRC size
|
||||
#define CRC_SIZE 2
|
||||
// Minimum frame size (header + CRC, no payload)
|
||||
#define MIN_FRAME_SIZE (HEADER_SIZE + CRC_SIZE)
|
||||
// Maximum raw frame size (header + max payload + CRC)
|
||||
#define MAX_FRAME_SIZE (HEADER_SIZE + PKT_MAX_PAYLOAD + CRC_SIZE)
|
||||
// Maximum stuffed frame size (worst case: every byte stuffed + flag)
|
||||
#define MAX_STUFFED_SIZE (1 + MAX_FRAME_SIZE * 2)
|
||||
|
||||
// Receive buffer must hold at least one max-size stuffed frame
|
||||
#define RX_BUF_SIZE (MAX_STUFFED_SIZE + 64)
|
||||
|
||||
// Retransmit timeout in poll cycles (caller-dependent; ~50ms worth at typical poll rates)
|
||||
#define RETRANSMIT_TIMEOUT 500
|
||||
|
||||
// Receive state machine
|
||||
#define RX_STATE_HUNT 0 // scanning for FLAG_BYTE
|
||||
#define RX_STATE_ACTIVE 1 // receiving frame data
|
||||
#define RX_STATE_ESCAPE 2 // next byte is escaped
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Types
|
||||
// ========================================================================
|
||||
|
||||
// Transmit window slot
|
||||
typedef struct {
|
||||
uint8_t data[PKT_MAX_PAYLOAD];
|
||||
int len;
|
||||
uint8_t seq;
|
||||
uint32_t timer;
|
||||
} TxSlotT;
|
||||
|
||||
// Connection state
|
||||
struct PktConnS {
|
||||
int com;
|
||||
int windowSize;
|
||||
PktRecvCallbackT callback;
|
||||
void *callbackCtx;
|
||||
|
||||
// Transmit state
|
||||
uint8_t txNextSeq; // next sequence number to assign
|
||||
uint8_t txAckSeq; // oldest unacknowledged sequence
|
||||
TxSlotT txSlots[PKT_MAX_WINDOW];
|
||||
int txCount; // number of slots in use
|
||||
|
||||
// Receive state
|
||||
uint8_t rxExpectSeq; // next expected sequence number
|
||||
uint8_t rxState; // RX_STATE_*
|
||||
uint8_t rxFrame[MAX_FRAME_SIZE];
|
||||
int rxFrameLen;
|
||||
|
||||
// Poll counter (simple timer)
|
||||
uint32_t pollCount;
|
||||
};
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// CRC-16-CCITT lookup table
|
||||
// ========================================================================
|
||||
|
||||
static const uint16_t sCrcTable[256] = {
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
|
||||
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
|
||||
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
|
||||
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
|
||||
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x54A5,
|
||||
0xA54A, 0xB56B, 0x8508, 0x9529, 0xE5CE, 0xF5EF, 0xC58C, 0xD5AD,
|
||||
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
|
||||
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
|
||||
0x4864, 0x5845, 0x6826, 0x7807, 0x08E0, 0x18C1, 0x28A2, 0x38A3,
|
||||
0xC94C, 0xD96D, 0xE90E, 0xF92F, 0x89C8, 0x99E9, 0xA98A, 0xB9AB,
|
||||
0x5A55, 0x4A74, 0x7A17, 0x6A36, 0x1AD1, 0x0AF0, 0x3A93, 0x2AB2,
|
||||
0xDB5D, 0xCB7C, 0xFB1F, 0xEB3E, 0x9BD9, 0x8BF8, 0xBB9B, 0xAB9A,
|
||||
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
||||
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
|
||||
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
|
||||
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
|
||||
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
|
||||
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
|
||||
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
|
||||
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
|
||||
0x26D3, 0x36F2, 0x06B1, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
|
||||
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
||||
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
|
||||
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
|
||||
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
|
||||
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
|
||||
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
|
||||
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
|
||||
};
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Static prototypes (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
static uint16_t crcCalc(const uint8_t *data, int len);
|
||||
static void processFrame(PktConnT *conn, const uint8_t *frame, int len);
|
||||
static void retransmitCheck(PktConnT *conn);
|
||||
static void rxProcessByte(PktConnT *conn, uint8_t byte);
|
||||
static void sendAck(PktConnT *conn, uint8_t seq);
|
||||
static void sendFrame(PktConnT *conn, uint8_t seq, uint8_t type, const uint8_t *payload, int len);
|
||||
static void sendNak(PktConnT *conn, uint8_t seq);
|
||||
static void sendRst(PktConnT *conn);
|
||||
static int seqInWindow(uint8_t seq, uint8_t base, int size);
|
||||
static int txSlotIndex(PktConnT *conn, uint8_t seq);
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// CRC computation
|
||||
// ========================================================================
|
||||
|
||||
static uint16_t crcCalc(const uint8_t *data, int len) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
crc = (crc << 8) ^ sCrcTable[(crc >> 8) ^ data[i]];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Frame transmission
|
||||
// ========================================================================
|
||||
|
||||
static void sendFrame(PktConnT *conn, uint8_t seq, uint8_t type, const uint8_t *payload, int len) {
|
||||
uint8_t raw[MAX_FRAME_SIZE];
|
||||
uint8_t stuffed[MAX_STUFFED_SIZE];
|
||||
int rawLen;
|
||||
uint16_t crc;
|
||||
int out;
|
||||
|
||||
// Build raw frame: SEQ + TYPE + LEN_LO + LEN_HI + PAYLOAD
|
||||
raw[0] = seq;
|
||||
raw[1] = type;
|
||||
raw[2] = (uint8_t)(len & 0xFF);
|
||||
raw[3] = (uint8_t)((len >> 8) & 0xFF);
|
||||
if (payload && len > 0) {
|
||||
memcpy(&raw[4], payload, len);
|
||||
}
|
||||
rawLen = HEADER_SIZE + len;
|
||||
|
||||
// Append CRC
|
||||
crc = crcCalc(raw, rawLen);
|
||||
raw[rawLen] = (uint8_t)(crc & 0xFF);
|
||||
raw[rawLen + 1] = (uint8_t)((crc >> 8) & 0xFF);
|
||||
rawLen += CRC_SIZE;
|
||||
|
||||
// Byte-stuff into output buffer with leading flag
|
||||
out = 0;
|
||||
stuffed[out++] = FLAG_BYTE;
|
||||
for (int i = 0; i < rawLen; i++) {
|
||||
if (raw[i] == FLAG_BYTE) {
|
||||
stuffed[out++] = ESC_BYTE;
|
||||
stuffed[out++] = FLAG_BYTE ^ ESC_XOR;
|
||||
} else if (raw[i] == ESC_BYTE) {
|
||||
stuffed[out++] = ESC_BYTE;
|
||||
stuffed[out++] = ESC_BYTE ^ ESC_XOR;
|
||||
} else {
|
||||
stuffed[out++] = raw[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Send via serial port (blocking write)
|
||||
rs232Write(conn->com, (const char *)stuffed, out);
|
||||
}
|
||||
|
||||
|
||||
static void sendAck(PktConnT *conn, uint8_t seq) {
|
||||
sendFrame(conn, seq, FRAME_ACK, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
static void sendNak(PktConnT *conn, uint8_t seq) {
|
||||
sendFrame(conn, seq, FRAME_NAK, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
static void sendRst(PktConnT *conn) {
|
||||
sendFrame(conn, 0, FRAME_RST, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Sequence number helpers
|
||||
// ========================================================================
|
||||
|
||||
static int seqInWindow(uint8_t seq, uint8_t base, int size) {
|
||||
uint8_t diff = seq - base;
|
||||
return diff < (uint8_t)size;
|
||||
}
|
||||
|
||||
|
||||
static int txSlotIndex(PktConnT *conn, uint8_t seq) {
|
||||
uint8_t diff = seq - conn->txAckSeq;
|
||||
if (diff >= (uint8_t)conn->txCount) {
|
||||
return -1;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Frame processing
|
||||
// ========================================================================
|
||||
|
||||
static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
|
||||
uint8_t seq;
|
||||
uint8_t type;
|
||||
int payloadLen;
|
||||
uint16_t rxCrc;
|
||||
uint16_t calcCrc;
|
||||
|
||||
if (len < MIN_FRAME_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify CRC
|
||||
calcCrc = crcCalc(frame, len - CRC_SIZE);
|
||||
rxCrc = frame[len - 2] | ((uint16_t)frame[len - 1] << 8);
|
||||
if (calcCrc != rxCrc) {
|
||||
// CRC mismatch — request retransmit of what we expect
|
||||
sendNak(conn, conn->rxExpectSeq);
|
||||
return;
|
||||
}
|
||||
|
||||
seq = frame[0];
|
||||
type = frame[1];
|
||||
payloadLen = frame[2] | ((int)frame[3] << 8);
|
||||
|
||||
// Validate payload length against actual frame size
|
||||
if (payloadLen + MIN_FRAME_SIZE != len) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case FRAME_DATA:
|
||||
if (seq == conn->rxExpectSeq) {
|
||||
// In-order delivery
|
||||
if (conn->callback) {
|
||||
conn->callback(conn->callbackCtx, &frame[HEADER_SIZE], payloadLen);
|
||||
}
|
||||
conn->rxExpectSeq++;
|
||||
sendAck(conn, conn->rxExpectSeq);
|
||||
} else if (seqInWindow(seq, conn->rxExpectSeq, conn->windowSize)) {
|
||||
// Out of order but in window — NAK the one we want
|
||||
sendNak(conn, conn->rxExpectSeq);
|
||||
}
|
||||
// else: duplicate or out of window, silently discard
|
||||
break;
|
||||
|
||||
case FRAME_ACK: {
|
||||
// ACK carries the next expected sequence number (cumulative)
|
||||
// Advance txAckSeq and free all slots up to seq
|
||||
while (conn->txCount > 0 && conn->txAckSeq != seq) {
|
||||
conn->txAckSeq++;
|
||||
conn->txCount--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FRAME_NAK: {
|
||||
// Retransmit from the requested sequence
|
||||
int idx = txSlotIndex(conn, seq);
|
||||
if (idx >= 0) {
|
||||
// Retransmit this slot and all after it (go-back-N)
|
||||
for (int i = idx; i < conn->txCount; i++) {
|
||||
TxSlotT *slot = &conn->txSlots[i];
|
||||
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
|
||||
slot->timer = conn->pollCount;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FRAME_RST:
|
||||
// Remote requested reset — clear state and respond with RST
|
||||
conn->txNextSeq = 0;
|
||||
conn->txAckSeq = 0;
|
||||
conn->txCount = 0;
|
||||
conn->rxExpectSeq = 0;
|
||||
sendRst(conn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Receive byte processing (state machine)
|
||||
// ========================================================================
|
||||
|
||||
static void rxProcessByte(PktConnT *conn, uint8_t byte) {
|
||||
switch (conn->rxState) {
|
||||
case RX_STATE_HUNT:
|
||||
if (byte == FLAG_BYTE) {
|
||||
conn->rxState = RX_STATE_ACTIVE;
|
||||
conn->rxFrameLen = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RX_STATE_ACTIVE:
|
||||
if (byte == FLAG_BYTE) {
|
||||
// End of frame (or start of next)
|
||||
if (conn->rxFrameLen >= MIN_FRAME_SIZE) {
|
||||
processFrame(conn, conn->rxFrame, conn->rxFrameLen);
|
||||
}
|
||||
// Reset for next frame
|
||||
conn->rxFrameLen = 0;
|
||||
} else if (byte == ESC_BYTE) {
|
||||
conn->rxState = RX_STATE_ESCAPE;
|
||||
} else {
|
||||
if (conn->rxFrameLen < MAX_FRAME_SIZE) {
|
||||
conn->rxFrame[conn->rxFrameLen++] = byte;
|
||||
} else {
|
||||
// Frame too large, discard and hunt for next flag
|
||||
conn->rxState = RX_STATE_HUNT;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RX_STATE_ESCAPE:
|
||||
conn->rxState = RX_STATE_ACTIVE;
|
||||
byte ^= ESC_XOR;
|
||||
if (conn->rxFrameLen < MAX_FRAME_SIZE) {
|
||||
conn->rxFrame[conn->rxFrameLen++] = byte;
|
||||
} else {
|
||||
conn->rxState = RX_STATE_HUNT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Retransmit check
|
||||
// ========================================================================
|
||||
|
||||
static void retransmitCheck(PktConnT *conn) {
|
||||
for (int i = 0; i < conn->txCount; i++) {
|
||||
TxSlotT *slot = &conn->txSlots[i];
|
||||
if (conn->pollCount - slot->timer >= RETRANSMIT_TIMEOUT) {
|
||||
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
|
||||
slot->timer = conn->pollCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Public functions (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
void pktClose(PktConnT *conn) {
|
||||
if (conn) {
|
||||
free(conn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int pktGetPending(PktConnT *conn) {
|
||||
if (!conn) {
|
||||
return PKT_ERR_INVALID_PARAM;
|
||||
}
|
||||
return conn->txCount;
|
||||
}
|
||||
|
||||
|
||||
PktConnT *pktOpen(int com, int windowSize, PktRecvCallbackT callback, void *callbackCtx) {
|
||||
PktConnT *conn;
|
||||
|
||||
if (windowSize <= 0) {
|
||||
windowSize = PKT_DEFAULT_WINDOW;
|
||||
}
|
||||
if (windowSize > PKT_MAX_WINDOW) {
|
||||
windowSize = PKT_MAX_WINDOW;
|
||||
}
|
||||
|
||||
conn = (PktConnT *)calloc(1, sizeof(PktConnT));
|
||||
if (!conn) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
conn->com = com;
|
||||
conn->windowSize = windowSize;
|
||||
conn->callback = callback;
|
||||
conn->callbackCtx = callbackCtx;
|
||||
conn->txNextSeq = 0;
|
||||
conn->txAckSeq = 0;
|
||||
conn->txCount = 0;
|
||||
conn->rxExpectSeq = 0;
|
||||
conn->rxState = RX_STATE_HUNT;
|
||||
conn->rxFrameLen = 0;
|
||||
conn->pollCount = 0;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
|
||||
int pktPoll(PktConnT *conn) {
|
||||
char buf[128];
|
||||
int nRead;
|
||||
int delivered = 0;
|
||||
|
||||
if (!conn) {
|
||||
return PKT_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
conn->pollCount++;
|
||||
|
||||
// Read available serial data and feed to state machine
|
||||
while ((nRead = rs232Read(conn->com, buf, sizeof(buf))) > 0) {
|
||||
for (int i = 0; i < nRead; i++) {
|
||||
uint8_t prevExpect = conn->rxExpectSeq;
|
||||
rxProcessByte(conn, (uint8_t)buf[i]);
|
||||
if (conn->rxExpectSeq != prevExpect) {
|
||||
delivered++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for retransmit timeouts
|
||||
retransmitCheck(conn);
|
||||
|
||||
return delivered;
|
||||
}
|
||||
|
||||
|
||||
int pktReset(PktConnT *conn) {
|
||||
if (!conn) {
|
||||
return PKT_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
conn->txNextSeq = 0;
|
||||
conn->txAckSeq = 0;
|
||||
conn->txCount = 0;
|
||||
conn->rxExpectSeq = 0;
|
||||
conn->rxState = RX_STATE_HUNT;
|
||||
conn->rxFrameLen = 0;
|
||||
|
||||
sendRst(conn);
|
||||
|
||||
return PKT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block) {
|
||||
TxSlotT *slot;
|
||||
|
||||
if (!conn) {
|
||||
return PKT_ERR_INVALID_PARAM;
|
||||
}
|
||||
if (!data || len <= 0 || len > PKT_MAX_PAYLOAD) {
|
||||
return PKT_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// Wait for window space if blocking
|
||||
if (block) {
|
||||
while (conn->txCount >= conn->windowSize) {
|
||||
pktPoll(conn);
|
||||
}
|
||||
} else {
|
||||
if (conn->txCount >= conn->windowSize) {
|
||||
return PKT_ERR_TX_FULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Store in retransmit buffer
|
||||
slot = &conn->txSlots[conn->txCount];
|
||||
memcpy(slot->data, data, len);
|
||||
slot->len = len;
|
||||
slot->seq = conn->txNextSeq;
|
||||
slot->timer = conn->pollCount;
|
||||
conn->txCount++;
|
||||
conn->txNextSeq++;
|
||||
|
||||
// Transmit
|
||||
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
|
||||
|
||||
return PKT_SUCCESS;
|
||||
}
|
||||
68
packet/packet.h
Normal file
68
packet/packet.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// Packetized serial transport with HDLC-style framing and sliding window
|
||||
// Provides reliable, ordered delivery over an unreliable serial link.
|
||||
|
||||
#ifndef PACKET_H
|
||||
#define PACKET_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Maximum payload per packet (excluding header/CRC)
|
||||
#define PKT_MAX_PAYLOAD 256
|
||||
|
||||
// Default sliding window size (1-8)
|
||||
#define PKT_DEFAULT_WINDOW 4
|
||||
|
||||
// Maximum window size
|
||||
#define PKT_MAX_WINDOW 8
|
||||
|
||||
// Error codes
|
||||
#define PKT_SUCCESS 0
|
||||
#define PKT_ERR_INVALID_PORT -1
|
||||
#define PKT_ERR_NOT_OPEN -2
|
||||
#define PKT_ERR_ALREADY_OPEN -3
|
||||
#define PKT_ERR_WOULD_BLOCK -4
|
||||
#define PKT_ERR_OVERFLOW -5
|
||||
#define PKT_ERR_INVALID_PARAM -6
|
||||
#define PKT_ERR_TX_FULL -7
|
||||
#define PKT_ERR_NO_DATA -8
|
||||
|
||||
// Callback for received packets
|
||||
// ctx: user context pointer
|
||||
// data: payload (valid only during callback)
|
||||
// len: payload length
|
||||
typedef void (*PktRecvCallbackT)(void *ctx, const uint8_t *data, int len);
|
||||
|
||||
// Connection handle (opaque)
|
||||
typedef struct PktConnS PktConnT;
|
||||
|
||||
|
||||
// Open a packetized connection over a COM port.
|
||||
// com: RS232 COM port index (RS232_COM1..RS232_COM4), must already be open
|
||||
// windowSize: sliding window size (1..PKT_MAX_WINDOW), 0 for default
|
||||
// callback: called when a complete, verified packet is received
|
||||
// callbackCtx: user pointer passed to callback
|
||||
// Returns connection handle, or 0 on failure.
|
||||
PktConnT *pktOpen(int com, int windowSize, PktRecvCallbackT callback, void *callbackCtx);
|
||||
|
||||
// Close a packetized connection. Does not close the underlying COM port.
|
||||
void pktClose(PktConnT *conn);
|
||||
|
||||
// Send a packet. Returns PKT_SUCCESS or error.
|
||||
// Blocks if the transmit window is full and block is true.
|
||||
// Returns PKT_ERR_TX_FULL if window is full and block is false.
|
||||
int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block);
|
||||
|
||||
// Poll for incoming data, process received frames, handle retransmits.
|
||||
// Must be called frequently (e.g. in your main loop).
|
||||
// Returns the number of valid data packets delivered to the callback.
|
||||
int pktPoll(PktConnT *conn);
|
||||
|
||||
// Reset the connection state (sequence numbers, buffers).
|
||||
// Sends a RST frame to the remote side.
|
||||
int pktReset(PktConnT *conn);
|
||||
|
||||
// Get number of unacknowledged packets in the transmit window.
|
||||
int pktGetPending(PktConnT *conn);
|
||||
|
||||
#endif
|
||||
212
rs232/README.md
Normal file
212
rs232/README.md
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
# RS232 — Serial Port Library for DJGPP
|
||||
|
||||
ISR-driven UART communication library supporting up to 4 simultaneous
|
||||
COM ports with ring buffers and hardware/software flow control.
|
||||
|
||||
Ported from the DOS Serial Library 1.4 by Karl Stenerud (MIT License),
|
||||
stripped to DJGPP-only codepaths and restyled.
|
||||
|
||||
## Features
|
||||
|
||||
- ISR-driven receive and transmit with 2048-byte ring buffers
|
||||
- Auto-detected IRQ from BIOS data area
|
||||
- 16550 FIFO detection and configurable trigger threshold
|
||||
- XON/XOFF, RTS/CTS, and DTR/DSR flow control
|
||||
- DPMI memory locking for ISR safety
|
||||
- Speeds from 50 to 115200 bps
|
||||
- 5-8 data bits, N/O/E/M/S parity, 1-2 stop bits
|
||||
|
||||
## API Reference
|
||||
|
||||
### Types
|
||||
|
||||
All functions take a COM port index (`int com`) as their first argument:
|
||||
|
||||
| Constant | Value | Description |
|
||||
|--------------|-------|-------------|
|
||||
| `RS232_COM1` | 0 | COM1 |
|
||||
| `RS232_COM2` | 1 | COM2 |
|
||||
| `RS232_COM3` | 2 | COM3 |
|
||||
| `RS232_COM4` | 3 | COM4 |
|
||||
|
||||
### Handshaking Modes
|
||||
|
||||
| Constant | Value | Description |
|
||||
|--------------------------|-------|---------------------------|
|
||||
| `RS232_HANDSHAKE_NONE` | 0 | No flow control |
|
||||
| `RS232_HANDSHAKE_XONXOFF`| 1 | Software (XON/XOFF) |
|
||||
| `RS232_HANDSHAKE_RTSCTS` | 2 | Hardware (RTS/CTS) |
|
||||
| `RS232_HANDSHAKE_DTRDSR` | 3 | Hardware (DTR/DSR) |
|
||||
|
||||
### Error Codes
|
||||
|
||||
| Constant | Value | Description |
|
||||
|-------------------------------|-------|---------------------------|
|
||||
| `RS232_SUCCESS` | 0 | Success |
|
||||
| `RS232_ERR_UNKNOWN` | -1 | Unknown error |
|
||||
| `RS232_ERR_NOT_OPEN` | -2 | Port not open |
|
||||
| `RS232_ERR_ALREADY_OPEN` | -3 | Port already open |
|
||||
| `RS232_ERR_NO_UART` | -4 | No UART detected |
|
||||
| `RS232_ERR_INVALID_PORT` | -5 | Bad port index |
|
||||
| `RS232_ERR_INVALID_BASE` | -6 | Bad I/O base address |
|
||||
| `RS232_ERR_INVALID_IRQ` | -7 | Bad IRQ number |
|
||||
| `RS232_ERR_INVALID_BPS` | -8 | Unsupported baud rate |
|
||||
| `RS232_ERR_INVALID_DATA` | -9 | Bad data bits (not 5-8) |
|
||||
| `RS232_ERR_INVALID_PARITY` | -10 | Bad parity character |
|
||||
| `RS232_ERR_INVALID_STOP` | -11 | Bad stop bits (not 1-2) |
|
||||
| `RS232_ERR_INVALID_HANDSHAKE` | -12 | Bad handshaking mode |
|
||||
| `RS232_ERR_INVALID_FIFO` | -13 | Bad FIFO threshold |
|
||||
| `RS232_ERR_NULL_PTR` | -14 | NULL pointer argument |
|
||||
| `RS232_ERR_IRQ_NOT_FOUND` | -15 | Could not detect IRQ |
|
||||
| `RS232_ERR_LOCK_MEM` | -16 | DPMI memory lock failed |
|
||||
|
||||
### Functions
|
||||
|
||||
#### Open / Close
|
||||
|
||||
```c
|
||||
int rs232Open(int com, int32_t bps, int dataBits, char parity,
|
||||
int stopBits, int handshake);
|
||||
```
|
||||
|
||||
Opens a COM port. Detects the UART base address from the BIOS data
|
||||
area, auto-detects the IRQ, installs the ISR, and configures the port.
|
||||
|
||||
- `bps` — baud rate (50, 75, 110, 150, 300, 600, 1200, 1800, 2400,
|
||||
3800, 4800, 7200, 9600, 19200, 38400, 57600, 115200)
|
||||
- `dataBits` — 5, 6, 7, or 8
|
||||
- `parity` — `'N'` (none), `'O'` (odd), `'E'` (even), `'M'` (mark),
|
||||
`'S'` (space)
|
||||
- `stopBits` — 1 or 2
|
||||
- `handshake` — `RS232_HANDSHAKE_*` constant
|
||||
|
||||
```c
|
||||
int rs232Close(int com);
|
||||
```
|
||||
|
||||
Closes the port, removes the ISR, and restores the original interrupt
|
||||
vector.
|
||||
|
||||
#### Read / Write
|
||||
|
||||
```c
|
||||
int rs232Read(int com, char *data, int len);
|
||||
```
|
||||
|
||||
Reads up to `len` bytes from the receive buffer. Returns the number of
|
||||
bytes actually read (0 if the buffer is empty).
|
||||
|
||||
```c
|
||||
int rs232Write(int com, const char *data, int len);
|
||||
```
|
||||
|
||||
Blocking write. Sends `len` bytes, waiting for transmit buffer space
|
||||
as needed. Returns `RS232_SUCCESS` or an error code.
|
||||
|
||||
```c
|
||||
int rs232WriteBuf(int com, const char *data, int len);
|
||||
```
|
||||
|
||||
Non-blocking write. Copies as many bytes as will fit into the transmit
|
||||
buffer. Returns the number of bytes actually queued.
|
||||
|
||||
#### Buffer Management
|
||||
|
||||
```c
|
||||
int rs232ClearRxBuffer(int com);
|
||||
int rs232ClearTxBuffer(int com);
|
||||
```
|
||||
|
||||
Discard all data in the receive or transmit ring buffer.
|
||||
|
||||
#### Getters
|
||||
|
||||
```c
|
||||
int rs232GetBase(int com); // UART I/O base address
|
||||
int32_t rs232GetBps(int com); // Current baud rate
|
||||
int rs232GetCts(int com); // CTS line state (0 or 1)
|
||||
int rs232GetData(int com); // Data bits setting
|
||||
int rs232GetDsr(int com); // DSR line state (0 or 1)
|
||||
int rs232GetDtr(int com); // DTR line state (0 or 1)
|
||||
int rs232GetHandshake(int com); // Handshaking mode
|
||||
int rs232GetIrq(int com); // IRQ number
|
||||
int rs232GetLsr(int com); // Line status register
|
||||
int rs232GetMcr(int com); // Modem control register
|
||||
int rs232GetMsr(int com); // Modem status register
|
||||
char rs232GetParity(int com); // Parity setting ('N','O','E','M','S')
|
||||
int rs232GetRts(int com); // RTS line state (0 or 1)
|
||||
int rs232GetRxBuffered(int com); // Bytes in receive buffer
|
||||
int rs232GetStop(int com); // Stop bits setting
|
||||
int rs232GetTxBuffered(int com); // Bytes in transmit buffer
|
||||
```
|
||||
|
||||
#### Setters
|
||||
|
||||
```c
|
||||
int rs232Set(int com, int32_t bps, int dataBits, char parity,
|
||||
int stopBits, int handshake);
|
||||
```
|
||||
|
||||
Reconfigure all port parameters at once (port must be open).
|
||||
|
||||
```c
|
||||
int rs232SetBase(int com, int base); // Override I/O base address
|
||||
int rs232SetBps(int com, int32_t bps); // Change baud rate
|
||||
int rs232SetData(int com, int dataBits); // Change data bits
|
||||
int rs232SetDtr(int com, bool dtr); // Assert/deassert DTR
|
||||
int rs232SetFifoThreshold(int com, int thr); // FIFO trigger level (1,4,8,14)
|
||||
int rs232SetHandshake(int com, int handshake); // Change flow control mode
|
||||
int rs232SetIrq(int com, int irq); // Override IRQ (before Open)
|
||||
int rs232SetMcr(int com, int mcr); // Write modem control register
|
||||
int rs232SetParity(int com, char parity); // Change parity
|
||||
int rs232SetRts(int com, bool rts); // Assert/deassert RTS
|
||||
int rs232SetStop(int com, int stopBits); // Change stop bits
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```c
|
||||
#include "rs232.h"
|
||||
|
||||
int main(void) {
|
||||
// Open COM1 at 115200 8N1, no flow control
|
||||
int rc = rs232Open(RS232_COM1, 115200, 8, 'N', 1, RS232_HANDSHAKE_NONE);
|
||||
if (rc != RS232_SUCCESS) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Blocking send
|
||||
rs232Write(RS232_COM1, "Hello\r\n", 7);
|
||||
|
||||
// Non-blocking receive
|
||||
char buf[128];
|
||||
int n;
|
||||
while ((n = rs232Read(RS232_COM1, buf, sizeof(buf))) > 0) {
|
||||
// process buf[0..n-1]
|
||||
}
|
||||
|
||||
rs232Close(RS232_COM1);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The ISR handles all four COM ports from a single shared handler.
|
||||
On entry it disables UART interrupts for all open ports, then
|
||||
re-enables CPU interrupts so higher-priority devices are serviced.
|
||||
- Ring buffers use power-of-2 sizes (2048 bytes) with bitmask indexing
|
||||
for zero-branch wraparound.
|
||||
- Flow control watermarks are at 80% (assert) and 20% (deassert) of
|
||||
buffer capacity.
|
||||
- DPMI `__dpmi_lock_linear_region` is used to pin the ISR, ring
|
||||
buffers, and port state in physical memory.
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
make # builds ../lib/librs232.a
|
||||
make clean # removes objects and library
|
||||
```
|
||||
|
||||
Target: DJGPP cross-compiler, 486+ CPU.
|
||||
|
|
@ -1143,7 +1143,7 @@ int rs232SetData(int com, int dataBits) {
|
|||
}
|
||||
|
||||
|
||||
int rs232SetDtr(int com, int dtr) {
|
||||
int rs232SetDtr(int com, bool dtr) {
|
||||
Rs232StateT *port = &sComPorts[com];
|
||||
|
||||
if (com < COM_MIN || com > COM_MAX) {
|
||||
|
|
@ -1287,7 +1287,7 @@ int rs232SetParity(int com, char parity) {
|
|||
}
|
||||
|
||||
|
||||
int rs232SetRts(int com, int rts) {
|
||||
int rs232SetRts(int com, bool rts) {
|
||||
Rs232StateT *port = &sComPorts[com];
|
||||
|
||||
if (com < COM_MIN || com > COM_MAX) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#define RS232_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// COM Ports
|
||||
#define RS232_COM1 0
|
||||
|
|
@ -77,13 +78,13 @@ int rs232Set(int com, int32_t bps, int dataBits, char parity, int stopBits, int
|
|||
int rs232SetBase(int com, int base);
|
||||
int rs232SetBps(int com, int32_t bps);
|
||||
int rs232SetData(int com, int dataBits);
|
||||
int rs232SetDtr(int com, int dtr);
|
||||
int rs232SetDtr(int com, bool dtr);
|
||||
int rs232SetFifoThreshold(int com, int threshold);
|
||||
int rs232SetHandshake(int com, int handshake);
|
||||
int rs232SetIrq(int com, int irq);
|
||||
int rs232SetMcr(int com, int mcr);
|
||||
int rs232SetParity(int com, char parity);
|
||||
int rs232SetRts(int com, int rts);
|
||||
int rs232SetRts(int com, bool rts);
|
||||
int rs232SetStop(int com, int stopBits);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
38
seclink/Makefile
Normal file
38
seclink/Makefile
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# SecLink Library Makefile for DJGPP cross-compilation
|
||||
|
||||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
AR = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ar
|
||||
RANLIB = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ranlib
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586
|
||||
|
||||
OBJDIR = ../obj/seclink
|
||||
LIBDIR = ../lib
|
||||
|
||||
SRCS = secLink.c
|
||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||
TARGET = $(LIBDIR)/libseclink.a
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) | $(LIBDIR)
|
||||
$(AR) rcs $@ $(OBJS)
|
||||
$(RANLIB) $@
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(LIBDIR):
|
||||
mkdir -p $(LIBDIR)
|
||||
|
||||
# Dependencies
|
||||
$(OBJDIR)/secLink.o: secLink.c secLink.h ../rs232/rs232.h ../packet/packet.h ../security/security.h
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
295
seclink/README.md
Normal file
295
seclink/README.md
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
# SecLink — Secure Serial Link Library
|
||||
|
||||
SecLink is a convenience wrapper that ties together three lower-level
|
||||
libraries into a single API for reliable, optionally encrypted serial
|
||||
communication:
|
||||
|
||||
- **rs232** — ISR-driven UART I/O with ring buffers and flow control
|
||||
- **packet** — HDLC-style framing with CRC-16 and sliding window reliability
|
||||
- **security** — 1024-bit Diffie-Hellman key exchange and XTEA-CTR encryption
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Application
|
||||
|
|
||||
[secLink] channels, optional encryption
|
||||
|
|
||||
[packet] framing, CRC, retransmit, ordering
|
||||
|
|
||||
[rs232] ISR-driven UART, ring buffers, flow control
|
||||
|
|
||||
UART
|
||||
```
|
||||
|
||||
SecLink adds a one-byte header to every packet:
|
||||
|
||||
```
|
||||
Bit 7 Bits 6..0
|
||||
----- ---------
|
||||
Encrypt Channel (0-127)
|
||||
```
|
||||
|
||||
This allows mixing encrypted and cleartext traffic on up to 128
|
||||
independent logical channels over a single serial link.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
```
|
||||
secLinkOpen() Open COM port and packet layer
|
||||
secLinkHandshake() DH key exchange (blocks until complete)
|
||||
secLinkSend() Send a packet (encrypted or clear)
|
||||
secLinkPoll() Receive and deliver packets to callback
|
||||
secLinkClose() Tear down everything
|
||||
```
|
||||
|
||||
The handshake is only required if you intend to send encrypted packets.
|
||||
Cleartext packets can be sent immediately after `secLinkOpen()`.
|
||||
|
||||
## API Reference
|
||||
|
||||
### Types
|
||||
|
||||
```c
|
||||
// Receive callback — called for each incoming packet with plaintext
|
||||
typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
||||
|
||||
// Opaque connection handle
|
||||
typedef struct SecLinkS SecLinkT;
|
||||
```
|
||||
|
||||
### Constants
|
||||
|
||||
| Name | Value | Description |
|
||||
|-------------------------|-------|----------------------------------------|
|
||||
| `SECLINK_MAX_PAYLOAD` | 255 | Max bytes per `secLinkSend()` call |
|
||||
| `SECLINK_MAX_CHANNEL` | 127 | Highest valid channel number |
|
||||
| `SECLINK_SUCCESS` | 0 | Operation succeeded |
|
||||
| `SECLINK_ERR_PARAM` | -1 | Invalid parameter |
|
||||
| `SECLINK_ERR_SERIAL` | -2 | Serial port error |
|
||||
| `SECLINK_ERR_ALLOC` | -3 | Memory allocation failed |
|
||||
| `SECLINK_ERR_HANDSHAKE` | -4 | Key exchange failed |
|
||||
| `SECLINK_ERR_NOT_READY` | -5 | Encryption requested before handshake |
|
||||
| `SECLINK_ERR_SEND` | -6 | Packet layer send failed |
|
||||
|
||||
### Functions
|
||||
|
||||
#### secLinkOpen
|
||||
|
||||
```c
|
||||
SecLinkT *secLinkOpen(int com, int32_t bps, int dataBits, char parity,
|
||||
int stopBits, int handshake,
|
||||
SecLinkRecvT callback, void *ctx);
|
||||
```
|
||||
|
||||
Opens the COM port via rs232, creates the packet layer, and returns a
|
||||
link handle. Returns `NULL` on failure. The callback is invoked from
|
||||
`secLinkPoll()` for each received packet.
|
||||
|
||||
#### secLinkClose
|
||||
|
||||
```c
|
||||
void secLinkClose(SecLinkT *link);
|
||||
```
|
||||
|
||||
Destroys cipher contexts, closes the packet layer and COM port, and
|
||||
frees all memory.
|
||||
|
||||
#### secLinkHandshake
|
||||
|
||||
```c
|
||||
int secLinkHandshake(SecLinkT *link);
|
||||
```
|
||||
|
||||
Performs a Diffie-Hellman key exchange. Blocks until both sides have
|
||||
exchanged public keys and derived cipher keys. The RNG must be seeded
|
||||
(via `secRngSeed()` or `secRngAddEntropy()`) before calling this.
|
||||
|
||||
Each side derives separate TX and RX keys from the shared secret,
|
||||
using public key ordering to determine directionality. This prevents
|
||||
CTR counter collisions.
|
||||
|
||||
#### secLinkGetPending
|
||||
|
||||
```c
|
||||
int secLinkGetPending(SecLinkT *link);
|
||||
```
|
||||
|
||||
Returns the number of unacknowledged packets in the transmit window.
|
||||
Useful for non-blocking send loops to determine if there is room to
|
||||
send more data.
|
||||
|
||||
#### secLinkIsReady
|
||||
|
||||
```c
|
||||
bool secLinkIsReady(SecLinkT *link);
|
||||
```
|
||||
|
||||
Returns `true` if the handshake is complete and the link is ready for
|
||||
encrypted communication.
|
||||
|
||||
#### secLinkPoll
|
||||
|
||||
```c
|
||||
int secLinkPoll(SecLinkT *link);
|
||||
```
|
||||
|
||||
Reads available serial data, processes received frames, handles ACKs
|
||||
and retransmits. Decrypts encrypted packets and delivers plaintext to
|
||||
the receive callback. Returns the number of packets delivered, or
|
||||
negative on error.
|
||||
|
||||
Must be called frequently (e.g. in your main loop).
|
||||
|
||||
#### secLinkSend
|
||||
|
||||
```c
|
||||
int secLinkSend(SecLinkT *link, const uint8_t *data, int len,
|
||||
uint8_t channel, bool encrypt, bool block);
|
||||
```
|
||||
|
||||
Sends up to `SECLINK_MAX_PAYLOAD` (255) bytes on the given channel.
|
||||
|
||||
- `channel` — logical channel number (0-127)
|
||||
- `encrypt` — if `true`, encrypts the payload (requires completed handshake)
|
||||
- `block` — if `true`, waits for transmit window space; if `false`,
|
||||
returns `SECLINK_ERR_SEND` when the window is full
|
||||
|
||||
Cleartext packets (`encrypt = false`) can be sent before the handshake.
|
||||
|
||||
#### secLinkSendBuf
|
||||
|
||||
```c
|
||||
int secLinkSendBuf(SecLinkT *link, const uint8_t *data, int len,
|
||||
uint8_t channel, bool encrypt);
|
||||
```
|
||||
|
||||
Sends an arbitrarily large buffer by splitting it into
|
||||
`SECLINK_MAX_PAYLOAD`-byte chunks. Always blocks until all data is
|
||||
sent. Returns `SECLINK_SUCCESS` or the first error encountered.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic encrypted link
|
||||
|
||||
```c
|
||||
#include "secLink.h"
|
||||
#include "../security/security.h"
|
||||
|
||||
void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) {
|
||||
// handle received plaintext on 'channel'
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// Seed the RNG before handshake
|
||||
uint8_t entropy[16];
|
||||
secRngGatherEntropy(entropy, sizeof(entropy));
|
||||
secRngSeed(entropy, sizeof(entropy));
|
||||
|
||||
// Open link on COM1 at 115200 8N1
|
||||
SecLinkT *link = secLinkOpen(0, 115200, 8, 'N', 1, 0, onRecv, NULL);
|
||||
if (!link) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Key exchange (blocks until both sides complete)
|
||||
if (secLinkHandshake(link) != SECLINK_SUCCESS) {
|
||||
secLinkClose(link);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Send encrypted data on channel 0
|
||||
const char *msg = "Hello, secure world!";
|
||||
secLinkSend(link, (const uint8_t *)msg, strlen(msg), 0, true, true);
|
||||
|
||||
// Main loop
|
||||
while (1) {
|
||||
secLinkPoll(link);
|
||||
}
|
||||
|
||||
secLinkClose(link);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Mixed encrypted and cleartext channels
|
||||
|
||||
```c
|
||||
#define CHAN_CONTROL 0 // cleartext control channel
|
||||
#define CHAN_DATA 1 // encrypted data channel
|
||||
|
||||
// Send a cleartext status message (no handshake needed)
|
||||
secLinkSend(link, statusMsg, statusLen, CHAN_CONTROL, false, true);
|
||||
|
||||
// Send encrypted payload (requires completed handshake)
|
||||
secLinkSend(link, payload, payloadLen, CHAN_DATA, true, true);
|
||||
```
|
||||
|
||||
### Non-blocking file transfer
|
||||
|
||||
```c
|
||||
int sendFile(SecLinkT *link, const uint8_t *fileData, int fileSize,
|
||||
uint8_t channel, bool encrypt, int windowSize) {
|
||||
int offset = 0;
|
||||
int bytesLeft = fileSize;
|
||||
|
||||
while (bytesLeft > 0) {
|
||||
secLinkPoll(link); // process ACKs, free window slots
|
||||
|
||||
if (secLinkGetPending(link) < windowSize) {
|
||||
int chunk = bytesLeft;
|
||||
if (chunk > SECLINK_MAX_PAYLOAD) {
|
||||
chunk = SECLINK_MAX_PAYLOAD;
|
||||
}
|
||||
|
||||
int rc = secLinkSend(link, fileData + offset, chunk,
|
||||
channel, encrypt, false);
|
||||
if (rc == SECLINK_SUCCESS) {
|
||||
offset += chunk;
|
||||
bytesLeft -= chunk;
|
||||
}
|
||||
// SECLINK_ERR_SEND means window full, just retry next iteration
|
||||
}
|
||||
|
||||
// Application can do other work here:
|
||||
// update progress bar, check for cancel, etc.
|
||||
}
|
||||
|
||||
// Drain remaining ACKs
|
||||
while (secLinkGetPending(link) > 0) {
|
||||
secLinkPoll(link);
|
||||
}
|
||||
|
||||
return SECLINK_SUCCESS;
|
||||
}
|
||||
```
|
||||
|
||||
### Blocking bulk transfer
|
||||
|
||||
```c
|
||||
// Send an entire file in one call (blocks until complete)
|
||||
secLinkSendBuf(link, fileData, fileSize, CHAN_DATA, true);
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
make # builds ../lib/libseclink.a
|
||||
make clean # removes objects and library
|
||||
```
|
||||
|
||||
Link against all four libraries:
|
||||
|
||||
```
|
||||
-lseclink -lpacket -lsecurity -lrs232
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
SecLink requires these libraries (all in `../lib/`):
|
||||
|
||||
- `librs232.a` — serial port driver
|
||||
- `libpacket.a` — packet framing and reliability
|
||||
- `libsecurity.a` — DH key exchange and XTEA cipher
|
||||
|
||||
Target: DJGPP cross-compiler, 486+ CPU.
|
||||
338
seclink/secLink.c
Normal file
338
seclink/secLink.c
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
// Secure serial link — ties rs232, packet, and security into one API
|
||||
//
|
||||
// Handshake protocol:
|
||||
// 1. Both sides send their DH public key (128 bytes) as a single packet
|
||||
// 2. On receiving the remote key, compute shared secret immediately
|
||||
// 3. Derive separate TX/RX cipher keys based on public key ordering
|
||||
// 4. Transition to READY — all subsequent encrypted packets use the keys
|
||||
//
|
||||
// Directionality: the side with the lexicographically lower public key
|
||||
// uses master XOR 0xAA for TX and master XOR 0x55 for RX. The other
|
||||
// side uses the reverse. This prevents CTR counter collisions.
|
||||
//
|
||||
// Channel header byte: bit 7 = encrypted, bits 6..0 = channel (0-127)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "secLink.h"
|
||||
#include "../rs232/rs232.h"
|
||||
#include "../packet/packet.h"
|
||||
#include "../security/security.h"
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Internal defines
|
||||
// ========================================================================
|
||||
|
||||
#define STATE_INIT 0
|
||||
#define STATE_HANDSHAKE 1
|
||||
#define STATE_READY 2
|
||||
|
||||
#define TX_KEY_XOR 0xAA
|
||||
#define RX_KEY_XOR 0x55
|
||||
|
||||
#define ENCRYPT_FLAG 0x80
|
||||
#define CHANNEL_MASK 0x7F
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Types
|
||||
// ========================================================================
|
||||
|
||||
struct SecLinkS {
|
||||
int com;
|
||||
PktConnT *pkt;
|
||||
SecDhT *dh;
|
||||
SecCipherT *txCipher;
|
||||
SecCipherT *rxCipher;
|
||||
SecLinkRecvT userCallback;
|
||||
void *userCtx;
|
||||
int state;
|
||||
uint8_t myPub[SEC_DH_KEY_SIZE];
|
||||
uint8_t remoteKey[SEC_DH_KEY_SIZE];
|
||||
bool gotRemoteKey;
|
||||
};
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Static prototypes (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
static void completeHandshake(SecLinkT *link);
|
||||
static void internalRecv(void *ctx, const uint8_t *data, int len);
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Static functions (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
static void completeHandshake(SecLinkT *link) {
|
||||
uint8_t masterKey[SEC_XTEA_KEY_SIZE];
|
||||
uint8_t txKey[SEC_XTEA_KEY_SIZE];
|
||||
uint8_t rxKey[SEC_XTEA_KEY_SIZE];
|
||||
bool weAreLower;
|
||||
|
||||
secDhComputeSecret(link->dh, link->remoteKey, SEC_DH_KEY_SIZE);
|
||||
secDhDeriveKey(link->dh, masterKey, SEC_XTEA_KEY_SIZE);
|
||||
|
||||
// Derive directional keys so each side encrypts with a unique key
|
||||
weAreLower = (memcmp(link->myPub, link->remoteKey, SEC_DH_KEY_SIZE) < 0);
|
||||
for (int i = 0; i < SEC_XTEA_KEY_SIZE; i++) {
|
||||
txKey[i] = masterKey[i] ^ (weAreLower ? TX_KEY_XOR : RX_KEY_XOR);
|
||||
rxKey[i] = masterKey[i] ^ (weAreLower ? RX_KEY_XOR : TX_KEY_XOR);
|
||||
}
|
||||
|
||||
link->txCipher = secCipherCreate(txKey);
|
||||
link->rxCipher = secCipherCreate(rxKey);
|
||||
|
||||
memset(masterKey, 0, sizeof(masterKey));
|
||||
memset(txKey, 0, sizeof(txKey));
|
||||
memset(rxKey, 0, sizeof(rxKey));
|
||||
|
||||
// Destroy DH context — private key no longer needed
|
||||
secDhDestroy(link->dh);
|
||||
link->dh = 0;
|
||||
|
||||
link->state = STATE_READY;
|
||||
}
|
||||
|
||||
|
||||
static void internalRecv(void *ctx, const uint8_t *data, int len) {
|
||||
SecLinkT *link = (SecLinkT *)ctx;
|
||||
|
||||
if (link->state == STATE_HANDSHAKE && !link->gotRemoteKey) {
|
||||
// During handshake, expect exactly one 128-byte public key
|
||||
if (len == SEC_DH_KEY_SIZE) {
|
||||
memcpy(link->remoteKey, data, len);
|
||||
link->gotRemoteKey = true;
|
||||
completeHandshake(link);
|
||||
}
|
||||
} else if (link->state == STATE_READY || link->state == STATE_INIT) {
|
||||
// Need at least the channel header byte
|
||||
if (len < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t hdr = data[0];
|
||||
uint8_t channel = hdr & CHANNEL_MASK;
|
||||
bool encrypted = (hdr & ENCRYPT_FLAG) != 0;
|
||||
const uint8_t *payload = data + 1;
|
||||
int payloadLen = len - 1;
|
||||
|
||||
if (encrypted && link->state == STATE_READY) {
|
||||
uint8_t plaintext[PKT_MAX_PAYLOAD];
|
||||
if (payloadLen > (int)sizeof(plaintext)) {
|
||||
payloadLen = (int)sizeof(plaintext);
|
||||
}
|
||||
memcpy(plaintext, payload, payloadLen);
|
||||
secCipherCrypt(link->rxCipher, plaintext, payloadLen);
|
||||
if (link->userCallback) {
|
||||
link->userCallback(link->userCtx, plaintext, payloadLen, channel);
|
||||
}
|
||||
} else if (!encrypted) {
|
||||
if (link->userCallback) {
|
||||
link->userCallback(link->userCtx, payload, payloadLen, channel);
|
||||
}
|
||||
}
|
||||
// encrypted but not READY: silently drop
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Public functions (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
void secLinkClose(SecLinkT *link) {
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (link->txCipher) {
|
||||
secCipherDestroy(link->txCipher);
|
||||
}
|
||||
if (link->rxCipher) {
|
||||
secCipherDestroy(link->rxCipher);
|
||||
}
|
||||
if (link->dh) {
|
||||
secDhDestroy(link->dh);
|
||||
}
|
||||
if (link->pkt) {
|
||||
pktClose(link->pkt);
|
||||
}
|
||||
rs232Close(link->com);
|
||||
|
||||
memset(link, 0, sizeof(SecLinkT));
|
||||
free(link);
|
||||
}
|
||||
|
||||
|
||||
int secLinkGetPending(SecLinkT *link) {
|
||||
if (!link) {
|
||||
return SECLINK_ERR_PARAM;
|
||||
}
|
||||
|
||||
return pktGetPending(link->pkt);
|
||||
}
|
||||
|
||||
|
||||
int secLinkHandshake(SecLinkT *link) {
|
||||
int len;
|
||||
int rc;
|
||||
|
||||
if (!link) {
|
||||
return SECLINK_ERR_PARAM;
|
||||
}
|
||||
|
||||
// Generate DH keypair
|
||||
link->dh = secDhCreate();
|
||||
if (!link->dh) {
|
||||
return SECLINK_ERR_ALLOC;
|
||||
}
|
||||
|
||||
rc = secDhGenerateKeys(link->dh);
|
||||
if (rc != SEC_SUCCESS) {
|
||||
return SECLINK_ERR_HANDSHAKE;
|
||||
}
|
||||
|
||||
// Export our public key
|
||||
len = SEC_DH_KEY_SIZE;
|
||||
secDhGetPublicKey(link->dh, link->myPub, &len);
|
||||
|
||||
// Enter handshake state and send our key
|
||||
link->state = STATE_HANDSHAKE;
|
||||
link->gotRemoteKey = false;
|
||||
|
||||
rc = pktSend(link->pkt, link->myPub, SEC_DH_KEY_SIZE, true);
|
||||
if (rc != PKT_SUCCESS) {
|
||||
return SECLINK_ERR_HANDSHAKE;
|
||||
}
|
||||
|
||||
// Poll until the callback completes the handshake
|
||||
while (link->state != STATE_READY) {
|
||||
pktPoll(link->pkt);
|
||||
}
|
||||
|
||||
return SECLINK_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
bool secLinkIsReady(SecLinkT *link) {
|
||||
if (!link) {
|
||||
return false;
|
||||
}
|
||||
return link->state == STATE_READY;
|
||||
}
|
||||
|
||||
|
||||
SecLinkT *secLinkOpen(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake, SecLinkRecvT callback, void *ctx) {
|
||||
SecLinkT *link;
|
||||
int rc;
|
||||
|
||||
link = (SecLinkT *)calloc(1, sizeof(SecLinkT));
|
||||
if (!link) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
link->com = com;
|
||||
link->userCallback = callback;
|
||||
link->userCtx = ctx;
|
||||
link->state = STATE_INIT;
|
||||
|
||||
// Open serial port
|
||||
rc = rs232Open(com, bps, dataBits, parity, stopBits, handshake);
|
||||
if (rc != RS232_SUCCESS) {
|
||||
free(link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Open packet layer with our internal callback
|
||||
link->pkt = pktOpen(com, 0, internalRecv, link);
|
||||
if (!link->pkt) {
|
||||
rs232Close(com);
|
||||
free(link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
|
||||
int secLinkPoll(SecLinkT *link) {
|
||||
if (!link) {
|
||||
return SECLINK_ERR_PARAM;
|
||||
}
|
||||
|
||||
return pktPoll(link->pkt);
|
||||
}
|
||||
|
||||
|
||||
int secLinkSend(SecLinkT *link, const uint8_t *data, int len, uint8_t channel, bool encrypt, bool block) {
|
||||
uint8_t buf[PKT_MAX_PAYLOAD];
|
||||
int rc;
|
||||
|
||||
if (!link || !data) {
|
||||
return SECLINK_ERR_PARAM;
|
||||
}
|
||||
if (channel > SECLINK_MAX_CHANNEL) {
|
||||
return SECLINK_ERR_PARAM;
|
||||
}
|
||||
if (encrypt && link->state != STATE_READY) {
|
||||
return SECLINK_ERR_NOT_READY;
|
||||
}
|
||||
if (len <= 0 || len > SECLINK_MAX_PAYLOAD) {
|
||||
return SECLINK_ERR_PARAM;
|
||||
}
|
||||
|
||||
// Build channel header byte
|
||||
buf[0] = channel & CHANNEL_MASK;
|
||||
if (encrypt) {
|
||||
buf[0] |= ENCRYPT_FLAG;
|
||||
}
|
||||
|
||||
// Copy payload after header
|
||||
memcpy(buf + 1, data, len);
|
||||
|
||||
// Encrypt the payload portion only (not the header)
|
||||
if (encrypt) {
|
||||
secCipherCrypt(link->txCipher, buf + 1, len);
|
||||
}
|
||||
|
||||
rc = pktSend(link->pkt, buf, len + 1, block);
|
||||
if (rc != PKT_SUCCESS) {
|
||||
return SECLINK_ERR_SEND;
|
||||
}
|
||||
|
||||
return SECLINK_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int secLinkSendBuf(SecLinkT *link, const uint8_t *data, int len, uint8_t channel, bool encrypt) {
|
||||
int offset = 0;
|
||||
int rc;
|
||||
|
||||
if (!link || !data) {
|
||||
return SECLINK_ERR_PARAM;
|
||||
}
|
||||
if (len <= 0) {
|
||||
return SECLINK_ERR_PARAM;
|
||||
}
|
||||
|
||||
while (offset < len) {
|
||||
int chunk = len - offset;
|
||||
if (chunk > SECLINK_MAX_PAYLOAD) {
|
||||
chunk = SECLINK_MAX_PAYLOAD;
|
||||
}
|
||||
|
||||
rc = secLinkSend(link, data + offset, chunk, channel, encrypt, true);
|
||||
if (rc != SECLINK_SUCCESS) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
offset += chunk;
|
||||
}
|
||||
|
||||
return SECLINK_SUCCESS;
|
||||
}
|
||||
74
seclink/secLink.h
Normal file
74
seclink/secLink.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// Secure serial link — convenience wrapper tying rs232 + packet + security
|
||||
//
|
||||
// Usage:
|
||||
// 1. secLinkOpen() — opens COM port, sets up packet framing
|
||||
// 2. secLinkHandshake() — DH key exchange (blocks until both sides complete)
|
||||
// 3. secLinkSend() — send data (optionally encrypted) on a channel
|
||||
// 4. secLinkPoll() — receive, decrypt if needed, deliver to callback
|
||||
// 5. secLinkClose() — tear everything down
|
||||
//
|
||||
// Each packet carries a one-byte header: bit 7 = encrypted flag,
|
||||
// bits 6..0 = channel number (0-127). The callback receives plaintext
|
||||
// regardless of whether encryption was used.
|
||||
|
||||
#ifndef SECLINK_H
|
||||
#define SECLINK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Error codes
|
||||
#define SECLINK_SUCCESS 0
|
||||
#define SECLINK_ERR_PARAM -1
|
||||
#define SECLINK_ERR_SERIAL -2
|
||||
#define SECLINK_ERR_ALLOC -3
|
||||
#define SECLINK_ERR_HANDSHAKE -4
|
||||
#define SECLINK_ERR_NOT_READY -5
|
||||
#define SECLINK_ERR_SEND -6
|
||||
|
||||
// Max plaintext payload per send (packet max minus 1-byte channel header)
|
||||
#define SECLINK_MAX_PAYLOAD 255
|
||||
|
||||
// Channel limits
|
||||
#define SECLINK_MAX_CHANNEL 127
|
||||
|
||||
// Receive callback — delivers plaintext with channel number
|
||||
typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
||||
|
||||
// Opaque handle
|
||||
typedef struct SecLinkS SecLinkT;
|
||||
|
||||
|
||||
// Open a secure serial link. Opens the COM port and packet layer.
|
||||
// Handshake must be called separately before sending encrypted data.
|
||||
SecLinkT *secLinkOpen(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake, SecLinkRecvT callback, void *ctx);
|
||||
|
||||
// Close the link. Frees all resources and closes the COM port.
|
||||
void secLinkClose(SecLinkT *link);
|
||||
|
||||
// Perform DH key exchange. Blocks until both sides have exchanged keys
|
||||
// and derived cipher keys. RNG must be seeded before calling this.
|
||||
int secLinkHandshake(SecLinkT *link);
|
||||
|
||||
// Get number of unacknowledged packets in the transmit window.
|
||||
int secLinkGetPending(SecLinkT *link);
|
||||
|
||||
// Returns true if handshake is complete and link is ready for data.
|
||||
bool secLinkIsReady(SecLinkT *link);
|
||||
|
||||
// Poll for incoming data. Decrypts if needed and delivers to callback.
|
||||
// Returns number of packets delivered, or negative on error.
|
||||
int secLinkPoll(SecLinkT *link);
|
||||
|
||||
// Send data on a channel. If encrypt is true, data is encrypted before
|
||||
// sending (requires completed handshake). Clear packets can be sent
|
||||
// without a handshake. len must be 1..SECLINK_MAX_PAYLOAD.
|
||||
// If block is true, waits for transmit window space.
|
||||
int secLinkSend(SecLinkT *link, const uint8_t *data, int len, uint8_t channel, bool encrypt, bool block);
|
||||
|
||||
// Send an arbitrarily large buffer by splitting it into SECLINK_MAX_PAYLOAD
|
||||
// chunks. Always blocks until all data is sent. Returns SECLINK_SUCCESS
|
||||
// or the first error encountered.
|
||||
int secLinkSendBuf(SecLinkT *link, const uint8_t *data, int len, uint8_t channel, bool encrypt);
|
||||
|
||||
#endif
|
||||
38
security/Makefile
Normal file
38
security/Makefile
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Security Library Makefile for DJGPP cross-compilation
|
||||
|
||||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
AR = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ar
|
||||
RANLIB = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ranlib
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586
|
||||
|
||||
OBJDIR = ../obj/security
|
||||
LIBDIR = ../lib
|
||||
|
||||
SRCS = security.c
|
||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||
TARGET = $(LIBDIR)/libsecurity.a
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) | $(LIBDIR)
|
||||
$(AR) rcs $@ $(OBJS)
|
||||
$(RANLIB) $@
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(LIBDIR):
|
||||
mkdir -p $(LIBDIR)
|
||||
|
||||
# Dependencies
|
||||
$(OBJDIR)/security.o: security.c security.h
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
259
security/README.md
Normal file
259
security/README.md
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
# Security — DH Key Exchange and XTEA-CTR Cipher
|
||||
|
||||
Cryptographic library providing Diffie-Hellman key exchange and XTEA
|
||||
symmetric encryption, optimized for 486-class DOS hardware running
|
||||
under DJGPP/DPMI.
|
||||
|
||||
## Components
|
||||
|
||||
### Diffie-Hellman Key Exchange
|
||||
|
||||
- 1024-bit MODP group (RFC 2409 Group 2 safe prime)
|
||||
- 256-bit private exponents for fast computation on 486 CPUs
|
||||
- Montgomery multiplication (CIOS variant) for modular exponentiation
|
||||
- Lazy-initialized Montgomery constants (R^2 mod p, -p0^-1 mod 2^32)
|
||||
|
||||
### XTEA Cipher (CTR Mode)
|
||||
|
||||
- 128-bit key, 64-bit block size, 32 rounds
|
||||
- CTR mode — encrypt and decrypt are the same XOR operation
|
||||
- No lookup tables, no key schedule — just shifts, adds, and XORs
|
||||
- Ideal for constrained environments with small key setup cost
|
||||
|
||||
### Pseudo-Random Number Generator
|
||||
|
||||
- XTEA-CTR based DRBG (deterministic random bit generator)
|
||||
- Hardware entropy from PIT counter (~10 bits) and BIOS tick count
|
||||
- Supports additional entropy injection (keyboard timing, mouse, etc.)
|
||||
- Auto-seeds from hardware on first use if not explicitly seeded
|
||||
|
||||
## Performance
|
||||
|
||||
At serial port speeds, encryption overhead is minimal:
|
||||
|
||||
| Speed | Blocks/sec | CPU cycles/sec | % of 33 MHz 486 |
|
||||
|----------|------------|----------------|------------------|
|
||||
| 9600 | 120 | ~240K | < 1% |
|
||||
| 57600 | 720 | ~1.4M | ~4% |
|
||||
| 115200 | 1440 | ~2.9M | ~9% |
|
||||
|
||||
DH key exchange takes approximately 0.3s at 66 MHz or 0.6s at 33 MHz
|
||||
(256-bit private exponent, 1024-bit modulus).
|
||||
|
||||
## API Reference
|
||||
|
||||
### Constants
|
||||
|
||||
| Name | Value | Description |
|
||||
|---------------------|-------|--------------------------------|
|
||||
| `SEC_DH_KEY_SIZE` | 128 | DH public key size (bytes) |
|
||||
| `SEC_XTEA_KEY_SIZE` | 16 | XTEA key size (bytes) |
|
||||
| `SEC_SUCCESS` | 0 | Success |
|
||||
| `SEC_ERR_PARAM` | -1 | Invalid parameter |
|
||||
| `SEC_ERR_NOT_READY` | -2 | Keys not yet generated/derived |
|
||||
| `SEC_ERR_ALLOC` | -3 | Memory allocation failed |
|
||||
|
||||
### Types
|
||||
|
||||
```c
|
||||
typedef struct SecDhS SecDhT; // Opaque DH context
|
||||
typedef struct SecCipherS SecCipherT; // Opaque cipher context
|
||||
```
|
||||
|
||||
### RNG Functions
|
||||
|
||||
```c
|
||||
int secRngGatherEntropy(uint8_t *buf, int len);
|
||||
```
|
||||
|
||||
Reads hardware entropy sources (PIT counter, BIOS tick count). Returns
|
||||
the number of bytes written. Provides roughly 20 bits of true entropy.
|
||||
|
||||
```c
|
||||
void secRngSeed(const uint8_t *entropy, int len);
|
||||
```
|
||||
|
||||
Initializes the DRBG with the given entropy. XOR-folds the input into
|
||||
the XTEA key, derives the counter, and mixes state by generating and
|
||||
discarding 64 bytes.
|
||||
|
||||
```c
|
||||
void secRngAddEntropy(const uint8_t *data, int len);
|
||||
```
|
||||
|
||||
Mixes additional entropy into the running RNG state without resetting
|
||||
it. Use this to stir in keyboard timing, mouse jitter, or other
|
||||
runtime entropy.
|
||||
|
||||
```c
|
||||
void secRngBytes(uint8_t *buf, int len);
|
||||
```
|
||||
|
||||
Generates `len` pseudo-random bytes. Auto-seeds from hardware if not
|
||||
previously seeded.
|
||||
|
||||
### Diffie-Hellman Functions
|
||||
|
||||
```c
|
||||
SecDhT *secDhCreate(void);
|
||||
```
|
||||
|
||||
Allocates a new DH context. Returns `NULL` on allocation failure.
|
||||
|
||||
```c
|
||||
int secDhGenerateKeys(SecDhT *dh);
|
||||
```
|
||||
|
||||
Generates a 256-bit random private key and computes the corresponding
|
||||
1024-bit public key (g^private mod p). The RNG should be seeded first.
|
||||
|
||||
```c
|
||||
int secDhGetPublicKey(SecDhT *dh, uint8_t *buf, int *len);
|
||||
```
|
||||
|
||||
Exports the public key into `buf`. On entry, `*len` must be at least
|
||||
`SEC_DH_KEY_SIZE` (128). On return, `*len` is set to 128.
|
||||
|
||||
```c
|
||||
int secDhComputeSecret(SecDhT *dh, const uint8_t *remotePub, int len);
|
||||
```
|
||||
|
||||
Computes the shared secret from the remote side's public key.
|
||||
Validates that the remote key is in range [2, p-2] to prevent
|
||||
small-subgroup attacks.
|
||||
|
||||
```c
|
||||
int secDhDeriveKey(SecDhT *dh, uint8_t *key, int keyLen);
|
||||
```
|
||||
|
||||
Derives a symmetric key by XOR-folding the 128-byte shared secret
|
||||
down to `keyLen` bytes.
|
||||
|
||||
```c
|
||||
void secDhDestroy(SecDhT *dh);
|
||||
```
|
||||
|
||||
Securely zeroes and frees the DH context (private key, shared secret).
|
||||
|
||||
### Cipher Functions
|
||||
|
||||
```c
|
||||
SecCipherT *secCipherCreate(const uint8_t *key);
|
||||
```
|
||||
|
||||
Creates an XTEA-CTR cipher context with the given 16-byte key. Counter
|
||||
starts at zero.
|
||||
|
||||
```c
|
||||
void secCipherCrypt(SecCipherT *c, uint8_t *data, int len);
|
||||
```
|
||||
|
||||
Encrypts or decrypts `data` in place. CTR mode is symmetric — the
|
||||
same operation encrypts and decrypts.
|
||||
|
||||
```c
|
||||
void secCipherSetNonce(SecCipherT *c, uint32_t nonceLo, uint32_t nonceHi);
|
||||
```
|
||||
|
||||
Sets the 64-bit nonce/counter. Call before encrypting if you need a
|
||||
specific starting counter value.
|
||||
|
||||
```c
|
||||
void secCipherDestroy(SecCipherT *c);
|
||||
```
|
||||
|
||||
Securely zeroes and frees the cipher context.
|
||||
|
||||
## Example
|
||||
|
||||
### Full Key Exchange
|
||||
|
||||
```c
|
||||
#include "security.h"
|
||||
#include <string.h>
|
||||
|
||||
// Seed the RNG
|
||||
uint8_t entropy[16];
|
||||
secRngGatherEntropy(entropy, sizeof(entropy));
|
||||
secRngSeed(entropy, sizeof(entropy));
|
||||
|
||||
// Create DH context and generate keys
|
||||
SecDhT *dh = secDhCreate();
|
||||
secDhGenerateKeys(dh);
|
||||
|
||||
// Export public key to send to remote
|
||||
uint8_t myPub[SEC_DH_KEY_SIZE];
|
||||
int pubLen = SEC_DH_KEY_SIZE;
|
||||
secDhGetPublicKey(dh, myPub, &pubLen);
|
||||
// ... send myPub to remote, receive remotePub ...
|
||||
|
||||
// Compute shared secret and derive a 16-byte key
|
||||
secDhComputeSecret(dh, remotePub, SEC_DH_KEY_SIZE);
|
||||
|
||||
uint8_t key[SEC_XTEA_KEY_SIZE];
|
||||
secDhDeriveKey(dh, key, SEC_XTEA_KEY_SIZE);
|
||||
secDhDestroy(dh);
|
||||
|
||||
// Create cipher and encrypt
|
||||
SecCipherT *cipher = secCipherCreate(key);
|
||||
uint8_t message[] = "Secret message";
|
||||
secCipherCrypt(cipher, message, sizeof(message));
|
||||
// message is now encrypted
|
||||
|
||||
// Decrypt (same operation — CTR mode is symmetric)
|
||||
// Reset counter first if using the same cipher context
|
||||
secCipherSetNonce(cipher, 0, 0);
|
||||
secCipherCrypt(cipher, message, sizeof(message));
|
||||
// message is now plaintext again
|
||||
|
||||
secCipherDestroy(cipher);
|
||||
```
|
||||
|
||||
### Standalone Encryption
|
||||
|
||||
```c
|
||||
// XTEA-CTR can be used independently of DH
|
||||
uint8_t key[SEC_XTEA_KEY_SIZE] = { /* your key */ };
|
||||
SecCipherT *c = secCipherCreate(key);
|
||||
|
||||
uint8_t data[1024];
|
||||
// ... fill data ...
|
||||
secCipherCrypt(c, data, sizeof(data)); // encrypt in place
|
||||
|
||||
secCipherDestroy(c);
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### BigNum Arithmetic
|
||||
|
||||
All modular arithmetic uses a 1024-bit big number type (`BigNumT`)
|
||||
stored as 32 x `uint32_t` words in little-endian order. Operations:
|
||||
|
||||
- Add, subtract, compare, shift-left-1, bit test
|
||||
- Montgomery multiplication (CIOS with implicit right-shift)
|
||||
- Modular exponentiation (left-to-right binary square-and-multiply)
|
||||
|
||||
### Montgomery Multiplication
|
||||
|
||||
The CIOS (Coarsely Integrated Operand Scanning) variant computes
|
||||
`a * b * R^-1 mod m` in a single pass with implicit division by the
|
||||
word base. Constants are computed once on first DH use:
|
||||
|
||||
- `R^2 mod p` — via 2048 iterations of shift-and-conditional-subtract
|
||||
- `-p[0]^-1 mod 2^32` — via Newton's method (5 iterations)
|
||||
|
||||
### Secure Zeroing
|
||||
|
||||
Key material is erased using a volatile-pointer loop that the compiler
|
||||
cannot optimize away, preventing sensitive data from lingering in
|
||||
memory.
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
make # builds ../lib/libsecurity.a
|
||||
make clean # removes objects and library
|
||||
```
|
||||
|
||||
Target: DJGPP cross-compiler, 486+ CPU.
|
||||
688
security/security.c
Normal file
688
security/security.c
Normal file
|
|
@ -0,0 +1,688 @@
|
|||
// Security library: DH key exchange + XTEA-CTR cipher for DJGPP
|
||||
//
|
||||
// Diffie-Hellman uses the RFC 2409 Group 2 (1024-bit) safe prime with
|
||||
// Montgomery multiplication for modular exponentiation. Private exponents
|
||||
// are 256 bits for fast computation on 486-class hardware.
|
||||
//
|
||||
// XTEA in CTR mode provides symmetric encryption. No lookup tables,
|
||||
// no key schedule — just shifts, adds, and XORs.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pc.h>
|
||||
#include <sys/farptr.h>
|
||||
#include <go32.h>
|
||||
#include "security.h"
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Internal defines
|
||||
// ========================================================================
|
||||
|
||||
#define BN_BITS 1024
|
||||
#define BN_WORDS (BN_BITS / 32)
|
||||
#define BN_BYTES (BN_BITS / 8)
|
||||
|
||||
#define DH_PRIVATE_BITS 256
|
||||
#define DH_PRIVATE_BYTES (DH_PRIVATE_BITS / 8)
|
||||
|
||||
#define XTEA_ROUNDS 32
|
||||
#define XTEA_DELTA 0x9E3779B9
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Types
|
||||
// ========================================================================
|
||||
|
||||
typedef struct {
|
||||
uint32_t w[BN_WORDS];
|
||||
} BigNumT;
|
||||
|
||||
struct SecDhS {
|
||||
BigNumT privateKey;
|
||||
BigNumT publicKey;
|
||||
BigNumT sharedSecret;
|
||||
bool hasKeys;
|
||||
bool hasSecret;
|
||||
};
|
||||
|
||||
struct SecCipherS {
|
||||
uint32_t key[4];
|
||||
uint32_t nonce[2];
|
||||
uint32_t counter[2];
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t key[4];
|
||||
uint32_t counter[2];
|
||||
bool seeded;
|
||||
} RngStateT;
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Static globals
|
||||
// ========================================================================
|
||||
|
||||
// RFC 2409 Group 2 (1024-bit MODP) prime, little-endian word order
|
||||
static const BigNumT sDhPrime = { .w = {
|
||||
0x39E38FAF, 0xCDB1CEDC, 0x51FF5DB8, 0x85E28A20,
|
||||
0x1E9C284F, 0x2BB72AE0, 0x60F89D81, 0x4E664FD5,
|
||||
0x45E6F3A1, 0x92F2129E, 0xB8E51B21, 0x35C7D431,
|
||||
0x14A0C959, 0x137E2179, 0x5BE0CD19, 0x7A51F1D7,
|
||||
0xF25F1468, 0x302B0A6D, 0xCD3A431B, 0xEF9519B3,
|
||||
0x8E3404DD, 0x514A0879, 0x3B139B22, 0x020BBEA6,
|
||||
0x8A67CC74, 0x29024E08, 0x80DC1CD1, 0xC4C6628B,
|
||||
0x2168C234, 0xC90FDAA2, 0xFFFFFFFF, 0xFFFFFFFF
|
||||
}};
|
||||
|
||||
// Generator g = 2
|
||||
static const BigNumT sDhGenerator = { .w = { 2 } };
|
||||
|
||||
// Montgomery constants (computed lazily)
|
||||
static BigNumT sDhR2; // R^2 mod p
|
||||
static uint32_t sDhM0Inv; // -p[0]^(-1) mod 2^32
|
||||
static bool sDhInited = false;
|
||||
|
||||
// RNG state
|
||||
static RngStateT sRng = { .seeded = false };
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Static prototypes (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
static int bnAdd(BigNumT *result, const BigNumT *a, const BigNumT *b);
|
||||
static int bnBit(const BigNumT *a, int n);
|
||||
static int bnBitLength(const BigNumT *a);
|
||||
static void bnClear(BigNumT *a);
|
||||
static int bnCmp(const BigNumT *a, const BigNumT *b);
|
||||
static void bnCopy(BigNumT *dst, const BigNumT *src);
|
||||
static void bnFromBytes(BigNumT *a, const uint8_t *buf);
|
||||
static void bnModExp(BigNumT *result, const BigNumT *base, const BigNumT *exp, const BigNumT *mod, uint32_t m0inv, const BigNumT *r2);
|
||||
static void bnMontMul(BigNumT *result, const BigNumT *a, const BigNumT *b, const BigNumT *mod, uint32_t m0inv);
|
||||
static void bnSet(BigNumT *a, uint32_t val);
|
||||
static int bnShiftLeft1(BigNumT *a);
|
||||
static int bnSub(BigNumT *result, const BigNumT *a, const BigNumT *b);
|
||||
static void bnToBytes(uint8_t *buf, const BigNumT *a);
|
||||
static uint32_t computeM0Inv(uint32_t m0);
|
||||
static void computeR2(BigNumT *r2, const BigNumT *m);
|
||||
static void dhInit(void);
|
||||
static void secureZero(void *ptr, int len);
|
||||
static void xteaEncryptBlock(uint32_t v[2], const uint32_t key[4]);
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// BigNum functions (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
static int __attribute__((unused)) bnAdd(BigNumT *result, const BigNumT *a, const BigNumT *b) {
|
||||
uint64_t carry = 0;
|
||||
|
||||
for (int i = 0; i < BN_WORDS; i++) {
|
||||
uint64_t sum = (uint64_t)a->w[i] + b->w[i] + carry;
|
||||
result->w[i] = (uint32_t)sum;
|
||||
carry = sum >> 32;
|
||||
}
|
||||
|
||||
return (int)carry;
|
||||
}
|
||||
|
||||
|
||||
static int bnBit(const BigNumT *a, int n) {
|
||||
return (a->w[n / 32] >> (n % 32)) & 1;
|
||||
}
|
||||
|
||||
|
||||
static int bnBitLength(const BigNumT *a) {
|
||||
for (int i = BN_WORDS - 1; i >= 0; i--) {
|
||||
if (a->w[i]) {
|
||||
uint32_t v = a->w[i];
|
||||
int bits = i * 32;
|
||||
while (v) {
|
||||
bits++;
|
||||
v >>= 1;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void bnClear(BigNumT *a) {
|
||||
memset(a->w, 0, sizeof(a->w));
|
||||
}
|
||||
|
||||
|
||||
static int bnCmp(const BigNumT *a, const BigNumT *b) {
|
||||
for (int i = BN_WORDS - 1; i >= 0; i--) {
|
||||
if (a->w[i] > b->w[i]) {
|
||||
return 1;
|
||||
}
|
||||
if (a->w[i] < b->w[i]) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void bnCopy(BigNumT *dst, const BigNumT *src) {
|
||||
memcpy(dst->w, src->w, sizeof(dst->w));
|
||||
}
|
||||
|
||||
|
||||
static void bnFromBytes(BigNumT *a, const uint8_t *buf) {
|
||||
for (int i = 0; i < BN_WORDS; i++) {
|
||||
int j = (BN_WORDS - 1 - i) * 4;
|
||||
a->w[i] = ((uint32_t)buf[j] << 24) |
|
||||
((uint32_t)buf[j + 1] << 16) |
|
||||
((uint32_t)buf[j + 2] << 8) |
|
||||
(uint32_t)buf[j + 3];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void bnModExp(BigNumT *result, const BigNumT *base, const BigNumT *exp, const BigNumT *mod, uint32_t m0inv, const BigNumT *r2) {
|
||||
BigNumT montBase;
|
||||
BigNumT montResult;
|
||||
BigNumT one;
|
||||
int bits;
|
||||
bool started;
|
||||
|
||||
// Convert base to Montgomery form: montBase = base * R mod m
|
||||
bnMontMul(&montBase, base, r2, mod, m0inv);
|
||||
|
||||
// Initialize montResult to 1 in Montgomery form (= R mod m)
|
||||
bnClear(&one);
|
||||
one.w[0] = 1;
|
||||
bnMontMul(&montResult, &one, r2, mod, m0inv);
|
||||
|
||||
// Left-to-right binary square-and-multiply
|
||||
bits = bnBitLength(exp);
|
||||
started = false;
|
||||
for (int i = bits - 1; i >= 0; i--) {
|
||||
if (started) {
|
||||
bnMontMul(&montResult, &montResult, &montResult, mod, m0inv);
|
||||
}
|
||||
if (bnBit(exp, i)) {
|
||||
if (!started) {
|
||||
bnCopy(&montResult, &montBase);
|
||||
started = true;
|
||||
} else {
|
||||
bnMontMul(&montResult, &montResult, &montBase, mod, m0inv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back from Montgomery form: result = montResult * 1 * R^(-1) mod m
|
||||
bnClear(&one);
|
||||
one.w[0] = 1;
|
||||
bnMontMul(result, &montResult, &one, mod, m0inv);
|
||||
}
|
||||
|
||||
|
||||
static void bnMontMul(BigNumT *result, const BigNumT *a, const BigNumT *b, const BigNumT *mod, uint32_t m0inv) {
|
||||
uint32_t t[BN_WORDS + 1];
|
||||
uint32_t u;
|
||||
uint64_t carry;
|
||||
uint64_t prod;
|
||||
uint64_t sum;
|
||||
|
||||
memset(t, 0, sizeof(t));
|
||||
|
||||
for (int i = 0; i < BN_WORDS; i++) {
|
||||
// Step 1: t += a[i] * b
|
||||
carry = 0;
|
||||
for (int j = 0; j < BN_WORDS; j++) {
|
||||
prod = (uint64_t)a->w[i] * b->w[j] + t[j] + carry;
|
||||
t[j] = (uint32_t)prod;
|
||||
carry = prod >> 32;
|
||||
}
|
||||
t[BN_WORDS] += (uint32_t)carry;
|
||||
|
||||
// Step 2: Montgomery reduction factor
|
||||
u = t[0] * m0inv;
|
||||
|
||||
// Step 3: t = (t + u * mod) >> 32
|
||||
// First word: result is zero by construction, take carry only
|
||||
prod = (uint64_t)u * mod->w[0] + t[0];
|
||||
carry = prod >> 32;
|
||||
// Remaining words: shift result left by one position
|
||||
for (int j = 1; j < BN_WORDS; j++) {
|
||||
prod = (uint64_t)u * mod->w[j] + t[j] + carry;
|
||||
t[j - 1] = (uint32_t)prod;
|
||||
carry = prod >> 32;
|
||||
}
|
||||
sum = (uint64_t)t[BN_WORDS] + carry;
|
||||
t[BN_WORDS - 1] = (uint32_t)sum;
|
||||
t[BN_WORDS] = (uint32_t)(sum >> 32);
|
||||
}
|
||||
|
||||
// Copy result
|
||||
memcpy(result->w, t, BN_WORDS * sizeof(uint32_t));
|
||||
|
||||
// Conditional subtract if result >= mod
|
||||
if (t[BN_WORDS] || bnCmp(result, mod) >= 0) {
|
||||
bnSub(result, result, mod);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void bnSet(BigNumT *a, uint32_t val) {
|
||||
bnClear(a);
|
||||
a->w[0] = val;
|
||||
}
|
||||
|
||||
|
||||
static int bnShiftLeft1(BigNumT *a) {
|
||||
uint32_t carry = 0;
|
||||
|
||||
for (int i = 0; i < BN_WORDS; i++) {
|
||||
uint32_t newCarry = a->w[i] >> 31;
|
||||
a->w[i] = (a->w[i] << 1) | carry;
|
||||
carry = newCarry;
|
||||
}
|
||||
|
||||
return carry;
|
||||
}
|
||||
|
||||
|
||||
static int bnSub(BigNumT *result, const BigNumT *a, const BigNumT *b) {
|
||||
uint64_t borrow = 0;
|
||||
|
||||
for (int i = 0; i < BN_WORDS; i++) {
|
||||
uint64_t diff = (uint64_t)a->w[i] - b->w[i] - borrow;
|
||||
result->w[i] = (uint32_t)diff;
|
||||
borrow = (diff >> 63) & 1;
|
||||
}
|
||||
|
||||
return (int)borrow;
|
||||
}
|
||||
|
||||
|
||||
static void bnToBytes(uint8_t *buf, const BigNumT *a) {
|
||||
for (int i = 0; i < BN_WORDS; i++) {
|
||||
int j = (BN_WORDS - 1 - i) * 4;
|
||||
uint32_t w = a->w[i];
|
||||
buf[j] = (uint8_t)(w >> 24);
|
||||
buf[j + 1] = (uint8_t)(w >> 16);
|
||||
buf[j + 2] = (uint8_t)(w >> 8);
|
||||
buf[j + 3] = (uint8_t)(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Helper functions (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
static uint32_t computeM0Inv(uint32_t m0) {
|
||||
// Newton's method: compute m0^(-1) mod 2^32
|
||||
// Converges quadratically: 1 → 2 → 4 → 8 → 16 → 32 correct bits
|
||||
uint32_t x = 1;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
x = x * (2 - m0 * x);
|
||||
}
|
||||
|
||||
// Return -m0^(-1) mod 2^32
|
||||
return ~x + 1;
|
||||
}
|
||||
|
||||
|
||||
static void computeR2(BigNumT *r2, const BigNumT *m) {
|
||||
// Compute R^2 mod m where R = 2^1024
|
||||
// Method: start with 1, double 2048 times, reduce mod m each step
|
||||
bnSet(r2, 1);
|
||||
|
||||
for (int i = 0; i < 2 * BN_BITS; i++) {
|
||||
bnShiftLeft1(r2);
|
||||
if (bnCmp(r2, m) >= 0) {
|
||||
bnSub(r2, r2, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void dhInit(void) {
|
||||
if (sDhInited) {
|
||||
return;
|
||||
}
|
||||
|
||||
sDhM0Inv = computeM0Inv(sDhPrime.w[0]);
|
||||
computeR2(&sDhR2, &sDhPrime);
|
||||
sDhInited = true;
|
||||
}
|
||||
|
||||
|
||||
static void secureZero(void *ptr, int len) {
|
||||
// Volatile prevents the compiler from optimizing away the zeroing
|
||||
volatile uint8_t *p = (volatile uint8_t *)ptr;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
p[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void xteaEncryptBlock(uint32_t v[2], const uint32_t key[4]) {
|
||||
uint32_t v0 = v[0];
|
||||
uint32_t v1 = v[1];
|
||||
uint32_t sum = 0;
|
||||
|
||||
for (int i = 0; i < XTEA_ROUNDS; i++) {
|
||||
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
|
||||
sum += XTEA_DELTA;
|
||||
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
|
||||
}
|
||||
|
||||
v[0] = v0;
|
||||
v[1] = v1;
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// RNG functions (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
void secRngAddEntropy(const uint8_t *data, int len) {
|
||||
// XOR additional entropy into the key
|
||||
for (int i = 0; i < len; i++) {
|
||||
((uint8_t *)sRng.key)[i % 16] ^= data[i];
|
||||
}
|
||||
|
||||
// Re-mix: encrypt the key with itself
|
||||
uint32_t block[2];
|
||||
block[0] = sRng.key[0] ^ sRng.key[2];
|
||||
block[1] = sRng.key[1] ^ sRng.key[3];
|
||||
xteaEncryptBlock(block, sRng.key);
|
||||
sRng.key[0] ^= block[0];
|
||||
sRng.key[1] ^= block[1];
|
||||
block[0] = sRng.key[2] ^ sRng.key[0];
|
||||
block[1] = sRng.key[3] ^ sRng.key[1];
|
||||
xteaEncryptBlock(block, sRng.key);
|
||||
sRng.key[2] ^= block[0];
|
||||
sRng.key[3] ^= block[1];
|
||||
}
|
||||
|
||||
|
||||
void secRngBytes(uint8_t *buf, int len) {
|
||||
// Auto-seed from hardware if never seeded
|
||||
if (!sRng.seeded) {
|
||||
uint8_t entropy[16];
|
||||
int got = secRngGatherEntropy(entropy, sizeof(entropy));
|
||||
secRngSeed(entropy, got);
|
||||
}
|
||||
|
||||
uint32_t block[2];
|
||||
int pos = 0;
|
||||
|
||||
while (pos < len) {
|
||||
block[0] = sRng.counter[0];
|
||||
block[1] = sRng.counter[1];
|
||||
xteaEncryptBlock(block, sRng.key);
|
||||
|
||||
int take = len - pos;
|
||||
if (take > 8) {
|
||||
take = 8;
|
||||
}
|
||||
memcpy(buf + pos, block, take);
|
||||
pos += take;
|
||||
|
||||
// Increment counter
|
||||
if (++sRng.counter[0] == 0) {
|
||||
sRng.counter[1]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int secRngGatherEntropy(uint8_t *buf, int len) {
|
||||
int out = 0;
|
||||
|
||||
// Read PIT channel 0 counter (1.193 MHz, ~10 bits of entropy in LSBs)
|
||||
outportb(0x43, 0x00);
|
||||
uint8_t pitLo = inportb(0x40);
|
||||
uint8_t pitHi = inportb(0x40);
|
||||
|
||||
// BIOS tick count (18.2 Hz)
|
||||
uint32_t ticks = _farpeekl(_dos_ds, 0x46C);
|
||||
|
||||
if (out < len) { buf[out++] = pitLo; }
|
||||
if (out < len) { buf[out++] = pitHi; }
|
||||
if (out < len) { buf[out++] = (uint8_t)(ticks); }
|
||||
if (out < len) { buf[out++] = (uint8_t)(ticks >> 8); }
|
||||
if (out < len) { buf[out++] = (uint8_t)(ticks >> 16); }
|
||||
if (out < len) { buf[out++] = (uint8_t)(ticks >> 24); }
|
||||
|
||||
// Second PIT reading for jitter
|
||||
outportb(0x43, 0x00);
|
||||
pitLo = inportb(0x40);
|
||||
pitHi = inportb(0x40);
|
||||
if (out < len) { buf[out++] = pitLo; }
|
||||
if (out < len) { buf[out++] = pitHi; }
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
void secRngSeed(const uint8_t *entropy, int len) {
|
||||
memset(&sRng, 0, sizeof(sRng));
|
||||
|
||||
// XOR-fold entropy into the key
|
||||
for (int i = 0; i < len; i++) {
|
||||
((uint8_t *)sRng.key)[i % 16] ^= entropy[i];
|
||||
}
|
||||
|
||||
// Derive counter from key bits
|
||||
sRng.counter[0] = sRng.key[2] ^ sRng.key[0];
|
||||
sRng.counter[1] = sRng.key[3] ^ sRng.key[1];
|
||||
sRng.seeded = true;
|
||||
|
||||
// Mix state by generating and discarding 64 bytes
|
||||
uint8_t discard[64];
|
||||
sRng.seeded = true; // prevent recursion in secRngBytes
|
||||
secRngBytes(discard, sizeof(discard));
|
||||
secureZero(discard, sizeof(discard));
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// DH functions (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
int secDhComputeSecret(SecDhT *dh, const uint8_t *remotePub, int len) {
|
||||
BigNumT remote;
|
||||
BigNumT two;
|
||||
|
||||
if (!dh || !remotePub) {
|
||||
return SEC_ERR_PARAM;
|
||||
}
|
||||
if (len != SEC_DH_KEY_SIZE) {
|
||||
return SEC_ERR_PARAM;
|
||||
}
|
||||
if (!dh->hasKeys) {
|
||||
return SEC_ERR_NOT_READY;
|
||||
}
|
||||
|
||||
dhInit();
|
||||
|
||||
bnFromBytes(&remote, remotePub);
|
||||
|
||||
// Validate remote public key: must be in range [2, p-2]
|
||||
bnSet(&two, 2);
|
||||
if (bnCmp(&remote, &two) < 0 || bnCmp(&remote, &sDhPrime) >= 0) {
|
||||
secureZero(&remote, sizeof(remote));
|
||||
return SEC_ERR_PARAM;
|
||||
}
|
||||
|
||||
// shared = remote^private mod p
|
||||
bnModExp(&dh->sharedSecret, &remote, &dh->privateKey, &sDhPrime, sDhM0Inv, &sDhR2);
|
||||
dh->hasSecret = true;
|
||||
|
||||
secureZero(&remote, sizeof(remote));
|
||||
return SEC_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
SecDhT *secDhCreate(void) {
|
||||
SecDhT *dh = (SecDhT *)calloc(1, sizeof(SecDhT));
|
||||
return dh;
|
||||
}
|
||||
|
||||
|
||||
int secDhDeriveKey(SecDhT *dh, uint8_t *key, int keyLen) {
|
||||
uint8_t secretBytes[BN_BYTES];
|
||||
|
||||
if (!dh || !key || keyLen <= 0) {
|
||||
return SEC_ERR_PARAM;
|
||||
}
|
||||
if (!dh->hasSecret) {
|
||||
return SEC_ERR_NOT_READY;
|
||||
}
|
||||
if (keyLen > BN_BYTES) {
|
||||
keyLen = BN_BYTES;
|
||||
}
|
||||
|
||||
bnToBytes(secretBytes, &dh->sharedSecret);
|
||||
|
||||
// XOR-fold 128-byte shared secret down to keyLen bytes
|
||||
memset(key, 0, keyLen);
|
||||
for (int i = 0; i < BN_BYTES; i++) {
|
||||
key[i % keyLen] ^= secretBytes[i];
|
||||
}
|
||||
|
||||
secureZero(secretBytes, sizeof(secretBytes));
|
||||
return SEC_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void secDhDestroy(SecDhT *dh) {
|
||||
if (dh) {
|
||||
secureZero(dh, sizeof(SecDhT));
|
||||
free(dh);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int secDhGenerateKeys(SecDhT *dh) {
|
||||
if (!dh) {
|
||||
return SEC_ERR_PARAM;
|
||||
}
|
||||
|
||||
dhInit();
|
||||
|
||||
// Generate 256-bit random private key
|
||||
bnClear(&dh->privateKey);
|
||||
secRngBytes((uint8_t *)dh->privateKey.w, DH_PRIVATE_BYTES);
|
||||
|
||||
// Ensure private key >= 2
|
||||
if (bnBitLength(&dh->privateKey) <= 1) {
|
||||
dh->privateKey.w[0] = 2;
|
||||
}
|
||||
|
||||
// public = g^private mod p
|
||||
bnModExp(&dh->publicKey, &sDhGenerator, &dh->privateKey, &sDhPrime, sDhM0Inv, &sDhR2);
|
||||
dh->hasKeys = true;
|
||||
dh->hasSecret = false;
|
||||
|
||||
return SEC_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int secDhGetPublicKey(SecDhT *dh, uint8_t *buf, int *len) {
|
||||
if (!dh || !buf || !len) {
|
||||
return SEC_ERR_PARAM;
|
||||
}
|
||||
if (*len < SEC_DH_KEY_SIZE) {
|
||||
return SEC_ERR_PARAM;
|
||||
}
|
||||
if (!dh->hasKeys) {
|
||||
return SEC_ERR_NOT_READY;
|
||||
}
|
||||
|
||||
bnToBytes(buf, &dh->publicKey);
|
||||
*len = SEC_DH_KEY_SIZE;
|
||||
return SEC_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Cipher functions (alphabetical)
|
||||
// ========================================================================
|
||||
|
||||
SecCipherT *secCipherCreate(const uint8_t *key) {
|
||||
SecCipherT *c;
|
||||
|
||||
if (!key) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
c = (SecCipherT *)calloc(1, sizeof(SecCipherT));
|
||||
if (!c) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(c->key, key, SEC_XTEA_KEY_SIZE);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
void secCipherCrypt(SecCipherT *c, uint8_t *data, int len) {
|
||||
uint32_t block[2];
|
||||
uint8_t *keystream;
|
||||
int pos;
|
||||
int take;
|
||||
|
||||
if (!c || !data || len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
keystream = (uint8_t *)block;
|
||||
pos = 0;
|
||||
|
||||
while (pos < len) {
|
||||
// Encrypt counter to generate keystream
|
||||
block[0] = c->counter[0];
|
||||
block[1] = c->counter[1];
|
||||
xteaEncryptBlock(block, c->key);
|
||||
|
||||
// XOR keystream with data
|
||||
take = len - pos;
|
||||
if (take > 8) {
|
||||
take = 8;
|
||||
}
|
||||
for (int i = 0; i < take; i++) {
|
||||
data[pos + i] ^= keystream[i];
|
||||
}
|
||||
pos += take;
|
||||
|
||||
// Increment counter
|
||||
if (++c->counter[0] == 0) {
|
||||
c->counter[1]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void secCipherDestroy(SecCipherT *c) {
|
||||
if (c) {
|
||||
secureZero(c, sizeof(SecCipherT));
|
||||
free(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void secCipherSetNonce(SecCipherT *c, uint32_t nonceLo, uint32_t nonceHi) {
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
|
||||
c->nonce[0] = nonceLo;
|
||||
c->nonce[1] = nonceHi;
|
||||
c->counter[0] = nonceLo;
|
||||
c->counter[1] = nonceHi;
|
||||
}
|
||||
47
security/security.h
Normal file
47
security/security.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Security library: Diffie-Hellman key exchange + XTEA-CTR cipher
|
||||
// Targets 486-class hardware with 1024-bit DH (256-bit private exponent)
|
||||
// and XTEA in CTR mode for symmetric encryption.
|
||||
|
||||
#ifndef SECURITY_H
|
||||
#define SECURITY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Key sizes (bytes)
|
||||
#define SEC_DH_KEY_SIZE 128 // 1024-bit DH public key
|
||||
#define SEC_XTEA_KEY_SIZE 16 // 128-bit XTEA key
|
||||
|
||||
// Error codes
|
||||
#define SEC_SUCCESS 0
|
||||
#define SEC_ERR_PARAM -1
|
||||
#define SEC_ERR_NOT_READY -2
|
||||
#define SEC_ERR_ALLOC -3
|
||||
|
||||
// Opaque types
|
||||
typedef struct SecDhS SecDhT;
|
||||
typedef struct SecCipherS SecCipherT;
|
||||
|
||||
|
||||
// RNG — seed before generating keys. Hardware entropy is weak (~20 bits);
|
||||
// callers should supplement with keyboard timing, mouse jitter, etc.
|
||||
int secRngGatherEntropy(uint8_t *buf, int len);
|
||||
void secRngAddEntropy(const uint8_t *data, int len);
|
||||
void secRngBytes(uint8_t *buf, int len);
|
||||
void secRngSeed(const uint8_t *entropy, int len);
|
||||
|
||||
// Diffie-Hellman key exchange (1024-bit, RFC 2409 Group 2)
|
||||
SecDhT *secDhCreate(void);
|
||||
int secDhComputeSecret(SecDhT *dh, const uint8_t *remotePub, int len);
|
||||
int secDhDeriveKey(SecDhT *dh, uint8_t *key, int keyLen);
|
||||
void secDhDestroy(SecDhT *dh);
|
||||
int secDhGenerateKeys(SecDhT *dh);
|
||||
int secDhGetPublicKey(SecDhT *dh, uint8_t *buf, int *len);
|
||||
|
||||
// XTEA cipher in CTR mode (encrypt and decrypt are the same operation)
|
||||
SecCipherT *secCipherCreate(const uint8_t *key);
|
||||
void secCipherCrypt(SecCipherT *c, uint8_t *data, int len);
|
||||
void secCipherDestroy(SecCipherT *c);
|
||||
void secCipherSetNonce(SecCipherT *c, uint32_t nonceLo, uint32_t nonceHi);
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Reference in a new issue