1use core::result::Result;
15
16use embassy_futures::select::{Either3, select3};
17use embassy_net::{
18 IpListenEndpoint, Stack,
19 tcp::{AcceptError, TcpSocket},
20};
21use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel};
22use heapless::String;
23use log::{debug, error, info, warn};
24use ssh_stamp_hal::{BandMode, WifiApConfigStatic};
25#[cfg(feature = "can")]
26use sunset::ChanHandle;
27use sunset::SignKey;
28use sunset_async::SunsetMutex;
29#[cfg(feature = "mem-probe")]
30use {
31 core::pin::pin,
32 embassy_futures::select::{Either, select},
33 embassy_time::Timer,
34};
35
36use crate::config::SSHStampConfig;
37use crate::handle::{self, SessionType};
38use crate::mem_probe::{Checkpoint, checkpoint, mark_kex_start, replay_checkpoints};
39use crate::platform::PlatformServices;
40use crate::serial::BufferedSerial;
41use crate::serve;
42use crate::settings::{
43 SSH_STAMP_IDENT, TCP_RX_BUF, TCP_TX_BUF, UART_BUFFER_SIZE, WIFI_PASSWORD_CHARS,
44};
45
46pub async fn prepare_ap_config<P: PlatformServices>(
63 config: &SunsetMutex<SSHStampConfig>,
64 platform: &P,
65) -> Result<WifiApConfigStatic, sunset::Error> {
66 let mut guard = config.lock().await;
67
68 info!("SSH server ident: {SSH_STAMP_IDENT}");
69
70 if guard.wifi_ap_pw.is_empty() {
71 let pw = generate_wifi_password()?;
72 warn!("wifi_pw missing from config, generated new password");
73 guard.wifi_ap_pw = pw;
74 platform
75 .save_config(&guard)
76 .await
77 .map_err(|_| sunset::error::BadUsage.build())?;
78 }
79 log_ap_credentials(&guard);
80
81 let mac = guard
82 .resolve_mac()
83 .map_err(|_| sunset::error::BadUsage.build())?;
84 info!(
85 "WIFI MAC: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
86 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
87 );
88
89 print_hostkey_fingerprint(&guard.hostkey);
90
91 let band = BandMode::from(guard.wifi_ap_band);
95 let channel = if guard.wifi_ap_band == 0 { 1 } else { 36 };
97
98 info!("WIFI AP band: {band:?} (channel {channel})");
99
100 Ok(WifiApConfigStatic {
101 ap_ssid: guard.wifi_ap_ssid.clone(),
102 ap_password: guard.wifi_ap_pw.clone(),
103 sta_ssid: guard.wifi_sta_ssid.clone(),
104 sta_password: guard.wifi_sta_pw.clone(),
105 channel,
106 band,
107 mac,
108 })
109}
110
111fn log_ap_credentials(config: &SSHStampConfig) {
113 info!("WIFI SSID: {}", config.wifi_ap_ssid);
114 info!("WIFI PSK: {}", config.wifi_ap_pw);
115}
116
117#[cfg(feature = "mem-probe")]
123struct SessionAcceptor {
124 awaiting_first_session: bool,
125}
126
127#[cfg(feature = "mem-probe")]
128impl SessionAcceptor {
129 fn new() -> Self {
130 Self {
131 awaiting_first_session: true,
132 }
133 }
134
135 async fn accept(
138 &mut self,
139 tcp_socket: &mut TcpSocket<'_>,
140 endpoint: IpListenEndpoint,
141 config: &SunsetMutex<SSHStampConfig>,
142 ) -> Result<(), AcceptError> {
143 if !self.awaiting_first_session {
144 return tcp_socket.accept(endpoint).await;
145 }
146
147 let mut accept = pin!(tcp_socket.accept(endpoint));
148 let accepted = loop {
149 match select(&mut accept, Timer::after_secs(3)).await {
150 Either::First(accepted) => break accepted,
151 Either::Second(()) => {
152 replay_checkpoints();
153 log_ap_credentials(&*config.lock().await);
154 }
155 }
156 };
157
158 if accepted.is_ok() {
159 self.awaiting_first_session = false;
160 }
161 accepted
162 }
163}
164
165#[cfg(not(feature = "mem-probe"))]
167struct SessionAcceptor;
168
169#[cfg(not(feature = "mem-probe"))]
170impl SessionAcceptor {
171 fn new() -> Self {
172 Self
173 }
174
175 async fn accept(
177 &mut self,
178 tcp_socket: &mut TcpSocket<'_>,
179 endpoint: IpListenEndpoint,
180 _config: &SunsetMutex<SSHStampConfig>,
181 ) -> Result<(), AcceptError> {
182 tcp_socket.accept(endpoint).await
183 }
184}
185
186pub async fn run_app<U, P>(
193 stack: Stack<'static>,
194 uart: &U,
195 config: &'static SunsetMutex<SSHStampConfig>,
196 platform: &P,
197) -> Result<(), sunset::Error>
198where
199 U: BufferedSerial,
200 P: PlatformServices,
201{
202 let mut rx_buffer = [0u8; TCP_RX_BUF];
203 let mut tx_buffer = [0u8; TCP_TX_BUF];
204
205 checkpoint(Checkpoint::TcpListening);
206 replay_checkpoints();
207 log_ap_credentials(&*config.lock().await);
208 let mut acceptor = SessionAcceptor::new();
209 loop {
210 debug!("HSM: accepting TCP on port 22");
211 let mut tcp_socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
212 let endpoint = IpListenEndpoint {
213 addr: None,
214 port: 22,
215 };
216
217 if let Err(e) = acceptor.accept(&mut tcp_socket, endpoint, config).await {
218 error!("TCP accept error: {e:?}");
219 continue;
220 }
221 debug!("HSM: TCP connected on port 22");
222
223 mark_kex_start();
224 checkpoint(Checkpoint::TcpAccept);
225
226 let mut inbuf = [0u8; UART_BUFFER_SIZE];
227 let mut outbuf = [0u8; UART_BUFFER_SIZE];
228 let ssh_server = serve::ssh_wait_for_initialisation(&mut inbuf, &mut outbuf);
229
230 let chan_pipe = Channel::<NoopRawMutex, SessionType, 1>::new();
231 #[cfg(feature = "can")]
232 let can_queue = Channel::<NoopRawMutex, ChanHandle, 1>::new();
233 #[cfg(feature = "can")]
234 let connection =
235 serve::connection_loop(&ssh_server, &chan_pipe, config, platform, &can_queue);
236 #[cfg(not(feature = "can"))]
237 let connection = serve::connection_loop(&ssh_server, &chan_pipe, config, platform);
238 #[cfg(feature = "can")]
239 let bridge = handle::ssh_client(uart, &ssh_server, &chan_pipe, platform, &can_queue);
240 #[cfg(not(feature = "can"))]
241 let bridge = handle::ssh_client(uart, &ssh_server, &chan_pipe, platform);
242
243 let (mut rsock, mut wsock) = tcp_socket.split();
244 let server = ssh_server.run(&mut rsock, &mut wsock);
245
246 match select3(server, connection, bridge).await {
247 Either3::First(r) | Either3::Second(r) | Either3::Third(r) => {
248 if let Err(e) = r {
249 warn!("Session ended: {e}");
250 }
251 }
252 }
253 }
254}
255
256fn generate_wifi_password() -> Result<String<63>, sunset::Error> {
257 let mut rnd = [0u8; 24];
258 getrandom::fill(&mut rnd).map_err(|_| sunset::Error::msg("RNG failed"))?;
259 let mut pw = String::<63>::new();
260 for &byte in &rnd {
261 let _ = pw.push(WIFI_PASSWORD_CHARS[(byte as usize) % 62] as char);
262 }
263 Ok(pw)
264}
265
266fn print_hostkey_fingerprint(hostkey: &SignKey) {
267 match hostkey {
268 SignKey::Ed25519(_) => {
269 let pubkey = hostkey.pubkey();
270 match pubkey.fingerprint() {
271 Ok(fp) => info!("SSH hostkey fingerprint: {fp}"),
272 Err(e) => warn!("Failed to compute fingerprint: {e:?}"),
273 }
274 }
275 SignKey::AgentEd25519(_) => {
276 warn!("Unsupported key type for fingerprint");
277 }
278 }
279}