use crate::{
errors::{APIError, CatBridgeError, FSError},
fsemul::{HostFilesystem, dlf::DiskLayoutFile},
net::{
DEFAULT_CAT_DEV_CHUNK_SIZE, DEFAULT_CAT_DEV_SLOWDOWN,
additions::{RequestIDLayer, StreamIDLayer},
server::{
Router, TCPServer,
requestable::{Body, State},
},
},
};
use bytes::{BufMut, Bytes, BytesMut};
use local_ip_address::local_ip;
use std::{
net::{IpAddr, Ipv4Addr, SocketAddrV4},
time::Duration,
};
use tokio::{
fs::{File, read as fs_read},
io::{AsyncReadExt, AsyncSeekExt, SeekFrom},
};
use tower::ServiceBuilder;
use tracing::debug;
use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
pub const DEFAULT_ATAPI_PORT: u16 = 7974_u16;
#[derive(Clone, Debug)]
pub struct AtapiServerBuilder {
address: Option<Ipv4Addr>,
cat_dev_sleep_override: Option<Duration>,
chunk_override: Option<usize>,
fully_disable_cat_dev_sleep: bool,
fully_disable_chunk_override: bool,
host_filesystem: HostFilesystem,
port: Option<u16>,
trace_during_debug: bool,
}
impl AtapiServerBuilder {
#[must_use]
pub const fn new(host_filesystem: HostFilesystem) -> Self {
Self {
address: None,
cat_dev_sleep_override: None,
chunk_override: None,
fully_disable_cat_dev_sleep: false,
fully_disable_chunk_override: false,
host_filesystem,
port: None,
trace_during_debug: false,
}
}
#[must_use]
pub const fn address(&self) -> Option<Ipv4Addr> {
self.address
}
#[must_use]
pub const fn set_address(mut self, new_address: Option<Ipv4Addr>) -> Self {
self.address = new_address;
self
}
#[must_use]
pub const fn cat_dev_sleep_override(&self) -> Option<Duration> {
self.cat_dev_sleep_override
}
#[must_use]
pub const fn set_cat_dev_sleep_override(mut self, new_override: Option<Duration>) -> Self {
self.cat_dev_sleep_override = new_override;
self
}
#[must_use]
pub const fn chunk_override(&self) -> Option<usize> {
self.chunk_override
}
#[must_use]
pub const fn set_chunk_override(mut self, new_override: Option<usize>) -> Self {
self.chunk_override = new_override;
self
}
#[must_use]
pub const fn fully_disable_cat_dev_sleep(&self) -> bool {
self.fully_disable_cat_dev_sleep
}
#[must_use]
pub const fn set_fully_disable_cat_dev_sleep(mut self, new_value: bool) -> Self {
self.fully_disable_cat_dev_sleep = new_value;
self
}
#[must_use]
pub const fn fully_disable_chunking(&self) -> bool {
self.fully_disable_chunk_override
}
#[must_use]
pub const fn set_fully_disable_chunking(mut self, disable_chunk: bool) -> Self {
self.fully_disable_chunk_override = disable_chunk;
self
}
#[must_use]
pub const fn host_filesystem(&self) -> &HostFilesystem {
&self.host_filesystem
}
#[must_use]
pub fn set_host_filesystem(mut self, new: HostFilesystem) -> Self {
self.host_filesystem = new;
self
}
#[must_use]
pub const fn port(&self) -> Option<u16> {
self.port
}
#[must_use]
pub const fn set_port(mut self, new: Option<u16>) -> Self {
self.port = new;
self
}
#[must_use]
pub const fn trace_during_debug(&self) -> bool {
self.trace_during_debug
}
#[must_use]
pub const fn set_trace_during_debug(mut self, trace: bool) -> Self {
self.trace_during_debug = trace;
self
}
pub async fn build(self) -> Result<TCPServer<HostFilesystem>, CatBridgeError> {
let ip = self
.address
.or_else(|| {
local_ip().ok().map(|ip| match ip {
IpAddr::V4(v4) => v4,
IpAddr::V6(_v6) => unreachable!(),
})
})
.ok_or(APIError::NoHostIpFound)?;
let bound_address = SocketAddrV4::new(ip, self.port.unwrap_or(DEFAULT_ATAPI_PORT));
let mut router = Router::<HostFilesystem>::new();
router.fallback_handler(temporary_fallback_handle_all)?;
let mut server = TCPServer::new_with_state(
"atapi",
bound_address,
router,
(None, None),
12,
self.host_filesystem,
self.trace_during_debug,
)
.await?;
if self.trace_during_debug {
server.layer_initial_service(
ServiceBuilder::new()
.layer(RequestIDLayer::new("atapi".to_owned()))
.layer(StreamIDLayer),
);
} else {
server.layer_initial_service(
ServiceBuilder::new().layer(RequestIDLayer::new("atapi".to_owned())),
);
}
server.set_chunk_output_at_size(if self.fully_disable_chunk_override {
None
} else if let Some(over_ride) = self.chunk_override {
Some(over_ride)
} else {
Some(DEFAULT_CAT_DEV_CHUNK_SIZE)
});
server.set_cat_dev_slowdown(if self.fully_disable_cat_dev_sleep {
None
} else if let Some(over_ride) = self.cat_dev_sleep_override {
Some(over_ride)
} else {
Some(DEFAULT_CAT_DEV_SLOWDOWN)
});
Ok(server)
}
}
const ATAPI_SERVER_BUILDER_FIELDS: &[NamedField<'static>] = &[
NamedField::new("address"),
NamedField::new("cat_dev_sleep_override"),
NamedField::new("chunk_override"),
NamedField::new("fully_disable_cat_dev_sleep"),
NamedField::new("fully_disable_chunk_override"),
NamedField::new("host_filesystem"),
NamedField::new("port"),
NamedField::new("trace_during_debug"),
];
impl Structable for AtapiServerBuilder {
fn definition(&self) -> StructDef<'_> {
StructDef::new_static(
"AtapiServerBuilder",
Fields::Named(ATAPI_SERVER_BUILDER_FIELDS),
)
}
}
impl Valuable for AtapiServerBuilder {
fn as_value(&self) -> Value<'_> {
Value::Structable(self)
}
fn visit(&self, visitor: &mut dyn Visit) {
visitor.visit_named_fields(&NamedValues::new(
ATAPI_SERVER_BUILDER_FIELDS,
&[
Valuable::as_value(
&self
.address
.map_or_else(|| "<none>".to_owned(), |ip| format!("{ip}")),
),
Valuable::as_value(
&self
.cat_dev_sleep_override
.map_or_else(|| "<none>".to_owned(), |dur| format!("{}s", dur.as_secs())),
),
Valuable::as_value(&self.chunk_override),
Valuable::as_value(&self.fully_disable_cat_dev_sleep),
Valuable::as_value(&self.fully_disable_chunk_override),
Valuable::as_value(&self.host_filesystem),
Valuable::as_value(&self.port),
Valuable::as_value(&self.trace_during_debug),
],
));
}
}
async fn temporary_fallback_handle_all(
State(fs): State<HostFilesystem>,
Body(packet): Body<Bytes>,
) -> Result<Option<Bytes>, CatBridgeError> {
match &packet[..2] {
[0x3, _] => {
debug!("Would have sent 32 bytes of various descriptions back... not sure which...");
}
[0xCF, 0x80] => {
debug!("ATAPI Event packet sent");
}
[0xF0, _] => {
return Ok(Some(Bytes::from(vec![0x0; 4])));
}
[0xF1, 0x00 | 0x02] => {
return Ok(Some(Bytes::from(vec![0x69; 32])));
}
[0xF1, 0x01 | 0x03] => {
debug!("Unknown 0xF1 packet, doesn't do anything on the network...");
}
[0xF2, _] => {
debug!("Got unknown 0xF2 packet: [{packet:02X?}]");
}
[0xF3, 0x00] => {
return handle_read_dlf(packet, &fs).await;
}
[0xF3, 0x01] => {
let mut data = BytesMut::with_capacity(32);
data.extend_from_slice(b"PC SATA EMUL");
data.extend_from_slice(&[0_u8; 20]);
return Ok(Some(data.freeze()));
}
[0xF3, 0x02 | 0x03] | [0xF5 | 0xF7, _] => {
debug!("Sending empty 32 bytes!");
return Ok(Some(Bytes::from(vec![0x0; 32])));
}
[0xF6, _] => {
if packet[1] & 3 != 0 {
debug!("F6 second byte & 3 != 0, not sending reply!");
} else {
let mut data = BytesMut::with_capacity(4);
data.put_u32_le(1);
debug!("Sent F6 reply!");
return Ok(Some(data.freeze()));
}
}
[0x12, _] => {
debug!("Would have sent 96 bytes of various descriptions back... not sure which...");
}
_ => {}
}
Ok(None)
}
async fn handle_read_dlf(
packet: Bytes,
host_filesystem: &HostFilesystem,
) -> Result<Option<Bytes>, CatBridgeError> {
let read_address = u128::from(u32::from_be_bytes([
packet[0x4],
packet[0x5],
packet[0x6],
packet[0x7],
])) << 11_u128;
let read_length = u128::from(u32::from_be_bytes([
packet[0x8],
packet[0x9],
packet[0xA],
packet[0xB],
])) << 11_u128;
let rl_as_usize =
usize::try_from(read_length).map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?;
debug!(
atapi.packet_type = "read_address",
atapi.read_address.address = %read_address,
atapi.read_address.length = %read_length,
"Handling atapi read request!"
);
let bytes_of_dlf = fs_read(host_filesystem.ppc_boot_dlf_path().await?)
.await
.map_err(FSError::from)?;
let dlf = DiskLayoutFile::try_from(Bytes::from(bytes_of_dlf))?;
if let Some((path, offset)) = dlf.get_path_and_offset_for_file(read_address).await {
let buff = {
let mut handle = File::open(&path).await.map_err(FSError::from)?;
handle
.seek(SeekFrom::Start(offset))
.await
.map_err(FSError::from)?;
let mut file_buff = BytesMut::zeroed(rl_as_usize);
let mut bytes_read = 0;
while bytes_read < rl_as_usize {
let read_this_go = handle
.read(&mut file_buff[bytes_read..])
.await
.map_err(FSError::IO)?;
if read_this_go == 0 {
break;
}
bytes_read += read_this_go;
}
file_buff
};
Ok(Some(buff.freeze()))
} else {
Ok(Some(BytesMut::zeroed(rl_as_usize).freeze()))
}
}