use super::{
Arc, Decided, LysSession, MembershipAction, MembershipGate, MembershipRefusal,
MembershipSubject, bump,
};
#[derive(Debug, Default)]
pub struct ConnectionMembership {
pub(super) bound: Option<BoundMembership>,
}
#[derive(Debug)]
pub(super) struct BoundMembership {
pub(super) gate: Arc<MembershipGate>,
pub(super) pid: u64,
pub(super) holder: Result<MembershipSubject, MembershipRefusal>,
}
impl ConnectionMembership {
#[must_use]
pub fn bind(
gate: Option<Arc<MembershipGate>>,
pid: u64,
session: Option<&dyn LysSession>,
) -> Self {
Self {
bound: gate.map(|gate| BoundMembership {
gate,
pid,
holder: session.map_or(
Err(MembershipRefusal::HolderUnverified),
MembershipSubject::from_session,
),
}),
}
}
#[must_use]
pub fn gate(&self) -> Option<&Arc<MembershipGate>> {
self.bound.as_ref().map(|bound| &bound.gate)
}
pub fn holder(&self) -> Result<&MembershipSubject, MembershipRefusal> {
self.bound
.as_ref()
.ok_or(MembershipRefusal::HolderUnverified)
.and_then(|bound| bound.holder.as_ref().map_err(Clone::clone))
}
pub fn admit_connect(&self) -> Result<Option<u64>, MembershipRefusal> {
let Some(bound) = &self.bound else {
return Ok(None);
};
if bound.gate.connect_workspace.is_none() {
return Ok(None);
}
let holder = bound.holder.as_ref().map_err(Clone::clone)?;
bound.gate.admit_connect(holder)
}
pub fn check(
&self,
channel: &str,
action: MembershipAction,
) -> Result<Decided, MembershipRefusal> {
self.decide(channel, action, false)
}
pub fn release(
&self,
channel: &str,
action: MembershipAction,
) -> Result<Decided, MembershipRefusal> {
self.decide(channel, action, true)
}
pub fn release_conversation(&self, conversation: u64) -> Result<Decided, MembershipRefusal> {
let Some(bound) = &self.bound else {
return Ok(Decided::NotProtected);
};
let Some(channel) = bound.gate.channel_for_conversation(conversation) else {
return Ok(Decided::NotProtected);
};
self.decide(channel, MembershipAction::Read, true)
}
pub(super) fn decide(
&self,
channel: &str,
action: MembershipAction,
release: bool,
) -> Result<Decided, MembershipRefusal> {
let Some(bound) = &self.bound else {
return Ok(Decided::NotProtected);
};
if !bound.gate.protects(channel) {
return Ok(Decided::NotProtected);
}
let holder = match &bound.holder {
Ok(holder) => holder,
Err(refusal) => {
bump(&bound.gate.counters.refused);
return Err(refusal.clone());
}
};
if release {
bound
.gate
.check_release(bound.pid, Some(holder), channel, action)
} else {
bound.gate.check(bound.pid, Some(holder), channel, action)
}
}
}
impl ConnectionMembership {
#[must_use]
pub fn release_check(&self, channel: &str, action: MembershipAction) -> Option<ReleaseCheck> {
let bound = self.bound.as_ref()?;
if !bound.gate.protects(channel) {
return None;
}
let holder = bound.holder.as_ref().ok()?;
Some(ReleaseCheck {
gate: Arc::clone(&bound.gate),
pid: bound.pid,
holder: holder.clone(),
channel: channel.to_owned(),
action,
})
}
}
#[derive(Clone, Debug)]
pub struct ReleaseCheck {
pub(super) gate: Arc<MembershipGate>,
pub(super) pid: u64,
pub(super) holder: MembershipSubject,
pub(super) channel: String,
pub(super) action: MembershipAction,
}
impl ReleaseCheck {
pub fn check(&self) -> Result<Decided, MembershipRefusal> {
self.gate
.check_release(self.pid, Some(&self.holder), &self.channel, self.action)
}
}
impl Drop for ConnectionMembership {
fn drop(&mut self) {
if let Some(bound) = &self.bound {
bound.gate.release_connection(bound.pid);
}
}
}