use super::{
super::peer_id::PeerId,
super::read_write::ReadWrite,
established::ConnectionPrototype,
multistream_select,
noise::{self, NoiseKey},
yamux,
};
use alloc::boxed::Box;
use core::fmt;
mod tests;
#[derive(Debug, derive_more::From)]
pub enum Handshake {
Healthy(HealthyHandshake),
Success {
remote_peer_id: PeerId,
connection: ConnectionPrototype,
},
}
impl Handshake {
pub fn noise_yamux(
noise_key: &NoiseKey,
noise_ephemeral_secret_key: &[u8; 32],
is_initiator: bool,
) -> Self {
HealthyHandshake::noise_yamux(noise_key, noise_ephemeral_secret_key, is_initiator).into()
}
}
pub struct HealthyHandshake {
state: NegotiationState,
}
enum NegotiationState {
EncryptionProtocol {
negotiation: multistream_select::InProgress<&'static str>,
handshake: noise::HandshakeInProgress,
},
Encryption {
handshake: noise::HandshakeInProgress,
},
Multiplexing {
peer_id: PeerId,
encryption: Box<noise::Noise>,
negotiation: multistream_select::InProgress<&'static str>,
},
}
impl HealthyHandshake {
pub fn noise_yamux(
noise_key: &NoiseKey,
noise_ephemeral_secret_key: &[u8; 32],
is_initiator: bool,
) -> Self {
let negotiation = multistream_select::InProgress::new(if is_initiator {
multistream_select::Config::Dialer {
requested_protocol: noise::PROTOCOL_NAME,
}
} else {
multistream_select::Config::Listener {
max_protocol_name_len: noise::PROTOCOL_NAME.len(),
}
});
HealthyHandshake {
state: NegotiationState::EncryptionProtocol {
negotiation,
handshake: noise::HandshakeInProgress::new(noise::Config {
key: noise_key,
is_initiator,
prologue: &[],
ephemeral_secret_key: noise_ephemeral_secret_key,
}),
},
}
}
pub fn read_write<TNow: Clone>(
mut self,
read_write: &mut ReadWrite<TNow>,
) -> Result<Handshake, HandshakeError> {
loop {
match self.state {
NegotiationState::EncryptionProtocol {
negotiation,
handshake,
} => {
let updated = negotiation
.read_write(read_write)
.map_err(HandshakeError::EncryptionMultistreamSelect)?;
return match updated {
multistream_select::Negotiation::InProgress(updated) => {
Ok(Handshake::Healthy(HealthyHandshake {
state: NegotiationState::EncryptionProtocol {
negotiation: updated,
handshake,
},
}))
}
multistream_select::Negotiation::Success => {
self.state = NegotiationState::Encryption { handshake };
continue;
}
multistream_select::Negotiation::ListenerAcceptOrDeny(accept_reject) => {
let negotiation =
if accept_reject.requested_protocol() == noise::PROTOCOL_NAME {
accept_reject.accept()
} else {
accept_reject.reject()
};
self.state = NegotiationState::EncryptionProtocol {
negotiation,
handshake,
};
continue;
}
multistream_select::Negotiation::NotAvailable => {
Err(HandshakeError::NoEncryptionProtocol)
}
};
}
NegotiationState::Encryption { handshake } => {
let updated = handshake.read_write(read_write).map_err(|err| {
debug_assert!(!matches!(err, noise::HandshakeError::WriteClosed));
HandshakeError::NoiseHandshake(err)
})?;
match updated {
noise::NoiseHandshake::Success {
cipher,
remote_peer_id,
} => {
let negotiation =
multistream_select::InProgress::new(if cipher.is_initiator() {
multistream_select::Config::Dialer {
requested_protocol: yamux::PROTOCOL_NAME,
}
} else {
multistream_select::Config::Listener {
max_protocol_name_len: yamux::PROTOCOL_NAME.len(),
}
});
self.state = NegotiationState::Multiplexing {
peer_id: remote_peer_id,
encryption: Box::new(cipher),
negotiation,
};
continue;
}
noise::NoiseHandshake::InProgress(updated) => {
return Ok(Handshake::Healthy(HealthyHandshake {
state: NegotiationState::Encryption { handshake: updated },
}));
}
};
}
NegotiationState::Multiplexing {
negotiation,
mut encryption,
peer_id,
} => {
if read_write.expected_incoming_bytes.is_none() {
return Err(HandshakeError::MultiplexingMultistreamSelect(
multistream_select::Error::ReadClosed,
));
}
if read_write.write_bytes_queueable.is_none() {
return Err(HandshakeError::MultiplexingMultistreamSelect(
multistream_select::Error::WriteClosed,
));
}
let negotiation_update = {
let mut decrypted_stream = encryption
.read_write(read_write)
.map_err(HandshakeError::Noise)?;
negotiation
.read_write(&mut *decrypted_stream)
.map_err(HandshakeError::MultiplexingMultistreamSelect)?
};
return match negotiation_update {
multistream_select::Negotiation::InProgress(updated) => {
Ok(Handshake::Healthy(HealthyHandshake {
state: NegotiationState::Multiplexing {
negotiation: updated,
encryption,
peer_id,
},
}))
}
multistream_select::Negotiation::ListenerAcceptOrDeny(accept_reject) => {
let negotiation =
if accept_reject.requested_protocol() == yamux::PROTOCOL_NAME {
accept_reject.accept()
} else {
accept_reject.reject()
};
self.state = NegotiationState::Multiplexing {
peer_id,
encryption,
negotiation,
};
continue;
}
multistream_select::Negotiation::Success => Ok(Handshake::Success {
connection: ConnectionPrototype::from_noise_yamux(*encryption),
remote_peer_id: peer_id,
}),
multistream_select::Negotiation::NotAvailable => {
Err(HandshakeError::NoMultiplexingProtocol)
}
};
}
}
}
}
}
impl fmt::Debug for HealthyHandshake {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("HealthyHandshake").finish()
}
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum HandshakeError {
#[display("Encryption protocol selection error: {_0}")]
EncryptionMultistreamSelect(multistream_select::Error),
#[display("Multiplexing protocol selection error: {_0}")]
MultiplexingMultistreamSelect(multistream_select::Error),
#[display("Noise handshake error: {_0}")]
NoiseHandshake(noise::HandshakeError),
NoEncryptionProtocol,
NoMultiplexingProtocol,
#[display("Noise cipher error: {_0}")]
Noise(noise::CipherError),
}