Skip to content

File baton_protocol.c

File List > src > baton_protocol.c

Go to the documentation of this file

/*******************************************************************************
 * Baton-C: Deterministic Master/Slave Messaging over a Single-Wire UART
 *
 * Project:     Baton-C - Deterministic Master/Slave Messaging over a Single-Wire UART
 * Description: Codec: CRC-16/CCITT-FALSE, incremental frame parser and encoder
 *
 * Author:      Anton Vynohradov
 * Email:       avynohradov@systemfromscratch.com
 *
 * License:     Apache License 2.0
 *
 * Copyright (c) 2026 Anton Vynohradov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 ******************************************************************************/

/* ==============================================================================
 * INCLUDES
 * =============================================================================*/

#include <string.h>

#include "baton_protocol.h"

/* ==============================================================================
 * DEFINES & MACROS
 * =============================================================================*/

#define BATON_CRC_POLY 0x1021U

#define PARSE_SYNC BATON_PARSER_IDLE
#define PARSE_HEADER 1U
#define PARSE_PAYLOAD 2U
#define PARSE_CRC_HIGH 3U
#define PARSE_CRC_LOW 4U

#define PARSE_HEADER_BYTES 4U

/* ==============================================================================
 * PRIVATE FUNCTION DECLARATIONS (static)
 * =============================================================================*/

static int parse_header(struct baton_parser* parser, uint8_t byte);
static int parse_payload(struct baton_parser* parser, uint8_t byte);
static int parse_crc_low(struct baton_parser* parser, uint8_t byte);

/* ==============================================================================
 * PRIVATE FUNCTION IMPLEMENTATIONS
 * =============================================================================*/

static int parse_header(struct baton_parser* parser, uint8_t byte)
{
    int result = 0;

    parser->crc = baton_crc16_update(parser->crc, byte);
    switch (parser->index)
    {
        case 0U:
            parser->frame.dst = byte;
            parser->index = 1U;
            break;
        case 1U:
            parser->frame.src = byte;
            parser->index = 2U;
            break;
        case 2U:
            parser->frame.ctrl = byte;
            parser->index = 3U;
            break;
        case 3U:
            parser->index = 0U;
            if (byte > BATON_MAX_PAYLOAD)
            {
                parser->state = PARSE_SYNC;
                result = BATON_PARSE_ERROR;
            }
            else
            {
                parser->frame.length = byte;
                parser->state = (byte == 0U) ? PARSE_CRC_HIGH : PARSE_PAYLOAD;
            }
            break;
        default:
            /* Corrupted parser structure: drop the frame instead of writing anywhere. */
            parser->state = PARSE_SYNC;
            result = BATON_PARSE_ERROR;
            break;
    }

    return result;
}

static int parse_payload(struct baton_parser* parser, uint8_t byte)
{
    int result = 0;

    if ((parser->index >= (uint16_t) parser->frame.length)
        || (parser->frame.length > BATON_MAX_PAYLOAD))
    {
        parser->state = PARSE_SYNC;
        result = BATON_PARSE_ERROR;
    }
    else
    {
        parser->crc = baton_crc16_update(parser->crc, byte);
        parser->frame.payload[parser->index] = byte;
        parser->index++;
        if (parser->index == (uint16_t) parser->frame.length)
        {
            parser->state = PARSE_CRC_HIGH;
        }
    }

    return result;
}

static int parse_crc_low(struct baton_parser* parser, uint8_t byte)
{
    const uint16_t received = (uint16_t) ((uint16_t) (parser->index << 8U) | (uint16_t) byte);

    parser->state = PARSE_SYNC;
    parser->index = 0U;

    return (received == parser->crc) ? 1 : BATON_PARSE_ERROR;
}

/* ==============================================================================
 * PUBLIC FUNCTION IMPLEMENTATIONS
 * =============================================================================*/

uint16_t baton_crc16_update(uint16_t crc, uint8_t byte)
{
    uint16_t value = (uint16_t) (crc ^ (uint16_t) ((uint16_t) byte << 8U));

    for (uint8_t bit = 0U; bit < 8U; ++bit)
    {
        if ((value & 0x8000U) != 0U)
        {
            value = (uint16_t) ((uint16_t) (value << 1U) ^ BATON_CRC_POLY);
        }
        else
        {
            value = (uint16_t) (value << 1U);
        }
    }

    return value;
}

uint16_t baton_crc16(const uint8_t* data, size_t length)
{
    uint16_t crc = BATON_CRC_INIT;

    if (data != NULL)
    {
        for (size_t i = 0U; i < length; ++i)
        {
            crc = baton_crc16_update(crc, data[i]);
        }
    }

    return crc;
}

void baton_parser_init(struct baton_parser* parser)
{
    if (parser != NULL)
    {
        (void) memset(parser, 0, sizeof(*parser));
        parser->state = PARSE_SYNC;
        parser->crc = BATON_CRC_INIT;
    }
}

int baton_parser_feed(struct baton_parser* parser, uint8_t byte)
{
    int result = BATON_PARSE_ERROR;

    if (parser != NULL)
    {
        result = 0;
        switch (parser->state)
        {
            case PARSE_SYNC:
                if (byte == BATON_SYNC)
                {
                    parser->state = PARSE_HEADER;
                    parser->index = 0U;
                    parser->crc = BATON_CRC_INIT;
                }
                break;
            case PARSE_HEADER:
                result = parse_header(parser, byte);
                break;
            case PARSE_PAYLOAD:
                result = parse_payload(parser, byte);
                break;
            case PARSE_CRC_HIGH:
                /* index keeps the received high byte until the low byte arrives */
                parser->index = (uint16_t) byte;
                parser->state = PARSE_CRC_LOW;
                break;
            case PARSE_CRC_LOW:
                result = parse_crc_low(parser, byte);
                break;
            default:
                parser->state = PARSE_SYNC;
                result = BATON_PARSE_ERROR;
                break;
        }
    }

    return result;
}

int baton_encode(uint8_t dst, uint8_t src, uint8_t ctrl, const uint8_t* payload,
                 size_t payload_length, uint8_t* output, size_t output_capacity,
                 size_t* output_length)
{
    int result = BATON_PARSE_ERROR;
    const size_t frame_length = payload_length + BATON_FRAME_OVERHEAD;

    if ((output != NULL) && (output_length != NULL) && (payload_length <= BATON_MAX_PAYLOAD)
        && ((payload != NULL) || (payload_length == 0U)) && (output_capacity >= frame_length))
    {
        output[0] = BATON_SYNC;
        output[1] = dst;
        output[2] = src;
        output[3] = ctrl;
        output[4] = (uint8_t) payload_length;
        if ((payload_length > 0U) && (payload != &output[BATON_HEADER_SIZE]))
        {
            (void) memmove(&output[BATON_HEADER_SIZE], payload, payload_length);
        }
        baton_put_u16be(&output[BATON_HEADER_SIZE + payload_length],
                        baton_crc16(&output[1], payload_length + (BATON_HEADER_SIZE - 1U)));
        *output_length = frame_length;
        result = 0;
    }

    return result;
}