1use heapless::String;
18use log::{debug, info, warn};
19
20use crate::config::SSHStampConfig;
21use crate::platform::PlatformServices;
22use crate::serial::{BufferedSerial, serial_bridge};
23
24#[cfg(feature = "can")]
25use crate::can::can_bridge;
26
27#[cfg(feature = "can")]
28use embassy_futures::select::{Either, select};
29use embassy_sync::blocking_mutex::raw::NoopRawMutex;
30use embassy_sync::channel::Channel;
31
32use core::result::Result;
33
34use sunset::packets::PubKey;
35use sunset::{ChanFail, ChanHandle, ServEvent};
36use sunset_async::{ChanInOut, SSHServer, SunsetMutex};
37
38pub mod env_parser {
39 use super::String;
40 use core::str::FromStr;
41
42 const PUBKEY_MAX_LEN: usize = 256;
45
46 #[must_use]
51 pub fn env_sanitize(s: &str) -> bool {
52 !s.is_empty() && s.bytes().all(|b| b.is_ascii_graphic())
53 }
54
55 #[must_use]
62 pub fn parse_pubkey(value: &str) -> Option<&str> {
63 let trimmed = value.trim();
64
65 if trimmed.is_empty() || trimmed.len() > PUBKEY_MAX_LEN {
66 return None;
67 }
68 if !trimmed.bytes().all(|b| b.is_ascii_graphic() || b == b' ') {
69 return None;
70 }
71
72 Some(trimmed)
73 }
74
75 #[must_use]
79 pub fn parse_wifi_ap_ssid(value: &str) -> Option<String<32>> {
80 if !env_sanitize(value) {
81 return None;
82 }
83 let mut s = String::new();
84 s.push_str(value).ok()?;
85 Some(s)
86 }
87
88 #[must_use]
92 pub fn parse_wifi_station_ssid(value: &str) -> Option<String<32>> {
93 if !value.is_empty() && !env_sanitize(value) {
94 return None;
95 }
96 let mut s = String::new();
97 s.push_str(value).ok()?;
98 Some(s)
99 }
100
101 #[must_use]
106 pub fn parse_wifi_psk(value: &str) -> Option<String<63>> {
107 if value.len() < 8 || value.len() > 63 {
108 return None;
109 }
110 if !env_sanitize(value) {
111 return None;
112 }
113 let mut s = String::new();
114 s.push_str(value).ok()?;
115 Some(s)
116 }
117
118 #[must_use]
123 pub fn parse_mac_address(value: &str) -> Option<[u8; 6]> {
124 if !env_sanitize(value) {
125 return None;
126 }
127 if value.len() != 17 {
128 return None;
129 }
130 let parts: heapless::Vec<u8, 6> = value
131 .split(':')
132 .filter_map(|p| u8::from_str_radix(p, 16).ok())
133 .collect();
134 if parts.len() != 6 {
135 return None;
136 }
137 Some([parts[0], parts[1], parts[2], parts[3], parts[4], parts[5]])
138 }
139
140 #[must_use]
145 pub fn parse_wifi_band(value: &str) -> Option<u8> {
146 ssh_stamp_hal::BandMode::from_str(value)
147 .ok()
148 .map(|band| band as u8)
149 }
150
151 const UART_BAUD_MIN: u32 = 300;
154 const UART_BAUD_MAX: u32 = 5_000_000;
155
156 #[must_use]
158 pub fn parse_uart_baud(value: &str) -> Option<u32> {
159 let baud: u32 = value.trim().parse().ok()?;
160 (UART_BAUD_MIN..=UART_BAUD_MAX)
161 .contains(&baud)
162 .then_some(baud)
163 }
164
165 #[must_use]
167 pub fn parse_uart_data_bits(value: &str) -> Option<u8> {
168 let bits: u8 = value.trim().parse().ok()?;
169 (5..=8).contains(&bits).then_some(bits)
170 }
171
172 #[must_use]
174 pub fn parse_uart_parity(value: &str) -> Option<ssh_stamp_hal::Parity> {
175 ssh_stamp_hal::Parity::from_str(value.trim()).ok()
176 }
177
178 #[must_use]
180 pub fn parse_uart_stop_bits(value: &str) -> Option<u8> {
181 let bits: u8 = value.trim().parse().ok()?;
182 matches!(bits, 1 | 2).then_some(bits)
183 }
184}
185
186#[derive(Debug)]
187pub enum SessionType {
188 Bridge(ChanHandle),
189 #[cfg(feature = "sftp-ota")]
190 Sftp(ChanHandle),
191}
192
193pub struct EventContext<'a> {
194 pub session: &'a mut Option<ChanHandle>,
195 pub auth_checked: &'a mut bool,
196 pub config_changed: &'a mut bool,
197 pub needs_reset: &'a mut bool,
198 #[cfg(feature = "can")]
201 pub can_queue: &'a Channel<NoopRawMutex, ChanHandle, 1>,
202 #[cfg(all(feature = "sftp-ota", feature = "can"))]
205 pub can_dispatched: &'a mut bool,
206}
207
208pub fn session_subsystem(
214 ev: ServEvent<'_, '_>,
215 ctx: &mut EventContext<'_>,
216 #[cfg(feature = "sftp-ota")] chan_pipe: &Channel<NoopRawMutex, SessionType, 1>,
217) -> Result<(), sunset::Error> {
218 if let ServEvent::SessionSubsystem(a) = ev {
219 debug!("ServEvent::SessionSubsystem");
220
221 if !*ctx.auth_checked {
222 warn!("Unauthenticated SessionSubsystem rejected");
223 a.fail()?;
224 } else if a.command()?.to_lowercase().as_str() == "sftp" {
225 #[cfg(feature = "sftp-ota")]
226 {
227 #[cfg(feature = "can")]
230 let can_active = *ctx.can_dispatched;
231 #[cfg(not(feature = "can"))]
232 let can_active = false;
233 if can_active {
234 warn!("SFTP subsystem refused: a CAN session is active on this connection");
235 a.fail()?;
236 } else if let Some(ch) = ctx.session.take() {
237 debug_assert_eq!(ch.num(), a.channel());
238 a.succeed()?;
239 debug!("We got SFTP subsystem");
240 match chan_pipe.try_send(SessionType::Sftp(ch)) {
241 Ok(()) => *ctx.auth_checked = false,
242 Err(e) => log::error!("Could not send the channel: {e:?}"),
243 }
244 } else {
245 a.fail()?;
246 }
247 }
248 #[cfg(not(feature = "sftp-ota"))]
249 {
250 warn!("SFTP subsystem requested but not supported in this build");
251 a.fail()?;
252 }
253 } else if a.command()?.to_lowercase().as_str() == "can" {
254 #[cfg(feature = "can")]
255 if let Some(ch) = ctx.session.take() {
256 debug_assert_eq!(ch.num(), a.channel());
257 a.succeed()?;
258 debug!("We got CAN subsystem");
259 if let Err(e) = ctx.can_queue.try_send(ch) {
263 log::error!("Could not send the CAN channel: {e:?}");
264 }
265 #[cfg(feature = "sftp-ota")]
266 {
267 *ctx.can_dispatched = true;
268 }
269 } else {
270 a.fail()?;
271 }
272 #[cfg(not(feature = "can"))]
273 {
274 warn!("CAN subsystem requested but not supported in this build");
275 a.fail()?;
276 }
277 } else {
278 a.fail()?;
279 }
280 }
281 Ok(())
282}
283
284pub async fn session_shell<P: PlatformServices>(
290 ev: ServEvent<'_, '_>,
291 ctx: &mut EventContext<'_>,
292 config: &SunsetMutex<SSHStampConfig>,
293 chan_pipe: &Channel<NoopRawMutex, SessionType, 1>,
294 platform: &P,
295) -> Result<(), sunset::Error> {
296 if let ServEvent::SessionShell(a) = ev {
297 debug!("ServEvent::SessionShell");
298
299 if !*ctx.auth_checked {
300 warn!("Unauthenticated SessionShell rejected");
301 a.fail()?;
302 } else if let Some(ch) = ctx.session.take() {
303 if *ctx.config_changed {
304 *ctx.config_changed = false;
305 let config_guard = config.lock().await;
306 platform
307 .save_config(&config_guard)
308 .await
309 .map_err(|_| sunset::error::BadUsage.build())?;
310 drop(config_guard);
311 if *ctx.needs_reset {
312 info!("Configuration saved. Rebooting to apply the changes...");
313 platform.reset();
314 }
315 }
316 debug_assert_eq!(ch.num(), a.channel());
317 a.succeed()?;
318 debug!("We got shell");
319 platform.activate_uart();
320 debug!("Connection loop: UART activated");
321 match chan_pipe.try_send(SessionType::Bridge(ch)) {
322 Ok(()) => *ctx.auth_checked = false,
323 Err(e) => log::error!("Could not send the channel: {e:?}"),
324 }
325 } else {
326 a.fail()?;
327 }
328 }
329 Ok(())
330}
331
332pub async fn first_auth(
338 ev: ServEvent<'_, '_>,
339 config: &SunsetMutex<SSHStampConfig>,
340) -> Result<(), sunset::Error> {
341 if let ServEvent::FirstAuth(mut a) = ev {
342 debug!("ServEvent::FirstAuth");
343 let config_guard = config.lock().await;
344
345 a.enable_password_auth(false)?;
346
347 a.enable_pubkey_auth(true)?;
348 if config_guard.first_login {
349 a.allow()?;
350 } else {
351 debug!("FirstAuth received but not first-login, rejecting");
352 a.reject()?;
353 }
354 }
355 Ok(())
356}
357
358pub async fn hostkeys(
364 ev: ServEvent<'_, '_>,
365 config: &SunsetMutex<SSHStampConfig>,
366) -> Result<(), sunset::Error> {
367 if let ServEvent::Hostkeys(h) = ev {
368 debug!("ServEvent::Hostkeys");
369 let config_guard = config.lock().await;
370 h.hostkeys(&[&config_guard.hostkey])?;
371 }
372 Ok(())
373}
374
375pub fn password_auth(ev: ServEvent<'_, '_>) -> Result<(), sunset::Error> {
381 if let ServEvent::PasswordAuth(a) = ev {
382 warn!("Password auth is not supported, use public key auth instead.");
383 a.reject()?;
384 }
385 Ok(())
386}
387
388pub async fn pubkey_auth(
394 ev: ServEvent<'_, '_>,
395 ctx: &mut EventContext<'_>,
396 config: &SunsetMutex<SSHStampConfig>,
397) -> Result<(), sunset::Error> {
398 if let ServEvent::PubkeyAuth(a) = ev {
399 debug!("ServEvent::PubkeyAuth");
400 let config_guard = config.lock().await;
401 let client_pubkey = a.pubkey()?;
402
403 let matched = match &client_pubkey {
404 PubKey::Ed25519(presented) => config_guard
405 .pubkeys
406 .iter()
407 .any(|slot| slot.as_ref().is_some_and(|stored| stored == presented)),
408 PubKey::Unknown(_) => false,
409 };
410
411 match client_pubkey.fingerprint() {
412 Ok(fingerprint) if matched => info!("Accepted pubkey {fingerprint}"),
413 Ok(fingerprint) => warn!("Rejected pubkey {fingerprint}: not enrolled in any slot"),
414 Err(err) => warn!("Rejected pubkey: {err:?}"),
415 }
416
417 if matched {
418 *ctx.auth_checked = true;
419 a.allow()?;
420 } else {
421 a.reject()?;
422 }
423 }
424 Ok(())
425}
426
427pub fn open_session(
433 ev: ServEvent<'_, '_>,
434 ctx: &mut EventContext<'_>,
435) -> Result<(), sunset::Error> {
436 if let ServEvent::OpenSession(a) = ev {
437 debug!("ServEvent::OpenSession");
438 match ctx.session {
439 Some(_) => {
440 warn!("Rejecting duplicate session channel");
441 a.reject(ChanFail::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED)?;
442 }
443 None => {
444 *ctx.session = Some(a.accept()?);
445 }
446 }
447 }
448 Ok(())
449}
450
451pub async fn session_env(
457 ev: ServEvent<'_, '_>,
458 ctx: &mut EventContext<'_>,
459 config: &SunsetMutex<SSHStampConfig>,
460) -> Result<(), sunset::Error> {
461 if let ServEvent::SessionEnv(a) = ev {
462 debug!("Got ENV request");
463 debug!("ENV name: {}", a.name()?);
464 match a.name()? {
468 "LANG" => {
469 a.succeed()?;
470 }
471 "SSH_STAMP_PUBKEY" => {
472 pubkey_env(a, config, ctx).await?;
473 }
474 "SSH_STAMP_WIFI_AP_SSID" => {
475 wifi_ap_ssid_env(a, config, ctx).await?;
476 }
477 "SSH_STAMP_WIFI_AP_PSK" => {
478 wifi_ap_psk_env(a, config, ctx).await?;
479 }
480 "SSH_STAMP_WIFI_BAND" => {
481 wifi_band_env(a, config, ctx).await?;
482 }
483 "SSH_STAMP_WIFI_STA_SSID" => {
484 wifi_sta_ssid_env(a, config, ctx).await?;
485 }
486 "SSH_STAMP_WIFI_STA_PW" => {
487 wifi_sta_psk_env(a, config, ctx).await?;
488 }
489 "SSH_STAMP_WIFI_MAC_ADDRESS" => {
490 wifi_mac_address_env(a, config, ctx).await?;
491 }
492 "SSH_STAMP_WIFI_MAC_RANDOM" => {
493 wifi_mac_random_env(a, config, ctx).await?;
494 }
495 "SSH_STAMP_UART_BAUD" => {
496 uart_env(UartParam::Baud, a, config, ctx).await?;
497 }
498 "SSH_STAMP_UART_DATA_BITS" => {
499 uart_env(UartParam::DataBits, a, config, ctx).await?;
500 }
501 "SSH_STAMP_UART_PARITY" => {
502 uart_env(UartParam::Parity, a, config, ctx).await?;
503 }
504 "SSH_STAMP_UART_STOP_BITS" => {
505 uart_env(UartParam::StopBits, a, config, ctx).await?;
506 }
507 _ => {
508 debug!("Ignoring unknown environment variable: {}", a.name()?);
509 a.succeed()?;
510 }
511 }
512 }
513 Ok(())
514}
515
516pub async fn pubkey_env(
522 a: sunset::event::ServEnvironmentRequest<'_, '_>,
523 config: &SunsetMutex<SSHStampConfig>,
524 ctx: &mut EventContext<'_>,
525) -> Result<(), sunset::Error> {
526 let mut config_guard = config.lock().await;
527
528 if config_guard.first_login {
529 match env_parser::parse_pubkey(a.value()?) {
530 None => {
531 warn!("SSH_STAMP_PUBKEY contains invalid characters");
532 a.fail()?;
533 }
534 Some(trimmed) => {
535 if config_guard.add_pubkey(trimmed).is_ok() {
536 debug!("Added new pubkey from ENV");
537 a.succeed()?;
538 if config_guard.first_login {
539 config_guard.first_login = false;
540 *ctx.config_changed = true;
541 *ctx.auth_checked = true;
542 }
543 } else {
544 warn!("Failed to add new pubkey from ENV");
545 a.fail()?;
546 }
547 }
548 }
549 } else {
550 warn!("SSH_STAMP_PUBKEY env received but not first-login; rejecting");
551 a.fail()?;
552 }
553
554 Ok(())
555}
556
557pub async fn wifi_ap_ssid_env(
562 a: sunset::event::ServEnvironmentRequest<'_, '_>,
563 config: &SunsetMutex<SSHStampConfig>,
564 ctx: &mut EventContext<'_>,
565) -> Result<(), sunset::Error> {
566 let mut config_guard = config.lock().await;
567 if *ctx.auth_checked || config_guard.first_login {
568 if let Some(s) = env_parser::parse_wifi_ap_ssid(a.value()?) {
569 config_guard.wifi_ap_ssid = s;
570 debug!("Set wifi Access Point SSID from ENV");
571 a.succeed()?;
572 *ctx.config_changed = true;
573 *ctx.needs_reset = true;
574 } else {
575 warn!("SSH_STAMP_WIFI_AP_SSID invalid and/or too long");
576 a.fail()?;
577 }
578 } else {
579 warn!("SSH_STAMP_WIFI_AP_SSID env received but not authenticated; rejecting");
580 a.fail()?;
581 }
582 Ok(())
583}
584
585pub async fn wifi_ap_psk_env(
590 a: sunset::event::ServEnvironmentRequest<'_, '_>,
591 config: &SunsetMutex<SSHStampConfig>,
592 ctx: &mut EventContext<'_>,
593) -> Result<(), sunset::Error> {
594 let mut config_guard = config.lock().await;
595 if *ctx.auth_checked || config_guard.first_login {
596 if let Some(s) = env_parser::parse_wifi_psk(a.value()?) {
597 config_guard.wifi_ap_pw = s;
598 debug!("Set WIFI AP PSK from ENV");
599 a.succeed()?;
600 *ctx.config_changed = true;
601 *ctx.needs_reset = true;
602 } else {
603 warn!("SSH_STAMP_WIFI_AP_PSK invalid and/or not within 8-63 characters");
604 a.fail()?;
605 }
606 } else {
607 warn!("SSH_STAMP_WIFI_AP_PSK env received but not authenticated; rejecting");
608 a.fail()?;
609 }
610 Ok(())
611}
612
613pub async fn wifi_band_env(
622 a: sunset::event::ServEnvironmentRequest<'_, '_>,
623 config: &SunsetMutex<SSHStampConfig>,
624 ctx: &mut EventContext<'_>,
625) -> Result<(), sunset::Error> {
626 let mut config_guard = config.lock().await;
627 if *ctx.auth_checked || config_guard.first_login {
628 if let Some(band) = env_parser::parse_wifi_band(a.value()?) {
629 config_guard.wifi_ap_band = band;
630 debug!("Set WIFI AP band from ENV: {band}");
631 a.succeed()?;
632 *ctx.config_changed = true;
633 *ctx.needs_reset = true;
634 } else {
635 warn!("SSH_STAMP_WIFI_BAND must be 2.4g, 5g, or auto");
636 a.fail()?;
637 }
638 } else {
639 warn!("SSH_STAMP_WIFI_BAND env received but not authenticated; rejecting");
640 a.fail()?;
641 }
642 Ok(())
643}
644
645pub async fn wifi_sta_ssid_env(
650 a: sunset::event::ServEnvironmentRequest<'_, '_>,
651 config: &SunsetMutex<SSHStampConfig>,
652 ctx: &mut EventContext<'_>,
653) -> Result<(), sunset::Error> {
654 let mut config_guard = config.lock().await;
655 if *ctx.auth_checked || config_guard.first_login {
656 if let Some(s) = env_parser::parse_wifi_station_ssid(a.value()?) {
657 config_guard.wifi_sta_ssid = s;
658 debug!("Set wifi STATION SSID from ENV");
659 a.succeed()?;
660 *ctx.config_changed = true;
661 *ctx.needs_reset = true;
662 } else {
663 warn!("SSH_STAMP_WIFI_STA_SSID invalid and/or too long");
664 a.fail()?;
665 }
666 } else {
667 warn!("SSH_STAMP_WIFI_STA_SSID env received but not authenticated; rejecting");
668 a.fail()?;
669 }
670 Ok(())
671}
672
673pub async fn wifi_sta_psk_env(
678 a: sunset::event::ServEnvironmentRequest<'_, '_>,
679 config: &SunsetMutex<SSHStampConfig>,
680 ctx: &mut EventContext<'_>,
681) -> Result<(), sunset::Error> {
682 let mut config_guard = config.lock().await;
683 if *ctx.auth_checked || config_guard.first_login {
684 if let Some(s) = env_parser::parse_wifi_psk(a.value()?) {
685 config_guard.wifi_sta_pw = s;
686 debug!("Set wifi STATION PSK from ENV");
687 a.succeed()?;
688 *ctx.config_changed = true;
689 *ctx.needs_reset = true;
690 } else {
691 warn!("SSH_STAMP_WIFI_STA_PSK invalid and/or not within 8-63 characters");
692 a.fail()?;
693 }
694 } else {
695 warn!("SSH_STAMP_WIFI_STA_PSK env received but not authenticated; rejecting");
696 a.fail()?;
697 }
698 Ok(())
699}
700
701pub async fn wifi_mac_address_env(
706 a: sunset::event::ServEnvironmentRequest<'_, '_>,
707 config: &SunsetMutex<SSHStampConfig>,
708 ctx: &mut EventContext<'_>,
709) -> Result<(), sunset::Error> {
710 let mut config_guard = config.lock().await;
711 if *ctx.auth_checked || config_guard.first_login {
712 if let Some(mac) = env_parser::parse_mac_address(a.value()?) {
713 config_guard.mac = mac;
714 debug!("Set MAC address from ENV: {mac:02X?}");
715 a.succeed()?;
716 *ctx.config_changed = true;
717 *ctx.needs_reset = true;
718 } else {
719 warn!("SSH_STAMP_WIFI_MAC_ADDRESS must be XX:XX:XX:XX:XX:XX format");
720 a.fail()?;
721 }
722 } else {
723 warn!("SSH_STAMP_WIFI_MAC_ADDRESS env received but not authenticated; rejecting");
724 a.fail()?;
725 }
726 Ok(())
727}
728
729pub async fn wifi_mac_random_env(
734 a: sunset::event::ServEnvironmentRequest<'_, '_>,
735 config: &SunsetMutex<SSHStampConfig>,
736 ctx: &mut EventContext<'_>,
737) -> Result<(), sunset::Error> {
738 let mut config_guard = config.lock().await;
739 if *ctx.auth_checked || config_guard.first_login {
740 config_guard.mac = [0xFF; 6];
741 debug!("Set MAC address to random mode");
742 a.succeed()?;
743 *ctx.config_changed = true;
744 *ctx.needs_reset = true;
745 } else {
746 warn!("SSH_STAMP_WIFI_MAC_RANDOM env received but not authenticated; rejecting");
747 a.fail()?;
748 }
749 Ok(())
750}
751
752#[derive(Clone, Copy, Debug)]
754pub enum UartParam {
755 Baud,
756 DataBits,
757 Parity,
758 StopBits,
759}
760
761impl UartParam {
762 const fn env_name(self) -> &'static str {
764 match self {
765 Self::Baud => "SSH_STAMP_UART_BAUD",
766 Self::DataBits => "SSH_STAMP_UART_DATA_BITS",
767 Self::Parity => "SSH_STAMP_UART_PARITY",
768 Self::StopBits => "SSH_STAMP_UART_STOP_BITS",
769 }
770 }
771
772 const fn accepted(self) -> &'static str {
774 match self {
775 Self::Baud => "300-5000000",
776 Self::DataBits => "5-8",
777 Self::Parity => "none, even or odd",
778 Self::StopBits => "1 or 2",
779 }
780 }
781}
782
783pub async fn uart_env(
792 param: UartParam,
793 a: sunset::event::ServEnvironmentRequest<'_, '_>,
794 config: &SunsetMutex<SSHStampConfig>,
795 ctx: &mut EventContext<'_>,
796) -> Result<(), sunset::Error> {
797 let mut config_guard = config.lock().await;
798 if !(*ctx.auth_checked || config_guard.first_login) {
799 warn!(
800 "{} env received but not authenticated; rejecting",
801 param.env_name()
802 );
803 return a.fail();
804 }
805
806 let value = a.value()?;
807 let uart = &mut config_guard.uart_params;
808 let applied = match param {
809 UartParam::Baud => env_parser::parse_uart_baud(value).map(|v| uart.baud = v),
810 UartParam::DataBits => env_parser::parse_uart_data_bits(value).map(|v| uart.data_bits = v),
811 UartParam::Parity => env_parser::parse_uart_parity(value).map(|v| uart.parity = v),
812 UartParam::StopBits => env_parser::parse_uart_stop_bits(value).map(|v| uart.stop_bits = v),
813 };
814
815 if applied.is_some() {
816 debug!("Set UART {param:?} from ENV: {uart:?}");
817 a.succeed()?;
818 *ctx.config_changed = true;
819 *ctx.needs_reset = true;
820 } else {
821 warn!("{} must be {}", param.env_name(), param.accepted());
822 a.fail()?;
823 }
824 Ok(())
825}
826
827pub async fn session_pty(
833 ev: ServEvent<'_, '_>,
834 ctx: &mut EventContext<'_>,
835 config: &SunsetMutex<SSHStampConfig>,
836) -> Result<(), sunset::Error> {
837 if let ServEvent::SessionPty(a) = ev {
838 let first_login = { config.lock().await.first_login };
839
840 if *ctx.auth_checked || first_login {
841 debug!("ServEvent::SessionPty: Session granted");
842 a.succeed()?;
843 } else {
844 debug!("ServEvent::SessionPty: No auth not session");
845 a.fail()?;
846 }
847 }
848 Ok(())
849}
850
851pub fn session_exec(ev: ServEvent<'_, '_>) -> Result<(), sunset::Error> {
857 if let ServEvent::SessionExec(a) = ev {
858 a.fail()?;
859 }
860 Ok(())
861}
862
863pub fn defunct() -> Result<(), sunset::Error> {
869 debug!("Expected caller to handle event");
870 sunset::error::BadUsage.fail()
871}
872
873#[cfg_attr(
876 feature = "can",
877 doc = "A `can` subsystem channel is bridged concurrently with the shell",
878 doc = "(UART) session on the same connection. The whole connection is",
879 doc = "torn down when either bridge finishes.",
880 doc = ""
881)]
882pub async fn ssh_client<'a, 'b, U, P>(
885 uart_buff: &'a U,
886 ssh_server: &'b SSHServer<'a>,
887 chan_pipe: &'b Channel<NoopRawMutex, SessionType, 1>,
888 #[cfg_attr(
889 not(any(feature = "sftp-ota", feature = "can")),
890 allow(unused_variables)
891 )]
892 platform: &'b P,
893 #[cfg(feature = "can")] can_queue: &'b Channel<NoopRawMutex, ChanHandle, 1>,
894) -> Result<(), sunset::Error>
895where
896 U: BufferedSerial,
897 P: PlatformServices,
898{
899 debug!("Preparing bridge");
900 let session = async {
901 let session_type = chan_pipe.receive().await;
902 debug!("Checking bridge session type");
903 match session_type {
904 SessionType::Bridge(ch) => {
905 info!("Handling bridge session");
906 let chan_io: ChanInOut<'_> = ssh_server.stdio(ch).await?;
907 let (stdin, stdout) = chan_io.split();
908 info!("Starting bridge");
909 serial_bridge(stdin, stdout, uart_buff).await?;
910 }
911 #[cfg(feature = "sftp-ota")]
912 SessionType::Sftp(ch) => {
913 debug!("Handling SFTP session");
914 let stdio = ssh_server.stdio(ch).await?;
915 let ota_writer = platform.ota_writer();
916 ssh_stamp_ota::run_ota_server::<P::OtaWriter>(stdio, ota_writer).await?;
917 }
918 }
919 Ok(())
920 };
921
922 #[cfg(feature = "can")]
923 let result = {
924 let can_session = async {
925 let ch = can_queue.receive().await;
926 info!("Handling CAN session");
927 let chan_io: ChanInOut<'_> = ssh_server.stdio(ch).await?;
928 let (stdin, stdout) = chan_io.split();
929 info!("Starting CAN bridge");
930 can_bridge(stdin, stdout, platform.can()).await
931 };
932 match select(session, can_session).await {
933 Either::First(r) | Either::Second(r) => r,
934 }
935 };
936 #[cfg(not(feature = "can"))]
937 let result = session.await;
938 result
939}
940
941pub fn bridge_disable() {
942 debug!("Bridge disabled: WIP");
943}