use super::{PunchError, PunchedLink, Puncher, UdpCarrier, fresh_nonce, require};
use crate::link_handshake::MachineIdentity;
use serde_json::json;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use std::sync::Arc;
impl Puncher {
pub fn rebind_broker(&mut self, mut broker: super::WireBroker) -> Result<(), PunchError> {
broker.subscribe(self.local.machine())?;
self.broker = broker;
Ok(())
}
pub fn prepare_open(&mut self) -> Result<(), PunchError> {
if self.observed.is_some() {
return Ok(());
}
let nonce = fresh_nonce()?;
let mut request = [0; 20];
request[..4].copy_from_slice(b"LMOB");
request[4..].copy_from_slice(&nonce);
let (answer, source) = self
.carrier()?
.observe_raw(&request, self.echo, self.bound)?;
self.observed = Some(observed_endpoint(&nonce, self.echo, source, &answer)?);
self.register()
}
pub fn shared_carrier(&self) -> Result<Arc<UdpCarrier>, PunchError> {
self.carrier
.clone()
.ok_or(PunchError::Named("device_not_observed"))
}
pub fn lookup_addresses(&mut self, peer: MachineIdentity) -> Result<Vec<String>, PunchError> {
self.validate_peer(peer)?;
let value = self.broker.call(
"net.broker.lookup",
&json!({"device": hex::encode(peer.ed25519_public())}),
)?;
serde_json::from_value(
value
.get("addresses")
.cloned()
.ok_or(PunchError::Named("invalid_broker_answer"))?,
)
.map_err(|_| PunchError::Named("invalid_broker_answer"))
}
pub fn refresh(&mut self, peer: MachineIdentity, index: u32) -> Result<(), PunchError> {
self.validate_peer(peer)?;
let lookup = self.broker.call(
"net.broker.lookup",
&json!({"device": hex::encode(peer.ed25519_public())}),
)?;
let endpoint: SocketAddr = serde_json::from_value(
lookup
.get("observed")
.cloned()
.ok_or(PunchError::Named("peer_not_observed"))?,
)
.map_err(|_| PunchError::Named("peer_not_observed"))?;
let nonce = punch_nonce(false)?;
self.probe(endpoint)?;
self.request_punch(peer, &nonce)?;
let word = self.broker.word(peer, Some(&nonce))?;
require(word.observed == Some(endpoint), "peer_observation_changed")?;
self.carrier()?.refresh_endpoint(index, endpoint)?;
Ok(())
}
pub fn accept_refresh(&mut self, peer: MachineIdentity, index: u32) -> Result<(), PunchError> {
self.validate_peer(peer)?;
let word = self.broker.word(peer, None)?;
let endpoint = word
.observed
.ok_or(PunchError::Named("peer_not_observed"))?;
self.probe(endpoint)?;
self.request_punch(peer, &word.nonce)?;
self.carrier()?.refresh_endpoint(index, endpoint)?;
Ok(())
}
pub fn punch(&mut self, peer: MachineIdentity, index: u32) -> Result<PunchedLink, PunchError> {
self.validate_peer(peer)?;
require(self.observed.take().is_some(), "device_not_observed")?;
let nonce = punch_nonce(true)?;
let lookup = self.broker.call(
"net.broker.lookup",
&json!({"device": hex::encode(peer.ed25519_public())}),
)?;
let endpoint: SocketAddr = serde_json::from_value(
lookup
.get("observed")
.cloned()
.ok_or(PunchError::Named("peer_not_observed"))?,
)
.map_err(|_| PunchError::Named("peer_not_observed"))?;
self.probe(endpoint)?;
self.request_punch(peer, &nonce)?;
let word = self.broker.word(peer, Some(&nonce))?;
require(word.observed == Some(endpoint), "peer_observation_changed")?;
self.handshake(peer, endpoint, index)
}
pub fn accept(&mut self, peer: MachineIdentity, index: u32) -> Result<PunchedLink, PunchError> {
self.validate_peer(peer)?;
require(self.observed.take().is_some(), "device_not_observed")?;
let word = self.broker.word(peer, None)?;
let endpoint = word
.observed
.ok_or(PunchError::Named("peer_not_observed"))?;
self.probe(endpoint)?;
self.request_punch(peer, &word.nonce)?;
self.handshake(peer, endpoint, index)
}
pub fn carrier(&self) -> Result<&UdpCarrier, PunchError> {
self.carrier
.as_deref()
.ok_or(PunchError::Named("device_not_observed"))
}
}
pub(super) fn observed_endpoint(
nonce: &[u8; 16],
expected: SocketAddr,
source: SocketAddr,
answer: &[u8],
) -> Result<SocketAddr, PunchError> {
let length = answer.len();
if source != expected || length < 23 || &answer[..4] != b"LMOA" || answer[4..20] != *nonce {
return Err(PunchError::Named("invalid_broker_echo"));
}
let address = match (answer[20], length) {
(4, 27) => SocketAddr::from((
Ipv4Addr::new(answer[21], answer[22], answer[23], answer[24]),
u16::from_be_bytes([answer[25], answer[26]]),
)),
(6, 39) => SocketAddr::from((
Ipv6Addr::from(
<[u8; 16]>::try_from(&answer[21..37])
.map_err(|_| PunchError::Named("invalid_broker_echo"))?,
),
u16::from_be_bytes([answer[37], answer[38]]),
)),
_ => return Err(PunchError::Named("invalid_broker_echo")),
};
require(address.port() != 0, "invalid_broker_echo")?;
Ok(address)
}
#[derive(Debug)]
pub struct Instruction {
pub peer: MachineIdentity,
pub endpoint: SocketAddr,
pub nonce: String,
}
impl Puncher {
pub fn fork_listener(
&self,
local: super::LocalMachine,
mut broker: super::WireBroker,
) -> Result<Self, PunchError> {
broker.subscribe(local.machine())?;
broker.words.set_read_timeout(None)?;
Ok(Self {
local,
socket: Arc::clone(&self.socket),
broker,
echo: self.echo,
bound: self.bound,
observed: self.observed,
carrier: Some(self.shared_carrier()?),
attempted: true,
requests: Arc::clone(&self.requests),
})
}
pub fn next_instruction(&mut self) -> Result<Instruction, PunchError> {
loop {
let Some(liminal::protocol::Frame::Deliver {
stream_id: 1,
envelope,
..
}) = crate::link_carrier::read_frame(&mut self.broker.words)?
else {
return Err(PunchError::Named("punch_word_missing"));
};
require(
Some(envelope.schema_id) == self.broker.schema,
"invalid_punch_schema",
)?;
let word: super::Word = serde_json::from_slice(&envelope.payload)
.map_err(|_| PunchError::Named("invalid_punch_word"))?;
let mut key = [0; 32];
hex::decode_to_slice(&word.from, &mut key)
.map_err(|_| PunchError::Named("invalid_punch_peer"))?;
require(
hex::decode(&word.nonce).is_ok_and(|nonce| nonce.len() == 16),
"invalid_punch_nonce",
)?;
if self
.requests
.lock()
.map_err(|_| PunchError::Named("punch_request_state_poisoned"))?
.contains(&word.nonce)
{
continue;
}
let peer = MachineIdentity::new(key);
self.validate_peer(peer)?;
let endpoint = word
.observed
.ok_or(PunchError::Named("peer_not_observed"))?;
require(endpoint.port() != 0, "peer_not_observed")?;
return Ok(Instruction {
peer,
endpoint,
nonce: word.nonce,
});
}
}
pub fn answer_instruction(
&mut self,
word: &Instruction,
index: u32,
refresh: bool,
) -> Result<(), PunchError> {
self.probe(word.endpoint)?;
self.request_punch(word.peer, &word.nonce)?;
if refresh {
self.carrier()?.refresh_peer(word.peer, word.endpoint)?;
} else {
self.handshake(word.peer, word.endpoint, index)?;
}
Ok(())
}
}
fn punch_nonce(opening: bool) -> Result<String, PunchError> {
let mut nonce = fresh_nonce()?;
if opening {
nonce[0] |= 128;
} else {
nonce[0] &= 127;
}
Ok(hex::encode(nonce))
}
impl Instruction {
pub(crate) fn opening(&self) -> bool {
matches!(
self.nonce.as_bytes().first(),
Some(b'8'..=b'9' | b'a'..=b'f' | b'A'..=b'F')
)
}
}