Skip to main content

ssh_stamp/
serve.rs

1// SPDX-FileCopyrightText: 2026 Roman Valls Guimera <brainstorm@nopcode.org>
2// SPDX-FileCopyrightText: 2026 Julio Beltran Ortega <jubeormk1@gmail.com>
3// SPDX-FileCopyrightText: 2026 Angus Gratton <gus@projectgus.com>
4// SPDX-FileCopyrightText: 2026 Sergio Gasquez <sergio.gasquez@gmail.com>
5// SPDX-FileCopyrightText: 2026 Gabriel Ku Wei Bin <gabriel.ku@fsfe.org>
6// SPDX-FileCopyrightText: 2026 Anthony Tambasco <anthony.tambasco@fastmail.com>
7// SPDX-FileCopyrightText: 2026 Marko Malenic <mmalenic1@gmail.com>
8//
9// SPDX-License-Identifier: GPL-3.0-or-later
10
11//! SSH connection loop orchestration.
12//!
13//! [`connection_loop`] processes `ServEvent` enums from the `sunset` SSH library
14//! and dispatches to the appropriate handler in [`handle`](crate::handle).
15
16use log::trace;
17
18use crate::config::SSHStampConfig;
19use crate::handle::{
20    EventContext, SessionType, defunct, first_auth, hostkeys, open_session, password_auth,
21    pubkey_auth, session_env, session_exec, session_pty, session_shell, session_subsystem,
22};
23use crate::mem_probe::{Checkpoint, checkpoint, log_kex_elapsed};
24use crate::platform::PlatformServices;
25use crate::settings::UART_BUFFER_SIZE;
26use sunset::{ChanHandle, ServEvent};
27use sunset_async::{ProgressHolder, SSHServer, SunsetMutex};
28
29use core::result::Result;
30
31use embassy_sync::blocking_mutex::raw::NoopRawMutex;
32use embassy_sync::channel::Channel;
33
34/// Handles the SSH connection loop, processing events from clients.
35///
36/// # Errors
37/// Returns an error if SSH protocol operations fail.
38///
39/// # Panics
40/// Panics if flash storage lock cannot be acquired when saving configuration.
41pub async fn connection_loop<P: PlatformServices>(
42    serv: &SSHServer<'_>,
43    chan_pipe: &Channel<NoopRawMutex, SessionType, 1>,
44    config: &SunsetMutex<SSHStampConfig>,
45    platform: &P,
46    #[cfg(feature = "can")] can_queue: &Channel<NoopRawMutex, ChanHandle, 1>,
47) -> Result<(), sunset::Error> {
48    let mut session: Option<ChanHandle> = None;
49    let mut config_changed = false;
50    let mut needs_reset = false;
51    let mut auth_checked = false;
52    #[cfg(all(feature = "sftp-ota", feature = "can"))]
53    let mut can_dispatched = false;
54
55    loop {
56        let mut ph = ProgressHolder::new();
57        let ev = serv.progress(&mut ph).await?;
58
59        trace!("{ev:?}");
60
61        let mut ctx = EventContext {
62            session: &mut session,
63            auth_checked: &mut auth_checked,
64            config_changed: &mut config_changed,
65            needs_reset: &mut needs_reset,
66            #[cfg(feature = "can")]
67            can_queue,
68            #[cfg(all(feature = "sftp-ota", feature = "can"))]
69            can_dispatched: &mut can_dispatched,
70        };
71
72        match ev {
73            ServEvent::SessionSubsystem(_) => {
74                #[cfg(feature = "sftp-ota")]
75                session_subsystem(ev, &mut ctx, chan_pipe)?;
76                #[cfg(not(feature = "sftp-ota"))]
77                session_subsystem(ev, &mut ctx)?;
78            }
79            ServEvent::SessionShell(_) => {
80                session_shell(ev, &mut ctx, config, chan_pipe, platform).await?;
81            }
82            ServEvent::FirstAuth(_) => {
83                checkpoint(Checkpoint::KexComplete);
84                log_kex_elapsed("accept->firstauth");
85                first_auth(ev, config).await?;
86            }
87            ServEvent::Hostkeys(_) => {
88                hostkeys(ev, config).await?;
89            }
90            ServEvent::PasswordAuth(_) => {
91                password_auth(ev)?;
92            }
93            ServEvent::PubkeyAuth(_) => {
94                pubkey_auth(ev, &mut ctx, config).await?;
95            }
96            ServEvent::OpenSession(_) => {
97                checkpoint(Checkpoint::ChannelOpen);
98                open_session(ev, &mut ctx)?;
99            }
100            ServEvent::SessionEnv(_) => {
101                session_env(ev, &mut ctx, config).await?;
102            }
103            ServEvent::SessionPty(_) => {
104                session_pty(ev, &mut ctx, config).await?;
105            }
106            ServEvent::SessionExec(_) => {
107                session_exec(ev)?;
108            }
109            ServEvent::Defunct => {
110                defunct()?;
111            }
112            ServEvent::Authenticated => {
113                checkpoint(Checkpoint::AuthSuccess);
114            }
115            ServEvent::PollAgain => {}
116        }
117    }
118}
119
120/// Creates a new [`SSHServer`] with the provided I/O buffers.
121pub fn ssh_wait_for_initialisation<'server>(
122    inbuf: &'server mut [u8; UART_BUFFER_SIZE],
123    outbuf: &'server mut [u8; UART_BUFFER_SIZE],
124) -> SSHServer<'server> {
125    SSHServer::new(inbuf, outbuf)
126}