use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
use crate::SessionId;
use crate::constants::{MAX_CLIENT_BACKLOG_BYTES, MAX_OUTPUT_CHUNK_BYTES};
use crate::outbox::Outbox;
use crate::pal::ids::{ConnId, PtyId};
use crate::pal::transport::Transport;
use crate::protocol::Message;
pub(super) struct Shared<T: Transport, C> {
pub(super) transport: T,
pub(super) pty_host: C,
pub(super) pty: PtyId,
pub(super) session_id: SessionId,
pub(super) client: Mutex<Option<Client<T>>>,
pub(super) attach: Mutex<()>,
pub(super) attached_generation: Arc<AtomicU64>,
pub(super) preamble: Mutex<Option<Vec<u8>>>,
pub(super) first_attach: Mutex<FirstAttach>,
pub(super) first_attach_changed: Condvar,
pub(super) stopping: AtomicBool,
}
pub(super) struct Client<T: Transport> {
pub(super) conn: ConnId,
pub(super) outbox: Arc<Outbox<T>>,
}
impl<T: Transport> Clone for Client<T> {
fn clone(&self) -> Self {
Self {
conn: self.conn,
outbox: Arc::clone(&self.outbox),
}
}
}
#[derive(Debug, Default)]
pub(super) struct FirstAttach {
pub(super) claimed: bool,
pub(super) initiator_gone: bool,
}
impl<T: Transport, C> Shared<T, C> {
pub(super) fn first_attach(&self) -> MutexGuard<'_, FirstAttach> {
self.first_attach
.lock()
.expect("first-attach flags are only set, never held across a panic")
}
#[cfg_attr(test, mutants::skip)]
pub(super) fn note_claimed(&self) {
self.first_attach().claimed = true;
self.first_attach_changed.notify_all();
}
#[cfg_attr(test, mutants::skip)]
pub(super) fn note_initiator_gone(&self) {
self.first_attach().initiator_gone = true;
self.first_attach_changed.notify_all();
}
#[cfg_attr(test, mutants::skip)]
pub(super) fn await_first_attach(&self) {
let mut state = self.first_attach();
while !state.claimed && !state.initiator_gone {
state = self
.first_attach_changed
.wait(state)
.expect("first-attach flags are only set, never held across a panic");
}
}
pub(super) fn client(&self) -> MutexGuard<'_, Option<Client<T>>> {
self.client
.lock()
.expect("client slot is only copied or replaced, never held across a panic")
}
#[cfg_attr(test, mutants::skip)]
pub(super) fn next_attached_generation(&self) -> u64 {
let previous = self
.attached_generation
.try_update(Ordering::SeqCst, Ordering::SeqCst, |generation| {
generation.checked_add(1)
})
.expect("the process cannot perform enough ownership changes to exhaust u64");
previous
.checked_add(1)
.expect("try_update only succeeds when the next generation exists")
}
pub(super) fn hold_for_first_client(&self, bytes: &[u8]) {
let mut preamble = self
.preamble
.lock()
.expect("the preamble is only appended to or taken, never held across a panic");
let Some(held) = preamble.as_mut() else {
return;
};
let free = MAX_CLIENT_BACKLOG_BYTES.saturating_sub(held.len());
held.extend(bytes.iter().take(free));
}
pub(super) fn take_preamble(&self) -> Option<Vec<u8>> {
self.preamble
.lock()
.expect("the preamble is only appended to or taken, never held across a panic")
.take()
.filter(|held| !held.is_empty())
}
}
pub(super) fn preamble_messages(held: &[u8]) -> impl Iterator<Item = Message> + use<'_> {
held.chunks(MAX_OUTPUT_CHUNK_BYTES.get())
.map(|chunk| Message::Output(chunk.to_vec()))
}