use super::{spawn_server_task, BLAZE_PORT, REDIRECTOR_PORT};
use crate::fire::{FireCodec, Frame};
use blaze_ssl_async::{BlazeAccept, BlazeListener};
use futures::{SinkExt, TryStreamExt};
use log::{debug, error};
use std::{io, net::Ipv4Addr, time::Duration};
use tdf::TdfSerialize;
use thiserror::Error;
use tokio::time::{error::Elapsed, timeout};
use tokio_util::codec::Framed;
pub async fn start_redirector_server() -> std::io::Result<()> {
let listener =
BlazeListener::bind((Ipv4Addr::LOCALHOST, REDIRECTOR_PORT), Default::default()).await?;
loop {
let client_accept = listener.accept().await?;
spawn_server_task(async move {
debug!("Redirector connection");
if let Err(err) = handle(client_accept).await {
error!("Error while redirecting: {}", err);
}
});
}
}
#[derive(Debug, Error)]
pub enum RedirectError {
#[error("Accept error: {0}")]
Accept(io::Error),
#[error("Timed out")]
Timeout(Elapsed),
#[error("Read error: {0}")]
Read(io::Error),
#[error("Write error: {0}")]
Write(io::Error),
}
const REDIRECT_TIMEOUT: Duration = Duration::from_secs(60);
const COMPONENT_REDIRECTOR: u16 = 0x5;
const COMMAND_GET_SERVER_INSTANCE: u16 = 0x1;
async fn handle(client_accept: BlazeAccept) -> Result<(), RedirectError> {
let (stream, _) = client_accept
.finish_accept()
.await
.map_err(RedirectError::Accept)?;
debug!("Accepted redirect connection");
let mut framed = Framed::new(stream, FireCodec::default());
while let Some(packet) = timeout(REDIRECT_TIMEOUT, framed.try_next())
.await
.map_err(RedirectError::Timeout)?
.map_err(RedirectError::Read)?
{
let header = &packet.header;
if header.component != COMPONENT_REDIRECTOR || header.command != COMMAND_GET_SERVER_INSTANCE
{
debug!(
"Redirector got unexpected request {} {}",
header.component, header.command
);
framed
.send(Frame::response_empty(header))
.await
.map_err(RedirectError::Write)?;
continue;
}
debug!("Redirector responding");
framed
.send(Frame::response(header, LocalInstanceResponse))
.await
.map_err(RedirectError::Write)?;
break;
}
Ok(())
}
struct LocalInstanceResponse;
impl TdfSerialize for LocalInstanceResponse {
fn serialize<S: tdf::prelude::TdfSerializer>(&self, w: &mut S) {
w.tag_union_start(b"ADDR", 0x0);
w.group(b"VALU", |w| {
w.tag_u32(b"IP", u32::from_be_bytes([127, 0, 0, 1]));
w.tag_u16(b"PORT", BLAZE_PORT);
});
w.tag_bool(b"SECU", false);
w.tag_bool(b"XDNS", false);
}
}