diff --git a/packet/README.md b/packet/README.md index 450e79c..1464688 100644 --- a/packet/README.md +++ b/packet/README.md @@ -22,7 +22,7 @@ does not open or close the serial port itself. Before byte stuffing: ``` -[0x7E] [SEQ] [TYPE] [LEN_LO] [LEN_HI] [PAYLOAD...] [CRC_LO] [CRC_HI] +[0x7E] [SEQ] [TYPE] [LEN] [PAYLOAD...] [CRC_LO] [CRC_HI] ``` | Field | Size | Description | @@ -30,8 +30,8 @@ Before byte stuffing: | `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 | +| `LEN` | 1 byte | Payload length (0-255) | +| Payload | 0-255 | Application data | | `CRC` | 2 bytes | CRC-16-CCITT over SEQ+TYPE+LEN+payload | ### Frame Types @@ -82,7 +82,7 @@ typedef struct PktConnS PktConnT; | Name | Value | Description | |-----------------------|-------|-------------------------------------| -| `PKT_MAX_PAYLOAD` | 256 | Max payload bytes per packet | +| `PKT_MAX_PAYLOAD` | 255 | 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 | diff --git a/packet/packet.c b/packet/packet.c index bce3fe2..239af2a 100644 --- a/packet/packet.c +++ b/packet/packet.c @@ -1,7 +1,7 @@ // 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] +// [0x7E] [SEQ] [TYPE] [LEN] [PAYLOAD...] [CRC_LO] [CRC_HI] // // Byte stuffing: // 0x7E -> 0x7D 0x5E @@ -32,8 +32,8 @@ #define FRAME_NAK 0x02 #define FRAME_RST 0x03 -// Header size: SEQ + TYPE + LEN_LO + LEN_HI -#define HEADER_SIZE 4 +// Header size: SEQ + TYPE + LEN +#define HEADER_SIZE 3 // CRC size #define CRC_SIZE 2 // Minimum frame size (header + CRC, no payload) @@ -173,13 +173,12 @@ static void sendFrame(PktConnT *conn, uint8_t seq, uint8_t type, const uint8_t * uint16_t crc; int out; - // Build raw frame: SEQ + TYPE + LEN_LO + LEN_HI + PAYLOAD + // Build raw frame: SEQ + TYPE + LEN + PAYLOAD raw[0] = seq; raw[1] = type; - raw[2] = (uint8_t)(len & 0xFF); - raw[3] = (uint8_t)((len >> 8) & 0xFF); + raw[2] = (uint8_t)len; if (payload && len > 0) { - memcpy(&raw[4], payload, len); + memcpy(&raw[3], payload, len); } rawLen = HEADER_SIZE + len; @@ -269,7 +268,7 @@ static void processFrame(PktConnT *conn, const uint8_t *frame, int len) { seq = frame[0]; type = frame[1]; - payloadLen = frame[2] | ((int)frame[3] << 8); + payloadLen = frame[2]; // Validate payload length against actual frame size if (payloadLen + MIN_FRAME_SIZE != len) { diff --git a/packet/packet.h b/packet/packet.h index 21a4935..7f99287 100644 --- a/packet/packet.h +++ b/packet/packet.h @@ -8,7 +8,7 @@ #include // Maximum payload per packet (excluding header/CRC) -#define PKT_MAX_PAYLOAD 256 +#define PKT_MAX_PAYLOAD 255 // Default sliding window size (1-8) #define PKT_DEFAULT_WINDOW 4 diff --git a/proxy/Makefile b/proxy/Makefile new file mode 100644 index 0000000..5c7f111 --- /dev/null +++ b/proxy/Makefile @@ -0,0 +1,49 @@ +# SecLink Proxy — Linux build +# Compiles the packet, security, and secLink layers against a socket +# shim instead of the DJGPP rs232 driver. + +CC = gcc +CFLAGS = -O2 -Wall -Wextra + +OBJDIR = ../obj/proxy +BINDIR = ../bin + +TARGET = $(BINDIR)/secproxy + +OBJS = $(OBJDIR)/sockShim.o $(OBJDIR)/packet.o $(OBJDIR)/security.o \ + $(OBJDIR)/secLink.o $(OBJDIR)/proxy.o + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJS) | $(BINDIR) + $(CC) -o $@ $(OBJS) + +# Local sources +$(OBJDIR)/sockShim.o: sockShim.c sockShim.h | $(OBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR)/proxy.o: proxy.c sockShim.h | $(OBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< + +# Packet layer — block real rs232.h, inject socket shim +$(OBJDIR)/packet.o: ../packet/packet.c sockShim.h | $(OBJDIR) + $(CC) $(CFLAGS) -I. -Istubs/ -include sockShim.h -c -o $@ $< + +# Security layer — stub DOS-specific headers +$(OBJDIR)/security.o: ../security/security.c | $(OBJDIR) + $(CC) $(CFLAGS) -Istubs/ -c -o $@ $< + +# SecLink layer — block real rs232.h, inject socket shim +$(OBJDIR)/secLink.o: ../seclink/secLink.c sockShim.h | $(OBJDIR) + $(CC) $(CFLAGS) -I. -include sockShim.h -c -o $@ $< + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(BINDIR): + mkdir -p $(BINDIR) + +clean: + rm -rf $(OBJDIR) $(TARGET) diff --git a/proxy/README.md b/proxy/README.md new file mode 100644 index 0000000..5fefeb3 --- /dev/null +++ b/proxy/README.md @@ -0,0 +1,132 @@ +# SecLink Proxy + +Linux-hosted proxy that bridges an 86Box emulated serial port to a +remote telnet BBS. The 86Box side communicates using the secLink +protocol (packet framing, DH key exchange, XTEA encryption). The BBS +side is plain telnet over TCP. + +## Architecture + +``` +86Box (DOS terminal) Remote BBS + | | + emulated modem telnet:23 + | | + TCP:2323 TCP:23 + | | + +--- secproxy ----------------------------------+ + secLink ←→ plaintext + (encrypted, reliable) +``` + +The proxy accepts a single TCP connection from 86Box, performs the +secLink handshake (DH key exchange), then connects to the BBS. All +traffic between 86Box and the proxy is encrypted via XTEA-CTR on +channel 0. Traffic between the proxy and the BBS is unencrypted +telnet. + +## Usage + +``` +secproxy [listen_port] [bbs_host] [bbs_port] +``` + +| Argument | Default | Description | +|---------------|------------------------|---------------------------------| +| `listen_port` | 2323 | TCP port for 86Box connection | +| `bbs_host` | bbs.duensing.digital | BBS hostname | +| `bbs_port` | 23 | BBS TCP port | + +``` +secproxy # all defaults +secproxy 5000 # listen on port 5000 +secproxy 2323 bbs.example.com 23 # different BBS +secproxy --help # show usage +``` + +## Startup Sequence + +1. Listen on the configured TCP port +2. Wait for 86Box to connect (blocks on accept) +3. Connect to the remote BBS +4. Seed the RNG from `/dev/urandom` +5. Open secLink and perform the DH handshake (blocks until the DOS + side completes its handshake) +6. Enter the proxy loop + +## Proxy Loop + +The main loop uses `poll()` with a 10ms timeout to multiplex between +the two TCP connections: + +- **86Box → BBS**: `secLinkPoll()` reads from the 86Box socket via + the socket shim, decrypts incoming packets, and the receive callback + writes plaintext to the BBS socket. +- **BBS → 86Box**: `read()` from the BBS socket, then + `secLinkSend()` encrypts and sends to 86Box via the socket shim. +- **Maintenance**: `secLinkPoll()` also handles packet-layer retransmit + timers on every iteration. + +The proxy exits cleanly on Ctrl+C (SIGINT), SIGTERM, or when either +side disconnects. + +## 86Box Configuration + +Configure the 86Box serial port to use a telnet connection: + +1. In 86Box settings, set a COM port to "TCP (server)" or + "TCP (client)" mode pointing at the proxy's listen port +2. Enable "No telnet negotiation" to send raw bytes +3. The DOS terminal application running inside 86Box uses secLink + over this serial port + +## Socket Shim + +The proxy reuses the same packet, security, and secLink source code +as the DOS build. A socket shim (`sockShim.h`/`sockShim.c`) provides +rs232-compatible `rs232Read()`/`rs232Write()` functions backed by TCP +sockets instead of UART hardware: + +| rs232 function | Socket shim behavior | +|----------------|-----------------------------------------------| +| `rs232Open()` | No-op (socket already connected) | +| `rs232Close()` | Marks port closed (socket managed by caller) | +| `rs232Read()` | Non-blocking `recv()` with `MSG_DONTWAIT` | +| `rs232Write()` | Blocking `send()` loop with `MSG_NOSIGNAL` | + +The shim maps COM port indices (0-3) to socket file descriptors via +`sockShimSetFd()`, which must be called before opening the secLink +layer. + +DOS-specific headers (``, ``, ``) are +replaced by minimal stubs in `stubs/` that provide no-op +implementations. The security library's hardware entropy function +returns zeros on Linux, which is harmless since the proxy seeds the +RNG from `/dev/urandom` before the handshake. + +## Building + +``` +make # builds ../bin/secproxy +make clean # removes objects and binary +``` + +Objects are placed in `../obj/proxy/`, the binary in `../bin/`. + +Requires only a standard Linux C toolchain (gcc, libc). No external +dependencies. + +## Files + +``` +proxy/ + proxy.c main proxy program + sockShim.h rs232-compatible socket API (header) + sockShim.c socket shim implementation + Makefile Linux build + stubs/ + pc.h stub for DJGPP + go32.h stub for DJGPP + sys/ + farptr.h stub for DJGPP +``` diff --git a/proxy/proxy.c b/proxy/proxy.c new file mode 100644 index 0000000..96c67b0 --- /dev/null +++ b/proxy/proxy.c @@ -0,0 +1,296 @@ +// SecLink proxy — bridges an 86Box serial connection to a telnet BBS +// +// 86Box (DOS terminal) ←→ TCP ←→ proxy ←→ TCP ←→ BBS +// secLink protocol plain telnet +// +// Usage: proxy [listen_port] [bbs_host] [bbs_port] +// Defaults: 2323 bbs.duensing.digital 23 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sockShim.h" +#include "../seclink/secLink.h" +#include "../security/security.h" + + +// ======================================================================== +// Defines +// ======================================================================== + +#define DEFAULT_LISTEN_PORT 2323 +#define DEFAULT_BBS_HOST "bbs.duensing.digital" +#define DEFAULT_BBS_PORT 23 + +#define CHANNEL_TERMINAL 0 +#define POLL_TIMEOUT_MS 10 + + +// ======================================================================== +// Static globals +// ======================================================================== + +static volatile bool sRunning = true; + + +// ======================================================================== +// Static prototypes (alphabetical) +// ======================================================================== + +static int connectToBbs(const char *host, int port); +static int createListenSocket(int port); +static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel); +static void seedRng(void); +static void sigHandler(int sig); + + +// ======================================================================== +// Static functions (alphabetical) +// ======================================================================== + +static int connectToBbs(const char *host, int port) { + struct addrinfo hints; + struct addrinfo *res; + char portStr[16]; + int fd; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + snprintf(portStr, sizeof(portStr), "%d", port); + + if (getaddrinfo(host, portStr, &hints, &res) != 0) { + return -1; + } + + fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (fd < 0) { + freeaddrinfo(res); + return -1; + } + + if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { + close(fd); + freeaddrinfo(res); + return -1; + } + + freeaddrinfo(res); + return fd; +} + + +static int createListenSocket(int port) { + struct sockaddr_in addr; + int fd; + int opt = 1; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + return -1; + } + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(fd); + return -1; + } + + if (listen(fd, 1) < 0) { + close(fd); + return -1; + } + + return fd; +} + + +static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel) { + int bbsFd = *(int *)ctx; + + (void)channel; + + int sent = 0; + while (sent < len) { + ssize_t n = write(bbsFd, data + sent, len - sent); + if (n <= 0) { + break; + } + sent += (int)n; + } +} + + +static void seedRng(void) { + uint8_t entropy[32]; + FILE *f; + + f = fopen("/dev/urandom", "rb"); + if (f) { + if (fread(entropy, 1, sizeof(entropy), f) < sizeof(entropy)) { + fprintf(stderr, "Warning: short read from /dev/urandom\n"); + } + fclose(f); + } + + secRngSeed(entropy, sizeof(entropy)); +} + + +static void sigHandler(int sig) { + (void)sig; + sRunning = false; +} + + +// ======================================================================== +// Main +// ======================================================================== + +int main(int argc, char *argv[]) { + int listenPort = DEFAULT_LISTEN_PORT; + const char *bbsHost = DEFAULT_BBS_HOST; + int bbsPort = DEFAULT_BBS_PORT; + int listenFd; + int clientFd; + int bbsFd; + SecLinkT *link; + struct sockaddr_in clientAddr; + socklen_t clientLen; + struct pollfd fds[2]; + int rc; + + if (argc > 1 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) { + printf("Usage: %s [listen_port] [bbs_host] [bbs_port]\n", argv[0]); + printf("Defaults: %d %s %d\n", DEFAULT_LISTEN_PORT, DEFAULT_BBS_HOST, DEFAULT_BBS_PORT); + return 0; + } + + if (argc > 1) { + listenPort = atoi(argv[1]); + } + if (argc > 2) { + bbsHost = argv[2]; + } + if (argc > 3) { + bbsPort = atoi(argv[3]); + } + + signal(SIGPIPE, SIG_IGN); + signal(SIGINT, sigHandler); + signal(SIGTERM, sigHandler); + + // Listen for 86Box connection + listenFd = createListenSocket(listenPort); + if (listenFd < 0) { + fprintf(stderr, "Failed to listen on port %d: %s\n", listenPort, strerror(errno)); + return 1; + } + printf("Listening on port %d...\n", listenPort); + + // Accept connection from 86Box + clientLen = sizeof(clientAddr); + clientFd = accept(listenFd, (struct sockaddr *)&clientAddr, &clientLen); + close(listenFd); + if (clientFd < 0) { + fprintf(stderr, "Accept failed: %s\n", strerror(errno)); + return 1; + } + printf("86Box connected.\n"); + + // Associate socket with COM0 for the secLink stack + sockShimSetFd(0, clientFd); + + // Connect to BBS + printf("Connecting to %s:%d...\n", bbsHost, bbsPort); + bbsFd = connectToBbs(bbsHost, bbsPort); + if (bbsFd < 0) { + fprintf(stderr, "Failed to connect to BBS: %s\n", strerror(errno)); + close(clientFd); + return 1; + } + printf("BBS connected.\n"); + + // Seed RNG from /dev/urandom and open secLink + seedRng(); + link = secLinkOpen(0, 115200, 8, 'N', 1, 0, onRecvFromDos, &bbsFd); + if (!link) { + fprintf(stderr, "Failed to open secLink.\n"); + close(bbsFd); + close(clientFd); + return 1; + } + + // DH key exchange (blocks until both sides complete) + printf("Waiting for secLink handshake...\n"); + rc = secLinkHandshake(link); + if (rc != SECLINK_SUCCESS) { + fprintf(stderr, "Handshake failed: %d\n", rc); + secLinkClose(link); + close(bbsFd); + return 1; + } + printf("Handshake complete. Proxying traffic.\n"); + + // Set BBS socket non-blocking for the main loop + int flags = fcntl(bbsFd, F_GETFL, 0); + fcntl(bbsFd, F_SETFL, flags | O_NONBLOCK); + + // Main proxy loop + fds[0].fd = clientFd; + fds[0].events = POLLIN; + fds[1].fd = bbsFd; + fds[1].events = POLLIN; + + while (sRunning) { + poll(fds, 2, POLL_TIMEOUT_MS); + + // Process incoming secLink packets from 86Box + // (callback forwards decrypted data to BBS) + secLinkPoll(link); + + // Read from BBS and send encrypted to 86Box + if (fds[1].revents & POLLIN) { + uint8_t buf[SECLINK_MAX_PAYLOAD]; + ssize_t n = read(bbsFd, buf, sizeof(buf)); + if (n <= 0) { + printf("BBS disconnected.\n"); + break; + } + secLinkSend(link, buf, (int)n, CHANNEL_TERMINAL, true, true); + } + + // Check for disconnects + if (fds[0].revents & (POLLERR | POLLHUP)) { + printf("86Box disconnected.\n"); + break; + } + if (fds[1].revents & (POLLERR | POLLHUP)) { + printf("BBS disconnected.\n"); + break; + } + } + + printf("Shutting down.\n"); + secLinkClose(link); + close(bbsFd); + close(clientFd); + + return 0; +} diff --git a/proxy/sockShim.c b/proxy/sockShim.c new file mode 100644 index 0000000..7ff69c0 --- /dev/null +++ b/proxy/sockShim.c @@ -0,0 +1,99 @@ +// Socket shim — rs232-compatible API over TCP sockets + +#include "sockShim.h" +#include +#include +#include +#include + + +// ======================================================================== +// Internal state +// ======================================================================== + +#define MAX_PORTS 4 + +static int sFds[MAX_PORTS] = { -1, -1, -1, -1 }; +static bool sOpen[MAX_PORTS] = { false, false, false, false }; + + +// ======================================================================== +// Public functions (alphabetical) +// ======================================================================== + +int rs232Close(int com) { + if (com < 0 || com >= MAX_PORTS) { + return RS232_ERR_INVALID_PORT; + } + + sOpen[com] = false; + // Socket lifecycle is managed by the caller, not the shim + return RS232_SUCCESS; +} + + +int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake) { + (void)bps; + (void)dataBits; + (void)parity; + (void)stopBits; + (void)handshake; + + if (com < 0 || com >= MAX_PORTS) { + return RS232_ERR_INVALID_PORT; + } + if (sFds[com] < 0) { + return RS232_ERR_NOT_OPEN; + } + + sOpen[com] = true; + return RS232_SUCCESS; +} + + +int rs232Read(int com, char *data, int len) { + if (com < 0 || com >= MAX_PORTS || sFds[com] < 0) { + return 0; + } + + ssize_t n = recv(sFds[com], data, len, MSG_DONTWAIT); + if (n <= 0) { + return 0; + } + + return (int)n; +} + + +int rs232Write(int com, const char *data, int len) { + if (com < 0 || com >= MAX_PORTS || sFds[com] < 0) { + return RS232_ERR_NOT_OPEN; + } + + int sent = 0; + while (sent < len) { + ssize_t n = send(sFds[com], data + sent, len - sent, MSG_NOSIGNAL); + if (n < 0) { + if (errno == EINTR) { + continue; + } + return RS232_ERR_NOT_OPEN; + } + sent += (int)n; + } + + return RS232_SUCCESS; +} + + +void sockShimSetFd(int com, int fd) { + if (com < 0 || com >= MAX_PORTS) { + return; + } + + sFds[com] = fd; + + // Set non-blocking so rs232Read returns immediately when empty + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} diff --git a/proxy/sockShim.h b/proxy/sockShim.h new file mode 100644 index 0000000..39a58ac --- /dev/null +++ b/proxy/sockShim.h @@ -0,0 +1,36 @@ +// Socket shim — provides rs232-compatible API backed by TCP sockets +// Used by the Linux proxy to reuse the packet and secLink layers. + +#ifndef SOCKSHIM_H +#define SOCKSHIM_H + +// Block the real rs232.h from being included +#define RS232_H + +#include +#include + +// rs232-compatible constants (subset used by packet and secLink) +#define RS232_COM1 0 +#define RS232_COM2 1 +#define RS232_COM3 2 +#define RS232_COM4 3 + +#define RS232_HANDSHAKE_NONE 0 + +#define RS232_SUCCESS 0 +#define RS232_ERR_NOT_OPEN -2 +#define RS232_ERR_INVALID_PORT -5 + + +// Associate a socket fd with a COM index. Must be called before +// rs232Open/pktOpen/secLinkOpen. Sets the socket to non-blocking. +void sockShimSetFd(int com, int fd); + +// rs232-compatible functions backed by TCP sockets +int rs232Close(int com); +int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake); +int rs232Read(int com, char *data, int len); +int rs232Write(int com, const char *data, int len); + +#endif diff --git a/proxy/stubs/go32.h b/proxy/stubs/go32.h new file mode 100644 index 0000000..914322a --- /dev/null +++ b/proxy/stubs/go32.h @@ -0,0 +1,7 @@ +// Stub for DJGPP — Linux proxy build +#ifndef GO32_H_STUB +#define GO32_H_STUB + +#define _dos_ds 0 + +#endif diff --git a/proxy/stubs/pc.h b/proxy/stubs/pc.h new file mode 100644 index 0000000..ea6ea6e --- /dev/null +++ b/proxy/stubs/pc.h @@ -0,0 +1,8 @@ +// Stub for DJGPP — Linux proxy build +#ifndef PC_H_STUB +#define PC_H_STUB + +static inline void outportb(unsigned short port, unsigned char val) { (void)port; (void)val; } +static inline unsigned char inportb(unsigned short port) { (void)port; return 0; } + +#endif diff --git a/proxy/stubs/sys/farptr.h b/proxy/stubs/sys/farptr.h new file mode 100644 index 0000000..4fee02c --- /dev/null +++ b/proxy/stubs/sys/farptr.h @@ -0,0 +1,7 @@ +// Stub for DJGPP — Linux proxy build +#ifndef FARPTR_H_STUB +#define FARPTR_H_STUB + +static inline unsigned long _farpeekl(unsigned short sel, unsigned long ofs) { (void)sel; (void)ofs; return 0; } + +#endif diff --git a/seclink/README.md b/seclink/README.md index 33d31da..33078d0 100644 --- a/seclink/README.md +++ b/seclink/README.md @@ -62,7 +62,7 @@ typedef struct SecLinkS SecLinkT; | Name | Value | Description | |-------------------------|-------|----------------------------------------| -| `SECLINK_MAX_PAYLOAD` | 255 | Max bytes per `secLinkSend()` call | +| `SECLINK_MAX_PAYLOAD` | 254 | Max bytes per `secLinkSend()` call | | `SECLINK_MAX_CHANNEL` | 127 | Highest valid channel number | | `SECLINK_SUCCESS` | 0 | Operation succeeded | | `SECLINK_ERR_PARAM` | -1 | Invalid parameter | diff --git a/seclink/secLink.h b/seclink/secLink.h index 53f2512..e953c01 100644 --- a/seclink/secLink.h +++ b/seclink/secLink.h @@ -27,7 +27,7 @@ #define SECLINK_ERR_SEND -6 // Max plaintext payload per send (packet max minus 1-byte channel header) -#define SECLINK_MAX_PAYLOAD 255 +#define SECLINK_MAX_PAYLOAD 254 // Channel limits #define SECLINK_MAX_CHANNEL 127