use super::{FrameAction, link_open_frame_response};
use crate::auth_lys::LysPassConsumer;
use crate::auth_lys::lys_pass_adapter::MachineLinkRights;
use crate::auth_lys::session::{ConsumerAdmitter, LysAdmitter};
use crate::link::LinkHost;
use crate::link_handshake::{LinkHandshake, LocalMachine};
use crate::link_pass::fixture::{LINK, Result, adapter, token};
use crate::server::connection::state::ConnectionProcessState;
use crate::server::connection::supervisor::ConnectionSupervisor;
use liminal::protocol::{Frame, LinkEnd, LinkOffer, LinkOpenOutcome};
use lys_core::keys::Ed25519Identity;
use std::io::Write;
use std::sync::{Arc, Mutex};
use zeroize::Zeroizing;
#[test]
fn a_renewed_pass_without_grant_is_named_in_the_wire_refusal() -> Result {
let fixture = crate::link_pass::fixture::Fixture::new(false, false)?;
let error = fixture
.session()?
.open_link(
&crate::link_pass::fixture::Fixture::link(),
crate::link::LinkAction::Export,
crate::link_pass::fixture::NOW,
)
.err()
.ok_or("renewed pass without grant admitted")?;
let reason = super::refusal_text(&error);
assert!(reason.starts_with("renewed-without-grant:"), "{reason}");
assert!(reason.contains("link_import") && reason.contains(LINK));
fixture.finish()
}
#[derive(Clone, Default)]
struct Logs(Arc<Mutex<Vec<u8>>>);
impl Write for Logs {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
self.0
.lock()
.map_err(|_| std::io::Error::other("log lock poisoned"))?
.extend_from_slice(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for Logs {
type Writer = Self;
fn make_writer(&'a self) -> Self {
self.clone()
}
}
fn open(refuse: bool) -> Result<(LinkOpenOutcome, String)> {
let logs = Logs::default();
let subscriber = tracing_subscriber::fmt()
.without_time()
.with_ansi(false)
.with_max_level(tracing::Level::WARN)
.with_writer(logs.clone())
.finish();
let guard = tracing::subscriber::set_default(subscriber);
let adapter = adapter()?;
let local = LocalMachine::new(Ed25519Identity::from_seed(&Zeroizing::new([2; 32])));
let peer = LocalMachine::new(Ed25519Identity::from_seed(&Zeroizing::new([1; 32])));
let pass = token("importer", 0, u64::MAX, true)?;
let rights = MachineLinkRights::new(
&adapter,
adapter.verify_offline(pass.as_bytes(), "liminal", 0)?,
);
let host = Arc::new(LinkHost::new(
local,
Box::new(rights),
std::collections::BTreeMap::from([("exporter".to_owned(), peer.machine())]),
));
let admitter = Arc::new(ConsumerAdmitter::new(adapter));
let peer_pass = token("exporter", 0, u64::MAX, true)?;
let supervisor = ConnectionSupervisor::new()?;
supervisor.install_lys_for_tests(admitter.clone(), "network".to_owned(), host.clone())?;
let state = ConnectionProcessState {
lys_session: Some(Arc::from(admitter.admit(peer_pass.as_bytes(), 0)?)),
..ConnectionProcessState::default()
};
let mut handshake = LinkHandshake::initiator(&peer, host.local().machine())?;
let mut bytes = vec![0; crate::link_handshake::NOISE_OVERHEAD];
let len = handshake.write(&mut bytes)?;
bytes.truncate(len);
let offer = LinkOffer {
link_id: LINK.to_owned(),
end: LinkEnd::Export,
machine: if refuse {
[0; 32]
} else {
*peer.machine().ed25519_public()
},
handshake: bytes,
};
let response = link_open_frame_response(1, &supervisor.runtime_for_test(), &state, 17, &offer);
supervisor.shutdown();
drop(guard);
let FrameAction::Respond(Frame::LinkOpenAck { outcome, .. }) = response else {
return Err("open did not return LinkOpenAck".into());
};
let text = String::from_utf8(logs.0.lock().map_err(|_| "log lock poisoned")?.clone())?;
assert!(!text.contains(pass.as_str()) && !text.contains(peer_pass.as_str()));
Ok((outcome, text))
}
#[test]
fn inbound_open_refusal_warns_once_with_link_action_peer_and_wire_reason() -> Result {
let (outcome, log) = open(true)?;
let LinkOpenOutcome::Refused { reason } = outcome else {
return Err("open was not refused".into());
};
assert_eq!(
log.lines().count(),
1,
"refusing end emitted no single WARN: {log}"
);
assert!(
log.contains("WARN")
&& log.contains(LINK)
&& log.contains("link_import")
&& log.contains("exporter")
&& log.contains(&reason),
"{log}"
);
Ok(())
}
#[test]
fn successful_inbound_open_keeps_the_log_quiet() -> Result {
let (outcome, log) = open(false)?;
assert!(
matches!(outcome, LinkOpenOutcome::Opened { .. }),
"{outcome:?}"
);
assert!(log.is_empty(), "successful open logged: {log}");
Ok(())
}