// Packetized serial transport with HDLC-style framing and sliding window // Provides reliable, ordered delivery over an unreliable serial link. // // Why HDLC framing: // HDLC's flag-byte + byte-stuffing scheme is the simplest way to delimit // variable-length frames on a raw byte stream. The 0x7E flag byte marks // frame boundaries; byte-stuffing (escaping 0x7E and 0x7D within data) // ensures the flag is unambiguous. This is proven, lightweight, and // requires zero buffering at the framing layer. Alternative approaches // like length-prefixed framing are fragile on noisy links because a // corrupted length field desynchronizes the receiver permanently. // // Why Go-Back-N ARQ: // Go-Back-N is simpler than Selective Repeat (no out-of-order reassembly // buffer needed on the receiver) and works well for the low bandwidth-delay // product of a serial link. On a 115200 bps local serial connection, the // round-trip time is negligible, so the window rarely fills. GBN's // retransmit-all-from-NAK behavior wastes bandwidth on lossy links, but // serial links are nearly lossless — the CRC check is primarily a safety // net for electrical noise, not a routine error recovery mechanism. // // CRC-16-CCITT: // Standard polynomial used in HDLC/X.25. Table-driven for speed // (256-entry lookup table trades 512 bytes of ROM for ~10x faster CRC // than the bit-by-bit method, important on a 486). #ifndef PACKET_H #define PACKET_H #include #include // Maximum payload per packet (excluding header/CRC). 255 bytes fits in a // single LEN byte and keeps total frame size manageable for serial buffers. #define PKT_MAX_PAYLOAD 255 // Default sliding window size (1-8). 4 allows up to 4 unacknowledged // frames in flight, providing good throughput without excessive buffering. #define PKT_DEFAULT_WINDOW 4 // Maximum window size. Limited to 8 because sequence numbers are 8-bit // and we need seq space > 2*window to avoid ambiguity (255 > 2*8). #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 #define PKT_ERR_DISCONNECTED -9 // 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); // Check if there is room in the transmit window. bool pktCanSend(PktConnT *conn); // Get number of unacknowledged packets in the transmit window. int pktGetPending(PktConnT *conn); #endif