/********************************************************************************************************* * ------------------------------------------------------------------------------------------------------ * 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); }