use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::vec;
use alloc::vec::Vec;
use crate::envelope::{Body, Envelope};
use crate::error::Error;
use crate::identity::{EndpointIdentity, EndpointSigner};
use crate::types::{
AttemptId, CloseCode, EnvelopeId, FederationId, WebRtcBinding, MAX_SEEN_ENVELOPES, PROTOCOL_ID,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Phase {
Idle,
OfferSent,
OfferReceived,
AnswerSent,
AnswerReceived,
Established,
Closed,
}
#[derive(Debug, Clone, Copy)]
struct NegotiationIds {
offer: EnvelopeId,
answer: Option<EnvelopeId>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Action {
Send(Box<Envelope>),
ApplyOffer {
binding: WebRtcBinding,
description: Vec<u8>,
},
ApplyAnswer {
binding: WebRtcBinding,
description: Vec<u8>,
},
AddCandidate {
sequence: u32,
candidate: Vec<u8>,
},
OpenControlChannel,
DeliverCfr {
envelope_id: EnvelopeId,
remote_endpoint: Box<EndpointIdentity>,
payload: Vec<u8>,
},
CloseTransport {
reason: CloseCode,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ControlChannelConfig {
pub label: &'static str,
pub protocol: &'static str,
pub ordered: bool,
pub reliable: bool,
pub binary: bool,
}
pub const CONTROL_CHANNEL: ControlChannelConfig = ControlChannelConfig {
label: "org.nixort.cfr.fcp.control/1",
protocol: PROTOCOL_ID,
ordered: true,
reliable: true,
binary: true,
};
#[derive(Debug)]
pub struct Connection {
federation: FederationId,
attempt: AttemptId,
local: EndpointIdentity,
remote: EndpointIdentity,
phase: Phase,
negotiation: Option<NegotiationIds>,
seen: VecDeque<EnvelopeId>,
}
impl Connection {
pub fn new(
federation: FederationId,
attempt: AttemptId,
local: EndpointIdentity,
remote: EndpointIdentity,
) -> Result<Self, Error> {
if local == remote {
return Err(Error::SameEndpoint);
}
Ok(Self {
federation,
attempt,
local,
remote,
phase: Phase::Idle,
negotiation: None,
seen: VecDeque::new(),
})
}
pub const fn federation(&self) -> FederationId {
self.federation
}
pub const fn attempt(&self) -> AttemptId {
self.attempt
}
pub const fn local_endpoint(&self) -> EndpointIdentity {
self.local
}
pub const fn remote_endpoint(&self) -> EndpointIdentity {
self.remote
}
pub const fn phase(&self) -> Phase {
self.phase
}
pub fn begin_offer<S: EndpointSigner>(
&mut self,
signer: &S,
binding: WebRtcBinding,
description: Vec<u8>,
) -> Result<Vec<Action>, Error> {
self.require_local_signer(signer)?;
if self.phase != Phase::Idle {
return Err(Error::InvalidState);
}
let offer = Envelope::sign(
signer,
self.federation,
self.attempt,
self.remote,
Body::Offer {
binding,
description,
},
)?;
self.negotiation = Some(NegotiationIds {
offer: offer.id()?,
answer: None,
});
self.phase = Phase::OfferSent;
Ok(vec![
Action::OpenControlChannel,
Action::Send(Box::new(offer)),
])
}
pub fn answer<S: EndpointSigner>(
&mut self,
signer: &S,
binding: WebRtcBinding,
description: Vec<u8>,
) -> Result<Action, Error> {
self.require_local_signer(signer)?;
if self.phase != Phase::OfferReceived {
return Err(Error::InvalidState);
}
let offer_id = self.negotiation.ok_or(Error::InvalidState)?.offer;
let answer = Envelope::sign(
signer,
self.federation,
self.attempt,
self.remote,
Body::Answer {
offer_id,
binding,
description,
},
)?;
self.negotiation = Some(NegotiationIds {
offer: offer_id,
answer: Some(answer.id()?),
});
self.phase = Phase::AnswerSent;
Ok(Action::Send(Box::new(answer)))
}
pub fn candidate<S: EndpointSigner>(
&self,
signer: &S,
sequence: u32,
candidate: Vec<u8>,
) -> Result<Action, Error> {
self.require_local_signer(signer)?;
if !matches!(
self.phase,
Phase::OfferSent
| Phase::OfferReceived
| Phase::AnswerSent
| Phase::AnswerReceived
| Phase::Established
) {
return Err(Error::InvalidState);
}
let ids = self.negotiation.ok_or(Error::InvalidState)?;
let parent_id = ids.answer.unwrap_or(ids.offer);
let envelope = Envelope::sign(
signer,
self.federation,
self.attempt,
self.remote,
Body::Candidate {
parent_id,
sequence,
candidate,
},
)?;
Ok(Action::Send(Box::new(envelope)))
}
pub fn cfr_control<S: EndpointSigner>(
&self,
signer: &S,
payload: Vec<u8>,
) -> Result<Action, Error> {
self.require_local_signer(signer)?;
if self.phase != Phase::Established {
return Err(Error::InvalidState);
}
Ok(Action::Send(Box::new(Envelope::sign(
signer,
self.federation,
self.attempt,
self.remote,
Body::CfrControl { payload },
)?)))
}
pub fn close<S: EndpointSigner>(
&mut self,
signer: &S,
reason: CloseCode,
) -> Result<Action, Error> {
self.require_local_signer(signer)?;
if matches!(self.phase, Phase::Idle | Phase::Closed) {
return Err(Error::InvalidState);
}
self.phase = Phase::Closed;
Ok(Action::Send(Box::new(Envelope::sign(
signer,
self.federation,
self.attempt,
self.remote,
Body::Close { reason },
)?)))
}
pub fn receive(&mut self, envelope: Envelope) -> Result<Vec<Action>, Error> {
self.validate_inbound(&envelope)?;
let id = envelope.id()?;
let remote_endpoint = envelope.sender;
if self.seen.iter().any(|known| known == &id) {
return Ok(Vec::new());
}
let actions = match envelope.body {
Body::Offer {
binding,
description,
} => {
if self.phase == Phase::OfferSent {
return Err(Error::Glare);
}
if self.phase != Phase::Idle {
return Err(Error::InvalidState);
}
self.negotiation = Some(NegotiationIds {
offer: id,
answer: None,
});
self.phase = Phase::OfferReceived;
vec![Action::ApplyOffer {
binding,
description,
}]
}
Body::Answer {
offer_id,
binding,
description,
} => {
if self.phase != Phase::OfferSent
|| self.negotiation.ok_or(Error::InvalidState)?.offer != offer_id
{
return Err(Error::InvalidState);
}
let offer = self.negotiation.ok_or(Error::InvalidState)?.offer;
self.negotiation = Some(NegotiationIds {
offer,
answer: Some(id),
});
self.phase = Phase::AnswerReceived;
vec![Action::ApplyAnswer {
binding,
description,
}]
}
Body::Candidate {
parent_id,
sequence,
candidate,
} => {
self.validate_candidate_parent(parent_id)?;
vec![Action::AddCandidate {
sequence,
candidate,
}]
}
Body::Close { reason } => {
if matches!(self.phase, Phase::Idle | Phase::Closed) {
return Err(Error::InvalidState);
}
self.phase = Phase::Closed;
vec![Action::CloseTransport { reason }]
}
Body::CfrControl { payload } => {
if self.phase != Phase::Established {
return Err(Error::InvalidState);
}
vec![Action::DeliverCfr {
envelope_id: id,
remote_endpoint: Box::new(remote_endpoint),
payload,
}]
}
};
self.note_seen(id);
Ok(actions)
}
pub fn transport_connected(&mut self) -> Result<Vec<Action>, Error> {
if !matches!(self.phase, Phase::AnswerSent | Phase::AnswerReceived) {
return Err(Error::InvalidState);
}
self.phase = Phase::Established;
Ok(Vec::new())
}
pub fn transport_failed(&mut self) -> Result<(), Error> {
if matches!(self.phase, Phase::Idle | Phase::Closed) {
return Err(Error::InvalidState);
}
self.phase = Phase::Closed;
Ok(())
}
fn require_local_signer<S: EndpointSigner>(&self, signer: &S) -> Result<(), Error> {
if signer.endpoint() != self.local {
return Err(Error::WrongLocalSigner);
}
Ok(())
}
fn validate_inbound(&self, envelope: &Envelope) -> Result<(), Error> {
if envelope.federation != self.federation {
return Err(Error::WrongFederation);
}
if envelope.attempt != self.attempt {
return Err(Error::WrongAttempt);
}
if envelope.sender != self.remote {
return Err(Error::WrongSender);
}
if envelope.recipient != self.local {
return Err(Error::WrongRecipient);
}
envelope.verify()
}
fn validate_candidate_parent(&self, parent: EnvelopeId) -> Result<(), Error> {
let ids = self.negotiation.ok_or(Error::InvalidState)?;
if !matches!(
self.phase,
Phase::OfferSent
| Phase::OfferReceived
| Phase::AnswerSent
| Phase::AnswerReceived
| Phase::Established
) {
return Err(Error::InvalidState);
}
if parent != ids.offer && ids.answer != Some(parent) {
return Err(Error::WrongCandidateParent);
}
Ok(())
}
fn note_seen(&mut self, id: EnvelopeId) {
if self.seen.len() == MAX_SEEN_ENVELOPES {
let _ = self.seen.pop_front();
}
self.seen.push_back(id);
}
}