use crate::{
errors::CatBridgeError,
fsemul::sdio::{
errors::SdioNetworkError,
proto::message::{
SdioControlMessageRequest, SdioControlTelnetChannel, SdioControlTelnetData,
SdioControlTelnetMessage,
},
server::{SDIO_PRINTF_BUFFS, SdioStreamState},
},
mion::proto::control::MionBootType,
net::{
additions::StreamID,
server::requestable::{Body, State},
},
};
use bytes::Bytes;
use std::sync::Arc;
use tokio::sync::{Mutex, mpsc::Sender as BoundedSender, oneshot::Sender as OneshotSender};
use tracing::{debug, info, warn};
#[allow(
// TODO(mythra): file issue with clippy, suggestion doesn't work.
clippy::assigning_clones,
)]
pub(super) async fn handle_telnet_message(
State(state): State<SdioStreamState>,
stream_id: StreamID,
Body(request): Body<SdioControlMessageRequest>,
) -> Result<Option<Bytes>, CatBridgeError> {
for message in request.messages_owned() {
match message.channel() {
SdioControlTelnetChannel::Arbitrary(arb) => {
warn!(
data = message.buffer_as_complete_string(),
sdio.channel = arb,
"Unknown data received on an unknown SDIO Telnet channel!"
);
}
SdioControlTelnetChannel::CafeOS => {
let data = message.buffer_as_complete_string();
debug!(lisa.subsystem = "cos", "{}", &data);
if let Some(channel) = state.sdio_telnet_message_hook.as_ref() {
_ = channel.send((SdioControlTelnetChannel::CafeOS, data)).await;
}
}
SdioControlTelnetChannel::DevkitMsg => {
return handle_devkit_msg(
state.active_hook.clone(),
state.boot_type,
&message.buffer_as_complete_string(),
state.sdio_telnet_message_hook.as_ref(),
)
.await;
}
SdioControlTelnetChannel::SysConfigTool => {
let Some(mut buff) = SDIO_PRINTF_BUFFS
.get_async(&(stream_id.to_raw(), message.channel()))
.await
else {
return Err(SdioNetworkError::PrintfMissingBuffer(stream_id.to_raw()).into());
};
for item in message.buffer() {
match item {
SdioControlTelnetData::Backspace => {
_ = buff.pop();
}
SdioControlTelnetData::ClearLine => {
*buff = String::new();
}
SdioControlTelnetData::StringData(bytes) => {
buff.push_str(&String::from_utf8_lossy(bytes));
}
}
}
while let Some(idx) = buff.find('\r') {
let (my_line, new_buff) = buff.split_at(idx);
let trimmed_line = my_line.trim();
if !trimmed_line.is_empty()
&& !trimmed_line.starts_with('#')
&& !trimmed_line.starts_with("Note")
&& !trimmed_line
.contains("Please press F12 or the Backtick key to enter command.")
{
info!(lisa.subsystem = "sct", "{}", trimmed_line);
}
if let Some(channel) = state.sdio_telnet_message_hook.as_ref() {
_ = channel
.send((
SdioControlTelnetChannel::SysConfigTool,
trimmed_line.to_owned(),
))
.await;
}
*buff = new_buff[1..].to_owned();
}
}
}
}
Ok(None)
}
async fn handle_devkit_msg(
active_marker: Option<Arc<Mutex<Option<OneshotSender<()>>>>>,
boot_type: MionBootType,
msg: &str,
hook: Option<&BoundedSender<(SdioControlTelnetChannel, String)>>,
) -> Result<Option<Bytes>, CatBridgeError> {
if msg == "CAFE_BOOT_MODE" {
if let Some(marker_ref) = active_marker {
let mut lock = marker_ref.lock().await;
if let Some(channel) = lock.take() {
channel
.send(())
.map_err(|()| CatBridgeError::ClosedChannel)?;
}
}
return Ok(Some(Bytes::try_from(SdioControlMessageRequest::new(
vec![SdioControlTelnetMessage::new(
match boot_type {
MionBootType::DUAL | MionBootType::PCFS | MionBootType::NAND => {
format!("{boot_type}")
}
MionBootType::Unk(_) => format!("{}", MionBootType::PCFS),
},
SdioControlTelnetChannel::DevkitMsg,
)?],
))?));
}
if let Some(channel) = hook {
_ = channel
.send((
SdioControlTelnetChannel::DevkitMsg,
msg.trim_end().to_owned(),
))
.await;
}
info!(lisa.subsystem = "dkm", "{}", msg.trim_end());
Ok(None)
}