254 lines
5.8 KiB
C
254 lines
5.8 KiB
C
/**
|
|
* @file tcp_server.c
|
|
* @brief lwIP RAW TCP server for the UART2 bridge.
|
|
*/
|
|
|
|
#include "tcp_server.h"
|
|
|
|
#include "lwip/pbuf.h"
|
|
#include "lwip/tcp.h"
|
|
|
|
#include <string.h>
|
|
|
|
typedef struct {
|
|
struct tcp_pcb *listen_pcb;
|
|
struct tcp_pcb *client_pcb;
|
|
uint8_t rx_ring[TCP_SERVER_RX_BUFFER_SIZE];
|
|
uint16_t rx_head;
|
|
uint16_t rx_tail;
|
|
tcp_server_config_t config;
|
|
tcp_server_status_t status;
|
|
} tcp_server_ctx_t;
|
|
|
|
static tcp_server_ctx_t g_server;
|
|
|
|
static uint16_t ring_free(uint16_t head, uint16_t tail, uint16_t size)
|
|
{
|
|
return (head >= tail) ? (uint16_t)(size - head + tail - 1u) : (uint16_t)(tail - head - 1u);
|
|
}
|
|
|
|
static err_t tcp_server_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
|
|
{
|
|
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
|
|
struct pbuf *q;
|
|
|
|
if (err != ERR_OK) {
|
|
if (p != NULL) {
|
|
pbuf_free(p);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
if (p == NULL) {
|
|
tcp_arg(pcb, NULL);
|
|
tcp_recv(pcb, NULL);
|
|
tcp_sent(pcb, NULL);
|
|
tcp_err(pcb, NULL);
|
|
if (tcp_close(pcb) != ERR_OK) {
|
|
tcp_abort(pcb);
|
|
}
|
|
ctx->client_pcb = NULL;
|
|
ctx->status.state = TCP_SERVER_STATE_LISTENING;
|
|
return ERR_OK;
|
|
}
|
|
|
|
for (q = p; q != NULL; q = q->next) {
|
|
const uint8_t *src = (const uint8_t *)q->payload;
|
|
for (uint16_t i = 0; i < q->len; ++i) {
|
|
if (ring_free(ctx->rx_head, ctx->rx_tail, TCP_SERVER_RX_BUFFER_SIZE) == 0u) {
|
|
ctx->status.errors++;
|
|
break;
|
|
}
|
|
ctx->rx_ring[ctx->rx_head] = src[i];
|
|
ctx->rx_head = (uint16_t)((ctx->rx_head + 1u) % TCP_SERVER_RX_BUFFER_SIZE);
|
|
ctx->status.rx_bytes++;
|
|
}
|
|
}
|
|
|
|
tcp_recved(pcb, p->tot_len);
|
|
pbuf_free(p);
|
|
return ERR_OK;
|
|
}
|
|
|
|
static err_t tcp_server_on_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
|
|
{
|
|
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
|
|
(void)pcb;
|
|
ctx->status.tx_bytes += len;
|
|
return ERR_OK;
|
|
}
|
|
|
|
static void tcp_server_on_err(void *arg, err_t err)
|
|
{
|
|
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
|
|
(void)err;
|
|
ctx->client_pcb = NULL;
|
|
ctx->status.state = TCP_SERVER_STATE_LISTENING;
|
|
ctx->status.errors++;
|
|
}
|
|
|
|
static err_t tcp_server_on_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
|
|
{
|
|
tcp_server_ctx_t *ctx = (tcp_server_ctx_t *)arg;
|
|
|
|
if (err != ERR_OK) {
|
|
return err;
|
|
}
|
|
|
|
if (ctx->client_pcb != NULL) {
|
|
tcp_abort(newpcb);
|
|
return ERR_ABRT;
|
|
}
|
|
|
|
ctx->client_pcb = newpcb;
|
|
ctx->status.state = TCP_SERVER_STATE_CONNECTED;
|
|
ctx->status.connections++;
|
|
|
|
tcp_arg(newpcb, ctx);
|
|
tcp_recv(newpcb, tcp_server_on_recv);
|
|
tcp_sent(newpcb, tcp_server_on_sent);
|
|
tcp_err(newpcb, tcp_server_on_err);
|
|
return ERR_OK;
|
|
}
|
|
|
|
int tcp_server_init(const tcp_server_config_t *config)
|
|
{
|
|
memset(&g_server, 0, sizeof(g_server));
|
|
g_server.config.port = TCP_SERVER_DEFAULT_PORT;
|
|
g_server.config.auto_reconnect = true;
|
|
g_server.status.state = TCP_SERVER_STATE_IDLE;
|
|
|
|
if (config != NULL) {
|
|
g_server.config = *config;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tcp_server_start(void)
|
|
{
|
|
struct tcp_pcb *pcb;
|
|
err_t err;
|
|
|
|
if (g_server.listen_pcb != NULL) {
|
|
return 0;
|
|
}
|
|
|
|
pcb = tcp_new_ip_type(IPADDR_TYPE_V4);
|
|
if (pcb == NULL) {
|
|
g_server.status.errors++;
|
|
return -1;
|
|
}
|
|
|
|
err = tcp_bind(pcb, IP_ANY_TYPE, g_server.config.port);
|
|
if (err != ERR_OK) {
|
|
tcp_abort(pcb);
|
|
g_server.status.errors++;
|
|
return -1;
|
|
}
|
|
|
|
g_server.listen_pcb = tcp_listen_with_backlog(pcb, 1);
|
|
if (g_server.listen_pcb == NULL) {
|
|
g_server.status.errors++;
|
|
return -1;
|
|
}
|
|
|
|
tcp_arg(g_server.listen_pcb, &g_server);
|
|
tcp_accept(g_server.listen_pcb, tcp_server_on_accept);
|
|
g_server.status.state = TCP_SERVER_STATE_LISTENING;
|
|
return 0;
|
|
}
|
|
|
|
int tcp_server_stop(void)
|
|
{
|
|
if (g_server.client_pcb != NULL) {
|
|
tcp_arg(g_server.client_pcb, NULL);
|
|
tcp_recv(g_server.client_pcb, NULL);
|
|
tcp_sent(g_server.client_pcb, NULL);
|
|
tcp_err(g_server.client_pcb, NULL);
|
|
tcp_abort(g_server.client_pcb);
|
|
g_server.client_pcb = NULL;
|
|
}
|
|
|
|
if (g_server.listen_pcb != NULL) {
|
|
tcp_arg(g_server.listen_pcb, NULL);
|
|
tcp_accept(g_server.listen_pcb, NULL);
|
|
tcp_close(g_server.listen_pcb);
|
|
g_server.listen_pcb = NULL;
|
|
}
|
|
|
|
g_server.status.state = TCP_SERVER_STATE_IDLE;
|
|
return 0;
|
|
}
|
|
|
|
int tcp_server_send(const uint8_t *data, uint16_t len)
|
|
{
|
|
err_t err;
|
|
|
|
if (g_server.client_pcb == NULL || data == NULL || len == 0u) {
|
|
return -1;
|
|
}
|
|
|
|
if (tcp_sndbuf(g_server.client_pcb) < len) {
|
|
return 0;
|
|
}
|
|
|
|
err = tcp_write(g_server.client_pcb, data, len, TCP_WRITE_FLAG_COPY);
|
|
if (err != ERR_OK) {
|
|
g_server.status.errors++;
|
|
return -1;
|
|
}
|
|
|
|
err = tcp_output(g_server.client_pcb);
|
|
if (err != ERR_OK) {
|
|
g_server.status.errors++;
|
|
return -1;
|
|
}
|
|
|
|
return (int)len;
|
|
}
|
|
|
|
int tcp_server_recv(uint8_t *data, uint16_t max_len, uint32_t timeout_ms)
|
|
{
|
|
uint16_t copied = 0u;
|
|
(void)timeout_ms;
|
|
|
|
if (data == NULL || max_len == 0u) {
|
|
return -1;
|
|
}
|
|
|
|
while (copied < max_len && g_server.rx_tail != g_server.rx_head) {
|
|
data[copied++] = g_server.rx_ring[g_server.rx_tail];
|
|
g_server.rx_tail = (uint16_t)((g_server.rx_tail + 1u) % TCP_SERVER_RX_BUFFER_SIZE);
|
|
}
|
|
|
|
return (int)copied;
|
|
}
|
|
|
|
bool tcp_server_is_connected(void)
|
|
{
|
|
return g_server.client_pcb != NULL;
|
|
}
|
|
|
|
void tcp_server_get_status(tcp_server_status_t *status)
|
|
{
|
|
if (status != NULL) {
|
|
*status = g_server.status;
|
|
}
|
|
}
|
|
|
|
void *tcp_server_get_rx_stream(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void *tcp_server_get_tx_stream(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void tcp_server_task(void *argument)
|
|
{
|
|
(void)argument;
|
|
}
|