zephyr/subsys/modem/modem_ppp.c
Tomi Fontanilles 11f22bf702 modem: ppp: fix receiving of successive frames when one 0x7E is omitted
The SOF delimiter byte may be omitted when a frame follows another
that just ended with that byte.
The parsing used to expect that second delimiter anyway,
which resulted in PPP frames going missing.

As an additional improvement, dropped bytes as well as the length
of received frames are now (debug) logged.

Signed-off-by: Tomi Fontanilles <tomi.fontanilles@nordicsemi.no>
2024-02-28 18:02:17 +00:00

540 lines
14 KiB
C

/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/net/ppp.h>
#include <zephyr/sys/crc.h>
#include <zephyr/modem/ppp.h>
#include <string.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_ppp, CONFIG_MODEM_MODULES_LOG_LEVEL);
#define MODEM_PPP_STATE_ATTACHED_BIT (0)
#define MODEM_PPP_FRAME_TAIL_SIZE (2)
#define MODEM_PPP_CODE_DELIMITER (0x7E)
#define MODEM_PPP_CODE_ESCAPE (0x7D)
#define MODEM_PPP_VALUE_ESCAPE (0x20)
static uint16_t modem_ppp_fcs_init(uint8_t byte)
{
return crc16_ccitt(0xFFFF, &byte, 1);
}
static uint16_t modem_ppp_fcs_update(uint16_t fcs, uint8_t byte)
{
return crc16_ccitt(fcs, &byte, 1);
}
static uint16_t modem_ppp_fcs_final(uint16_t fcs)
{
return fcs ^ 0xFFFF;
}
static uint16_t modem_ppp_ppp_protocol(struct net_pkt *pkt)
{
if (net_pkt_family(pkt) == AF_INET) {
return PPP_IP;
}
if (net_pkt_family(pkt) == AF_INET6) {
return PPP_IPV6;
}
LOG_WRN("Unsupported protocol");
return 0;
}
static uint8_t modem_ppp_wrap_net_pkt_byte(struct modem_ppp *ppp)
{
uint8_t byte;
switch (ppp->transmit_state) {
case MODEM_PPP_TRANSMIT_STATE_IDLE:
LOG_WRN("Invalid transmit state");
return 0;
/* Writing header */
case MODEM_PPP_TRANSMIT_STATE_SOF:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_HDR_FF;
return MODEM_PPP_CODE_DELIMITER;
case MODEM_PPP_TRANSMIT_STATE_HDR_FF:
net_pkt_cursor_init(ppp->tx_pkt);
ppp->tx_pkt_fcs = modem_ppp_fcs_init(0xFF);
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_HDR_7D;
return 0xFF;
case MODEM_PPP_TRANSMIT_STATE_HDR_7D:
ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, 0x03);
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_HDR_23;
return MODEM_PPP_CODE_ESCAPE;
case MODEM_PPP_TRANSMIT_STATE_HDR_23:
if (net_pkt_is_ppp(ppp->tx_pkt) == true) {
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_DATA;
} else {
ppp->tx_pkt_protocol = modem_ppp_ppp_protocol(ppp->tx_pkt);
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_PROTOCOL_HIGH;
}
return 0x23;
/* Writing protocol */
case MODEM_PPP_TRANSMIT_STATE_PROTOCOL_HIGH:
byte = (ppp->tx_pkt_protocol >> 8) & 0xFF;
ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, byte);
if ((byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) ||
(byte < MODEM_PPP_VALUE_ESCAPE)) {
ppp->tx_pkt_escaped = byte ^ MODEM_PPP_VALUE_ESCAPE;
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_ESCAPING_PROTOCOL_HIGH;
return MODEM_PPP_CODE_ESCAPE;
}
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_PROTOCOL_LOW;
return byte;
case MODEM_PPP_TRANSMIT_STATE_ESCAPING_PROTOCOL_HIGH:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_PROTOCOL_LOW;
return ppp->tx_pkt_escaped;
case MODEM_PPP_TRANSMIT_STATE_PROTOCOL_LOW:
byte = ppp->tx_pkt_protocol & 0xFF;
ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, byte);
if ((byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) ||
(byte < MODEM_PPP_VALUE_ESCAPE)) {
ppp->tx_pkt_escaped = byte ^ MODEM_PPP_VALUE_ESCAPE;
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_ESCAPING_PROTOCOL_LOW;
return MODEM_PPP_CODE_ESCAPE;
}
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_DATA;
return byte;
case MODEM_PPP_TRANSMIT_STATE_ESCAPING_PROTOCOL_LOW:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_DATA;
return ppp->tx_pkt_escaped;
/* Writing data */
case MODEM_PPP_TRANSMIT_STATE_DATA:
net_pkt_read_u8(ppp->tx_pkt, &byte);
ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, byte);
if ((byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) ||
(byte < MODEM_PPP_VALUE_ESCAPE)) {
ppp->tx_pkt_escaped = byte ^ MODEM_PPP_VALUE_ESCAPE;
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_ESCAPING_DATA;
return MODEM_PPP_CODE_ESCAPE;
}
if (net_pkt_remaining_data(ppp->tx_pkt) == 0) {
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_FCS_LOW;
}
return byte;
case MODEM_PPP_TRANSMIT_STATE_ESCAPING_DATA:
if (net_pkt_remaining_data(ppp->tx_pkt) == 0) {
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_FCS_LOW;
} else {
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_DATA;
}
return ppp->tx_pkt_escaped;
/* Writing FCS */
case MODEM_PPP_TRANSMIT_STATE_FCS_LOW:
ppp->tx_pkt_fcs = modem_ppp_fcs_final(ppp->tx_pkt_fcs);
byte = ppp->tx_pkt_fcs & 0xFF;
if ((byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) ||
(byte < MODEM_PPP_VALUE_ESCAPE)) {
ppp->tx_pkt_escaped = byte ^ MODEM_PPP_VALUE_ESCAPE;
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_ESCAPING_FCS_LOW;
return MODEM_PPP_CODE_ESCAPE;
}
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_FCS_HIGH;
return byte;
case MODEM_PPP_TRANSMIT_STATE_ESCAPING_FCS_LOW:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_FCS_HIGH;
return ppp->tx_pkt_escaped;
case MODEM_PPP_TRANSMIT_STATE_FCS_HIGH:
byte = (ppp->tx_pkt_fcs >> 8) & 0xFF;
if ((byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) ||
(byte < MODEM_PPP_VALUE_ESCAPE)) {
ppp->tx_pkt_escaped = byte ^ MODEM_PPP_VALUE_ESCAPE;
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_ESCAPING_FCS_HIGH;
return MODEM_PPP_CODE_ESCAPE;
}
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_EOF;
return byte;
case MODEM_PPP_TRANSMIT_STATE_ESCAPING_FCS_HIGH:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_EOF;
return ppp->tx_pkt_escaped;
/* Writing end of frame */
case MODEM_PPP_TRANSMIT_STATE_EOF:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_IDLE;
return MODEM_PPP_CODE_DELIMITER;
}
return 0;
}
static bool modem_ppp_is_byte_expected(uint8_t byte, uint8_t expected_byte)
{
if (byte == expected_byte) {
return true;
}
LOG_DBG("Dropping byte 0x%02hhx because 0x%02hhx was expected.", byte, expected_byte);
return false;
}
static void modem_ppp_process_received_byte(struct modem_ppp *ppp, uint8_t byte)
{
switch (ppp->receive_state) {
case MODEM_PPP_RECEIVE_STATE_HDR_SOF:
if (modem_ppp_is_byte_expected(byte, MODEM_PPP_CODE_DELIMITER)) {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_FF;
}
break;
case MODEM_PPP_RECEIVE_STATE_HDR_FF:
if (byte == MODEM_PPP_CODE_DELIMITER) {
break;
}
if (modem_ppp_is_byte_expected(byte, 0xFF)) {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_7D;
} else {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
}
break;
case MODEM_PPP_RECEIVE_STATE_HDR_7D:
if (modem_ppp_is_byte_expected(byte, MODEM_PPP_CODE_ESCAPE)) {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_23;
} else {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
}
break;
case MODEM_PPP_RECEIVE_STATE_HDR_23:
if (modem_ppp_is_byte_expected(byte, 0x23)) {
ppp->rx_pkt = net_pkt_rx_alloc_with_buffer(ppp->iface,
CONFIG_MODEM_PPP_NET_BUF_FRAG_SIZE, AF_UNSPEC, 0, K_NO_WAIT);
if (ppp->rx_pkt == NULL) {
LOG_WRN("Dropped frame, no net_pkt available");
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
break;
}
LOG_DBG("Receiving PPP frame");
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_WRITING;
net_pkt_cursor_init(ppp->rx_pkt);
} else {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
}
break;
case MODEM_PPP_RECEIVE_STATE_WRITING:
if (byte == MODEM_PPP_CODE_DELIMITER) {
LOG_DBG("Received PPP frame (len %zu)", net_pkt_get_len(ppp->rx_pkt));
/* Remove FCS */
net_pkt_remove_tail(ppp->rx_pkt, MODEM_PPP_FRAME_TAIL_SIZE);
net_pkt_set_ppp(ppp->rx_pkt, true);
if (net_recv_data(ppp->iface, ppp->rx_pkt) < 0) {
LOG_WRN("Net pkt could not be processed");
net_pkt_unref(ppp->rx_pkt);
}
ppp->rx_pkt = NULL;
/* Skip SOF because the delimiter may be omitted for successive frames. */
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_FF;
break;
}
if (net_pkt_available_buffer(ppp->rx_pkt) == 1) {
if (net_pkt_alloc_buffer(ppp->rx_pkt, CONFIG_MODEM_PPP_NET_BUF_FRAG_SIZE,
AF_INET, K_NO_WAIT) < 0) {
LOG_WRN("Failed to alloc buffer");
net_pkt_unref(ppp->rx_pkt);
ppp->rx_pkt = NULL;
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
break;
}
}
if (byte == MODEM_PPP_CODE_ESCAPE) {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_UNESCAPING;
break;
}
if (net_pkt_write_u8(ppp->rx_pkt, byte) < 0) {
LOG_WRN("Dropped PPP frame");
net_pkt_unref(ppp->rx_pkt);
ppp->rx_pkt = NULL;
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
#if defined(CONFIG_NET_STATISTICS_PPP)
ppp->stats.drop++;
#endif
}
break;
case MODEM_PPP_RECEIVE_STATE_UNESCAPING:
if (net_pkt_write_u8(ppp->rx_pkt, (byte ^ MODEM_PPP_VALUE_ESCAPE)) < 0) {
LOG_WRN("Dropped PPP frame");
net_pkt_unref(ppp->rx_pkt);
ppp->rx_pkt = NULL;
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
#if defined(CONFIG_NET_STATISTICS_PPP)
ppp->stats.drop++;
#endif
break;
}
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_WRITING;
break;
}
}
static void modem_ppp_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
void *user_data)
{
struct modem_ppp *ppp = (struct modem_ppp *)user_data;
switch (event) {
case MODEM_PIPE_EVENT_RECEIVE_READY:
k_work_submit(&ppp->process_work);
break;
case MODEM_PIPE_EVENT_OPENED:
case MODEM_PIPE_EVENT_TRANSMIT_IDLE:
k_work_submit(&ppp->send_work);
break;
default:
break;
}
}
static void modem_ppp_send_handler(struct k_work *item)
{
struct modem_ppp *ppp = CONTAINER_OF(item, struct modem_ppp, send_work);
uint8_t byte;
uint8_t *reserved;
uint32_t reserved_size;
int ret;
if (ppp->tx_pkt == NULL) {
ppp->tx_pkt = k_fifo_get(&ppp->tx_pkt_fifo, K_NO_WAIT);
}
if (ppp->tx_pkt != NULL) {
/* Initialize wrap */
if (ppp->transmit_state == MODEM_PPP_TRANSMIT_STATE_IDLE) {
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_SOF;
}
/* Fill transmit ring buffer */
while (ring_buf_space_get(&ppp->transmit_rb) > 0) {
byte = modem_ppp_wrap_net_pkt_byte(ppp);
ring_buf_put(&ppp->transmit_rb, &byte, 1);
if (ppp->transmit_state == MODEM_PPP_TRANSMIT_STATE_IDLE) {
net_pkt_unref(ppp->tx_pkt);
ppp->tx_pkt = k_fifo_get(&ppp->tx_pkt_fifo, K_NO_WAIT);
break;
}
}
}
while (!ring_buf_is_empty(&ppp->transmit_rb)) {
reserved_size = ring_buf_get_claim(&ppp->transmit_rb, &reserved, UINT32_MAX);
ret = modem_pipe_transmit(ppp->pipe, reserved, reserved_size);
if (ret < 0) {
ring_buf_get_finish(&ppp->transmit_rb, 0);
break;
}
ring_buf_get_finish(&ppp->transmit_rb, (uint32_t)ret);
if (ret < reserved_size) {
break;
}
}
}
static void modem_ppp_process_handler(struct k_work *item)
{
struct modem_ppp *ppp = CONTAINER_OF(item, struct modem_ppp, process_work);
int ret;
ret = modem_pipe_receive(ppp->pipe, ppp->receive_buf, ppp->buf_size);
if (ret < 1) {
return;
}
for (int i = 0; i < ret; i++) {
modem_ppp_process_received_byte(ppp, ppp->receive_buf[i]);
}
k_work_submit(&ppp->process_work);
}
static void modem_ppp_ppp_api_init(struct net_if *iface)
{
const struct device *dev = net_if_get_device(iface);
struct modem_ppp *ppp = (struct modem_ppp *)dev->data;
net_ppp_init(iface);
net_if_flag_set(iface, NET_IF_NO_AUTO_START);
net_if_carrier_off(iface);
if (ppp->init_iface != NULL) {
ppp->init_iface(iface);
}
ppp->iface = iface;
}
static int modem_ppp_ppp_api_start(const struct device *dev)
{
return 0;
}
static int modem_ppp_ppp_api_stop(const struct device *dev)
{
return 0;
}
static int modem_ppp_ppp_api_send(const struct device *dev, struct net_pkt *pkt)
{
struct modem_ppp *ppp = (struct modem_ppp *)dev->data;
if (atomic_test_bit(&ppp->state, MODEM_PPP_STATE_ATTACHED_BIT) == false) {
return -EPERM;
}
/* Validate packet protocol */
if ((net_pkt_is_ppp(pkt) == false) && (net_pkt_family(pkt) != AF_INET) &&
(net_pkt_family(pkt) != AF_INET6)) {
return -EPROTONOSUPPORT;
}
/* Validate packet data length */
if (((net_pkt_get_len(pkt) < 2) && (net_pkt_is_ppp(pkt) == true)) ||
((net_pkt_get_len(pkt) < 1))) {
return -ENODATA;
}
net_pkt_ref(pkt);
k_fifo_put(&ppp->tx_pkt_fifo, pkt);
k_work_submit(&ppp->send_work);
return 0;
}
#if defined(CONFIG_NET_STATISTICS_PPP)
static struct net_stats_ppp *modem_ppp_ppp_get_stats(const struct device *dev)
{
struct modem_ppp *ppp = (struct modem_ppp *)dev->data;
return &ppp->stats;
}
#endif
const struct ppp_api modem_ppp_ppp_api = {
.iface_api.init = modem_ppp_ppp_api_init,
.start = modem_ppp_ppp_api_start,
.stop = modem_ppp_ppp_api_stop,
.send = modem_ppp_ppp_api_send,
#if defined(CONFIG_NET_STATISTICS_PPP)
.get_stats = modem_ppp_ppp_get_stats,
#endif
};
int modem_ppp_attach(struct modem_ppp *ppp, struct modem_pipe *pipe)
{
if (atomic_test_bit(&ppp->state, MODEM_PPP_STATE_ATTACHED_BIT) == true) {
return 0;
}
ppp->pipe = pipe;
modem_pipe_attach(pipe, modem_ppp_pipe_callback, ppp);
atomic_set_bit(&ppp->state, MODEM_PPP_STATE_ATTACHED_BIT);
return 0;
}
struct net_if *modem_ppp_get_iface(struct modem_ppp *ppp)
{
return ppp->iface;
}
void modem_ppp_release(struct modem_ppp *ppp)
{
struct k_work_sync sync;
struct net_pkt *pkt;
if (atomic_test_and_clear_bit(&ppp->state, MODEM_PPP_STATE_ATTACHED_BIT) == false) {
return;
}
modem_pipe_release(ppp->pipe);
k_work_cancel_sync(&ppp->send_work, &sync);
k_work_cancel_sync(&ppp->process_work, &sync);
ppp->pipe = NULL;
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
if (ppp->rx_pkt != NULL) {
net_pkt_unref(ppp->rx_pkt);
ppp->rx_pkt = NULL;
}
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_IDLE;
if (ppp->tx_pkt != NULL) {
net_pkt_unref(ppp->tx_pkt);
ppp->tx_pkt = NULL;
}
while (1) {
pkt = k_fifo_get(&ppp->tx_pkt_fifo, K_NO_WAIT);
if (pkt == NULL) {
break;
}
net_pkt_unref(pkt);
}
}
int modem_ppp_init_internal(const struct device *dev)
{
struct modem_ppp *ppp = (struct modem_ppp *)dev->data;
atomic_set(&ppp->state, 0);
ring_buf_init(&ppp->transmit_rb, ppp->buf_size, ppp->transmit_buf);
k_work_init(&ppp->send_work, modem_ppp_send_handler);
k_work_init(&ppp->process_work, modem_ppp_process_handler);
k_fifo_init(&ppp->tx_pkt_fifo);
return 0;
}