Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .github/workflows/examples_build-host-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ jobs:
strategy:
matrix:
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"]
example: ["slip_custom_netif", "cslip_custom_netif"]
include:
- idf_ver: "latest"
warning: "Warning: The smallest app partition is nearly full"
runs-on: ubuntu-22.04
container: espressif/idf:${{ matrix.idf_ver }}
env:
TARGET_TEST: examples/esp_netif/slip_custom_netif/
TARGET_TEST: examples/esp_netif/${{ matrix.example }}/
TARGET_TEST_DIR: build_esp32c3_target
steps:
- name: Checkout esp-protocols
Expand All @@ -41,7 +42,7 @@ jobs:
zip -qur artifacts.zip ${TARGET_TEST_DIR}
- uses: actions/upload-artifact@v4
with:
name: slip_target_${{ matrix.idf_ver }}
name: ${{ matrix.example }}_target_${{ matrix.idf_ver }}
path: ${{ env.TARGET_TEST }}/artifacts.zip
if-no-files-found: error

Expand Down Expand Up @@ -71,22 +72,23 @@ jobs:
if: |
github.repository == 'espressif/esp-protocols' &&
( contains(github.event.pull_request.labels.*.name, 'examples') || github.event_name == 'push' )
name: Slip example target test
name: Netif example target test
needs: build_all_examples
strategy:
matrix:
idf_ver: ["release-v5.4", "latest"]
example: ["slip_custom_netif", "cslip_custom_netif"]
runs-on:
- self-hosted
- modem
env:
TARGET_TEST: examples/esp_netif/slip_custom_netif/
TARGET_TEST: examples/esp_netif/${{ matrix.example }}/
TARGET_TEST_DIR: build_esp32c3_target
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: slip_target_${{ matrix.idf_ver }}
name: ${{ matrix.example }}_target_${{ matrix.idf_ver }}
path: ${{ env.TARGET_TEST }}/ci/
- name: Run Test
working-directory: ${{ env.TARGET_TEST }}
Expand Down
6 changes: 6 additions & 0 deletions examples/esp_netif/cslip_custom_netif/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(cslip_client)
23 changes: 23 additions & 0 deletions examples/esp_netif/cslip_custom_netif/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- |

# CSLIP device client (initial)

This example mirrors the SLIP custom netif example, but prepares for CSLIP (Van Jacobson TCP/IP header compression over SLIP). In this initial version, the data path behaves like plain SLIP while exposing a minimal CSLIP-facing API and structure so compression can be added incrementally.

It demonstrates creating a custom network interface and attaching it to `esp_netif` with an external component (`cslip_modem`) similar to the `slip_modem` used in the SLIP example.

Notes:
- Compression is not yet implemented; packets are forwarded as standard SLIP.
- The structure allows later integration of VJ compression/decompression.

## How to use

Hardware and setup are identical to the SLIP example. You can point a Linux host to the serial port and use `slattach` with `-p slip` for initial testing.

Build and flash:
- idf.py set-target <chip>
- idf.py menuconfig (configure Example Configuration)
- idf.py -p <PORT> flash monitor

Troubleshooting and behavior should match the SLIP example until compression is added.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# CSLIP Modem Component (initial, pass-through)

idf_component_register(
SRCS "library/cslip_modem.c" "library/cslip_modem_netif.c"
INCLUDE_DIRS "include"
REQUIRES esp_netif driver
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <stdbool.h>
#include <stdint.h>

#include "esp_netif.h"
#include "driver/uart.h"

/**
* Minimal CSLIP-facing default netif config (mirrors SLIP example)
*/
#define ESP_NETIF_INHERENT_DEFAULT_CSLIP() \
{ \
ESP_COMPILER_DESIGNATED_INIT_AGGREGATE_TYPE_EMPTY(mac) \
ESP_COMPILER_DESIGNATED_INIT_AGGREGATE_TYPE_EMPTY(ip_info) \
.get_ip_event = 0, \
.lost_ip_event = 0, \
.if_key = "CSLP_DEF", \
.if_desc = "cslip", \
.route_prio = 16, \
.bridge_info = NULL \
}

extern const esp_netif_netstack_config_t *netstack_default_cslip;

typedef struct cslip_modem *cslip_modem_handle;

// Optional filter for application-specific serial messages in the stream
typedef bool cslip_rx_filter_cb_t(cslip_modem_handle cslip, uint8_t *data, uint32_t len);

// Minimal CSLIP config stub per requirements (values currently unused)
typedef struct {
bool enable; // default true
uint8_t vj_slots; // default 16
bool slotid_compression; // default true
bool safe_mode; // default true
} esp_cslip_config_t;

// Configuration structure for CSLIP modem interface (extends SLIP modem config)
typedef struct {
uart_port_t uart_dev; /* UART device for reading/writing */

int uart_tx_pin; /* UART TX pin number */
int uart_rx_pin; /* UART RX pin number */

uint32_t uart_baud; /* UART baud rate */

uint32_t rx_buffer_len; /* RX buffer length */

cslip_rx_filter_cb_t *rx_filter; /* Optional RX filter */
esp_ip6_addr_t *ipv6_addr; /* IPv6 address */

esp_cslip_config_t cslip; /* CSLIP options (currently informational) */
} cslip_modem_config_t;

// Create a CSLIP modem (initially pass-through SLIP behavior)
cslip_modem_handle cslip_modem_create(esp_netif_t *cslip_netif, const cslip_modem_config_t *modem_config);

// Destroy a CSLIP modem
esp_err_t cslip_modem_destroy(cslip_modem_handle cslip);

// Get configured IPv6 address
esp_ip6_addr_t cslip_modem_get_ipv6_address(cslip_modem_handle cslip);

// Raw write out the interface
void cslip_modem_raw_write(cslip_modem_handle cslip, void *buffer, size_t len);
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <string.h>
#include <inttypes.h>
#include "cslip_modem.h"

#include "esp_netif.h"
#include "cslip_modem_netif.h"
#include "esp_event.h"
#include "esp_log.h"

#define CSLIP_RX_TASK_PRIORITY 10
#define CSLIP_RX_TASK_STACK_SIZE (4 * 1024)

static const char *TAG = "cslip-modem";

typedef struct {
uart_port_t uart_dev;
uint32_t uart_baud;
int uart_tx_pin;
int uart_rx_pin;
QueueHandle_t uart_queue;
TaskHandle_t uart_rx_task;
} esp_cslip_uart_t;

struct cslip_modem {
esp_netif_driver_base_t base;
esp_cslip_uart_t uart;
uint8_t *buffer;
uint32_t buffer_len;
cslip_rx_filter_cb_t *rx_filter;
bool running;
esp_ip6_addr_t addr;
esp_cslip_config_t cslip_cfg; // currently informational
};

static void cslip_modem_uart_rx_task(void *arg);
static esp_err_t cslip_modem_post_attach(esp_netif_t *esp_netif, void *args);

cslip_modem_handle cslip_modem_create(esp_netif_t *cslip_netif, const cslip_modem_config_t *modem_config)
{
if (cslip_netif == NULL || modem_config == NULL) {
ESP_LOGE(TAG, "invalid parameters");
return NULL;
}
ESP_LOGI(TAG, "%s: Creating cslip modem (netif: %p)", __func__, cslip_netif);

cslip_modem_handle modem = calloc(1, sizeof(struct cslip_modem));
if (!modem) {
ESP_LOGE(TAG, "create netif glue failed");
return NULL;
}

modem->base.post_attach = cslip_modem_post_attach;
modem->base.netif = cslip_netif;

modem->buffer_len = modem_config->rx_buffer_len;
modem->rx_filter = modem_config->rx_filter;

modem->uart.uart_dev = modem_config->uart_dev;
modem->uart.uart_baud = modem_config->uart_baud;
modem->uart.uart_rx_pin = modem_config->uart_rx_pin;
modem->uart.uart_tx_pin = modem_config->uart_tx_pin;
modem->addr = *modem_config->ipv6_addr;
modem->cslip_cfg = modem_config->cslip;

return modem;
}

static esp_err_t esp_cslip_driver_start(cslip_modem_handle modem)
{
if (modem->buffer == NULL) {
modem->buffer = malloc(modem->buffer_len);
}
if (modem->buffer == NULL) {
ESP_LOGE(TAG, "error allocating rx buffer");
return ESP_ERR_NO_MEM;
}

uart_config_t uart_config = {
.baud_rate = modem->uart.uart_baud,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
};

ESP_ERROR_CHECK(uart_param_config(modem->uart.uart_dev, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(modem->uart.uart_dev, modem->uart.uart_tx_pin, modem->uart.uart_rx_pin, 0, 0));
ESP_ERROR_CHECK(uart_driver_install(modem->uart.uart_dev, modem->buffer_len, modem->buffer_len, 10, &modem->uart.uart_queue, 0));

modem->running = true;
xTaskCreate(cslip_modem_uart_rx_task, "cslip_modem_uart_rx_task", CSLIP_RX_TASK_STACK_SIZE, modem, CSLIP_RX_TASK_PRIORITY, &modem->uart.uart_rx_task);

esp_netif_action_start(modem->base.netif, 0, 0, 0);
ESP_ERROR_CHECK(cslip_modem_netif_start(modem->base.netif, &modem->addr));
return ESP_OK;
}

esp_err_t cslip_modem_destroy(cslip_modem_handle modem)
{
if (modem != NULL) {
esp_netif_action_stop(modem->base.netif, 0, 0, 0);
ESP_ERROR_CHECK(cslip_modem_netif_stop(modem->base.netif));
vTaskDelete(modem->uart.uart_rx_task);
uart_driver_delete(modem->uart.uart_dev);
free(modem);
}

return ESP_OK;
}

static esp_err_t cslip_modem_transmit(void *driver, void *buffer, size_t len)
{
cslip_modem_handle modem = (cslip_modem_handle)driver;
int32_t res = uart_write_bytes(modem->uart.uart_dev, (char *)buffer, len);
if (res < 0) {
ESP_LOGE(TAG, "%s: uart_write_bytes error %" PRId32, __func__, res);
return ESP_FAIL;
}
return ESP_OK;
}

static esp_err_t cslip_modem_post_attach(esp_netif_t *esp_netif, void *args)
{
cslip_modem_handle modem = (cslip_modem_handle) args;

const esp_netif_driver_ifconfig_t driver_ifconfig = {
.driver_free_rx_buffer = NULL,
.transmit = cslip_modem_transmit,
.handle = modem,
};

modem->base.netif = esp_netif;
ESP_ERROR_CHECK(esp_netif_set_driver_config(esp_netif, &driver_ifconfig));

esp_cslip_driver_start(modem);
return ESP_OK;
}

static void cslip_modem_uart_rx_task(void *arg)
{
if (arg == NULL) {
vTaskDelete(NULL);
}
cslip_modem_handle modem = (cslip_modem_handle) arg;

ESP_LOGD(TAG, "Start CSLIP modem RX task (filter: %p)", modem->rx_filter);

while (modem->running == true) {
int len = uart_read_bytes(modem->uart.uart_dev, modem->buffer, modem->buffer_len, 1 / portTICK_PERIOD_MS);

if (len > 0) {
modem->buffer[len] = '\0';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: UART RX Task Buffer Overflow Risk

In cslip_modem_uart_rx_task, writing modem->buffer[len] = '\0' can cause a buffer overflow. If uart_read_bytes returns modem->buffer_len bytes, this writes one byte past the allocated buffer boundary. Additionally, null-terminating SLIP-encoded binary data is generally not appropriate for this buffer.

Fix in Cursor Fix in Web

if ((modem->rx_filter != NULL) && modem->rx_filter(modem, modem->buffer, len)) {
continue;
}
esp_netif_receive(modem->base.netif, modem->buffer, len, NULL);
}
vTaskDelay(1 * portTICK_PERIOD_MS);
}
}

esp_ip6_addr_t cslip_modem_get_ipv6_address(cslip_modem_handle modem)
{
return modem->addr;
}

void cslip_modem_raw_write(cslip_modem_handle modem, void *buffer, size_t len)
{
cslip_modem_netif_raw_write(modem->base.netif, buffer, len);
}
Loading