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
97 changes: 97 additions & 0 deletions .github/workflows/espressif.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: wolfSSH Espressif Build Test

on:
schedule:
# Weekly: Mondays at 07:00 UTC
- cron: '0 7 * * 1'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
WOLFSSL_REF: v5.9.1-stable

jobs:
build_examples:
name: ESP-IDF ${{ matrix.idf-ref }}
strategy:
fail-fast: false
matrix:
# release-v5.5 is the current stable line; release-v5.1 is the version
# the example READMEs and VisualGDB projects document.
idf-ref: [ release-v5.5, release-v5.1 ]
runs-on: ubuntu-latest
container:
image: espressif/idf:${{ matrix.idf-ref }}
timeout-minutes: 30
steps:
- name: Checkout wolfSSH
uses: actions/checkout@v6
with:
path: wolfssh

- name: Checkout wolfSSL
uses: actions/checkout@v6
with:
repository: wolfssl/wolfssl
ref: ${{ env.WOLFSSL_REF }}
path: wolfssl

- name: Build the Espressif examples
env:
WOLFSSH_ROOT: ${{ github.workspace }}/wolfssh
WOLFSSL_ROOT: ${{ github.workspace }}/wolfssl
run: |
# The image sets IDF_PATH, but the ESP-IDF tool paths come from
# export.sh, which a job step does not get from the entrypoint.
. "${IDF_PATH}/export.sh"
"${WOLFSSH_ROOT}/ide/Espressif/ESP-IDF/compileAllExamples.sh"

notify_failure:
name: Open issue on scheduled failure
needs: [build_examples]
if: failure() && github.event_name == 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
issues: write
steps:
- uses: actions/github-script@v7
with:
script: |
const label = 'espressif-build-failure';
const runUrl = `${context.serverUrl}/${context.repo.owner}/` +
`${context.repo.repo}/actions/runs/${context.runId}`;
const body = [
'The weekly Espressif example build failed.',
'',
`Run: ${runUrl}`,
`Commit: ${context.sha}`,
].join('\n');
const existing = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: label,
});
if (existing.data.length > 0) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existing.data[0].number,
body: body,
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Weekly Espressif example build failed',
body: body,
labels: [label],
});
}
89 changes: 89 additions & 0 deletions ide/Espressif/ESP-IDF/compileAllExamples.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bash

# Build every wolfSSH ESP-IDF example project under ./examples/
#
# The examples carry local wolfssh and wolfssl components that compile the
# library sources in place, so both source trees have to be on disk. Their
# CMakeLists find them through the WOLFSSH_ROOT and WOLFSSL_ROOT environment
# variables, which this script fills in when they are not already set.
#
# Run from an ESP-IDF environment:
#
# . /opt/esp/idf/export.sh
# WOLFSSL_ROOT=/path/to/wolfssl ide/Espressif/ESP-IDF/compileAllExamples.sh
#
# WOLFSSH_ROOT defaults to the tree holding this script, WOLFSSL_ROOT to a
# wolfssl checkout beside it. IDF_TARGET selects the chip; default esp32.

if [ -z "${IDF_PATH}" ]; then
echo "ERROR: IDF_PATH is not set. Source the ESP-IDF export.sh first."
exit 1
fi

SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)

if [ -z "${WOLFSSH_ROOT}" ]; then
WOLFSSH_ROOT=$(cd "${SCRIPT_DIR}/../../.." && pwd)
fi

if [ -z "${WOLFSSL_ROOT}" ]; then
WOLFSSL_ROOT="$(dirname "${WOLFSSH_ROOT}")/wolfssl"
fi

export WOLFSSH_ROOT
export WOLFSSL_ROOT
export IDF_TARGET="${IDF_TARGET:-esp32}"

if [ ! -f "${WOLFSSH_ROOT}/wolfssh/ssh.h" ]; then
echo "ERROR: no wolfSSH source in WOLFSSH_ROOT=${WOLFSSH_ROOT}"
exit 1
fi

if [ ! -f "${WOLFSSL_ROOT}/wolfssl/ssl.h" ]; then
echo "ERROR: no wolfSSL source in WOLFSSL_ROOT=${WOLFSSL_ROOT}"
echo "Clone wolfssl beside wolfssh or set WOLFSSL_ROOT."
exit 1
fi

echo "IDF_PATH = ${IDF_PATH}"
echo "IDF_TARGET = ${IDF_TARGET}"
echo "WOLFSSH_ROOT = ${WOLFSSH_ROOT}"
echo "WOLFSSL_ROOT = ${WOLFSSL_ROOT}"

BUILT=""
FAILED=""

for PROJECT in "${SCRIPT_DIR}"/examples/*/; do
# A project directory is one with a top level CMakeLists.txt.
if [ ! -f "${PROJECT}/CMakeLists.txt" ]; then
continue
fi

NAME=$(basename "${PROJECT}")

echo
echo "--------------------------------------------------------------"
echo "Building ${NAME} for ${IDF_TARGET}"
echo "--------------------------------------------------------------"

if (cd "${PROJECT}" && idf.py fullclean && idf.py build); then
BUILT="${BUILT} ${NAME}"
else
FAILED="${FAILED} ${NAME}"
fi
done

if [ -z "${BUILT}" ] && [ -z "${FAILED}" ]; then
echo "ERROR: no example projects found in ${SCRIPT_DIR}/examples/"
exit 1
fi

echo
echo "Built: ${BUILT:- none}"

if [ -n "${FAILED}" ]; then
echo "Failed:${FAILED}"
exit 1
fi

echo "All wolfSSH Espressif examples built."
72 changes: 52 additions & 20 deletions ide/Espressif/ESP-IDF/examples/wolfssh_echoserver/main/echoserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -510,16 +510,23 @@ static int wolfSSH_FwdDefaultActions(WS_FwdCbAction action, void* vCtx,
ctx->state = FWD_STATE_DIRECT;
}
else if (action == WOLFSSH_FWD_LOCAL_CLEANUP) {
WCLOSESOCKET(ctx->appFd);
if (ctx->hostName) {
WFREE(ctx->hostName, NULL, 0);
ctx->hostName = NULL;
}
if (ctx->originName) {
WFREE(ctx->originName, NULL, 0);
ctx->originName = NULL;
/* Channel id rides in port. Only its holder may tear the slot down. */
if (port == ctx->channelId) {
/* The open can fail after setup, before anything connected. */
if (ctx->appFd != (WS_SOCKET_T)-1) {
WCLOSESOCKET(ctx->appFd);
ctx->appFd = -1;
}
if (ctx->hostName) {
WFREE(ctx->hostName, NULL, 0);
ctx->hostName = NULL;
}
if (ctx->originName) {
WFREE(ctx->originName, NULL, 0);
ctx->originName = NULL;
}
ctx->state = FWD_STATE_INIT;
}
ctx->state = FWD_STATE_INIT;
}
else if (action == WOLFSSH_FWD_REMOTE_SETUP) {
struct sockaddr_in addr;
Expand Down Expand Up @@ -1150,20 +1157,37 @@ static int ssh_worker(thread_ctx_t* threadCtx)
}
else if (rc == WS_CHANNEL_CLOSED) {
#ifdef WOLFSSH_FWD
if (threadCtx->fwdCbCtx.state == FWD_STATE_CONNECTED &&
lastChannel == threadCtx->fwdCbCtx.channelId) {
/* Read zero-returned. Socket is closed. Go back
to listening. */
if (fwdFd != -1) {
WCLOSESOCKET(fwdFd);
/* wolfSSH_worker() names the channel only for the
* data and EOF statuses; DoChannelClose() recorded
* the id it retired. */
wolfSSH_GetLastRxId(ssh, &lastChannel);
if (lastChannel == threadCtx->fwdCbCtx.channelId) {
/* Held bytes belong to the channel going away. */
fwdBufferIdx = 0;
if (threadCtx->fwdCbCtx.appFd == (WS_SOCKET_T)-1) {
/* The cleanup handler ran ahead of this and
* closed the socket; only this copy of the
* descriptor is stale. */
fwdFd = -1;
}
if (threadCtx->fwdCbCtx.originName != NULL) {
WFREE(threadCtx->fwdCbCtx.originName,
NULL, 0);
threadCtx->fwdCbCtx.originName = NULL;
else if (threadCtx->fwdCbCtx.state
== FWD_STATE_CONNECTED) {
/* A locally opened forward is armed by no
* LOCAL_SETUP and so draws no cleanup. Its
* teardown is still ours: go back to
* listening. */
if (fwdFd != -1) {
WCLOSESOCKET(fwdFd);
fwdFd = -1;
threadCtx->fwdCbCtx.appFd = -1;
}
if (threadCtx->fwdCbCtx.originName != NULL) {
WFREE(threadCtx->fwdCbCtx.originName,
NULL, 0);
threadCtx->fwdCbCtx.originName = NULL;
}
threadCtx->fwdCbCtx.state = FWD_STATE_LISTEN;
}
threadCtx->fwdCbCtx.state = FWD_STATE_LISTEN;
}
#endif
continue;
Expand Down Expand Up @@ -1290,6 +1314,8 @@ static int ssh_worker(thread_ctx_t* threadCtx)
to listening. */
WCLOSESOCKET(fwdFd);
fwdFd = -1;
fwdBufferIdx = 0;
threadCtx->fwdCbCtx.appFd = -1;
if (threadCtx->fwdCbCtx.hostName != NULL) {
WFREE(threadCtx->fwdCbCtx.hostName,
NULL, 0);
Expand All @@ -1310,6 +1336,9 @@ static int ssh_worker(thread_ctx_t* threadCtx)
/* Connection reset. Socket is closed.
* Go back to listening. */
WCLOSESOCKET(fwdFd);
fwdFd = -1;
fwdBufferIdx = 0;
threadCtx->fwdCbCtx.appFd = -1;
threadCtx->fwdCbCtx.state = FWD_STATE_LISTEN;
continue;
}
Expand Down Expand Up @@ -1405,6 +1434,9 @@ static int ssh_worker(thread_ctx_t* threadCtx)
threadCtx->fwdCbCtx.hostPort);

if (fwdFd > 0) {
/* The cleanup handler closes what it finds here, so a
* direct forward has to record its socket too. */
threadCtx->fwdCbCtx.appFd = fwdFd;
threadCtx->fwdCbCtx.state = FWD_STATE_CONNECTED;
}
}
Expand Down
Loading