781 lines
28 KiB
C

/*********************************************************************************************************
* ------------------------------------------------------------------------------------------------------
* file description
* ------------------------------------------------------------------------------------------------------
* \file slup.c
* \unit slup
* \brief This is a simple serial link universal protocol for C language
* \author Lamdonn
* \version v0.1.0
* \license GPL-2.0
* \copyright Copyright (C) 2023 Lamdonn.
********************************************************************************************************/
#include "slup.h"
/*
| HEAD | SN | FrameType | Length | Data | Check | TAIL |
*/
// Macro to send a character and perform some additional operations.
// It calls the putc function pointer in the slup structure to send the character 'c'.
// It also logs the character in hexadecimal format using SLUP_DEBUG and updates the upload statistics.
#define sputc(c) do { (slup->putc)(c); SLUP_DEBUG("%02x ", (c)); slup_statis_upload(slup); } while (0)
// Macro to access the i-th element of the head mask in the SLUP configuration.
#define head(i) (slup->cfg.head[(i)])
// Macro to access the i-th element of the tail mask in the SLUP configuration.
#define tail(i) (slup->cfg.tail[(i)])
// Macro to access the i-th byte of the sequence number (sn) in the slup structure.
#define sn(i) (((uint8_t *)(&(slup->sn)))[(i)])
// Macro to access the i-th byte of the length variable.
#define len(i) (((uint8_t *)(&(length)))[(i)])
// Define states for the SLUP receive state machine.
#define SLUP_RX_STATE_HEAD 0
#define SLUP_RX_STATE_SN 1
#define SLUP_RX_STATE_FRAMETYPE 2
#define SLUP_RX_STATE_LENGTH 3
#define SLUP_RX_STATE_DATA 4
#define SLUP_RX_STATE_CHECK 5
#define SLUP_RX_STATE_TAIL 6
// Debug macro, currently defined as an empty operation.
// Can be redefined to use printf for debugging purposes.
#define SLUP_DEBUG(...)
// #define SLUP_DEBUG printf
/**
* \brief Pushes a character into the SLUP queue.
* \param queue: Pointer to the SLUP queue structure.
* \param data: The character to be pushed into the queue.
* \return 1 if the push operation is successful, 0 otherwise.
*
* This function adds a character to the SLUP queue. If the queue is not full,
* it simply adds the character to the tail of the queue and updates the tail index and size.
* If the queue is full, it overwrites the oldest element and updates both the head and tail indices.
*/
static int slup_queue_push(SLUP_QUEUE *queue, char data)
{
if (!queue) return 0;
if (queue->size < SLUP_RXQUE_SIZE)
{
queue->base[queue->tail] = data;
queue->tail = (queue->tail + 1) % SLUP_RXQUE_SIZE;
queue->size++;
}
else
{
queue->base[queue->tail] = data;
queue->tail = (queue->tail + 1) % SLUP_RXQUE_SIZE;
queue->head = (queue->head + 1) % SLUP_RXQUE_SIZE;
}
return 1;
}
/**
* \brief Pops a specified number of elements from the SLUP queue.
* \param queue: Pointer to the SLUP queue structure.
* \param count: The number of elements to be popped from the queue.
* \return 1 if the pop operation is successful, 0 otherwise.
*
* This function removes a specified number of elements from the SLUP queue.
* If the number of elements to be popped is greater than the current size of the queue,
* it will only pop the existing elements in the queue.
*/
static int slup_queue_pop(SLUP_QUEUE *queue, uint32_t count)
{
if (!queue) return 0;
if (queue->size == 0) return 0;
if (count == 0) return 0;
if (count > queue->size) count = queue->size;
queue->head = (queue->head + count) % SLUP_RXQUE_SIZE;
queue->size -= count;
return 1;
}
/**
* \brief Retrieves an element at a specified index from the SLUP queue.
* \param queue: Pointer to the SLUP queue structure.
* \param index: The index of the element to be retrieved.
* \return The character at the specified index if the queue is valid and not empty, 0 otherwise.
*
* This function returns the element at a given index in the SLUP queue.
* The index is calculated relative to the head of the queue.
*/
static char slup_queue_at(SLUP_QUEUE *queue, uint32_t index)
{
if (!queue) return 0;
if (queue->size == 0) return 0;
return queue->base[((queue->head + index) % SLUP_RXQUE_SIZE)];
}
/**
* \brief Update the upload statistics of the SLUP structure.
* \param slup: Pointer to the SLUP structure.
*
* This function increments the 'upbits' field in the SLUP structure by 8,
* indicating that 8 bits of data have been uploaded. It is called within the 'sputc' macro
* when sending data to keep track of the amount of uploaded data.
*/
static void slup_statis_upload(SLUP *slup)
{
slup->upbits += 8;
}
/**
* \brief Update the download statistics of the SLUP structure.
* \param slup: Pointer to the SLUP structure.
*
* This function increments the 'downbits' field in the SLUP structure by 8,
* indicating that 8 bits of data have been downloaded. It is used to track
* the amount of data received in the SLUP communication process.
*/
static void slup_statis_download(SLUP *slup)
{
slup->downbits += 8;
}
/**
* \brief Receive and process an SLUP package.
* \param slup: Pointer to the SLUP structure.
* \return SLUP_E_OK: Indicates that the package has been successfully received and processed.
*
* This function first checks the 'frame' field in the 'parser' of the SLUP structure.
* If the 'frame' is 0x00, it extracts the link information from the first byte of the 'buffer'.
* Depending on the result of the bitwise AND operation between the link information and SLUP_LINK_RX,
* it updates the 'link' field in the SLUP structure. If the 'frame' is not 0x00 and the 'receive'
* function pointer in the SLUP structure is valid, it calls the 'receive' function to handle
* the received data in the 'buffer' with the specified size 'bsize'.
*/
static int slup_receive_package(SLUP *slup)
{
if (slup->parser.frame == 0x00)
{
uint8_t link = 0;
link = slup->buffer[0];
if (link & SLUP_LINK_RX)
{
slup->link |= SLUP_LINK_TX;
}
else
{
slup->link &= (~SLUP_LINK_TX);
}
}
else
{
if (slup->receive)
{
(slup->receive)(slup->buffer, slup->bsize);
}
}
return SLUP_E_OK;
}
/**
* \brief Send an SLUP package.
* \param slup: Pointer to the SLUP structure.
* \param frame: The frame type of the package.
* \param data: Pointer to the data buffer of the package.
* \param length: The length of the data in the package.
* \return SLUP_E_OK: Indicates that the package has been successfully sent.
* SLUP_E_INVALID: Returned if the 'slup' pointer is NULL.
* SLUP_E_DATA: Returned if the 'data' pointer is NULL.
* SLUP_E_LEN: Returned if the 'length' is 0.
*
* This function constructs and sends an SLUP package. It first validates the input parameters.
* Then it sends each part of the package in order: the head, sequence number (SN), frame type,
* data length, data, checksum, and tail. The sequence number is incremented after being sent.
* The checksum is calculated using the 'check' function pointer in the SLUP structure.
*/
static int slup_send_package(SLUP *slup, uint8_t frame, uint8_t *data, uint16_t length)
{
uint32_t i = 0;
uint32_t check = 0;
if (!slup) return SLUP_E_INVALID;
if (!data) return SLUP_E_DATA;
if (length == 0) return SLUP_E_LEN;
/* Send the HEAD part of the package */
for (i = 0; i < slup->cfg.hsize; i++)
{
sputc(head(i));
}
/* Send the SN (Sequence Number) part of the package */
sputc(sn(0));
sputc(sn(1));
sputc(sn(2));
sputc(sn(3));
slup->sn++;
/* Send the FrameType part of the package */
sputc(frame);
/* Send the Length part of the package */
sputc(len(0));
sputc(len(1));
/* Calculate the checksum of the data */
check = (slup->check)(data, length);
/* Send the Data part of the package */
for (i = 0; i < length; i++)
{
sputc(data[i]);
}
/* Send the Check part of the package */
for (i = 0; i < slup->cfg.csize; i++)
{
sputc((((uint8_t *)(&(check)))[(i)]));
}
/* Send the TAIL part of the package */
for (i = 0; i < slup->cfg.tsize; i++)
{
sputc(tail(i));
}
return SLUP_E_OK;
}
/**
* \brief Parses the received data in the SLUP queue and processes it according to the SLUP protocol.
* \param slup: Pointer to the SLUP structure.
*
* This function is responsible for parsing the data in the SLUP queue. It iterates through all the
* data in the queue when there is data available. For each byte of data, it uses a state machine
* (implemented with a switch statement) to process the data based on the current state of the parser.
* The states represent different parts of the SLUP package such as the head, sequence number (SN),
* frame type, length, data, checksum, and tail. Once a complete package is parsed and verified,
* it calls the slup_receive_package function to handle the received package and resets the buffer
* and parser state for the next package.
*/
static void slup_parse_task(SLUP *slup)
{
uint32_t i = 0;
char data = 0;
if (slup->queue.size > 0)
{
for (i = 0; i < slup->queue.size; i++)
{
data = slup_queue_at(&slup->queue, i);
SLUP_DEBUG("%02x ", data);
switch (slup->parser.state)
{
case SLUP_RX_STATE_HEAD:
{
// If the current index for the head (hindex) is less than the configured head size
if (slup->parser.hindex < slup->cfg.hsize)
{
// If the received data byte matches the expected head byte at the current index
if (data == slup->cfg.head[slup->parser.hindex])
{
// Increment the head index
slup->parser.hindex++;
// If the head index reaches the configured head size, it means the head is fully received
if (slup->parser.hindex == slup->cfg.hsize)
{
slup->parser.hindex = 0;
SLUP_DEBUG(" [SLUP_RX_STATE_HEAD] OK\r\n");
// Move to the next state which is to receive the sequence number (SN)
slup->parser.state = SLUP_RX_STATE_SN;
}
}
else
{
// If the received byte doesn't match, reset the head index
slup->parser.hindex = 0;
}
}
else
{
}
} break;
case SLUP_RX_STATE_SN:
{
// If the current index for the sequence number (sindex) is less than the configured SN size
if (slup->parser.sindex < slup->cfg.ssize)
{
// Store the received data byte into the appropriate position of the parser's SN
((uint8_t *)(&(slup->parser.sn)))[slup->parser.sindex] = data;
// Increment the sequence number index
slup->parser.sindex++;
// If the sequence number index reaches the configured SN size, it means the SN is fully received
if (slup->parser.sindex == slup->cfg.ssize)
{
slup->parser.sindex = 0;
SLUP_DEBUG(" [SLUP_RX_STATE_SN] OK\r\n");
// Move to the next state which is to receive the frame type
slup->parser.state = SLUP_RX_STATE_FRAMETYPE;
}
}
else
{
}
} break;
case SLUP_RX_STATE_FRAMETYPE:
{
// Store the received data byte as the frame type
slup->parser.frame = data;
SLUP_DEBUG(" [SLUP_RX_STATE_FRAMETYPE] OK\r\n");
// Move to the next state which is to receive the length
slup->parser.state = SLUP_RX_STATE_LENGTH;
} break;
case SLUP_RX_STATE_LENGTH:
{
// If the current index for the length (lindex) is less than the size of a uint16_t (since length is uint16_t)
if (slup->parser.lindex < sizeof(uint16_t))
{
// Store the received data byte into the appropriate position of the parser's length
((uint8_t *)(&(slup->parser.length)))[slup->parser.lindex] = data;
// Increment the length index
slup->parser.lindex++;
// If the length index reaches the size of a uint16_t, it means the length is fully received
if (slup->parser.lindex == sizeof(uint16_t))
{
slup->parser.lindex = 0;
SLUP_DEBUG(" [SLUP_RX_STATE_LENGTH] OK\r\n");
// Move to the next state which is to receive the data
slup->parser.state = SLUP_RX_STATE_DATA;
}
}
else
{
}
} break;
case SLUP_RX_STATE_DATA:
{
// Store the received data byte into the buffer and increment the buffer size
slup->buffer[slup->bsize++] = data;
// If the current index for the data (dindex) is less than the received length
if (slup->parser.dindex < slup->parser.length)
{
// Increment the data index
slup->parser.dindex++;
// If the data index reaches the received length, it means the data is fully received
if (slup->parser.dindex == slup->parser.length)
{
slup->parser.dindex = 0;
SLUP_DEBUG(" [SLUP_RX_STATE_DATA] OK\r\n");
// Move to the next state which is to receive the checksum
slup->parser.state = SLUP_RX_STATE_CHECK;
}
}
else
{
}
} break;
case SLUP_RX_STATE_CHECK:
{
// If the current index for the checksum (cindex) is less than the configured checksum size
if (slup->parser.cindex < slup->cfg.csize)
{
// Store the received data byte into the appropriate position of the parser's checksum
((uint8_t *)(&(slup->parser.check)))[slup->parser.cindex] = data;
// Increment the checksum index
slup->parser.cindex++;
// If the checksum index reaches the configured checksum size, it means the checksum is fully received
if (slup->parser.cindex == slup->cfg.csize)
{
slup->parser.cindex = 0;
uint32_t cvalue = 0;
// Calculate the checksum of the received data in the buffer
cvalue = (slup->check)(slup->buffer, slup->bsize);
// Compare the calculated checksum with the received checksum
if (memcmp(&cvalue, &slup->parser.check, slup->cfg.csize) == 0)
{
SLUP_DEBUG(" [SLUP_RX_STATE_CHECK] OK\r\n");
// Move to the next state which is to receive the tail
slup->parser.state = SLUP_RX_STATE_TAIL;
}
else
{
// If the checksums don't match, reset the buffer size for the next reception
slup->bsize = 0;
// Reset the parser state to start receiving a new package from the head
slup->parser.state = SLUP_RX_STATE_HEAD;
}
}
}
else
{
}
} break;
case SLUP_RX_STATE_TAIL:
{
// If the current index for the tail (tindex) is less than the configured tail size
if (slup->parser.tindex < slup->cfg.tsize)
{
// If the received data byte matches the expected tail byte at the current index
if (data == slup->cfg.tail[slup->parser.tindex])
{
// Increment the tail index
slup->parser.tindex++;
// If the tail index reaches the configured tail size, it means the tail is fully received
if (slup->parser.tindex == slup->cfg.tsize)
{
slup->parser.tindex = 0;
SLUP_DEBUG(" [SLUP_RX_STATE_TAIL] OK\r\n");
// Call the function to handle the received package
slup_receive_package(slup);
// Reset the buffer size for the next reception
slup->bsize = 0;
// Reset the parser state to start receiving a new package from the head
slup->parser.state = SLUP_RX_STATE_HEAD;
}
}
else
{
// If the received byte doesn't match, reset the tail index and the parser state to start from the head
slup->parser.tindex = 0;
slup->parser.state = SLUP_RX_STATE_HEAD;
}
}
else
{
}
} break;
default:
break;
}
}
// Pop all the data from the queue after processing
slup_queue_pop(&slup->queue, 0xFFFFF);
}
}
/**
* \brief Calculate the upload and download rates and reset the bit counters.
* \param slup: Pointer to the SLUP structure.
*
* This function is responsible for calculating the upload and download rates.
* It copies the current values of 'upbits' and 'downbits' to 'uprate' and 'downrate' respectively.
* Then it resets 'upbits' and 'downbits' to zero, preparing for the next rate calculation.
*/
static void slup_calrate_task(SLUP *slup)
{
slup->uprate = slup->upbits;
slup->downrate = slup->downbits;
slup->upbits = 0;
slup->downbits = 0;
}
/**
* \brief Update the dummy data parameters based on the upload rate.
* \param slup: Pointer to the SLUP structure.
*
* This function updates the 'gapcount', 'gapbase', 'compression', and 'rate' fields
* in the 'dummy' structure of the SLUP object. It first calculates the deviation
* between the upload rate and the target rate. If the upload rate is less than the target,
* it increases the 'gapcount' by a fixed resolution value. If the upload rate is greater
* than the target, it decreases the 'gapcount' by the same resolution value.
* Then it calculates the 'gapbase' as the integer part of 'gapcount' divided by 100,
* and the 'compression' as the fractional part of 'gapcount' divided by 100 minus 'gapbase'.
* Finally, it sets the 'rate' to the 'compression' value.
*/
static void slup_dummy_update(SLUP *slup)
{
const uint32_t resolution = 5;
/* Update the deviation value and record it in 'slup->dummy.gapcount' */
if (slup->uprate < slup->dummy.target)
{
if (slup->dummy.gapcount + resolution > slup->dummy.gapcount)
{
slup->dummy.gapcount += resolution;
}
}
else if (slup->uprate > slup->dummy.target)
{
if (slup->dummy.gapcount >= resolution)
{
slup->dummy.gapcount -= resolution;
}
}
/* Update the basic sending number and compression rate of the sending point */
slup->dummy.gapbase = (uint32_t)(slup->dummy.gapcount / 100);
slup->dummy.compression = (double)slup->dummy.gapcount / 100.0 - slup->dummy.gapbase;
slup->dummy.rate = slup->dummy.compression;
}
/**
* \brief Execute the dummy data sending process based on the calculated parameters.
* \param slup: Pointer to the SLUP structure.
*
* This function calculates the number of messages to send based on the 'gapbase'
* and 'compression' values in the 'dummy' structure of the SLUP object. It adds the
* 'compression' value to the 'rate'. If the 'rate' exceeds 1.0, it subtracts 1.0 from
* the 'rate' and increments the number of messages to send.
* If there are messages to send, it calls the 'slup_send' function to send the dummy data
* in the 'dummy' structure 'send' times.
*/
static void slup_dummy_execute(SLUP *slup)
{
uint32_t send = 0;
/* Calculate how many messages the current sending point needs to send */
send = slup->dummy.gapbase;
slup->dummy.rate += slup->dummy.compression;
if (slup->dummy.rate > 1.0)
{
slup->dummy.rate -= 1.0;
send++;
}
/* Evenly 'gapCount' the difference to each sending point for sending */
if (send > 0)
{
for (int i = 0; i < send; i++)
{
slup_send(slup, slup->dummy.data, sizeof(slup->dummy.data));
}
}
}
/**
* \brief Sends a heartbeat message in the SLUP protocol.
* \param slup: Pointer to the SLUP structure.
* \return SLUP_E_OK if the message is successfully sent, appropriate error code otherwise.
*
* This function constructs a heartbeat message. It sets the first byte of the data buffer
* to the current link status from the SLUP structure. Then it calls the slup_send_package
* function with a frame type of 0x00 to send the message.
*/
static int slup_send_heart(SLUP *slup)
{
uint8_t data[SLUP_SN_MAX];
uint16_t length = 0;
data[0] = slup->link;
length = 1;
return slup_send_package(slup, 0x00, data, length);
}
/**
* \brief Initializes the SLUP structure.
* \param slup: Pointer to the SLUP structure.
* \return SLUP_E_OK if the initialization is successful, SLUP_E_INVALID if the pointer is NULL.
*
* This function initializes various fields in the SLUP structure. It sets the buffer size (bsize),
* sequence number (sn), and timestamp to 0. It also initializes the queue's head, tail, and size to 0,
* and clears the parser structure using memset.
*/
int slup_init(SLUP *slup)
{
if (!slup) return SLUP_E_INVALID;
// memset(slup, 0, sizeof(SLUP));
slup->bsize = 0;
slup->sn = 0;
slup->timestamp = 0;
/* Init queue */
slup->queue.head = 0;
slup->queue.tail = 0;
slup->queue.size = 0;
/* Init parser */
memset(&slup->parser, 0, sizeof(SLUP_PARSER));
return SLUP_E_OK;
}
/**
* \brief Sends data in the SLUP protocol with a frame type of 0x01.
* \param slup: Pointer to the SLUP structure.
* \param data: Pointer to the data buffer to be sent.
* \param length: The length of the data buffer.
* \return SLUP_E_OK if the data is successfully sent, appropriate error code otherwise.
*
* This function simply calls the slup_send_package function with a frame type of 0x01
* to send the provided data.
*/
int slup_send(SLUP *slup, uint8_t *data, uint16_t length)
{
return slup_send_package(slup, 0x01, data, length);
}
/**
* \brief Handles the reception of a single character in the SLUP system.
* \param slup: Pointer to the SLUP structure.
* \param c: The received character.
*
* This function updates the link status to indicate a received character (sets SLUP_LINK_RX),
* clears the silent flag, updates the download statistics, and pushes the received character
* into the queue.
*/
void slup_getc(SLUP *slup, char c)
{
if (!slup) return;
slup->link |= SLUP_LINK_RX;
slup->silent = 0;
slup_statis_download(slup);
slup_queue_push(&slup->queue, c);
}
/**
* \brief Retrieves the current link status from the SLUP structure.
* \param slup: Pointer to the SLUP structure.
* \param link: Pointer to a variable where the link status will be stored.
* \return SLUP_E_OK if the retrieval is successful, appropriate error code otherwise.
*
* This function checks if the provided pointers are valid. If so, it copies the current
* link status from the SLUP structure to the provided variable.
*/
int slup_link_status(SLUP *slup, uint8_t *link)
{
if (!slup) return SLUP_E_INVALID;
if (!link) return SLUP_E_LINK;
*link = slup->link;
return SLUP_E_OK;
}
/**
* \brief Retrieves the upload rate from the SLUP structure.
* \param slup: Pointer to the SLUP structure.
* \param rate: Pointer to a variable where the upload rate will be stored.
* \return SLUP_E_OK if the retrieval is successful, appropriate error code otherwise.
*
* This function checks if the provided pointers are valid. If so, it copies the current
* upload rate from the SLUP structure to the provided variable.
*/
int slup_upload_rate(SLUP *slup, uint32_t *rate)
{
if (!slup) return SLUP_E_INVALID;
if (!rate) return SLUP_E_RATE;
*rate = slup->uprate;
return SLUP_E_OK;
}
/**
* \brief Retrieves the download rate from the SLUP structure.
* \param slup: Pointer to the SLUP structure.
* \param rate: Pointer to a variable where the download rate will be stored.
* \return SLUP_E_OK if the retrieval is successful, appropriate error code otherwise.
*
* This function checks if the provided pointers are valid. If so, it copies the current
* download rate from the SLUP structure to the provided variable.
*/
int slup_download_rate(SLUP *slup, uint32_t *rate)
{
if (!slup) return SLUP_E_INVALID;
if (!rate) return SLUP_E_RATE;
*rate = slup->downrate;
return SLUP_E_OK;
}
/**
* \brief Sets the target rate for the dummy data in the SLUP structure.
* \param slup: Pointer to the SLUP structure.
* \param rate: The target rate value to be set.
* \return SLUP_E_OK if the setting is successful, SLUP_E_INVALID if the pointer is NULL.
*
* This function checks if the provided pointer is valid. If so, it sets the target rate
* in the dummy data part of the SLUP structure to the provided value.
*/
int slup_set_dummy(SLUP *slup, uint32_t rate)
{
if (!slup) return SLUP_E_INVALID;
slup->dummy.target = rate;
return SLUP_E_OK;
}
/**
* \brief The main task function for the SLUP system.
* \param slup: Pointer to the SLUP structure.
*
* This function is the main task handler for the SLUP system. It updates the timestamp,
* and based on the timestamp value, performs various operations such as sending heartbeat
* messages, calculating rates, executing dummy data sending, and updating dummy data parameters.
* It also calls the slup_parse_task function to parse the received data in the queue.
*/
void slup_task(SLUP *slup)
{
if (!slup) return;
slup->timestamp += slup->period;
if (slup->timestamp >= 252000000) slup->timestamp = 0;
if (slup->timestamp % 500 == 0)
{
if (slup->silent < 3)
{
slup->silent++;
}
else
{
slup->link &= (~SLUP_LINK_RX);
}
slup_send_heart(slup);
}
if (slup->timestamp % 1000 == 0)
{
slup_calrate_task(slup);
}
if (slup->timestamp % 10 == 0)
{
slup_dummy_execute(slup);
}
if (slup->timestamp % 1000 == 0)
{
slup_dummy_update(slup);
}
slup_parse_task(slup);
}