231 lines
7.0 KiB
C
231 lines
7.0 KiB
C
#include "gui_frame.h"
|
|
|
|
uint32_t gui_crc32(const uint8_t *data, size_t len)
|
|
{
|
|
uint32_t crc = 0xFFFFFFFFUL;
|
|
for (size_t i = 0U; i < len; i++) {
|
|
crc ^= data[i];
|
|
for (uint8_t bit = 0U; bit < 8U; bit++) {
|
|
crc = (crc & 1UL) ? ((crc >> 1) ^ 0xEDB88320UL) : (crc >> 1);
|
|
}
|
|
}
|
|
return crc ^ 0xFFFFFFFFUL;
|
|
}
|
|
|
|
size_t gui_frame_encode(uint8_t type, uint16_t sequence,
|
|
const uint8_t *payload, uint16_t size,
|
|
uint8_t *out, size_t out_size)
|
|
{
|
|
if ((out == NULL) || (size > GUI_MAX_PAYLOAD) ||
|
|
((size > 0U) && (payload == NULL))) {
|
|
return 0U;
|
|
}
|
|
size_t total = (size_t)GUI_HEADER_SIZE + size + GUI_CRC_SIZE;
|
|
if (out_size < total) {
|
|
return 0U;
|
|
}
|
|
|
|
out[0] = GUI_SOF0;
|
|
out[1] = GUI_SOF1;
|
|
/* CRC считается от version до конца payload; SOF в него не входит. */
|
|
out[2] = GUI_PROTOCOL_VERSION;
|
|
out[3] = type;
|
|
out[4] = (uint8_t)(sequence >> 8); /* заголовок big-endian */
|
|
out[5] = (uint8_t)(sequence & 0xFFU);
|
|
out[6] = (uint8_t)(size >> 8);
|
|
out[7] = (uint8_t)(size & 0xFFU);
|
|
for (uint16_t i = 0U; i < size; i++) {
|
|
out[GUI_HEADER_SIZE + i] = payload[i];
|
|
}
|
|
|
|
uint32_t crc = gui_crc32(&out[2], (size_t)6U + size);
|
|
gui_put_u32(&out[GUI_HEADER_SIZE + size], crc); /* CRC little-endian */
|
|
return total;
|
|
}
|
|
|
|
void gui_parser_init(gui_parser_t *p)
|
|
{
|
|
p->state = GUI_ST_SOF0;
|
|
p->idx = 0U;
|
|
p->size = 0U;
|
|
p->stats.frames = 0U;
|
|
p->stats.crc_errors = 0U;
|
|
p->stats.version_errors = 0U;
|
|
p->stats.length_errors = 0U;
|
|
p->stats.stray_bytes = 0U;
|
|
}
|
|
|
|
bool gui_parser_push(gui_parser_t *p, uint8_t byte, gui_frame_t *out)
|
|
{
|
|
switch (p->state) {
|
|
case GUI_ST_SOF0:
|
|
if (byte == GUI_SOF0) {
|
|
p->state = GUI_ST_SOF1;
|
|
} else {
|
|
p->stats.stray_bytes++;
|
|
}
|
|
break;
|
|
|
|
case GUI_ST_SOF1:
|
|
if (byte == GUI_SOF1) {
|
|
p->idx = 0U;
|
|
p->state = GUI_ST_HEADER;
|
|
} else if (byte == GUI_SOF0) {
|
|
/* A5 A5 5A - тоже валидное начало, остаёмся здесь. */
|
|
p->stats.stray_bytes++;
|
|
} else {
|
|
p->stats.stray_bytes += 2U;
|
|
p->state = GUI_ST_SOF0;
|
|
}
|
|
break;
|
|
|
|
case GUI_ST_HEADER:
|
|
/* Версию проверяем сразу на первом байте заголовка. Если ждать все
|
|
шесть, ложная сигнатура успеет проглотить начало настоящего кадра
|
|
и он будет потерян целиком. */
|
|
if ((p->idx == 0U) && (byte != GUI_PROTOCOL_VERSION)) {
|
|
p->stats.version_errors++;
|
|
p->state = (byte == GUI_SOF0) ? GUI_ST_SOF1 : GUI_ST_SOF0;
|
|
break;
|
|
}
|
|
p->header[p->idx++] = byte;
|
|
if (p->idx < 6U) {
|
|
break;
|
|
}
|
|
p->size = (uint16_t)(((uint16_t)p->header[4] << 8) | p->header[5]);
|
|
if (p->size > GUI_RX_PAYLOAD_MAX) {
|
|
/* Либо мусор, либо кадр, который прибор обслужить не может.
|
|
И то и другое - повод вернуться к поиску сигнатуры. */
|
|
p->stats.length_errors++;
|
|
p->state = GUI_ST_SOF0;
|
|
break;
|
|
}
|
|
p->idx = 0U;
|
|
p->state = (p->size > 0U) ? GUI_ST_PAYLOAD : GUI_ST_CRC;
|
|
break;
|
|
|
|
case GUI_ST_PAYLOAD:
|
|
p->payload[p->idx++] = byte;
|
|
if (p->idx >= p->size) {
|
|
p->idx = 0U;
|
|
p->state = GUI_ST_CRC;
|
|
}
|
|
break;
|
|
|
|
case GUI_ST_CRC: {
|
|
p->crc_bytes[p->idx++] = byte;
|
|
if (p->idx < GUI_CRC_SIZE) {
|
|
break;
|
|
}
|
|
p->state = GUI_ST_SOF0;
|
|
|
|
uint32_t received = ((uint32_t)p->crc_bytes[0])
|
|
| ((uint32_t)p->crc_bytes[1] << 8)
|
|
| ((uint32_t)p->crc_bytes[2] << 16)
|
|
| ((uint32_t)p->crc_bytes[3] << 24);
|
|
|
|
/* CRC защищает header[0..5] и payload; считаем по частям,
|
|
чтобы не собирать кадр целиком во втором буфере. */
|
|
uint32_t crc = 0xFFFFFFFFUL;
|
|
for (uint8_t i = 0U; i < 6U; i++) {
|
|
crc ^= p->header[i];
|
|
for (uint8_t bit = 0U; bit < 8U; bit++) {
|
|
crc = (crc & 1UL) ? ((crc >> 1) ^ 0xEDB88320UL) : (crc >> 1);
|
|
}
|
|
}
|
|
for (uint16_t i = 0U; i < p->size; i++) {
|
|
crc ^= p->payload[i];
|
|
for (uint8_t bit = 0U; bit < 8U; bit++) {
|
|
crc = (crc & 1UL) ? ((crc >> 1) ^ 0xEDB88320UL) : (crc >> 1);
|
|
}
|
|
}
|
|
crc ^= 0xFFFFFFFFUL;
|
|
|
|
if (crc != received) {
|
|
p->stats.crc_errors++;
|
|
return false;
|
|
}
|
|
out->type = p->header[1];
|
|
out->sequence = (uint16_t)(((uint16_t)p->header[2] << 8) | p->header[3]);
|
|
out->size = p->size;
|
|
out->payload = p->payload;
|
|
p->stats.frames++;
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
p->state = GUI_ST_SOF0;
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
size_t gui_parser_feed(gui_parser_t *p, const uint8_t *data, size_t len,
|
|
gui_frame_cb_t cb, void *user)
|
|
{
|
|
gui_frame_t frame;
|
|
size_t count = 0U;
|
|
|
|
for (size_t i = 0U; i < len; i++) {
|
|
if (gui_parser_push(p, data[i], &frame)) {
|
|
count++;
|
|
if (cb != NULL) {
|
|
cb(&frame, user);
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool gui_link_init(gui_link_t *link, const pcan_io_t *io,
|
|
gui_frame_cb_t on_frame, void *user)
|
|
{
|
|
if ((link == NULL) || (io == NULL) || (io->write == NULL)) {
|
|
return false;
|
|
}
|
|
link->io = *io;
|
|
link->on_frame = on_frame;
|
|
link->user = user;
|
|
link->tx_frames = 0U;
|
|
link->tx_dropped = 0U;
|
|
gui_parser_init(&link->parser);
|
|
return true;
|
|
}
|
|
|
|
bool gui_link_can_send(const gui_link_t *link, uint16_t size)
|
|
{
|
|
if (link->io.tx_space == NULL) {
|
|
return true;
|
|
}
|
|
return link->io.tx_space(link->io.ctx) >= ((size_t)GUI_HEADER_SIZE + size + GUI_CRC_SIZE);
|
|
}
|
|
|
|
bool gui_link_send(gui_link_t *link, uint8_t type, uint16_t sequence,
|
|
const uint8_t *payload, uint16_t size)
|
|
{
|
|
size_t n = gui_frame_encode(type, sequence, payload, size,
|
|
link->tx_buf, sizeof link->tx_buf);
|
|
if (n == 0U) {
|
|
link->tx_dropped++;
|
|
return false;
|
|
}
|
|
if (link->io.write(link->io.ctx, link->tx_buf, n) != n) {
|
|
link->tx_dropped++;
|
|
return false;
|
|
}
|
|
link->tx_frames++;
|
|
return true;
|
|
}
|
|
|
|
bool gui_link_nack(gui_link_t *link, uint16_t sequence, uint16_t result)
|
|
{
|
|
uint8_t payload[2];
|
|
gui_put_u16(payload, result);
|
|
return gui_link_send(link, GUI_MSG_NACK, sequence, payload, sizeof payload);
|
|
}
|
|
|
|
size_t gui_link_feed(gui_link_t *link, const uint8_t *data, size_t len)
|
|
{
|
|
return gui_parser_feed(&link->parser, data, len, link->on_frame, link->user);
|
|
}
|