diff --git a/TF_Config.example.h b/TF_Config.example.h index 099d026..af59aa6 100644 --- a/TF_Config.example.h +++ b/TF_Config.example.h @@ -17,10 +17,10 @@ // If the connection is reliable, you can disable the SOF byte and checksums. // That can save up to 9 bytes of overhead. -// ,-----+-----+-----+------+------------+- - - -+-------------, -// | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | DATA_CKSUM | +// ,-----+-----+-----+------+------------+- - - -+-------------, +// | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | DATA_CKSUM | // | 0-1 | 1-4 | 1-4 | 1-4 | 0-4 | ... | 0-4 | <- size (bytes) -// '-----+-----+-----+------+------------+- - - -+-------------' +// '-----+-----+-----+------+------------+- - - -+-------------' // !!! BOTH PEERS MUST USE THE SAME SETTINGS !!! @@ -40,6 +40,14 @@ // Value of the SOF byte (if TF_USE_SOF_BYTE == 1) #define TF_SOF_BYTE 0x01 +// SOF *always* marks start of frame, if it appears elsewhere it will be escaped +// a SOF byte appearing mid-message resets parsing and drops the unprocessed half. +// When enabled, only 1 byte at a time is sent to TF_WriteImpl. This will incur +// a performance penalty if you expect to send large chunks with DMA +#define TF_ESCAPE_SOF_BYTE 1 +// if this byte is seen and TF_ESCAPE_SOF_BYTE == 1, the next byte is treated literally +#define TF_ESCAPE_BYTE 0x02 + //----------------------- PLATFORM COMPATIBILITY ---------------------------- // used for timeout tick counters - should be large enough for all used timeouts diff --git a/TinyFrame.c b/TinyFrame.c index 16baf38..980d511 100644 --- a/TinyFrame.c +++ b/TinyFrame.c @@ -207,6 +207,27 @@ //endregion +//region escaping + +#if TF_ESCAPE_SOF_BYTE + static void _TF_FN TF_WriteEscaped(TinyFrame *tf, const uint8_t *buff, uint32_t len) { + for(uint32_t i = 0; i < len; i++) { + uint8_t c = buff[i]; + if(c == TF_SOF_BYTE || c == TF_ESCAPE_BYTE) { + // invert the bits so we don't Tx a SOF byte + c ^= 0xff; + // send the escape char + TF_WriteImpl(tf, (uint8_t[]){TF_ESCAPE_BYTE}, 1); + } + // send the byte itself + TF_WriteImpl(tf, &c, sizeof(c)); + } + } +#else + #define TF_WriteEscaped TF_WriteImpl +#endif + +//endregion //region Init @@ -601,6 +622,26 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c) } #endif +#if TF_ESCAPE_SOF_BYTE + // only SOF/escape bytes can be escaped - anything else is treated normally + if(tf->escape_next && (c == (TF_SOF_BYTE ^ 0xff) || c == (TF_ESCAPE_BYTE ^ 0xff))) { + // restore the inverted byte + c ^= 0xFF; + } else { + if(c == TF_ESCAPE_BYTE) { + tf->escape_next = true; + return; + } + + if(c == TF_SOF_BYTE && tf->state != TFState_SOF) { + TF_ResetParser(tf); + TF_Error("Msg truncated"); + } + } + + tf->escape_next = false; +#endif + //@formatter:off switch (tf->state) { case TFState_SOF: @@ -796,7 +837,14 @@ static inline uint32_t _TF_FN TF_ComposeHead(TinyFrame *tf, uint8_t *outbuff, TF CKSUM_RESET(cksum); #if TF_USE_SOF_BYTE +#if TF_ESCAPE_SOF_BYTE + // write the raw SOF byte without escaping + TF_WriteImpl(tf, (uint8_t[]){TF_SOF_BYTE}, 1); +#else + // add it to the buffer as normal outbuff[pos++] = TF_SOF_BYTE; +#endif + // add it to the checksum regardless CKSUM_ADD(cksum, TF_SOF_BYTE); #endif @@ -910,7 +958,7 @@ static void _TF_FN TF_SendFrame_Chunk(TinyFrame *tf, const uint8_t *buff, uint32 // Flush if the buffer is full if (tf->tx_pos == TF_SENDBUF_LEN) { - TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, tf->tx_pos); + TF_WriteEscaped(tf, (const uint8_t *) tf->sendbuf, tf->tx_pos); tf->tx_pos = 0; } } @@ -927,7 +975,7 @@ static void _TF_FN TF_SendFrame_End(TinyFrame *tf) if (tf->tx_len > 0) { // Flush if checksum wouldn't fit in the buffer if (TF_SENDBUF_LEN - tf->tx_pos < sizeof(TF_CKSUM)) { - TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, tf->tx_pos); + TF_WriteEscaped(tf, (const uint8_t *) tf->sendbuf, tf->tx_pos); tf->tx_pos = 0; } @@ -935,7 +983,7 @@ static void _TF_FN TF_SendFrame_End(TinyFrame *tf) tf->tx_pos += TF_ComposeTail(tf->sendbuf + tf->tx_pos, &tf->tx_cksum); } - TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, tf->tx_pos); + TF_WriteEscaped(tf, (const uint8_t *) tf->sendbuf, tf->tx_pos); TF_ReleaseTx(tf); } diff --git a/TinyFrame.h b/TinyFrame.h index 5f6943c..1fe1384 100644 --- a/TinyFrame.h +++ b/TinyFrame.h @@ -81,6 +81,18 @@ //endregion +//region Sanity checks + +#if TF_ESCAPE_SOF_BYTE && !TF_USE_SOF_BYTE + #error TF_ESCAPE_SOF_BYTE enabled but TF_USE_SOF_BYTE disabled +#endif + +#if TF_SOF_BYTE == TF_ESCAPE_BYTE + #error SOF byte is identical to escape byte +#endif + +//endregion + //--------------------------------------------------------------------------- /** Peer bit enum (used for init) */ @@ -446,6 +458,10 @@ struct TinyFrame_ { TF_TYPE type; //!< Collected message type number bool discard_data; //!< Set if (len > TF_MAX_PAYLOAD) to read the frame, but ignore the data. +#if TF_ESCAPE_SOF_BYTE + bool escape_next; //!< Track whether to escape the next character +#endif + /* Tx state */ // Buffer for building frames uint8_t sendbuf[TF_SENDBUF_LEN]; //!< Transmit temporary buffer