- 1. Scope
- 2. Mental model: lwIP versus wolfIP
- 3. Configuration migration: from
lwipopts.hto wolfIPconfig.h - 4. Initialization and network-device wiring
- 5. Porting existing lwIP network drivers
- 6. Wiring a random-number source
- 7. Bare-metal socket API migration
- 8. Translating a simple TCP server from lwIP raw/classic API
- 9. Translating a simple TCP server from lwIP ALTCP interfaces
- 10. RTOS integration
- 11. Porting wolfIP to a new RTOS
- 12. Migration checklist
- 13. Common migration pitfalls
- 14. Quick API mapping
This guide is for developers and integrators moving embedded networking code from lwIP to wolfIP. It focuses on:
- the design difference between lwIP and wolfIP;
- replacing
lwipopts.hconfiguration with wolfIPconfig.h; - wiring a random-number source;
- porting existing lwIP Ethernet/network drivers;
- migrating bare-metal TCP server code from lwIP raw/classic and lwIP ALTCP-style APIs;
- integrating wolfIP into an RTOS, using the existing FreeRTOS BSD-socket wrapper as the model.
The examples are intentionally small. They are meant to show migration patterns, not production error handling, resource accounting, or board-specific Ethernet driver code.
lwIP is highly configurable. A typical lwIP application chooses among raw callbacks, Netconn, and BSD sockets, then configures memory pools, pbuf pools, protocol control blocks, mailbox/thread support, TCP/IP-thread behavior, and protocol features through lwipopts.h.
For example, lwIP exposes separate pool counts for raw PCBs, UDP PCBs, active TCP PCBs, listening TCP PCBs, queued TCP segments, Netconn objects, API messages, DNS API messages, socket/select helpers, pbuf pools, ARP queue entries, and timeouts. The official lwIP options documentation lists these as independent knobs such as MEMP_NUM_TCP_PCB, MEMP_NUM_TCP_PCB_LISTEN, MEMP_NUM_UDP_PCB, MEMP_NUM_TCP_SEG, MEMP_NUM_NETCONN, MEMP_NUM_TCPIP_MSG_API, and PBUF_POOL_SIZE.
wolfIP is more direct and more static. The stack instance owns fixed arrays of sockets and fixed-size buffers. The current struct wolfIP contains arrays such as:
tcpsockets[MAX_TCPSOCKETS]udpsockets[MAX_UDPSOCKETS]icmpsockets[MAX_ICMPSOCKETS]- optional raw and packet socket arrays when those features are enabled
The default config.h sets MAX_TCPSOCKETS to 4, MAX_UDPSOCKETS to 2, MAX_ICMPSOCKETS to 2, RXBUF_SIZE to 20 KiB, and TXBUF_SIZE to 32 KiB. It also sets defaults for MTU, neighbors, interfaces, loopback, raw sockets, packet sockets, forwarding, and static DNS.
The practical implication is that migration is not “find every lwIP pool and copy the number.” Instead, decide the maximum number of concurrently live TCP, UDP, ICMP, raw, and packet sockets your product needs, then size the RX/TX buffers and MTU around the traffic pattern.
wolfIP socket descriptors encode both socket type and socket index. The public header defines socket marks such as MARK_TCP_SOCKET, MARK_UDP_SOCKET, MARK_ICMP_SOCKET, MARK_RAW_SOCKET, and MARK_PACKET_SOCKET, plus helpers like IS_SOCKET_TCP(fd) and SOCKET_UNMARK(fd). The header also enforces that each socket count must fit under 256, because the low byte is used as the socket index.
This matters during migration:
- a listening TCP socket consumes a TCP socket slot;
- each accepted TCP connection consumes another TCP socket slot;
- UDP sockets consume from the UDP socket pool;
- DNS and DHCP may also consume sockets internally depending on how you use them;
- increasing backlog does not create an unbounded accept queue.
For a simple TCP server that allows one listener plus three simultaneous clients, MAX_TCPSOCKETS 4 is the minimum. If the product must accept one listener plus eight clients, set MAX_TCPSOCKETS to at least 9.
In lwIP, lwipopts.h is both a feature-selection file and a resource-pool tuning file. You may have options for:
- OS mode:
NO_SYS,SYS_LIGHTWEIGHT_PROT,LWIP_NETCONN,LWIP_SOCKET; - memory:
MEM_SIZE,MEMP_NUM_*,PBUF_POOL_SIZE,PBUF_POOL_BUFSIZE; - TCP:
TCP_MSS,TCP_WND,TCP_SND_BUF,TCP_SND_QUEUELEN,MEMP_NUM_TCP_SEG; - protocols:
LWIP_TCP,LWIP_UDP,LWIP_ICMP,LWIP_DHCP,LWIP_DNS,LWIP_IPV4,LWIP_IPV6; - API mode: raw, Netconn, sockets, ALTCP;
- port-specific system settings.
In wolfIP, start from config.h and keep it short. The important resource knobs are direct: number of sockets per type, buffer sizes, MTU, neighbor count, interface count, and optional features such as raw sockets, packet sockets, forwarding, loopback, and HTTP support.
| lwIP configuration area | Typical lwIP option | wolfIP migration decision |
|---|---|---|
| Active TCP PCBs | MEMP_NUM_TCP_PCB |
Use MAX_TCPSOCKETS. Count listeners and accepted/client sockets together. |
| TCP listen PCBs | MEMP_NUM_TCP_PCB_LISTEN |
No separate wolfIP listener pool. A listening server uses one TCP socket slot. |
| UDP PCBs | MEMP_NUM_UDP_PCB |
Use MAX_UDPSOCKETS. Include application UDP sockets and any internal users you enable. |
| ICMP/raw handling | LWIP_RAW, MEMP_NUM_RAW_PCB |
Use MAX_ICMPSOCKETS for ICMP sockets. Enable WOLFIP_RAWSOCKETS and set WOLFIP_MAX_RAWSOCKETS only when raw sockets are needed. |
| Packet sockets | usually port/platform specific | Enable WOLFIP_PACKET_SOCKETS and size WOLFIP_MAX_PACKETSOCKETS only when Ethernet packet sockets are needed. |
| pbuf pool | PBUF_POOL_SIZE, PBUF_POOL_BUFSIZE |
Size RXBUF_SIZE, TXBUF_SIZE, and LINK_MTU. |
| TCP segment queue | MEMP_NUM_TCP_SEG, TCP_SND_QUEUELEN |
Size TXBUF_SIZE and TCP socket count. wolfIP queues into its fixed TX memory. |
| TCP window/send buffer | TCP_WND, TCP_SND_BUF |
Review wolfIP TCP buffer behavior and increase RXBUF_SIZE/TXBUF_SIZE if application throughput stalls. |
| ARP neighbor cache | ARP_TABLE_SIZE, MEMP_NUM_ARP_QUEUE |
Use MAX_NEIGHBORS; wolfIP also has ARP pending request storage. |
| Interfaces | LWIP_SINGLE_NETIF, netif setup |
Use WOLFIP_MAX_INTERFACES and wolfIP_getdev_ex() / wolfIP_ipconfig_set_ex() for multiple interfaces. |
| IPv6 | LWIP_IPV6 |
Do not assume a direct config mapping. Migrate only IPv4 code unless your wolfIP version/fork explicitly supports the needed IPv6 paths. |
| Netconn/BSD sockets | LWIP_NETCONN, LWIP_SOCKET |
Bare metal uses wolfIP_sock_* directly. RTOS ports can add BSD-style wrappers around wolfIP_sock_*. |
| ALTCP/TLS | LWIP_ALTCP, LWIP_ALTCP_TLS |
Replace ALTCP transport callbacks with wolfIP sockets. For TLS, place wolfSSL above the wolfIP socket and use the wolfIP/wolfSSL integration hooks when enabled. |
| Static IP | IP_ADDR, NETMASK, GW or board config |
Use wolfIP_ipconfig_set() or wolfIP_ipconfig_set_ex(). |
| Static DNS | port-specific | Use WOLFIP_STATIC_DNS_IP or set DNS state through the stack APIs available in your version. |
#ifndef WOLF_CONFIG_H
#define WOLF_CONFIG_H
#define ETHERNET
#define LINK_MTU 1536
#ifndef LINK_MTU_MIN
#define LINK_MTU_MIN 64U
#endif
#define MAX_TCPSOCKETS 5 /* 1 listener + 4 clients */
#define MAX_UDPSOCKETS 2
#define MAX_ICMPSOCKETS 1
#define RXBUF_SIZE (20 * 1024)
#define TXBUF_SIZE (32 * 1024)
#define MAX_NEIGHBORS 16
#ifndef WOLFIP_MAX_INTERFACES
#define WOLFIP_MAX_INTERFACES 1
#endif
#ifndef WOLFIP_RAWSOCKETS
#define WOLFIP_RAWSOCKETS 0
#endif
#ifndef WOLFIP_PACKET_SOCKETS
#define WOLFIP_PACKET_SOCKETS 0
#endif
#ifndef WOLFIP_ENABLE_FORWARDING
#define WOLFIP_ENABLE_FORWARDING 0
#endif
#ifndef WOLFIP_ENABLE_LOOPBACK
#define WOLFIP_ENABLE_LOOPBACK 0
#endif
#define WOLFIP_STATIC_DNS_IP "9.9.9.9"
#endifFor a server, do not forget to include the listening socket in MAX_TCPSOCKETS. For example, a server with one listener and four clients needs at least five TCP sockets.
wolfIP exposes a small link-layer driver interface:
struct wolfIP_ll_dev {
uint8_t mac[6];
char ifname[16];
uint8_t non_ethernet;
uint32_t mtu;
int (*poll)(struct wolfIP_ll_dev *ll, void *buf, uint32_t len);
int (*send)(struct wolfIP_ll_dev *ll, void *buf, uint32_t len);
void *priv;
};The stack can be initialized with wolfIP_init() on caller-provided storage, or with wolfIP_init_static() when static stack storage is enabled. wolfIP_instance_size() returns the stack object size. The static initializer is disabled if WOLFIP_NOSTATIC is defined.
A typical bare-metal setup is:
#include "config.h"
#include "wolfip.h"
static struct wolfIP *ipstack;
static int eth_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t len)
{
/*
* Return:
* >0 number of bytes received
* 0 no packet available
* <0 driver error
*/
return board_eth_poll(ll->priv, buf, len);
}
static int eth_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t len)
{
/*
* Return:
* 0 or positive on success
* -WOLFIP_EAGAIN if the driver cannot accept the frame yet
* negative on hard error
*/
return board_eth_send(ll->priv, buf, len);
}
void network_init(void)
{
struct wolfIP_ll_dev *dev;
wolfIP_init_static(&ipstack);
dev = wolfIP_getdev(ipstack);
dev->priv = board_eth_context();
dev->poll = eth_poll;
dev->send = eth_send;
dev->mtu = LINK_MTU;
dev->mac[0] = 0x02;
dev->mac[1] = 0x00;
dev->mac[2] = 0x00;
dev->mac[3] = 0x00;
dev->mac[4] = 0x00;
dev->mac[5] = 0x01;
wolfIP_ipconfig_set(
ipstack,
atoip4("192.168.1.50"),
atoip4("255.255.255.0"),
atoip4("192.168.1.1")
);
}
void network_poll_forever(void)
{
for (;;) {
wolfIP_poll(ipstack, board_millis());
}
}wolfIP_poll() is the core progress function. It polls each configured link-layer device, processes received packets, runs timers, dispatches socket callbacks, and attempts to transmit pending TCP/UDP/ICMP/raw/packet data.
An lwIP Ethernet driver normally sits behind struct netif. The driver initialization callback fills fields such as MAC address, MTU, interface flags, netif->state, netif->output, and netif->linkoutput. Transmit usually receives a struct pbuf * chain from netif->linkoutput; receive usually allocates a PBUF_RAW pbuf or pbuf chain, copies the incoming frame into it, and passes it upward through netif->input(p, netif).
wolfIP removes that netif/pbuf boundary. The driver exposes one struct wolfIP_ll_dev per interface. The stack calls the driver's poll function from wolfIP_poll() to ask for one complete received frame, and calls the driver's send function when it has one complete frame ready for the hardware. The stack buffer passed to poll or send is linear; the driver does not receive or return a pbuf chain.
Use this mental mapping:
| lwIP driver concept | wolfIP driver concept |
|---|---|
struct netif |
struct wolfIP_ll_dev plus ll->priv for driver state |
netif->state |
ll->priv |
netif->hwaddr[] |
ll->mac[] |
netif->mtu |
ll->mtu or wolfIP_mtu_set(); for Ethernet this is the wolfIP frame budget, with the IPv4 payload MTU derived after link overhead |
netif->linkoutput(netif, pbuf) |
ll->send(ll, frame, len) |
low_level_input() allocating pbuf |
ll->poll(ll, buf, len) copying into wolfIP's buffer |
netif->input(p, netif) |
not called by the driver; wolfIP calls its receive path after poll returns a frame |
pbuf chain traversal |
not needed; wolfIP passes a single contiguous frame buffer |
netif_add() / netif_set_default() |
wolfIP_getdev() / wolfIP_getdev_ex() and wolfIP_ipconfig_set() / wolfIP_ipconfig_set_ex() |
The following is a made-up but representative lwIP Ethernet driver. It is intentionally small: the hardware functions are placeholders for your DMA descriptor or MAC driver.
#include "lwip/err.h"
#include "lwip/etharp.h"
#include "lwip/netif.h"
#include "lwip/pbuf.h"
struct my_lwip_eth {
void *hw;
uint8_t mac[6];
};
static err_t my_low_level_output(struct netif *netif, struct pbuf *p)
{
struct my_lwip_eth *eth = (struct my_lwip_eth *)netif->state;
struct pbuf *q;
/* p can be a chain. The driver must transmit all fragments as one frame. */
if (my_hw_tx_begin(eth->hw, p->tot_len) != 0) {
return ERR_IF;
}
for (q = p; q != NULL; q = q->next) {
if (my_hw_tx_write(eth->hw, q->payload, q->len) != 0) {
my_hw_tx_abort(eth->hw);
return ERR_IF;
}
}
if (my_hw_tx_commit(eth->hw) != 0) {
return ERR_IF;
}
return ERR_OK;
}
static struct pbuf *my_low_level_input(struct netif *netif)
{
struct my_lwip_eth *eth = (struct my_lwip_eth *)netif->state;
struct pbuf *p;
struct pbuf *q;
uint16_t frame_len;
if (!my_hw_rx_ready(eth->hw)) {
return NULL;
}
frame_len = my_hw_rx_frame_len(eth->hw);
p = pbuf_alloc(PBUF_RAW, frame_len, PBUF_POOL);
if (p == NULL) {
my_hw_rx_drop(eth->hw);
return NULL;
}
/* The incoming Ethernet frame is copied into the pbuf chain. */
for (q = p; q != NULL; q = q->next) {
if (my_hw_rx_read(eth->hw, q->payload, q->len) != 0) {
pbuf_free(p);
my_hw_rx_drop(eth->hw);
return NULL;
}
}
my_hw_rx_release(eth->hw);
return p;
}
void my_ethernetif_input(struct netif *netif)
{
struct pbuf *p = my_low_level_input(netif);
if (p == NULL) {
return;
}
if (netif->input(p, netif) != ERR_OK) {
pbuf_free(p);
}
}
err_t my_ethernetif_init(struct netif *netif)
{
static struct my_lwip_eth eth0;
eth0.hw = my_hw_open(0);
my_hw_get_mac(eth0.hw, eth0.mac);
netif->state = ð0;
netif->hwaddr_len = ETHARP_HWADDR_LEN;
memcpy(netif->hwaddr, eth0.mac, sizeof(eth0.mac));
netif->mtu = 1500;
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
netif->output = etharp_output;
netif->linkoutput = my_low_level_output;
return ERR_OK;
}The important lwIP details to preserve when translating are:
- transmit receives one logical Ethernet frame, but the bytes may be split across a pbuf chain;
- receive creates pbuf storage before handing the packet to lwIP;
- the driver often has a separate
ethernetif_input()path that must be called from a main loop, interrupt bottom-half, or RTOS task.
In wolfIP, make the hardware driver copy one full frame into or out of the linear buffer supplied by the stack. Do not keep the buf pointer after send or poll returns unless your hardware integration guarantees the memory remains valid, which a portable driver should not assume.
#include <stdint.h>
#include <string.h>
#include "config.h"
#include "wolfip.h"
struct my_wolfip_eth {
void *hw;
uint8_t mac[6];
};
static int my_wolfip_eth_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t len)
{
struct my_wolfip_eth *eth = (struct my_wolfip_eth *)ll->priv;
uint32_t frame_len;
if (!my_hw_rx_ready(eth->hw)) {
return 0; /* No frame available now. */
}
frame_len = my_hw_rx_frame_len(eth->hw);
if (frame_len > len) {
my_hw_rx_drop(eth->hw);
return -WOLFIP_EINVAL;
}
if (my_hw_rx_read_frame(eth->hw, buf, frame_len) != 0) {
my_hw_rx_drop(eth->hw);
return -WOLFIP_EINVAL;
}
my_hw_rx_release(eth->hw);
return (int)frame_len; /* One complete Ethernet frame, including header. */
}
static int my_wolfip_eth_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t len)
{
struct my_wolfip_eth *eth = (struct my_wolfip_eth *)ll->priv;
if (len > LINK_MTU) {
return -WOLFIP_EINVAL;
}
if (!my_hw_tx_has_free_desc(eth->hw)) {
return -WOLFIP_EAGAIN;
}
/* The wolfIP buffer is linear. Queue or copy it into hardware-owned memory. */
if (my_hw_tx_enqueue_copy(eth->hw, buf, len) != 0) {
return -WOLFIP_EAGAIN;
}
return 0;
}The receive path is inverted compared with the lwIP version. You no longer allocate a pbuf and call netif->input(). Instead, wolfIP_poll() calls my_wolfip_eth_poll(). If poll returns a positive frame length, wolfIP processes the frame internally. If it returns 0, there was no packet to process. If it returns a negative value, the stack does not process a frame for that poll attempt.
The transmit path is also simpler at the stack boundary. You no longer walk a pbuf chain. wolfIP passes a contiguous Ethernet frame to send. A return value of 0 means the driver accepted the frame. -WOLFIP_EAGAIN means the TX ring or hardware queue is temporarily full and the stack should try again on a later wolfIP_poll() cycle.
For one physical Ethernet interface, initialize the stack, retrieve the primary link-layer device, fill the driver callbacks and metadata, then set the IPv4 configuration. In the examples below, LINK_MTU is used as wolfIP's link-frame budget; wolfIP derives the IPv4 payload MTU from that value after subtracting Ethernet overhead.
static struct wolfIP *ipstack;
static struct my_wolfip_eth eth0;
void my_wolfip_network_init(void)
{
struct wolfIP_ll_dev *dev;
wolfIP_init_static(&ipstack);
eth0.hw = my_hw_open(0);
my_hw_get_mac(eth0.hw, eth0.mac);
dev = wolfIP_getdev(ipstack);
memset(dev, 0, sizeof(*dev));
memcpy(dev->mac, eth0.mac, sizeof(eth0.mac));
strncpy(dev->ifname, "e0", sizeof(dev->ifname) - 1);
dev->mtu = LINK_MTU;
dev->poll = my_wolfip_eth_poll;
dev->send = my_wolfip_eth_send;
dev->priv = ð0;
wolfIP_ipconfig_set(
ipstack,
atoip4("192.168.1.50"),
atoip4("255.255.255.0"),
atoip4("192.168.1.1")
);
}For multiple physical interfaces, set WOLFIP_MAX_INTERFACES in config.h, initialize each hardware instance, retrieve each device with wolfIP_getdev_ex(), and configure each interface with wolfIP_ipconfig_set_ex().
#define MY_ETH_PORTS 2
static struct wolfIP *ipstack;
static struct my_wolfip_eth eth[MY_ETH_PORTS];
static void my_wolfip_init_one_if(unsigned int if_idx,
const char *ifname,
const char *ip,
const char *mask,
const char *gw)
{
struct wolfIP_ll_dev *dev = wolfIP_getdev_ex(ipstack, if_idx);
eth[if_idx].hw = my_hw_open(if_idx);
my_hw_get_mac(eth[if_idx].hw, eth[if_idx].mac);
memset(dev, 0, sizeof(*dev));
memcpy(dev->mac, eth[if_idx].mac, sizeof(eth[if_idx].mac));
strncpy(dev->ifname, ifname, sizeof(dev->ifname) - 1);
dev->mtu = LINK_MTU;
dev->poll = my_wolfip_eth_poll;
dev->send = my_wolfip_eth_send;
dev->priv = ð[if_idx];
wolfIP_ipconfig_set_ex(ipstack, if_idx, atoip4(ip), atoip4(mask), atoip4(gw));
}
void my_wolfip_network_init_two_ports(void)
{
wolfIP_init_static(&ipstack);
my_wolfip_init_one_if(0, "e0", "192.168.1.50", "255.255.255.0", "192.168.1.1");
my_wolfip_init_one_if(1, "e1", "10.10.10.2", "255.255.255.0", "10.10.10.1");
}If your build enables wolfIP loopback, do not blindly overwrite interface index 0 unless that is still your physical interface in your configuration. Use wolfIP_getdev() for the primary interface, and use explicit _ex() indexes only when you have verified the interface layout for that build.
A bare-metal main loop usually becomes:
int main(void)
{
board_init();
my_wolfip_network_init();
for (;;) {
wolfIP_poll(ipstack, board_millis());
}
}For an interrupt-driven MAC, keep the ISR small. The ISR should acknowledge the hardware interrupt and wake the network loop or RTOS poll task. Let the poll callback drain RX descriptors and let send queue TX frames. This keeps all wolfIP stack processing on the same execution path and avoids re-entering the stack from an interrupt.
- Replace pbuf allocation on RX with copying one complete frame into the buffer passed to
ll->poll. - Replace pbuf-chain iteration on TX with transmitting the single contiguous frame passed to
ll->send. - Move
netif->statecontents to a driver-private structure referenced byll->priv. - Move MAC address and MTU setup from
netiffields toll->macandll->mtu. - Replace
netif_add()andnetif_set_default()withwolfIP_getdev()/wolfIP_getdev_ex()andwolfIP_ipconfig_set()/wolfIP_ipconfig_set_ex(). - Return
0frompollwhen no frame is available, a positive frame length when one frame was copied, and a negative error for driver errors. - Return
0fromsendafter the driver has accepted or copied the frame, and-WOLFIP_EAGAINwhen the TX queue is temporarily full.
wolfIP requires the application or platform port to provide:
uint32_t wolfIP_getrandom(void);The public header declares this as an external requirement. The stack uses it for values such as the IP packet counter seed, TCP sequence numbers, ephemeral ports, DNS IDs, and DNS retry jitter.
Use a hardware RNG, a properly seeded TRNG/DRBG, or your platform’s cryptographic random source. Do not use a constant, timer-only seed, or unseeded rand() in production.
Example using a board hardware RNG:
#include <stdint.h>
#include "wolfip.h"
uint32_t wolfIP_getrandom(void)
{
uint32_t value;
if (board_trng_read_u32(&value) == 0) {
return value;
}
/*
* Fallback should still be platform-specific and non-deterministic.
* In production, prefer failing closed over returning predictable data.
*/
return board_entropy_fallback_u32();
}Example using wolfCrypt when your product already initializes wolfSSL/wolfCrypt:
#include <stdint.h>
#include "wolfip.h"
#include <wolfssl/wolfcrypt/random.h>
uint32_t wolfIP_getrandom(void)
{
static WC_RNG rng;
static int rng_ready;
uint32_t value = 0;
if (!rng_ready) {
if (wc_InitRng(&rng) != 0) {
return 0; /* Replace with platform fail handling. */
}
rng_ready = 1;
}
if (wc_RNG_GenerateBlock(&rng, (byte *)&value, sizeof(value)) != 0) {
return 0; /* Replace with platform fail handling. */
}
return value;
}For production, decide how your product handles RNG failure. Returning zero keeps the code simple but is not acceptable for security-sensitive builds.
wolfIP exposes socket-style APIs with an explicit stack pointer:
int wolfIP_sock_socket(struct wolfIP *s, int domain, int type, int protocol);
int wolfIP_sock_bind(struct wolfIP *s, int sockfd,
const struct wolfIP_sockaddr *addr, socklen_t addrlen);
int wolfIP_sock_listen(struct wolfIP *s, int sockfd, int backlog);
int wolfIP_sock_accept(struct wolfIP *s, int sockfd,
struct wolfIP_sockaddr *addr, socklen_t *addrlen);
int wolfIP_sock_connect(struct wolfIP *s, int sockfd,
const struct wolfIP_sockaddr *addr, socklen_t addrlen);
int wolfIP_sock_send(struct wolfIP *s, int sockfd,
const void *buf, size_t len, int flags);
int wolfIP_sock_recv(struct wolfIP *s, int sockfd,
void *buf, size_t len, int flags);
int wolfIP_sock_close(struct wolfIP *s, int sockfd);The same header also exposes sendto, recvfrom, sendmsg, recvmsg, setsockopt, getsockopt, getsockname, getpeername, wolfIP_sock_can_read(), wolfIP_sock_can_write(), and callback registration. CB_EVENT_READABLE, CB_EVENT_WRITABLE, CB_EVENT_TIMEOUT, and CB_EVENT_CLOSED are the core event bits.
Important behavioral points:
- On bare metal, drive progress by calling
wolfIP_poll(). - Socket calls can return
-WOLFIP_EAGAINwhen an operation would block. wolfIP_sock_connect()returns-WOLFIP_EAGAINwhile a TCP connect is in progress and returns0once established.wolfIP_sock_accept()returns-WOLFIP_EAGAINwhen no connection is ready.wolfIP_sock_send()queues data into the socket TX buffer and may return-WOLFIP_EAGAINwhen there is no TX space.wolfIP_sock_recv()returns available data,0on orderly close in close-wait cases, or a negative error when no data or the socket state is invalid.
For non-POSIX wolfIP builds, use struct wolfIP_sockaddr_in. Ports and IPv4 addresses in socket addresses are stored in network byte order, so use ee16() and ee32() where appropriate.
static void fill_bind_addr(struct wolfIP_sockaddr_in *addr, uint16_t port)
{
memset(addr, 0, sizeof(*addr));
addr->sin_family = AF_INET;
addr->sin_port = ee16(port);
addr->sin_addr.s_addr = ee32(0); /* INADDR_ANY */
}This is the common bare-metal lwIP callback style: create a TCP PCB, bind, listen, register an accept callback, register a receive callback on each accepted PCB, call tcp_recved() after consuming data, and send with tcp_write() plus tcp_output(). The usual flow is tcp_new(), tcp_bind(), tcp_listen(), tcp_accept(), tcp_recv(), tcp_recved(), tcp_write(), and tcp_output().
#include "lwip/tcp.h"
#define ECHO_PORT 7
static err_t echo_recv(void *arg,
struct tcp_pcb *pcb,
struct pbuf *p,
err_t err)
{
struct pbuf *q;
if (p == NULL) {
tcp_close(pcb);
return ERR_OK;
}
if (err != ERR_OK) {
pbuf_free(p);
return err;
}
tcp_recved(pcb, p->tot_len);
for (q = p; q != NULL; q = q->next) {
err_t wr = tcp_write(pcb, q->payload, q->len, TCP_WRITE_FLAG_COPY);
if (wr != ERR_OK) {
break;
}
}
tcp_output(pcb);
pbuf_free(p);
return ERR_OK;
}
static err_t echo_accept(void *arg,
struct tcp_pcb *newpcb,
err_t err)
{
if (err != ERR_OK || newpcb == NULL) {
return err;
}
tcp_recv(newpcb, echo_recv);
return ERR_OK;
}
void lwip_raw_echo_server_init(void)
{
struct tcp_pcb *pcb;
err_t err;
pcb = tcp_new();
if (pcb == NULL) {
return;
}
err = tcp_bind(pcb, IP_ADDR_ANY, ECHO_PORT);
if (err != ERR_OK) {
tcp_abort(pcb);
return;
}
pcb = tcp_listen(pcb);
if (pcb == NULL) {
return;
}
tcp_accept(pcb, echo_accept);
}The wolfIP version uses one listening socket plus accepted sockets. It registers a socket callback and uses wolfIP_poll() to deliver network progress and events.
#include <string.h>
#include "config.h"
#include "wolfip.h"
#define ECHO_PORT 7
static struct wolfIP *g_ip;
static int g_listen_fd = -1;
static void echo_socket_cb(int fd, uint16_t events, void *arg);
static void close_client(int fd)
{
(void)wolfIP_sock_close(g_ip, fd);
wolfIP_register_callback(g_ip, fd, NULL, NULL);
}
static void service_client_readable(int fd)
{
uint8_t buf[512];
for (;;) {
int n = wolfIP_sock_recv(g_ip, fd, buf, sizeof(buf), 0);
if (n > 0) {
int off = 0;
while (off < n) {
int wr = wolfIP_sock_send(g_ip, fd, buf + off, (size_t)(n - off), 0);
if (wr > 0) {
off += wr;
continue;
}
if (wr == -WOLFIP_EAGAIN) {
/*
* This minimal example does not keep a per-client
* pending-send queue. If TX space runs out before the
* echo is fully queued, close the client instead of
* silently dropping the remainder.
*/
close_client(fd);
return;
}
close_client(fd);
return;
}
continue;
}
if (n == 0) {
close_client(fd);
return;
}
if (n == -WOLFIP_EAGAIN) {
return;
}
close_client(fd);
return;
}
}
static void accept_ready_clients(void)
{
for (;;) {
struct wolfIP_sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
int client_fd;
memset(&peer, 0, sizeof(peer));
client_fd = wolfIP_sock_accept(
g_ip,
g_listen_fd,
(struct wolfIP_sockaddr *)&peer,
&peer_len
);
if (client_fd >= 0) {
wolfIP_register_callback(g_ip, client_fd, echo_socket_cb, NULL);
continue;
}
if (client_fd == -WOLFIP_EAGAIN) {
return;
}
return;
}
}
static void echo_socket_cb(int fd, uint16_t events, void *arg)
{
(void)arg;
if (fd == g_listen_fd) {
if ((events & CB_EVENT_READABLE) != 0) {
accept_ready_clients();
}
return;
}
if ((events & CB_EVENT_CLOSED) != 0) {
close_client(fd);
return;
}
if ((events & CB_EVENT_READABLE) != 0) {
service_client_readable(fd);
}
if ((events & CB_EVENT_WRITABLE) != 0) {
/*
* If your application keeps a per-client pending-send queue,
* resume it here. This minimal echo example sends immediately
* from the receive path, so there may be nothing to do.
*/
}
}
int wolfip_echo_server_init(struct wolfIP *ip)
{
struct wolfIP_sockaddr_in local;
int ret;
g_ip = ip;
g_listen_fd = wolfIP_sock_socket(
g_ip,
AF_INET,
IPSTACK_SOCK_STREAM,
0
);
if (g_listen_fd < 0) {
return g_listen_fd;
}
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = ee16(ECHO_PORT);
local.sin_addr.s_addr = ee32(0); /* INADDR_ANY */
ret = wolfIP_sock_bind(
g_ip,
g_listen_fd,
(struct wolfIP_sockaddr *)&local,
sizeof(local)
);
if (ret < 0) {
wolfIP_sock_close(g_ip, g_listen_fd);
return ret;
}
ret = wolfIP_sock_listen(g_ip, g_listen_fd, 1);
if (ret < 0) {
wolfIP_sock_close(g_ip, g_listen_fd);
return ret;
}
wolfIP_register_callback(g_ip, g_listen_fd, echo_socket_cb, NULL);
return 0;
}Main loop:
int main(void)
{
network_init();
if (wolfip_echo_server_init(ipstack) < 0) {
board_fatal_error();
}
for (;;) {
wolfIP_poll(ipstack, board_millis());
}
}Migration notes:
- lwIP raw API receive callbacks hand you a
pbuf; wolfIP socket callbacks tell you the socket is readable, then you callwolfIP_sock_recv(). - lwIP requires
tcp_recved()to advertise consumed receive window; wolfIP handles this insidewolfIP_sock_recv(). - lwIP sends with
tcp_write()and thentcp_output(); wolfIP sends withwolfIP_sock_send(), then actual frame output progresses fromwolfIP_poll(). - lwIP raw callbacks are PCB-centric; wolfIP callbacks are socket-descriptor-centric.
- lwIP’s listen PCB and active PCBs are separate pool types; wolfIP uses the finite TCP socket array for both.
lwIP ALTCP is an abstraction layer over the TCP callback API. It is designed so an application can be written against altcp_* calls and then use plain TCP, TLS, proxy-connect, or another layer underneath. The interface mirrors the TCP callback API, replaces struct tcp_pcb with struct altcp_pcb, prefixes functions with altcp_, and uses an allocator object so the selected transport layer can decide what kind of PCB to create.
A plain TCP ALTCP echo server may look like this:
#include "lwip/altcp.h"
#include "lwip/altcp_tcp.h"
#define ECHO_PORT 7
static err_t alt_echo_recv(void *arg,
struct altcp_pcb *conn,
struct pbuf *p,
err_t err)
{
struct pbuf *q;
if (p == NULL) {
altcp_close(conn);
return ERR_OK;
}
if (err != ERR_OK) {
pbuf_free(p);
return err;
}
altcp_recved(conn, p->tot_len);
for (q = p; q != NULL; q = q->next) {
err_t wr = altcp_write(conn, q->payload, q->len, TCP_WRITE_FLAG_COPY);
if (wr != ERR_OK) {
break;
}
}
altcp_output(conn);
pbuf_free(p);
return ERR_OK;
}
static err_t alt_echo_accept(void *arg,
struct altcp_pcb *new_conn,
err_t err)
{
if (err != ERR_OK || new_conn == NULL) {
return err;
}
altcp_recv(new_conn, alt_echo_recv);
return ERR_OK;
}
void lwip_altcp_echo_server_init(void)
{
struct altcp_pcb *listener;
err_t err;
listener = altcp_tcp_new_ip_type(IPADDR_TYPE_ANY);
if (listener == NULL) {
return;
}
err = altcp_bind(listener, IP_ADDR_ANY, ECHO_PORT);
if (err != ERR_OK) {
altcp_abort(listener);
return;
}
listener = altcp_listen(listener);
if (listener == NULL) {
return;
}
altcp_accept(listener, alt_echo_accept);
}The ALTCP function set includes altcp_bind(), altcp_listen(), altcp_accept(), altcp_recv(), altcp_write(), altcp_output(), and altcp_close(), mirroring the raw TCP API.
wolfIP does not require an ALTCP abstraction layer for a plain TCP server. Migrate the server to the same wolfIP_sock_* pattern shown in the previous section.
Use this mapping:
| lwIP ALTCP | wolfIP |
|---|---|
struct altcp_pcb * |
int sockfd |
altcp_tcp_new_ip_type() / altcp_new() |
wolfIP_sock_socket() |
altcp_bind() |
wolfIP_sock_bind() |
altcp_listen() |
wolfIP_sock_listen() |
altcp_accept() callback |
CB_EVENT_READABLE on listening socket, then wolfIP_sock_accept() |
altcp_recv() callback |
CB_EVENT_READABLE, then wolfIP_sock_recv() |
altcp_recved() |
Not needed; receive-window update is handled by wolfIP_sock_recv() |
altcp_write() |
wolfIP_sock_send() |
altcp_output() |
Usually not needed; transmit progress occurs in wolfIP_poll() |
altcp_close() |
wolfIP_sock_close() |
altcp_abort() |
wolfIP_sock_close() plus application cleanup |
For plain TCP, the wolfIP replacement is the echo server from section 8.2.
For ALTCP-over-TLS migrations, separate the migration into two layers:
- First migrate the TCP transport from ALTCP to wolfIP sockets.
- Then attach TLS above the wolfIP socket.
wolfIP’s public header includes wolfSSL integration declarations when WOLFSSL_WOLFIP is enabled, including wolfSSL_SetIO_wolfIP() and wolfSSL_SetIO_wolfIP_CTX(). That is the wolfIP-side replacement point for applications that previously used lwIP ALTCP TLS.
Conceptual TLS shape:
/*
* Pseudocode: exact wolfSSL setup depends on your product's wolfSSL config.
*/
WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
WOLFSSL *ssl = wolfSSL_new(ctx);
wolfSSL_SetIO_wolfIP_CTX(ctx, ipstack);
wolfSSL_SetIO_wolfIP(ssl, client_fd);
ret = wolfSSL_accept(ssl);Keep in mind that TLS handshakes also need nonblocking progress. Under bare metal, call the TLS accept/read/write functions when the socket is readable or writable, and keep calling wolfIP_poll().
wolfIP includes a FreeRTOS POSIX-style socket wrapper in src/port/freeRTOS/bsd_socket.c and a matching header in src/port/freeRTOS/bsd_socket.h.
The wrapper provides BSD-like functions:
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen);
int send(int sockfd, const void *buf, size_t len, int flags);
int sendto(int sockfd, const void *buf, size_t len, int flags,
const struct wolfIP_sockaddr *dest_addr, socklen_t addrlen);
int recv(int sockfd, void *buf, size_t len, int flags);
int recvfrom(int sockfd, void *buf, size_t len, int flags,
struct wolfIP_sockaddr *src_addr, socklen_t *addrlen);
int close(int sockfd);The header also maps SOCK_STREAM to IPSTACK_SOCK_STREAM and SOCK_DGRAM to IPSTACK_SOCK_DGRAM, and exposes wolfip_freertos_socket_init().
Internally, the FreeRTOS port uses:
- a global
struct wolfIP *g_ipstack; - a global mutex
g_lock; - a public file-descriptor table with entries containing
internal_fd,ready_sem, andwait_events; - one binary semaphore per public socket;
- a poll task that calls
wolfIP_poll(); - callbacks from wolfIP that wake blocked tasks by giving the socket’s semaphore.
The FreeRTOS poll task locks the stack, calls wolfIP_poll(ipstack, now_ms), unlocks the stack, bounds the next sleep between a minimum and maximum, converts milliseconds to ticks, and then calls vTaskDelay(). The default wrapper constants include WOLFIP_FREERTOS_BSD_MAX_FDS 16, WOLFIP_FREERTOS_POLL_MAX_MS 20, and WOLFIP_FREERTOS_POLL_MIN_MS 5.
The underlying wolfIP socket calls are nonblocking-style: they may return -WOLFIP_EAGAIN. The FreeRTOS wrapper turns that into blocking BSD-like behavior:
- Lock the wolfIP core mutex.
- Call the corresponding
wolfIP_sock_*function. - If it succeeds, unlock and return.
- If it returns a hard error, unlock and return
-1. - If it returns
-WOLFIP_EAGAIN, register a callback for the needed event bits. - Clear the socket semaphore.
- Unlock the core mutex.
- Block on the semaphore.
- Retry the operation when the callback wakes the task.
For example:
accept()waits forCB_EVENT_READABLEorCB_EVENT_CLOSED;connect()waits forCB_EVENT_WRITABLEorCB_EVENT_CLOSED;send()waits forCB_EVENT_WRITABLEorCB_EVENT_CLOSED;recv()waits forCB_EVENT_READABLEorCB_EVENT_CLOSED;close()may wait forCB_EVENT_CLOSED.
The wrapper’s callback does not perform socket I/O. It checks whether the delivered event intersects the waiting event mask and then gives the socket’s semaphore.
This is the most important RTOS design rule: do not block while holding the wolfIP core lock, and do not call BSD wrapper functions from the wolfIP callback path if the poll task holds the core lock while dispatching callbacks.
Use the FreeRTOS wrapper as a template. The required OS primitives are small.
Your RTOS port needs:
| Primitive | Used for |
|---|---|
| Mutex | Protect all calls into the wolfIP core and wolfIP_poll(). |
| Binary semaphore or event object | Put application tasks to sleep while waiting for socket readiness. |
| Task/thread creation | Run the wolfIP poll task. |
| Tick/time API | Provide now_ms to wolfIP_poll(). |
| Delay/sleep API | Sleep the poll task between poll cycles. |
| Critical section or mutex around FD table | Protect public FD allocation/freeing if not already covered by the core lock. |
Use this architecture for a new OS:
+-------------------------+
| Application task |
| socket/send/recv/etc. |
+------------+------------+
|
v
+-------------------------+
| OS BSD wrapper |
| - public fd table |
| - per-fd semaphore |
| - core mutex |
+------------+------------+
|
v
+-------------------------+
| wolfIP core |
| wolfIP_sock_* APIs |
| wolfIP_poll() |
+------------+------------+
|
v
+-------------------------+
| Link-layer driver |
| ll->poll(), ll->send() |
+-------------------------+
- Create the global port state
struct os_wolfip_fd {
int in_use;
int internal_fd;
os_sem_t ready_sem;
volatile uint16_t wait_events;
};
static struct wolfIP *g_ipstack;
static os_mutex_t g_core_lock;
static struct os_wolfip_fd g_fds[OS_WOLFIP_MAX_FDS];- Create a poll task
static void wolfip_os_poll_task(void *arg)
{
struct wolfIP *ipstack = (struct wolfIP *)arg;
for (;;) {
uint64_t now_ms = os_time_millis();
uint32_t next_ms;
os_mutex_lock(&g_core_lock);
next_ms = (uint32_t)wolfIP_poll(ipstack, now_ms);
os_mutex_unlock(&g_core_lock);
if (next_ms < OS_WOLFIP_POLL_MIN_MS) {
next_ms = OS_WOLFIP_POLL_MIN_MS;
}
if (next_ms > OS_WOLFIP_POLL_MAX_MS) {
next_ms = OS_WOLFIP_POLL_MAX_MS;
}
os_sleep_ms(next_ms);
}
}- Initialize the wrapper
int wolfip_os_socket_init(struct wolfIP *ipstack,
int poll_task_priority,
size_t poll_task_stack_size)
{
int i;
if (ipstack == NULL) {
return -WOLFIP_EINVAL;
}
os_mutex_create(&g_core_lock);
for (i = 0; i < OS_WOLFIP_MAX_FDS; i++) {
g_fds[i].in_use = 0;
g_fds[i].internal_fd = -1;
g_fds[i].wait_events = 0;
os_sem_create_binary(&g_fds[i].ready_sem);
}
g_ipstack = ipstack;
if (os_task_create(wolfip_os_poll_task,
"wolfip_poll",
poll_task_stack_size,
ipstack,
poll_task_priority) != 0) {
return -WOLFIP_ENOMEM;
}
return 0;
}- Bridge wolfIP callbacks to OS wakeups
static void wolfip_os_socket_cb(int internal_fd,
uint16_t events,
void *arg)
{
struct os_wolfip_fd *entry = (struct os_wolfip_fd *)arg;
(void)internal_fd;
if (entry == NULL) {
return;
}
if ((events & entry->wait_events) != 0) {
os_sem_give(&entry->ready_sem);
}
}- Prepare a wait while the core is locked
static void prepare_wait_locked(struct os_wolfip_fd *entry,
uint16_t wait_events)
{
entry->wait_events = wait_events;
os_sem_drain(&entry->ready_sem);
wolfIP_register_callback(
g_ipstack,
entry->internal_fd,
wolfip_os_socket_cb,
entry
);
}- Wrap each socket function
Example recv() wrapper:
int recv(int public_fd, void *buf, size_t len, int flags)
{
struct os_wolfip_fd *entry;
int ret;
if (!fd_valid(public_fd)) {
return -1;
}
entry = &g_fds[public_fd];
for (;;) {
os_mutex_lock(&g_core_lock);
ret = wolfIP_sock_recv(
g_ipstack,
entry->internal_fd,
buf,
len,
flags
);
if (ret >= 0) {
os_mutex_unlock(&g_core_lock);
return ret;
}
if (ret != -WOLFIP_EAGAIN) {
os_mutex_unlock(&g_core_lock);
os_set_errno_from_wolfip(ret);
return -1;
}
prepare_wait_locked(
entry,
(uint16_t)(CB_EVENT_READABLE | CB_EVENT_CLOSED)
);
os_mutex_unlock(&g_core_lock);
if (os_sem_take(&entry->ready_sem, OS_WAIT_FOREVER) != 0) {
os_set_errno_from_wolfip(-WOLFIP_EAGAIN);
return -1;
}
}
}Repeat the same pattern for:
accept()waiting onCB_EVENT_READABLE | CB_EVENT_CLOSED;connect()waiting onCB_EVENT_WRITABLE | CB_EVENT_CLOSED;send()waiting onCB_EVENT_WRITABLE | CB_EVENT_CLOSED;close()waiting onCB_EVENT_CLOSEDwhen close returns-WOLFIP_EAGAIN.
Follow these rules in the new OS port:
- Hold the core mutex while calling
wolfIP_sock_*. - Hold the core mutex while calling
wolfIP_poll(). - Do not hold the core mutex while blocking on a semaphore.
- Keep wolfIP callbacks short; wake tasks, set flags, or post events only.
- Do not call blocking wrapper APIs from inside the callback.
- Protect the public FD table consistently.
- Delete per-FD semaphores when closing sockets.
- Clear callbacks before freeing FD entries.
- Decide whether your wrapper returns BSD-style
-1pluserrno, or wolfIP negative errors directly. Be consistent.
The FreeRTOS wrapper bounds the poll delay between 5 ms and 20 ms by default. That is a reasonable starting point for an RTOS port because it prevents the poll task from spinning while still giving TCP timers, ACKs, retransmissions, and queued TX work regular progress.
For latency-sensitive products, reduce the maximum delay. For power-sensitive products, allow a larger maximum delay only after confirming that retransmission behavior, DNS, DHCP, and application latency still meet product requirements.
- Count maximum simultaneous TCP sockets, including listeners.
- Count UDP sockets, including DNS/DHCP/application use.
- Decide whether ICMP, raw sockets, packet sockets, forwarding, loopback, multicast, or HTTP are required.
- Decide the MTU and RX/TX buffer sizes.
- Identify all lwIP raw callbacks, Netconn tasks, socket users, and ALTCP/TLS users.
- Identify your hardware RNG or cryptographic RNG source.
- Replace
lwipopts.hpool tuning with a short wolfIPconfig.h. - Set
MAX_TCPSOCKETS,MAX_UDPSOCKETS,MAX_ICMPSOCKETS. - Set
RXBUF_SIZE,TXBUF_SIZE,LINK_MTU, andMAX_NEIGHBORS. - Enable only the optional socket families and protocol features you need.
- For static IP, call
wolfIP_ipconfig_set()orwolfIP_ipconfig_set_ex()during network init.
- Move driver state from
netif->statetowolfIP_ll_dev.priv. - Move MAC address and MTU setup from
netiffields towolfIP_ll_dev. - Replace RX pbuf allocation with a
pollfunction that copies one complete frame into wolfIP's buffer. - Replace TX pbuf-chain traversal with a
sendfunction that accepts one contiguous frame. - Initialize each interface with
wolfIP_getdev()orwolfIP_getdev_ex()pluswolfIP_ipconfig_set()orwolfIP_ipconfig_set_ex().
- Initialize wolfIP with
wolfIP_init_static()orwolfIP_init(). - Fill
wolfIP_ll_devwithpoll,send, MAC, MTU, and driver context. - Provide
wolfIP_getrandom(). - Call
wolfIP_poll()regularly. - Replace lwIP PCBs with wolfIP socket descriptors.
- Replace raw/ALTCP callbacks with socket callbacks plus
wolfIP_sock_recv()/wolfIP_sock_send().
- Add one poll task.
- Add one core mutex.
- Add a public FD table if you want BSD-like descriptors.
- Add one semaphore/event object per FD.
- Convert
-WOLFIP_EAGAINinto wait-and-retry behavior. - Wake blocked tasks from
wolfIP_register_callback()callbacks. - Never block while holding the wolfIP core mutex.
If MAX_TCPSOCKETS is 4, a server can have one listener and at most three accepted TCP clients at the same time.
lwIP has MEMP_NUM_TCP_PCB and MEMP_NUM_TCP_PCB_LISTEN. wolfIP has one TCP socket array, so size it for both roles.
A callback may indicate that the listener is readable. Accept in a loop until -WOLFIP_EAGAIN so you drain all ready connection events.
wolfIP_sock_send() queues data. Actual frame transmission progresses from wolfIP_poll().
TCP sequence numbers, DNS IDs, source ports, and other protocol values need unpredictable randomness. Wire a real RNG.
In the FreeRTOS-style design, callbacks wake tasks. They should not call blocking socket wrappers.
Lock while calling wolfIP. Unlock before waiting on a semaphore. This prevents deadlocks and allows the poll task to make progress.
| Task | lwIP raw/classic | lwIP ALTCP | wolfIP bare metal |
|---|---|---|---|
| Create TCP endpoint | tcp_new() |
altcp_tcp_new_ip_type() / altcp_new() |
wolfIP_sock_socket(s, AF_INET, IPSTACK_SOCK_STREAM, 0) |
| Bind | tcp_bind() |
altcp_bind() |
wolfIP_sock_bind() |
| Listen | tcp_listen() |
altcp_listen() |
wolfIP_sock_listen() |
| Accept | tcp_accept() callback |
altcp_accept() callback |
callback event + wolfIP_sock_accept() |
| Receive | tcp_recv() callback with pbuf |
altcp_recv() callback with pbuf |
callback event + wolfIP_sock_recv() |
| Mark received | tcp_recved() |
altcp_recved() |
Not needed in application |
| Send | tcp_write() |
altcp_write() |
wolfIP_sock_send() |
| Flush output | tcp_output() |
altcp_output() |
wolfIP_poll() progresses output |
| Close | tcp_close() |
altcp_close() |
wolfIP_sock_close() |
| Abort | tcp_abort() |
altcp_abort() |
wolfIP_sock_close() plus cleanup |
| Main progress | Ethernet input + lwIP timers | Ethernet input + lwIP timers | wolfIP_poll() |
| TLS layering | Usually ALTCP TLS | ALTCP TLS | wolfSSL over wolfIP socket |