diff --git a/.gitmodules b/.gitmodules
index 46abd2e..035ddeb 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,15 @@
[submodule "make-testsuite/wolfssh"]
path = make-testsuite/wolfssh
url = https://github.com/wolfSSL/wolfssh.git
+[submodule "echo-server/wolfssl"]
+ path = echo-server/wolfssl
+ url = https://github.com/wolfSSL/wolfssl.git
+[submodule "echo-server/wolfssh"]
+ path = echo-server/wolfssh
+ url = https://github.com/wolfSSL/wolfssh.git
+[submodule "echo-server/FreeRTOS-Kernel"]
+ path = echo-server/FreeRTOS-Kernel
+ url = https://github.com/FreeRTOS/FreeRTOS-Kernel.git
+[submodule "echo-server/FreeRTOS-Plus-TCP"]
+ path = echo-server/FreeRTOS-Plus-TCP
+ url = https://github.com/FreeRTOS/FreeRTOS-Plus-TCP.git
diff --git a/README.md b/README.md
index fc5dd3f..8867dd0 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,13 @@ The following examples are available for Espressif devices:
* [ESP32-SSH-Server](./Espressif/ESP8266/ESP8266-SSH-Server/README.md)
SSH-to-UART.
+## echo-server
+
+A minimal SSH echo server running on FreeRTOS with FreeRTOS-Plus-TCP networking.
+Targets PIC32MZ with Microchip Harmony but runnable on Linux using the FreeRTOS
+POSIX simulator and a libpcap-based network interface. Uses a Makefile and has a
+preconfigured user_settings.h file.
+
## make-testsuite
This example isn't manufacturer specific, but it has only been tested on
diff --git a/echo-server/.gitignore b/echo-server/.gitignore
new file mode 100644
index 0000000..a252833
--- /dev/null
+++ b/echo-server/.gitignore
@@ -0,0 +1,5 @@
+*.o
+*.a
+obj/
+keys/
+echo-server
diff --git a/echo-server/FreeRTOS-Kernel b/echo-server/FreeRTOS-Kernel
new file mode 160000
index 0000000..2624889
--- /dev/null
+++ b/echo-server/FreeRTOS-Kernel
@@ -0,0 +1 @@
+Subproject commit 2624889925fe5be610245dd0f1bc12ecf162a1a1
diff --git a/echo-server/FreeRTOS-Plus-TCP b/echo-server/FreeRTOS-Plus-TCP
new file mode 160000
index 0000000..af6b379
--- /dev/null
+++ b/echo-server/FreeRTOS-Plus-TCP
@@ -0,0 +1 @@
+Subproject commit af6b379e3a580b86ac99f493918c6d18ffcdf264
diff --git a/echo-server/FreeRTOSConfig.h b/echo-server/FreeRTOSConfig.h
new file mode 100644
index 0000000..2fee8a3
--- /dev/null
+++ b/echo-server/FreeRTOSConfig.h
@@ -0,0 +1,102 @@
+/* FreeRTOSConfig.h
+ *
+ * Copyright (C) 2026 wolfSSL Inc.
+ *
+ * wolfSSH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfSSH. If not, see .
+ */
+
+#ifndef FREERTOS_CONFIG_H
+#define FREERTOS_CONFIG_H
+
+/* Scheduler */
+#define configUSE_PREEMPTION 1
+#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
+#define configUSE_TICKLESS_IDLE 0
+#define configCPU_CLOCK_HZ ( ( unsigned long ) 60000000 )
+#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
+#define configMAX_PRIORITIES 5
+#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 8192 )
+#define configMAX_TASK_NAME_LEN 16
+#define configUSE_16_BIT_TICKS 0
+#define configIDLE_SHOULD_YIELD 1
+#define configUSE_TASK_NOTIFICATIONS 1
+#define configTASK_NOTIFICATION_ARRAY_ENTRIES 3
+#define configUSE_MUTEXES 1
+#define configUSE_RECURSIVE_MUTEXES 1
+#define configUSE_COUNTING_SEMAPHORES 1
+#define configQUEUE_REGISTRY_SIZE 10
+#define configUSE_QUEUE_SETS 0
+#define configUSE_TIME_SLICING 1
+#define configUSE_NEWLIB_REENTRANT 0
+#define configENABLE_BACKWARD_COMPATIBILITY 1
+#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5
+#define configUSE_MINI_LIST_ITEM 1
+
+/* Memory */
+#define configSUPPORT_STATIC_ALLOCATION 0
+#define configSUPPORT_DYNAMIC_ALLOCATION 1
+#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 256 * 1024 ) )
+#define configAPPLICATION_ALLOCATED_HEAP 0
+
+/* Hook functions */
+#define configUSE_IDLE_HOOK 1
+#define configUSE_TICK_HOOK 0
+#define configCHECK_FOR_STACK_OVERFLOW 0
+#define configUSE_MALLOC_FAILED_HOOK 0
+#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
+
+/* Stats */
+#define configGENERATE_RUN_TIME_STATS 0
+#define configUSE_TRACE_FACILITY 0
+#define configUSE_STATS_FORMATTING_FUNCTIONS 0
+
+/* Co-routines */
+#define configUSE_CO_ROUTINES 0
+#define configMAX_CO_ROUTINE_PRIORITIES 1
+
+/* Software timers */
+#define configUSE_TIMERS 1
+#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )
+#define configTIMER_QUEUE_LENGTH 10
+#define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE
+
+/* Logging — used by FreeRTOS-Plus-TCP's FreeRTOS_printf / FreeRTOS_debug_printf */
+#define configPRINTF( X ) printf X
+
+/* Trap errors during development */
+#define configASSERT( x )
+
+/* Optional functions */
+#define INCLUDE_vTaskPrioritySet 1
+#define INCLUDE_uxTaskPriorityGet 1
+#define INCLUDE_vTaskDelete 1
+#define INCLUDE_vTaskSuspend 1
+#define INCLUDE_xResumeFromISR 1
+#define INCLUDE_vTaskDelayUntil 1
+#define INCLUDE_vTaskDelay 1
+#define INCLUDE_xTaskGetSchedulerState 1
+#define INCLUDE_xTaskGetCurrentTaskHandle 1
+#define INCLUDE_uxTaskGetStackHighWaterMark 0
+#define INCLUDE_xTaskGetIdleTaskHandle 0
+#define INCLUDE_eTaskGetState 0
+#define INCLUDE_xEventGroupSetBitFromISR 1
+#define INCLUDE_xTimerPendFunctionCall 0
+#define INCLUDE_xTaskAbortDelay 0
+#define INCLUDE_xTaskGetHandle 0
+#define INCLUDE_xTaskResumeFromISR 1
+
+/* POSIX port */
+#define configPOSIX_STACK_SIZE ( ( unsigned short ) 65536 )
+
+#endif /* FREERTOS_CONFIG_H */
diff --git a/echo-server/FreeRTOSIPConfig.h b/echo-server/FreeRTOSIPConfig.h
new file mode 100644
index 0000000..58636d7
--- /dev/null
+++ b/echo-server/FreeRTOSIPConfig.h
@@ -0,0 +1,91 @@
+/* FreeRTOSIPConfig.h
+ *
+ * Copyright (C) 2026 wolfSSL Inc.
+ *
+ * wolfSSH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfSSH. If not, see .
+ */
+
+#ifndef FREERTOS_IP_CONFIG_H
+#define FREERTOS_IP_CONFIG_H
+
+/* Use the backward-compatible single-interface FreeRTOS_IPInit() API */
+#define ipconfigIPv4_BACKWARD_COMPATIBLE 1
+#define ipconfigUSE_IPv6 0
+
+/* TCP/IP task */
+#define ipconfigIP_TASK_PRIORITY ( configMAX_PRIORITIES - 2 )
+#define ipconfigIP_TASK_STACK_SIZE_WORDS ( configMINIMAL_STACK_SIZE * 4 )
+
+/* Protocol support */
+#define ipconfigUSE_TCP 1
+#define ipconfigUSE_DNS 0
+#define ipconfigUSE_DHCP 0
+
+/* Network tuning */
+#define ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS 60
+#define ipconfigNETWORK_MTU 1500
+#define ipconfigTCP_MSS ( ipconfigNETWORK_MTU - 40 )
+#define ipconfigTCP_RX_BUFFER_LENGTH ( 16 * 1024 )
+#define ipconfigTCP_TX_BUFFER_LENGTH ( 16 * 1024 )
+#define ipconfigTCP_WIN_SEG_COUNT 64
+
+/* Event processing */
+#define ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES 1
+#define ipconfigETHERNET_MINIMUM_PACKET_BYTES 0
+#define ipconfigBUFFER_PADDING 0
+#define ipconfigPACKET_FILLER_SIZE 2
+#define ipconfigEVENT_QUEUE_LENGTH ( ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS + 5 )
+
+/* ARP */
+#define ipconfigUSE_ARP_REMOVE_ENTRY 1
+#define ipconfigUSE_ARP_REVERSED_LOOKUP 1
+#define ipconfigARP_CACHE_ENTRIES 6
+#define ipconfigMAX_ARP_RETRANSMISSIONS 5
+#define ipconfigMAX_ARP_AGE 150
+
+/* TCP parameters */
+#define ipconfigTCP_HANG_PROTECTION 1
+#define ipconfigTCP_HANG_PROTECTION_TIME 30
+#define ipconfigTCP_KEEP_ALIVE 1
+#define ipconfigTCP_KEEP_ALIVE_INTERVAL 20
+
+/* Misc */
+#define ipconfigZERO_COPY_TX_DRIVER 0
+#define ipconfigZERO_COPY_RX_DRIVER 0
+#define ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM 0
+#define ipconfigDRIVER_INCLUDED_RX_IP_CHECKSUM 0
+#define ipconfigSUPPORT_OUTGOING_PINGS 1
+#define ipconfigREPLY_TO_INCOMING_PINGS 1
+#define ipconfigSUPPORT_SELECT_FUNCTION 0
+#define ipconfigFILTER_OUT_NON_ETHERNET_II_FRAMES 1
+#define ipconfigBYTE_ORDER pdFREERTOS_LITTLE_ENDIAN
+#define ipconfigHAS_DEBUG_PRINTF 0
+#define ipconfigHAS_PRINTF 1
+
+/* Linux pcap network interface.
+ * Override at compile time with -DipconfigNETWORK_INTERFACE_TO_USE=N */
+#ifndef ipconfigNETWORK_INTERFACE_TO_USE
+ #define ipconfigNETWORK_INTERFACE_TO_USE 2
+#endif
+
+/* Linux NetworkInterface.c requires these defines */
+#define configNETWORK_INTERFACE_TO_USE ipconfigNETWORK_INTERFACE_TO_USE
+#define configMAC_ISR_SIMULATOR_PRIORITY ( configMAX_PRIORITIES - 1 )
+#define configWINDOWS_MAC_INTERRUPT_SIMULATOR_DELAY ( 2 / portTICK_PERIOD_MS )
+#define configNET_MASK0 255
+#define configNET_MASK1 255
+#define configNET_MASK2 255
+#define configNET_MASK3 0
+
+#endif /* FREERTOS_IP_CONFIG_H */
diff --git a/echo-server/Makefile b/echo-server/Makefile
new file mode 100644
index 0000000..f15b6ff
--- /dev/null
+++ b/echo-server/Makefile
@@ -0,0 +1,184 @@
+MKDIR ?= mkdir
+
+OBJ = obj
+
+# wolfSSH
+WOLFSSH ?= wolfssh
+SSHDIR = $(WOLFSSH)/src
+SSHINC = $(WOLFSSH)
+OBJSSH = $(OBJ)/$(WOLFSSH)
+
+# wolfSSL
+WOLFSSL ?= wolfssl
+CRYPTDIR = $(WOLFSSL)/wolfcrypt/src
+CRYPTINC = $(WOLFSSL)
+OBJCRYPT = $(OBJ)/$(WOLFSSL)
+
+# FreeRTOS Kernel
+FREERTOS_KERNEL ?= FreeRTOS-Kernel
+FREERTOS_PORT = $(FREERTOS_KERNEL)/portable/ThirdParty/GCC/Posix
+FREERTOS_HEAP = $(FREERTOS_KERNEL)/portable/MemMang
+FREERTOS_UTILS = $(FREERTOS_PORT)/utils
+OBJFREERTOS = $(OBJ)/freertos
+
+# FreeRTOS-Plus-TCP
+FREERTOS_TCP ?= FreeRTOS-Plus-TCP
+FREERTOS_TCP_SRC = $(FREERTOS_TCP)/source
+FREERTOS_TCP_INC = $(FREERTOS_TCP_SRC)/include
+FREERTOS_TCP_PORT = $(FREERTOS_TCP_SRC)/portable
+FREERTOS_TCP_NI = $(FREERTOS_TCP_PORT)/NetworkInterface/linux
+FREERTOS_TCP_BUF = $(FREERTOS_TCP_PORT)/BufferManagement
+OBJFREERTOS_TCP = $(OBJ)/freertos_tcp
+
+CPPFLAGS ?= -I. \
+ -I$(SSHINC) -I$(CRYPTINC) \
+ -I$(FREERTOS_KERNEL)/include -I$(FREERTOS_PORT) \
+ -I$(FREERTOS_TCP_INC) -I$(FREERTOS_TCP_PORT)/Compiler/GCC \
+ -DWOLFSSL_USER_SETTINGS
+
+ifeq ($(BUILD),debug)
+ DEBUG ?= -O0 -g -DDEBUG_WOLFSSH
+endif
+CFLAGS := $(DEBUG) $(CFLAGS)
+
+LDFLAGS ?= -lm -lpthread -lpcap
+
+.PHONY: clean all
+
+all: $(OBJ) echo-server keys/server-key-rsa.der
+
+# Application objects
+$(OBJ)/main.o: main.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+$(OBJ)/echo_server.o: echo_server.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+$(OBJ)/freertos_tcp_io.o: freertos_tcp_io.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+APP_OBJS = $(OBJ)/main.o $(OBJ)/echo_server.o $(OBJ)/freertos_tcp_io.o
+
+# Link
+echo-server: $(APP_OBJS) libwolfssh.a libfreertos.a libfreertos_tcp.a
+ $(CC) $(CFLAGS) -o $@ $(APP_OBJS) libfreertos_tcp.a libfreertos.a libwolfssh.a $(LDFLAGS)
+
+# --- wolfSSH + wolfSSL static library ---
+
+libwolfssh.a: $(OBJSSH)/internal.o $(OBJSSH)/log.o $(OBJSSH)/ssh.o \
+ $(OBJSSH)/io.o $(OBJSSH)/port.o $(OBJSSH)/keygen.o $(OBJSSH)/wolfterm.o \
+ $(OBJCRYPT)/aes.o $(OBJCRYPT)/dh.o $(OBJCRYPT)/integer.o $(OBJCRYPT)/tfm.o \
+ $(OBJCRYPT)/sha.o $(OBJCRYPT)/sha256.o $(OBJCRYPT)/sha512.o \
+ $(OBJCRYPT)/hash.o $(OBJCRYPT)/ecc.o $(OBJCRYPT)/rsa.o $(OBJCRYPT)/memory.o \
+ $(OBJCRYPT)/random.o $(OBJCRYPT)/hmac.o $(OBJCRYPT)/wolfmath.o \
+ $(OBJCRYPT)/asn.o $(OBJCRYPT)/coding.o $(OBJCRYPT)/signature.o \
+ $(OBJCRYPT)/wc_port.o $(OBJCRYPT)/sp_int.o $(OBJCRYPT)/sp_c64.o \
+ $(OBJCRYPT)/sp_c32.o
+ $(AR) $(ARFLAGS) $@ $^
+
+$(OBJSSH)/%.o: $(SSHDIR)/%.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+$(OBJCRYPT)/%.o: $(CRYPTDIR)/%.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+# --- FreeRTOS Kernel static library ---
+
+FREERTOS_OBJS = \
+ $(OBJFREERTOS)/tasks.o \
+ $(OBJFREERTOS)/queue.o \
+ $(OBJFREERTOS)/list.o \
+ $(OBJFREERTOS)/timers.o \
+ $(OBJFREERTOS)/event_groups.o \
+ $(OBJFREERTOS)/stream_buffer.o \
+ $(OBJFREERTOS)/port.o \
+ $(OBJFREERTOS)/heap_3.o \
+ $(OBJFREERTOS)/wait_for_event.o
+
+libfreertos.a: $(FREERTOS_OBJS)
+ $(AR) $(ARFLAGS) $@ $^
+
+$(OBJFREERTOS)/tasks.o: $(FREERTOS_KERNEL)/tasks.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+$(OBJFREERTOS)/queue.o: $(FREERTOS_KERNEL)/queue.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+$(OBJFREERTOS)/list.o: $(FREERTOS_KERNEL)/list.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+$(OBJFREERTOS)/timers.o: $(FREERTOS_KERNEL)/timers.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+$(OBJFREERTOS)/event_groups.o: $(FREERTOS_KERNEL)/event_groups.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+$(OBJFREERTOS)/stream_buffer.o: $(FREERTOS_KERNEL)/stream_buffer.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+$(OBJFREERTOS)/port.o: $(FREERTOS_PORT)/port.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+$(OBJFREERTOS)/heap_3.o: $(FREERTOS_HEAP)/heap_3.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+$(OBJFREERTOS)/wait_for_event.o: $(FREERTOS_UTILS)/wait_for_event.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+# --- FreeRTOS-Plus-TCP static library ---
+
+FREERTOS_TCP_OBJS = \
+ $(OBJFREERTOS_TCP)/FreeRTOS_IP.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_IP_Utils.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_IP_Timers.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_ARP.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_DHCP.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_DNS.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_DNS_Cache.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_DNS_Callback.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_DNS_Networking.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_DNS_Parser.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_ICMP.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_IPv4.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_IPv4_Sockets.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_IPv4_Utils.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_Sockets.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_Stream_Buffer.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_TCP_IP.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_TCP_IP_IPv4.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_TCP_Reception.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_TCP_State_Handling.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_TCP_State_Handling_IPv4.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_TCP_Transmission.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_TCP_Transmission_IPv4.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_TCP_Utils.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_TCP_Utils_IPv4.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_TCP_WIN.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_Tiny_TCP.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_UDP_IP.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_UDP_IPv4.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_Routing.o \
+ $(OBJFREERTOS_TCP)/FreeRTOS_BitConfig.o \
+ $(OBJFREERTOS_TCP)/BufferAllocation_2.o \
+ $(OBJFREERTOS_TCP)/NetworkInterface.o
+
+libfreertos_tcp.a: $(FREERTOS_TCP_OBJS)
+ $(AR) $(ARFLAGS) $@ $^
+
+$(OBJFREERTOS_TCP)/%.o: $(FREERTOS_TCP_SRC)/%.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+$(OBJFREERTOS_TCP)/BufferAllocation_2.o: $(FREERTOS_TCP_BUF)/BufferAllocation_2.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+$(OBJFREERTOS_TCP)/NetworkInterface.o: $(FREERTOS_TCP_NI)/NetworkInterface.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
+
+# --- Keys ---
+
+keys/server-key-rsa.der:
+ @$(MKDIR) -p keys
+ @cp $(WOLFSSH)/keys/server-key-rsa.der keys
+ @cp $(WOLFSSH)/keys/server-key-rsa.pem keys
+
+# --- Object directories ---
+
+$(OBJ):
+ @$(MKDIR) -p $(OBJSSH) $(OBJCRYPT) $(OBJFREERTOS) $(OBJFREERTOS_TCP)
+
+# --- Clean ---
+
+clean:
+ rm -rf echo-server libwolfssh.a libfreertos.a libfreertos_tcp.a $(OBJ) keys
diff --git a/echo-server/README.md b/echo-server/README.md
new file mode 100644
index 0000000..52f8f0c
--- /dev/null
+++ b/echo-server/README.md
@@ -0,0 +1,209 @@
+# wolfSSH Echo Server on FreeRTOS
+
+A minimal SSH echo server using wolfSSH, running on the FreeRTOS POSIX/Linux
+simulator with FreeRTOS-Plus-TCP networking via libpcap.
+
+## Dependencies
+
+* [wolfSSL](https://github.com/wolfSSL/wolfssl)
+* [wolfSSH](https://github.com/wolfSSL/wolfssh)
+* [FreeRTOS-Kernel](https://github.com/FreeRTOS/FreeRTOS-Kernel) (POSIX port)
+* [FreeRTOS-Plus-TCP](https://github.com/FreeRTOS/FreeRTOS-Plus-TCP) (Linux network interface)
+* libpcap development headers
+
+### Installing dependencies
+
+Fedora:
+
+```
+sudo dnf install libpcap-devel
+```
+
+Debian/Ubuntu:
+
+```
+sudo apt-get install libpcap-dev
+```
+
+Fetch the library sources (as submodules or cloned into the project directory):
+
+```
+git submodule update --init
+```
+
+## Building
+
+```
+make
+```
+
+Debug build (enables wolfSSH protocol logging):
+
+```
+make BUILD=debug
+```
+
+## Network Setup
+
+FreeRTOS-Plus-TCP uses libpcap to send and receive raw Ethernet frames,
+operating as a separate IP host. On Linux, a **veth pair** provides an isolated
+virtual link between the host and the FreeRTOS stack.
+
+### Create the veth pair
+
+```
+sudo ip link add veth0 type veth peer name veth1
+sudo ip link set veth0 up
+sudo ip link set veth1 up
+sudo ip addr add 10.0.0.1/24 dev veth0
+```
+
+This creates two linked virtual interfaces:
+
+* **veth0** -- the host side, with IP `10.0.0.1`
+* **veth1** -- the FreeRTOS side, accessed via libpcap
+
+### Disable checksum offload
+
+The Linux kernel uses TCP checksum offload by default, which leaves partial
+checksums in outgoing packets. Since there is no real NIC on a veth pair,
+FreeRTOS-Plus-TCP sees invalid checksums and drops the packets. Disable TX
+offload on the host side:
+
+```
+sudo ethtool -K veth0 tx off
+```
+
+### Verify the pcap interface number
+
+FreeRTOS-Plus-TCP opens a pcap device by index number. After creating the veth
+pair, check which number `veth1` gets:
+
+```
+tcpdump --list-interfaces
+```
+
+The output will look something like:
+
+```
+1.wlp170s0 [Up, Running, Wireless, Associated]
+2.veth1 [Up, Running, Connected]
+3.veth0 [Up, Running, Connected]
+...
+```
+
+The default in `FreeRTOSIPConfig.h` is interface 2. If `veth1` has a different
+number on your system, either edit the define or override at compile time:
+
+```
+make CPPFLAGS="-DipconfigNETWORK_INTERFACE_TO_USE=3 $(CPPFLAGS)"
+```
+
+### Teardown
+
+To remove the veth pair when done:
+
+```
+sudo ip link del veth0
+```
+
+## Running
+
+The server needs raw socket access for libpcap:
+
+```
+sudo ./echo-server
+```
+
+Once the server prints `Listening on port 22222...`, connect from the host:
+
+```
+ssh -p 22222 jill@10.0.0.2
+```
+
+Password: `upthehill`
+
+Other test credentials: `jack` / `fetchapail`
+
+### Control keys
+
+* **Ctrl+C** -- Disconnect
+* **Ctrl+F** -- Trigger SSH key re-exchange
+
+### Quick setup reference
+
+All the commands in one block for copy-paste:
+
+```
+# One-time network setup
+sudo ip link add veth0 type veth peer name veth1
+sudo ip link set veth0 up
+sudo ip link set veth1 up
+sudo ip addr add 10.0.0.1/24 dev veth0
+sudo ethtool -K veth0 tx off
+
+# Build and run
+make
+sudo ./echo-server
+```
+
+## Network Configuration
+
+The FreeRTOS-Plus-TCP stack uses a static IP configured in `main.c`:
+
+| Setting | Default |
+|------------|---------------|
+| IP address | `10.0.0.2` |
+| Netmask | `255.255.255.0` |
+| Gateway | `10.0.0.1` |
+| MAC | `02:00:00:00:00:01` |
+
+Edit these in `main.c` if your network setup differs.
+
+`FreeRTOSIPConfig.h` contains the pcap interface index and TCP/IP stack tuning
+parameters.
+
+## Architecture
+
+* **`main.c`** -- FreeRTOS entry point: initializes FreeRTOS-Plus-TCP, creates
+ the SSH echo server task, starts the scheduler. Also contains required
+ FreeRTOS hook functions.
+* **`echo_server.c`** -- Platform-agnostic core: wolfSSH initialization,
+ authentication (password + public key), echo read/send loop. Ported from
+ `wolfssh/ide/mplabx/wolfssh.c`.
+* **`freertos_tcp_io.c`** -- wolfSSH IO callbacks bridging
+ `FreeRTOS_recv()`/`FreeRTOS_send()` to wolfSSH's IO layer.
+* **`user_settings.h`** -- wolfSSL/wolfSSH build configuration.
+* **`FreeRTOSConfig.h`** -- FreeRTOS kernel configuration.
+* **`FreeRTOSIPConfig.h`** -- FreeRTOS-Plus-TCP stack configuration.
+
+### Task priorities
+
+The SSH echo server task must run at a **lower priority** than the
+FreeRTOS-Plus-TCP IP task. The defaults are:
+
+| Task | Priority | Default |
+|------------------|-----------------------------|---------|
+| MAC ISR simulator| `configMAX_PRIORITIES - 1` | 4 |
+| IP task | `configMAX_PRIORITIES - 2` | 3 |
+| SSH echo server | `tskIDLE_PRIORITY + 1` | 1 |
+
+## Embedded Integration (PIC32MZ / Harmony)
+
+To use on PIC32MZ with Microchip Harmony:
+
+1. Add `echo_server.c`, `echo_server.h`, `freertos_tcp_io.c`,
+ `freertos_tcp_io.h` to your MPLABX project
+2. In `user_settings.h`, uncomment the Harmony-specific defines
+3. Replace `main.c` with your Harmony `app.c`, calling `echoServerInit()`,
+ `echoServerAccept()`, and `echoServerLoop()` from your task or state machine
+4. Adjust `FreeRTOSConfig.h` and `FreeRTOSIPConfig.h` for your hardware
+
+## user_settings.h
+
+Key defines controlling the wolfSSL/wolfSSH build:
+
+* `WOLFSSH_USER_IO` -- Disables default BSD socket IO; custom callbacks in
+ `freertos_tcp_io.c` are used instead
+* `FREERTOS` / `WOLFSSL_FREERTOS` -- Enables FreeRTOS support
+* `WOLFSSH_NO_AGENT` -- Strips unused SSH agent support
diff --git a/echo-server/echo_server.c b/echo-server/echo_server.c
new file mode 100644
index 0000000..38033ac
--- /dev/null
+++ b/echo-server/echo_server.c
@@ -0,0 +1,539 @@
+/* echo_server.c
+ *
+ * Copyright (C) 2026 wolfSSL Inc.
+ *
+ * wolfSSH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfSSH. If not, see .
+ */
+
+/* Uses portions of code from wolfssh/ide/mplabx/wolfssh.c and
+ * wolfssh/examples/echoserver/echoserver.c */
+
+#include "echo_server.h"
+#include "freertos_tcp_io.h"
+
+#include
+#include
+#include
+#include
+
+/* Use embedded test keys */
+#ifndef NO_FILESYSTEM
+ #define NO_FILESYSTEM
+ #include
+ #undef NO_FILESYSTEM
+#else
+ #include
+#endif
+
+#include "FreeRTOS.h"
+#include "task.h"
+
+#include
+#include
+#include
+
+/*
+ * Helper functions ported from wolfssh/ide/mplabx/wolfssh.c
+ */
+
+static const char echoServerBanner[] = "wolfSSH Echo Server\n";
+
+static const char samplePasswordBuffer[] =
+ "jill:upthehill\n"
+ "jack:fetchapail\n";
+
+static const char samplePublicKeyEccBuffer[] =
+ "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAA"
+ "BBBNkI5JTP6D0lF42tbxX19cE87hztUS6FSDoGvPfiU0CgeNSbI+aFdKIzTP5CQEJSvm25"
+ "qUzgDtH7oyaQROUnNvk= hansel\n"
+ "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAA"
+ "BBBKAtH8cqaDbtJFjtviLobHBmjCtG56DMkP6A4M2H9zX2/YCg1h9bYS7WHd9UQDwXO1Hh"
+ "IZzRYecXh7SG9P4GhRY= gretel\n";
+
+static const char samplePublicKeyRsaBuffer[] =
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9P3ZFowOsONXHD5MwWiCciXytBRZGho"
+ "MNiisWSgUs5HdHcACuHYPi2W6Z1PBFmBWT9odOrGRjoZXJfDDoPi+j8SSfDGsc/hsCmc3G"
+ "p2yEhUZUEkDhtOXyqjns1ickC9Gh4u80aSVtwHRnJZh9xPhSq5tLOhId4eP61s+a5pwjTj"
+ "nEhBaIPUJO2C/M0pFnnbZxKgJlX7t1Doy7h5eXxviymOIvaCZKU+x5OopfzM/wFkey0EPW"
+ "NmzI5y/+pzU5afsdeEWdiQDIQc80H6Pz8fsoFPvYSG+s4/wz0duu7yeeV1Ypoho65Zr+pE"
+ "nIf7dO0B8EblgWt+ud+JI8wrAhfE4x hansel\n"
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqDwRVTRVk/wjPhoo66+Mztrc31KsxDZ"
+ "+kAV0139PHQ+wsueNpba6jNn5o6mUTEOrxrz0LMsDJOBM7CmG0983kF4gRIihECpQ0rcjO"
+ "P6BSfbVTE9mfIK5IsUiZGd8SoE9kSV2pJ2FvZeBQENoAxEFk0zZL9tchPS+OCUGbK4SDjz"
+ "uNZl/30Mczs73N3MBzi6J1oPo7sFlqzB6ecBjK2Kpjus4Y1rYFphJnUxtKvB0s+hoaadru"
+ "biE57dK6BrH5iZwVLTQKux31uCJLPhiktI3iLbdlGZEctJkTasfVSsUizwVIyRjhVKmbdI"
+ "RGwkU38D043AR1h0mUoGCPIKuqcFMf gretel\n";
+
+
+static INLINE void c32toa(word32 u32, byte* c)
+{
+ c[0] = (u32 >> 24) & 0xff;
+ c[1] = (u32 >> 16) & 0xff;
+ c[2] = (u32 >> 8) & 0xff;
+ c[3] = u32 & 0xff;
+}
+
+
+static PwMap* PwMapNew(PwMapList* list, byte type, const byte* username,
+ word32 usernameSz, const byte* p, word32 pSz)
+{
+ PwMap* map;
+
+ map = (PwMap*)malloc(sizeof(PwMap));
+ if (map != NULL) {
+ wc_Sha256 sha;
+ byte flatSz[4];
+
+ map->type = type;
+ if (usernameSz >= sizeof(map->username))
+ usernameSz = sizeof(map->username) - 1;
+ memcpy(map->username, username, usernameSz + 1);
+ map->username[usernameSz] = 0;
+ map->usernameSz = usernameSz;
+
+ wc_InitSha256(&sha);
+ c32toa(pSz, flatSz);
+ wc_Sha256Update(&sha, flatSz, sizeof(flatSz));
+ wc_Sha256Update(&sha, p, pSz);
+ wc_Sha256Final(&sha, map->p);
+
+ map->next = list->head;
+ list->head = map;
+ }
+
+ return map;
+}
+
+
+static void PwMapListDelete(PwMapList* list)
+{
+ if (list != NULL) {
+ PwMap* head = list->head;
+
+ while (head != NULL) {
+ PwMap* cur = head;
+ head = head->next;
+ memset(cur, 0, sizeof(PwMap));
+ free(cur);
+ }
+ list->head = NULL;
+ }
+}
+
+
+static int LoadPasswordBuffer(byte* buf, word32 bufSz, PwMapList* list)
+{
+ char* str = (char*)buf;
+ char* delimiter;
+ char* username;
+ char* password;
+
+ if (list == NULL)
+ return -1;
+
+ if (buf == NULL || bufSz == 0)
+ return 0;
+
+ while (*str != 0) {
+ delimiter = strchr(str, ':');
+ if (delimiter == NULL)
+ break;
+ username = str;
+ *delimiter = 0;
+ password = delimiter + 1;
+ str = strchr(password, '\n');
+ if (str == NULL)
+ break;
+ *str = 0;
+ str++;
+ if (PwMapNew(list, WOLFSSH_USERAUTH_PASSWORD,
+ (byte*)username, (word32)strlen(username),
+ (byte*)password, (word32)strlen(password)) == NULL) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+static int LoadPublicKeyBuffer(byte* buf, word32 bufSz, PwMapList* list)
+{
+ char* str = (char*)buf;
+ char* delimiter;
+ byte* publicKey64;
+ word32 publicKey64Sz;
+ byte* username;
+ word32 usernameSz;
+ byte publicKey[300];
+ word32 publicKeySz;
+
+ if (list == NULL)
+ return -1;
+
+ if (buf == NULL || bufSz == 0)
+ return 0;
+
+ while (*str != 0) {
+ /* Skip the public key type (e.g., ssh-rsa or ecdsa-sha2-nistp256) */
+ delimiter = strchr(str, ' ');
+ if (delimiter == NULL)
+ break;
+ str = delimiter + 1;
+ delimiter = strchr(str, ' ');
+ if (delimiter == NULL)
+ break;
+ publicKey64 = (byte*)str;
+ *delimiter = 0;
+ publicKey64Sz = (word32)(delimiter - str);
+ str = delimiter + 1;
+ delimiter = strchr(str, '\n');
+ if (delimiter == NULL)
+ break;
+ username = (byte*)str;
+ *delimiter = 0;
+ usernameSz = (word32)(delimiter - str);
+ str = delimiter + 1;
+ publicKeySz = sizeof(publicKey);
+
+ if (Base64_Decode(publicKey64, publicKey64Sz,
+ publicKey, &publicKeySz) != 0) {
+ return -1;
+ }
+
+ if (PwMapNew(list, WOLFSSH_USERAUTH_PUBLICKEY,
+ username, usernameSz,
+ publicKey, publicKeySz) == NULL) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+/* Load embedded DER key from certs_test.h */
+static int load_key(byte isEcc, byte* buf, word32 bufSz)
+{
+ word32 sz = 0;
+
+ if (isEcc) {
+ if (sizeof_ecc_key_der_256_ssh > bufSz) {
+ return 0;
+ }
+ WMEMCPY(buf, ecc_key_der_256_ssh, sizeof_ecc_key_der_256_ssh);
+ sz = sizeof_ecc_key_der_256_ssh;
+ }
+ else {
+ if (sizeof_rsa_key_der_2048_ssh > bufSz) {
+ return 0;
+ }
+ WMEMCPY(buf, (byte*)rsa_key_der_2048_ssh, sizeof_rsa_key_der_2048_ssh);
+ sz = sizeof_rsa_key_der_2048_ssh;
+ }
+
+ return sz;
+}
+
+
+static byte find_char(const byte* str, const byte* buf, word32 bufSz)
+{
+ const byte* cur;
+
+ while (bufSz) {
+ cur = str;
+ while (*cur != '\0') {
+ if (*cur == *buf)
+ return *cur;
+ cur++;
+ }
+ buf++;
+ bufSz--;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Authentication callback
+ */
+static int wsUserAuth(byte authType,
+ WS_UserAuthData* authData,
+ void* ctx)
+{
+ PwMapList* list;
+ PwMap* map;
+ byte authHash[WC_SHA256_DIGEST_SIZE];
+
+ if (ctx == NULL) {
+ return WOLFSSH_USERAUTH_FAILURE;
+ }
+
+ if (authType != WOLFSSH_USERAUTH_PASSWORD &&
+ authType != WOLFSSH_USERAUTH_PUBLICKEY) {
+ return WOLFSSH_USERAUTH_FAILURE;
+ }
+
+ /* Hash the password or public key with its length */
+ {
+ wc_Sha256 sha;
+ byte flatSz[4];
+ wc_InitSha256(&sha);
+ if (authType == WOLFSSH_USERAUTH_PASSWORD) {
+ c32toa(authData->sf.password.passwordSz, flatSz);
+ wc_Sha256Update(&sha, flatSz, sizeof(flatSz));
+ wc_Sha256Update(&sha,
+ authData->sf.password.password,
+ authData->sf.password.passwordSz);
+ }
+ else if (authType == WOLFSSH_USERAUTH_PUBLICKEY) {
+ c32toa(authData->sf.publicKey.publicKeySz, flatSz);
+ wc_Sha256Update(&sha, flatSz, sizeof(flatSz));
+ wc_Sha256Update(&sha,
+ authData->sf.publicKey.publicKey,
+ authData->sf.publicKey.publicKeySz);
+ }
+ wc_Sha256Final(&sha, authHash);
+ }
+
+ list = (PwMapList*)ctx;
+ map = list->head;
+
+ while (map != NULL) {
+ if (authData->usernameSz == map->usernameSz &&
+ memcmp(authData->username, map->username, map->usernameSz) == 0) {
+
+ if (authData->type == map->type) {
+ if (memcmp(map->p, authHash, WC_SHA256_DIGEST_SIZE) == 0) {
+ return WOLFSSH_USERAUTH_SUCCESS;
+ }
+ else {
+ return (authType == WOLFSSH_USERAUTH_PASSWORD ?
+ WOLFSSH_USERAUTH_INVALID_PASSWORD :
+ WOLFSSH_USERAUTH_INVALID_PUBLICKEY);
+ }
+ }
+ else {
+ return WOLFSSH_USERAUTH_INVALID_AUTHTYPE;
+ }
+ }
+ map = map->next;
+ }
+
+ return WOLFSSH_USERAUTH_INVALID_USER;
+}
+
+
+/*
+ * Public API
+ */
+
+int echoServerInit(WOLFSSH_CTX** ctx, PwMapList* pwMapList)
+{
+ int useEcc = 0;
+ byte buf[SCRATCH_BUFFER_SZ];
+ word32 bufSz;
+ const char* pubKeyBuf;
+
+ memset(pwMapList, 0, sizeof(PwMapList));
+
+ if (wolfSSH_Init() != WS_SUCCESS) {
+ printf("Couldn't initialize wolfSSH.\n");
+ return -1;
+ }
+
+ *ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
+ if (*ctx == NULL) {
+ printf("Couldn't allocate SSH CTX data.\n");
+ return -1;
+ }
+
+ wolfSSH_SetUserAuth(*ctx, wsUserAuth);
+ wolfSSH_CTX_SetBanner(*ctx, echoServerBanner);
+
+ /* Register FreeRTOS-Plus-TCP IO callbacks */
+ freertosIO_SetCallbacks(*ctx);
+
+ /* Load server private key */
+ bufSz = load_key(useEcc, buf, SCRATCH_BUFFER_SZ);
+ if (bufSz == 0) {
+ printf("Couldn't load key.\n");
+ return -1;
+ }
+ if (wolfSSH_CTX_UsePrivateKey_buffer(*ctx, buf, bufSz,
+ WOLFSSH_FORMAT_ASN1) < 0) {
+ printf("Couldn't use key buffer.\n");
+ return -1;
+ }
+
+ /* Load passwords */
+ bufSz = (word32)strlen(samplePasswordBuffer);
+ memcpy(buf, samplePasswordBuffer, bufSz);
+ buf[bufSz] = 0;
+ LoadPasswordBuffer(buf, bufSz, pwMapList);
+
+ /* Load public keys */
+ pubKeyBuf = useEcc ? samplePublicKeyEccBuffer : samplePublicKeyRsaBuffer;
+ bufSz = (word32)strlen(pubKeyBuf);
+ memcpy(buf, pubKeyBuf, bufSz);
+ buf[bufSz] = 0;
+ LoadPublicKeyBuffer(buf, bufSz, pwMapList);
+
+ printf("wolfSSH Echo Server initialized on port %d\n", ECHO_SERVER_PORT);
+ return 0;
+}
+
+
+int echoServerAccept(WOLFSSH_CTX* ctx, WOLFSSH** ssh,
+ PwMapList* pwMapList, void* clientSocket)
+{
+ int ret;
+ int error;
+
+ *ssh = wolfSSH_new(ctx);
+ if (*ssh == NULL) {
+ printf("Couldn't allocate SSH session.\n");
+ return -1;
+ }
+
+ wolfSSH_SetUserAuthCtx(*ssh, pwMapList);
+
+ /* Set FreeRTOS-Plus-TCP IO context (socket handle) */
+ freertosIO_SetContext(*ssh, clientSocket);
+
+ printf("Performing SSH accept...\n");
+
+ /* Non-blocking accept loop */
+ do {
+ ret = wolfSSH_accept(*ssh);
+ error = wolfSSH_get_error(*ssh);
+
+ if (ret == WS_SUCCESS || ret == WS_SFTP_COMPLETE) {
+ break;
+ }
+
+ if (error == WS_WANT_READ || error == WS_WANT_WRITE) {
+ vTaskDelay(pdMS_TO_TICKS(1));
+ continue;
+ }
+
+ printf("SSH accept error: %d (%s)\n", error,
+ wolfSSH_ErrorToName(error));
+ return -1;
+ } while (1);
+
+ printf("SSH connection accepted.\n");
+ return 0;
+}
+
+
+int echoServerLoop(WOLFSSH* ssh)
+{
+ byte* buf = NULL;
+ byte* tmpBuf;
+ int bufSz, backlogSz = 0, rxSz, txSz, stop = 0, txSum;
+
+ while (!stop) {
+ bufSz = ECHO_BUFFER_SZ + backlogSz;
+ tmpBuf = (byte*)realloc(buf, bufSz);
+ if (tmpBuf == NULL) {
+ stop = 1;
+ break;
+ }
+ buf = tmpBuf;
+
+ rxSz = wolfSSH_stream_read(ssh, buf + backlogSz, ECHO_BUFFER_SZ);
+
+ if (rxSz <= 0) {
+ int error = wolfSSH_get_error(ssh);
+
+ if (error == WS_WANT_READ || error == WS_WANT_WRITE) {
+ vTaskDelay(pdMS_TO_TICKS(1));
+ continue;
+ }
+ if (error == WS_EOF) {
+ printf("Client disconnected (EOF).\n");
+ stop = 1;
+ break;
+ }
+ if (error == WS_REKEYING) {
+ continue;
+ }
+
+ printf("Stream read error: %d (%s)\n", error,
+ wolfSSH_ErrorToName(error));
+ stop = 1;
+ break;
+ }
+
+ backlogSz += rxSz;
+ txSum = 0;
+
+ while (backlogSz != txSum && !stop) {
+ txSz = wolfSSH_stream_send(ssh, buf + txSum, backlogSz - txSum);
+
+ if (txSz > 0) {
+ byte c;
+ const byte matches[] = { 0x03, 0x06, 0x00 };
+
+ c = find_char(matches, buf + txSum, txSz);
+ switch (c) {
+ case 0x03: /* Ctrl+C */
+ printf("Ctrl+C received, disconnecting.\n");
+ stop = 1;
+ break;
+ case 0x06: /* Ctrl+F */
+ if (wolfSSH_TriggerKeyExchange(ssh) != WS_SUCCESS) {
+ stop = 1;
+ }
+ break;
+ default:
+ break;
+ }
+ txSum += txSz;
+ }
+ else if (txSz == WS_REKEYING) {
+ /* Continue turning the crank during rekey */
+ vTaskDelay(pdMS_TO_TICKS(1));
+ continue;
+ }
+ else {
+ int error = wolfSSH_get_error(ssh);
+ if (error == WS_WANT_WRITE) {
+ vTaskDelay(pdMS_TO_TICKS(1));
+ continue;
+ }
+ printf("Stream send error: %d\n", error);
+ stop = 1;
+ break;
+ }
+ }
+
+ if (txSum < backlogSz)
+ memmove(buf, buf + txSum, backlogSz - txSum);
+ backlogSz -= txSum;
+ }
+
+ free(buf);
+ return 0;
+}
+
+
+void echoServerCleanup(WOLFSSH_CTX* ctx, PwMapList* pwMapList)
+{
+ PwMapListDelete(pwMapList);
+ wolfSSH_CTX_free(ctx);
+ wolfSSH_Cleanup();
+}
diff --git a/echo-server/echo_server.h b/echo-server/echo_server.h
new file mode 100644
index 0000000..019f308
--- /dev/null
+++ b/echo-server/echo_server.h
@@ -0,0 +1,73 @@
+/* echo_server.h
+ *
+ * Copyright (C) 2026 wolfSSL Inc.
+ *
+ * wolfSSH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfSSH. If not, see .
+ */
+
+#ifndef ECHO_SERVER_H
+#define ECHO_SERVER_H
+
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef ECHO_SERVER_PORT
+ #define ECHO_SERVER_PORT 22222
+#endif
+#ifndef ECHO_BUFFER_SZ
+ #define ECHO_BUFFER_SZ 4096
+#endif
+#ifndef SCRATCH_BUFFER_SZ
+ #define SCRATCH_BUFFER_SZ 1200
+#endif
+#ifndef EXAMPLE_HIGHWATER_MARK
+ #define EXAMPLE_HIGHWATER_MARK 0x3FFF8000
+#endif
+
+/* Password map entry */
+typedef struct PwMap {
+ byte type;
+ byte username[32];
+ word32 usernameSz;
+ byte p[WC_SHA256_DIGEST_SIZE];
+ struct PwMap* next;
+} PwMap;
+
+typedef struct PwMapList {
+ PwMap* head;
+} PwMapList;
+
+/* Initialize wolfSSH context with keys, auth, and IO callbacks */
+int echoServerInit(WOLFSSH_CTX** ctx, PwMapList* pwMapList);
+
+/* Accept an SSH connection on the given FreeRTOS-Plus-TCP socket */
+int echoServerAccept(WOLFSSH_CTX* ctx, WOLFSSH** ssh,
+ PwMapList* pwMapList, void* clientSocket);
+
+/* Run the echo loop for one connected session */
+int echoServerLoop(WOLFSSH* ssh);
+
+/* Free all resources */
+void echoServerCleanup(WOLFSSH_CTX* ctx, PwMapList* pwMapList);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ECHO_SERVER_H */
diff --git a/echo-server/freertos_tcp_io.c b/echo-server/freertos_tcp_io.c
new file mode 100644
index 0000000..a9ff103
--- /dev/null
+++ b/echo-server/freertos_tcp_io.c
@@ -0,0 +1,105 @@
+/* freertos_tcp_io.c
+ *
+ * Copyright (C) 2026 wolfSSL Inc.
+ *
+ * wolfSSH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfSSH. If not, see .
+ */
+
+/* wolfSSH IO callbacks for FreeRTOS-Plus-TCP.
+ * Follows the pattern from wolfip/src/port/wolfssh_io.c */
+
+#include "freertos_tcp_io.h"
+
+#include
+#include
+
+#include "FreeRTOS.h"
+#include "FreeRTOS_IP.h"
+#include "FreeRTOS_Sockets.h"
+
+
+/* Receive callback: FreeRTOS_recv() -> wolfSSH IO return codes */
+static int freertosRecv(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
+{
+ Socket_t xSocket;
+ BaseType_t ret;
+
+ (void)ssh;
+
+ if (ctx == NULL)
+ return WS_CBIO_ERR_GENERAL;
+
+ xSocket = *(Socket_t*)ctx;
+
+ ret = FreeRTOS_recv(xSocket, buf, (size_t)sz, 0);
+
+ if (ret > 0)
+ return (int)ret;
+ if (ret == 0)
+ return WS_CBIO_ERR_CONN_CLOSE;
+ if (ret == -pdFREERTOS_ERRNO_EWOULDBLOCK ||
+ ret == -pdFREERTOS_ERRNO_EAGAIN)
+ return WS_CBIO_ERR_WANT_READ;
+ if (ret == -pdFREERTOS_ERRNO_ENOTCONN)
+ return WS_CBIO_ERR_CONN_CLOSE;
+
+ return WS_CBIO_ERR_GENERAL;
+}
+
+
+/* Send callback: FreeRTOS_send() -> wolfSSH IO return codes */
+static int freertosSend(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
+{
+ Socket_t xSocket;
+ BaseType_t ret;
+
+ (void)ssh;
+
+ if (ctx == NULL)
+ return WS_CBIO_ERR_GENERAL;
+
+ xSocket = *(Socket_t*)ctx;
+
+ ret = FreeRTOS_send(xSocket, buf, (size_t)sz, 0);
+
+ if (ret > 0)
+ return (int)ret;
+ if (ret == 0)
+ return WS_CBIO_ERR_CONN_CLOSE;
+ if (ret == -pdFREERTOS_ERRNO_EWOULDBLOCK ||
+ ret == -pdFREERTOS_ERRNO_EAGAIN)
+ return WS_CBIO_ERR_WANT_WRITE;
+ if (ret == -pdFREERTOS_ERRNO_ENOTCONN)
+ return WS_CBIO_ERR_CONN_CLOSE;
+
+ return WS_CBIO_ERR_GENERAL;
+}
+
+
+void freertosIO_SetCallbacks(WOLFSSH_CTX* ctx)
+{
+ if (ctx != NULL) {
+ wolfSSH_SetIORecv(ctx, freertosRecv);
+ wolfSSH_SetIOSend(ctx, freertosSend);
+ }
+}
+
+
+void freertosIO_SetContext(WOLFSSH* ssh, void* socketPtr)
+{
+ if (ssh != NULL) {
+ wolfSSH_SetIOReadCtx(ssh, socketPtr);
+ wolfSSH_SetIOWriteCtx(ssh, socketPtr);
+ }
+}
diff --git a/echo-server/freertos_tcp_io.h b/echo-server/freertos_tcp_io.h
new file mode 100644
index 0000000..6a6013e
--- /dev/null
+++ b/echo-server/freertos_tcp_io.h
@@ -0,0 +1,38 @@
+/* freertos_tcp_io.h
+ *
+ * Copyright (C) 2026 wolfSSL Inc.
+ *
+ * wolfSSH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfSSH. If not, see .
+ */
+
+#ifndef FREERTOS_TCP_IO_H
+#define FREERTOS_TCP_IO_H
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Register FreeRTOS-Plus-TCP IO callbacks on a wolfSSH context */
+void freertosIO_SetCallbacks(WOLFSSH_CTX* ctx);
+
+/* Set the FreeRTOS-Plus-TCP socket as the IO context for a session */
+void freertosIO_SetContext(WOLFSSH* ssh, void* socketPtr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FREERTOS_TCP_IO_H */
diff --git a/echo-server/main.c b/echo-server/main.c
new file mode 100644
index 0000000..712ad3e
--- /dev/null
+++ b/echo-server/main.c
@@ -0,0 +1,240 @@
+/* main.c
+ *
+ * Copyright (C) 2026 wolfSSL Inc.
+ *
+ * wolfSSH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfSSH. If not, see .
+ */
+
+/* FreeRTOS entry point for the wolfSSH echo server.
+ * Runs on the FreeRTOS POSIX/Linux simulator with FreeRTOS-Plus-TCP. */
+
+#include
+#include
+#include
+#include
+
+#include "FreeRTOS.h"
+#include "task.h"
+#include "FreeRTOS_IP.h"
+#include "FreeRTOS_Sockets.h"
+
+#include "echo_server.h"
+
+/* Network configuration for the FreeRTOS-Plus-TCP stack.
+ * The stack runs on one end of a veth pair with its own IP address.
+ * Adjust for your network environment. */
+static const uint8_t ucIPAddress[4] = { 10, 0, 0, 2 };
+static const uint8_t ucNetMask[4] = { 255, 255, 255, 0 };
+static const uint8_t ucGatewayAddress[4] = { 10, 0, 0, 1 };
+static const uint8_t ucDNSServerAddress[4] = { 8, 8, 8, 8 };
+static const uint8_t ucMACAddress[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x01 };
+
+
+static void sshEchoServerTask(void* pvParameters)
+{
+ Socket_t xListenSocket, xClientSocket;
+ struct freertos_sockaddr xBindAddr, xClientAddr;
+ socklen_t xClientAddrLen;
+ TickType_t xRecvTimeout = pdMS_TO_TICKS(1000);
+ WOLFSSH_CTX* ctx = NULL;
+ WOLFSSH* ssh = NULL;
+ PwMapList pwMapList;
+
+ (void)pvParameters;
+
+ /* Wait for the network stack to be ready */
+ printf("Waiting for network...\n");
+ while (FreeRTOS_IsNetworkUp() == pdFALSE) {
+ vTaskDelay(pdMS_TO_TICKS(500));
+ }
+
+ {
+ uint32_t ulIPAddress, ulNetMask, ulGateway, ulDNS;
+ char cBuf[16];
+ FreeRTOS_GetAddressConfiguration(&ulIPAddress, &ulNetMask,
+ &ulGateway, &ulDNS);
+ FreeRTOS_inet_ntoa(ulIPAddress, cBuf);
+ printf("Network up. IP Address: %s\n", cBuf);
+ printf("Connect with: ssh -p %d jill@%s (password: upthehill)\n",
+ ECHO_SERVER_PORT, cBuf);
+ }
+
+ /* Initialize wolfSSH */
+ if (echoServerInit(&ctx, &pwMapList) != 0) {
+ printf("Echo server init failed.\n");
+ vTaskDelete(NULL);
+ return;
+ }
+
+ /* Create TCP listening socket */
+ xListenSocket = FreeRTOS_socket(FREERTOS_AF_INET, FREERTOS_SOCK_STREAM,
+ FREERTOS_IPPROTO_TCP);
+ if (xListenSocket == FREERTOS_INVALID_SOCKET) {
+ printf("Failed to create listen socket.\n");
+ echoServerCleanup(ctx, &pwMapList);
+ vTaskDelete(NULL);
+ return;
+ }
+
+ /* Set receive timeout so accept doesn't block forever */
+ FreeRTOS_setsockopt(xListenSocket, 0, FREERTOS_SO_RCVTIMEO,
+ &xRecvTimeout, sizeof(xRecvTimeout));
+
+ memset(&xBindAddr, 0, sizeof(xBindAddr));
+ xBindAddr.sin_port = FreeRTOS_htons(ECHO_SERVER_PORT);
+
+ xBindAddr.sin_family = FREERTOS_AF_INET;
+
+ if (FreeRTOS_bind(xListenSocket, &xBindAddr, sizeof(xBindAddr)) != 0) {
+ printf("Failed to bind to port %d.\n", ECHO_SERVER_PORT);
+ FreeRTOS_closesocket(xListenSocket);
+ echoServerCleanup(ctx, &pwMapList);
+ vTaskDelete(NULL);
+ return;
+ }
+
+ if (FreeRTOS_listen(xListenSocket, 1) != 0) {
+ printf("FreeRTOS_listen failed.\n");
+ }
+ printf("Listening on port %d...\n", ECHO_SERVER_PORT);
+
+ /* Accept loop */
+ for (;;) {
+ xClientAddrLen = sizeof(xClientAddr);
+ xClientSocket = FreeRTOS_accept(xListenSocket, &xClientAddr,
+ &xClientAddrLen);
+
+ if (xClientSocket != NULL &&
+ xClientSocket != FREERTOS_INVALID_SOCKET) {
+
+ TickType_t xTimeout = pdMS_TO_TICKS(300000); /* 5 minutes */
+
+ /* Set timeouts on the client socket */
+ FreeRTOS_setsockopt(xClientSocket, 0, FREERTOS_SO_RCVTIMEO,
+ &xTimeout, sizeof(xTimeout));
+ FreeRTOS_setsockopt(xClientSocket, 0, FREERTOS_SO_SNDTIMEO,
+ &xTimeout, sizeof(xTimeout));
+
+ printf("Client connected.\n");
+
+ if (echoServerAccept(ctx, &ssh, &pwMapList,
+ &xClientSocket) == 0) {
+ echoServerLoop(ssh);
+ }
+
+ if (ssh != NULL) {
+ wolfSSH_free(ssh);
+ ssh = NULL;
+ }
+
+ FreeRTOS_closesocket(xClientSocket);
+ printf("Client disconnected. Waiting for next connection...\n");
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+}
+
+
+/*
+ * FreeRTOS and FreeRTOS-Plus-TCP required hooks/callbacks
+ */
+
+void vApplicationIdleHook(void)
+{
+ usleep(15000);
+}
+
+void vApplicationIPNetworkEventHook_Multi(eIPCallbackEvent_t eNetworkEvent,
+ struct xNetworkEndPoint* pxEndPoint)
+{
+ (void)pxEndPoint;
+ if (eNetworkEvent == eNetworkUp) {
+ printf("Network interface is up.\n");
+ }
+}
+
+/* DHCP hook — accept any address offered by the server */
+eDHCPCallbackAnswer_t xApplicationDHCPHook(eDHCPCallbackPhase_t eDHCPPhase,
+ uint32_t ulIPAddress)
+{
+ (void)eDHCPPhase;
+ (void)ulIPAddress;
+
+ return eDHCPContinue;
+}
+
+/* Called by FreeRTOS-Plus-TCP when a ping reply is received */
+void vApplicationPingReplyHook(ePingReplyStatus_t eStatus,
+ uint16_t usIdentifier)
+{
+ (void)eStatus;
+ (void)usIdentifier;
+}
+
+/* Provide a random number for TCP sequence numbers */
+uint32_t ulApplicationGetNextSequenceNumber(uint32_t ulSourceAddress,
+ uint16_t usSourcePort, uint32_t ulDestinationAddress,
+ uint16_t usDestinationPort)
+{
+ (void)ulSourceAddress;
+ (void)usSourcePort;
+ (void)ulDestinationAddress;
+ (void)usDestinationPort;
+
+ /* Use wolfCrypt RNG for production; simple rand() for this demo */
+ return (uint32_t)rand();
+}
+
+BaseType_t xApplicationGetRandomNumber(uint32_t* pulNumber)
+{
+ *pulNumber = (uint32_t)rand();
+ return pdTRUE;
+}
+
+/* heap_3.c uses malloc/free, so these aren't meaningful, but the TCP
+ * stack references them for diagnostic prints */
+size_t xPortGetMinimumEverFreeHeapSize(void)
+{
+ return 0;
+}
+
+size_t xPortGetFreeHeapSize(void)
+{
+ return 0;
+}
+
+
+int main(void)
+{
+ /* Disable stdout buffering so output shows immediately */
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ printf("Starting wolfSSH Echo Server with FreeRTOS + FreeRTOS-Plus-TCP\n");
+
+ /* Initialize the FreeRTOS-Plus-TCP stack */
+ FreeRTOS_IPInit(ucIPAddress, ucNetMask, ucGatewayAddress,
+ ucDNSServerAddress, ucMACAddress);
+
+ /* Create the SSH echo server task.
+ * Priority must be lower than the IP task (configMAX_PRIORITIES - 2). */
+ xTaskCreate(sshEchoServerTask, "SSHEcho",
+ configMINIMAL_STACK_SIZE * 4, NULL,
+ tskIDLE_PRIORITY + 1, NULL);
+
+ /* Start the FreeRTOS scheduler - does not return */
+ vTaskStartScheduler();
+
+ return 0;
+}
diff --git a/echo-server/user_settings.h b/echo-server/user_settings.h
new file mode 100644
index 0000000..89aaef7
--- /dev/null
+++ b/echo-server/user_settings.h
@@ -0,0 +1,70 @@
+/* user_settings.h
+ *
+ * Copyright (C) 2026 wolfSSL Inc.
+ *
+ * wolfSSH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfSSH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfSSH. If not, see .
+ */
+
+#ifndef USER_SETTINGS_H
+#define USER_SETTINGS_H
+
+/* Math library: single-precision by default */
+#define WOLFSSL_SP
+#define WOLFSSL_HAVE_SP_RSA
+#define WOLFSSL_HAVE_SP_DH
+#define WOLFSSL_HAVE_SP_ECC
+
+#if defined(__x86_64__) || defined(__aarch64__) || defined(_M_X64)
+ #define SP_WORD_SIZE 64
+ #define HAVE___UINT128_T
+#else
+ #define SP_WORD_SIZE 32
+#endif
+
+/* wolfSSL core */
+#define WOLFSSL_KEY_GEN
+#define NO_MD5
+#define NO_DSA
+#define WOLFCRYPT_ONLY
+#define NO_PKCS8
+#define NO_PKCS12
+#define HAVE_ECC
+#define TFM_TIMING_RESISTANT
+#define ECC_TIMING_RESISTANT
+#define WC_RSA_BLINDING
+#define HAVE_AESGCM
+#define HAVE_AESCCM
+#define WOLFSSL_SHA384
+#define WOLFSSL_SHA512
+
+/* FreeRTOS */
+#define FREERTOS
+#define WOLFSSL_FREERTOS
+
+/* wolfSSH */
+#define WOLFSSL_WOLFSSH
+#define WOLFSSH_USER_IO
+#define DEFAULT_WINDOW_SZ 16384
+#define HAVE_WC_ECC_SET_RNG
+#define NO_MAIN_DRIVER
+#define WOLFSSH_NO_AGENT
+
+/* Uncomment for Microchip Harmony on PIC32MZ:
+ * #define MICROCHIP_MPLAB_HARMONY
+ * #define NO_FILESYSTEM
+ * #define SINGLE_THREADED
+ * #define WOLFSSH_NO_EXIT
+ */
+
+#endif /* USER_SETTINGS_H */
diff --git a/echo-server/wolfssh b/echo-server/wolfssh
new file mode 160000
index 0000000..157cb01
--- /dev/null
+++ b/echo-server/wolfssh
@@ -0,0 +1 @@
+Subproject commit 157cb01f4b6061807c4f72c8c92d8c58ca889b6e
diff --git a/echo-server/wolfssl b/echo-server/wolfssl
new file mode 160000
index 0000000..b36a9ca
--- /dev/null
+++ b/echo-server/wolfssl
@@ -0,0 +1 @@
+Subproject commit b36a9ca80e8285e80dc0d1a45e057ea60d0fb1a1