use std::net::SocketAddr;
use std::time::{Duration, Instant};
use super::packets_array::*;
use toxcore::crypto_core::*;
use toxcore::dht::packet::*;
use toxcore::time::*;
pub const CRYPTO_SEND_PACKET_INTERVAL: u64 = 1;
pub const MAX_NUM_SENDPACKET_TRIES: u8 = 8;
pub const UDP_DIRECT_TIMEOUT: u64 = 8;
pub const DEFAULT_RTT: u64 = 1000;
pub const TCP_RTT: u64 = 500;
#[derive(Clone, Debug, Eq, PartialEq)]
enum StatusPacketEnum {
CookieRequest(CookieRequest),
CryptoHandshake(CryptoHandshake),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StatusPacket {
packet: StatusPacketEnum,
pub sent_time: Instant,
pub num_sent: u8
}
impl StatusPacket {
pub fn new_cookie_request(packet: CookieRequest) -> StatusPacket {
StatusPacket {
packet: StatusPacketEnum::CookieRequest(packet),
sent_time: clock_now(),
num_sent: 0
}
}
pub fn new_crypto_handshake(packet: CryptoHandshake) -> StatusPacket {
StatusPacket {
packet: StatusPacketEnum::CryptoHandshake(packet),
sent_time: clock_now(),
num_sent: 0
}
}
pub fn dht_packet(&self) -> DhtPacket {
match self.packet {
StatusPacketEnum::CookieRequest(ref packet) => DhtPacket::CookieRequest(packet.clone()),
StatusPacketEnum::CryptoHandshake(ref packet) => DhtPacket::CryptoHandshake(packet.clone()),
}
}
fn is_time_elapsed(&self) -> bool {
clock_elapsed(self.sent_time) > Duration::from_secs(CRYPTO_SEND_PACKET_INTERVAL)
}
pub fn should_be_sent(&self) -> bool {
self.num_sent == 0 || self.is_time_elapsed() && self.num_sent < MAX_NUM_SENDPACKET_TRIES
}
pub fn is_timed_out(&self) -> bool {
self.num_sent >= MAX_NUM_SENDPACKET_TRIES && self.is_time_elapsed()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ConnectionStatus {
CookieRequesting {
cookie_request_id: u64,
packet: StatusPacket,
},
HandshakeSending {
sent_nonce: Nonce,
packet: StatusPacket,
},
NotConfirmed {
sent_nonce: Nonce,
received_nonce: Nonce,
peer_session_pk: PublicKey,
session_precomputed_key: PrecomputedKey,
packet: StatusPacket,
},
Established {
sent_nonce: Nonce,
received_nonce: Nonce,
peer_session_pk: PublicKey,
session_precomputed_key: PrecomputedKey,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SentPacket {
pub data: Vec<u8>,
pub sent_time: Instant,
pub requested: bool
}
impl SentPacket {
pub fn new(data: Vec<u8>) -> SentPacket {
SentPacket {
data,
sent_time: clock_now(),
requested: false
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RecvPacket {
pub data: Vec<u8>
}
impl RecvPacket {
pub fn new(data: Vec<u8>) -> RecvPacket {
RecvPacket {
data
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CryptoConnection {
pub dht_precomputed_key: PrecomputedKey,
pub peer_real_pk: PublicKey,
pub peer_dht_pk: PublicKey,
pub session_sk: SecretKey,
pub session_pk: PublicKey,
pub status: ConnectionStatus,
pub udp_addr: Option<SocketAddr>, pub udp_received_time: Option<Instant>,
pub udp_send_attempt_time: Option<Instant>,
pub send_array: PacketsArray<SentPacket>,
pub recv_array: PacketsArray<RecvPacket>,
pub rtt: Duration,
}
impl CryptoConnection {
pub fn new(dht_sk: SecretKey, dht_pk: PublicKey, real_pk: PublicKey, peer_real_pk: PublicKey, peer_dht_pk: PublicKey) -> CryptoConnection {
let dht_precomputed_key = precompute(&peer_dht_pk, &dht_sk);
let (session_pk, session_sk) = gen_keypair();
let cookie_request_id = random_u64();
let cookie_request_payload = CookieRequestPayload {
pk: real_pk,
id: cookie_request_id
};
let cookie_request = CookieRequest::new(&dht_precomputed_key, &dht_pk, cookie_request_payload);
let status = ConnectionStatus::CookieRequesting {
cookie_request_id,
packet: StatusPacket::new_cookie_request(cookie_request)
};
CryptoConnection {
dht_precomputed_key,
peer_real_pk,
peer_dht_pk,
session_sk,
session_pk,
status,
udp_addr: None,
udp_received_time: None,
udp_send_attempt_time: None,
send_array: PacketsArray::new(),
recv_array: PacketsArray::new(),
rtt: Duration::from_millis(DEFAULT_RTT),
}
}
pub fn new_not_confirmed(
dht_sk: SecretKey,
peer_real_pk: PublicKey,
peer_dht_pk: PublicKey,
received_nonce: Nonce,
peer_session_pk: PublicKey,
cookie: EncryptedCookie,
symmetric_key: &secretbox::Key
) -> CryptoConnection {
let dht_precomputed_key = precompute(&peer_dht_pk, &dht_sk);
let (session_pk, session_sk) = gen_keypair();
let sent_nonce = gen_nonce();
let our_cookie = Cookie::new(peer_real_pk, peer_dht_pk);
let our_encrypted_cookie = EncryptedCookie::new(symmetric_key, our_cookie);
let handshake_payload = CryptoHandshakePayload {
base_nonce: sent_nonce,
session_pk,
cookie_hash: cookie.hash(),
cookie: our_encrypted_cookie,
};
let handshake = CryptoHandshake::new(&dht_precomputed_key, handshake_payload, cookie);
let status = ConnectionStatus::NotConfirmed {
sent_nonce,
received_nonce,
peer_session_pk,
session_precomputed_key: precompute(&peer_session_pk, &session_sk),
packet: StatusPacket::new_crypto_handshake(handshake)
};
CryptoConnection {
dht_precomputed_key,
peer_real_pk,
peer_dht_pk,
session_sk,
session_pk,
status,
udp_addr: None,
udp_received_time: None,
udp_send_attempt_time: None,
send_array: PacketsArray::new(),
recv_array: PacketsArray::new(),
rtt: Duration::from_millis(DEFAULT_RTT),
}
}
pub fn packet_to_send(&mut self) -> Option<DhtPacket> {
match self.status {
ConnectionStatus::CookieRequesting { ref mut packet, .. }
| ConnectionStatus::HandshakeSending { ref mut packet, .. }
| ConnectionStatus::NotConfirmed { ref mut packet, .. } => {
if packet.should_be_sent() {
packet.num_sent += 1;
packet.sent_time = clock_now();
Some(packet.dht_packet())
} else {
None
}
},
ConnectionStatus::Established { .. } => None,
}
}
pub fn is_timed_out(&self) -> bool {
match self.status {
ConnectionStatus::CookieRequesting { ref packet, .. }
| ConnectionStatus::HandshakeSending { ref packet, .. }
| ConnectionStatus::NotConfirmed { ref packet, .. } => packet.is_timed_out(),
ConnectionStatus::Established { .. } => false, }
}
pub fn update_udp_received_time(&mut self) {
self.udp_received_time = Some(clock_now())
}
pub fn update_udp_send_attempt_time(&mut self) {
self.udp_send_attempt_time = Some(clock_now())
}
pub fn is_udp_alive(&self) -> bool {
self.udp_received_time
.map(|time| clock_elapsed(time) < Duration::from_secs(UDP_DIRECT_TIMEOUT))
.unwrap_or(false)
}
pub fn udp_attempt_should_be_made(&self) -> bool {
self.udp_send_attempt_time
.map(|time| clock_elapsed(time) >= Duration::from_secs(UDP_DIRECT_TIMEOUT / 2))
.unwrap_or(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio_executor;
use tokio_timer::clock::*;
use toxcore::time::ConstNow;
#[test]
fn status_packet_should_be_sent() {
let mut packet = StatusPacket::new_cookie_request(CookieRequest {
pk: gen_keypair().0,
nonce: gen_nonce(),
payload: vec![42; 88]
});
assert!(packet.should_be_sent());
packet.num_sent += 1;
assert!(!packet.should_be_sent());
let mut enter = tokio_executor::enter().unwrap();
let clock = Clock::new_with_now(ConstNow(
packet.sent_time + Duration::from_secs(CRYPTO_SEND_PACKET_INTERVAL + 1)
));
with_default(&clock, &mut enter, |_| {
assert!(packet.should_be_sent());
packet.num_sent += MAX_NUM_SENDPACKET_TRIES;
assert!(!packet.should_be_sent());
});
}
#[test]
fn status_packet_is_timed_out() {
let mut packet = StatusPacket::new_cookie_request(CookieRequest {
pk: gen_keypair().0,
nonce: gen_nonce(),
payload: vec![42; 88]
});
assert!(!packet.is_timed_out());
packet.num_sent += MAX_NUM_SENDPACKET_TRIES;
let mut enter = tokio_executor::enter().unwrap();
let clock = Clock::new_with_now(ConstNow(
packet.sent_time + Duration::from_secs(CRYPTO_SEND_PACKET_INTERVAL + 1)
));
with_default(&clock, &mut enter, |_| {
assert!(packet.is_timed_out());
});
}
#[test]
fn sent_packet_clone() {
let sent_packet = SentPacket::new(vec![42; 123]);
let sent_packet_c = sent_packet.clone();
assert_eq!(sent_packet_c, sent_packet);
}
#[test]
fn recv_packet_clone() {
let recv_packet = RecvPacket::new(vec![42; 123]);
let recv_packet_c = recv_packet.clone();
assert_eq!(recv_packet_c, recv_packet);
}
#[test]
fn crypto_connection_clone() {
let (dht_pk, dht_sk) = gen_keypair();
let (real_pk, _real_sk) = gen_keypair();
let (peer_dht_pk, _peer_dht_sk) = gen_keypair();
let (peer_real_pk, _peer_real_sk) = gen_keypair();
let mut connection = CryptoConnection::new(dht_sk, dht_pk, real_pk, peer_real_pk, peer_dht_pk);
let connection_c = connection.clone();
assert_eq!(connection_c, connection);
let crypto_handshake = CryptoHandshake {
cookie: EncryptedCookie {
nonce: secretbox::gen_nonce(),
payload: vec![42; 88]
},
nonce: gen_nonce(),
payload: vec![42; 248]
};
connection.status = ConnectionStatus::HandshakeSending {
sent_nonce: gen_nonce(),
packet: StatusPacket::new_crypto_handshake(crypto_handshake.clone())
};
let connection_c = connection.clone();
assert_eq!(connection_c, connection);
let (peer_session_pk, _peer_session_sk) = gen_keypair();
let (_session_pk, session_sk) = gen_keypair();
let session_precomputed_key = precompute(&peer_session_pk, &session_sk);
connection.status = ConnectionStatus::NotConfirmed {
sent_nonce: gen_nonce(),
received_nonce: gen_nonce(),
peer_session_pk,
session_precomputed_key,
packet: StatusPacket::new_crypto_handshake(crypto_handshake),
};
let connection_c = connection.clone();
assert_eq!(connection_c, connection);
let (peer_session_pk, _peer_session_sk) = gen_keypair();
let (_session_pk, session_sk) = gen_keypair();
let session_precomputed_key = precompute(&peer_session_pk, &session_sk);
connection.status = ConnectionStatus::Established {
sent_nonce: gen_nonce(),
received_nonce: gen_nonce(),
peer_session_pk,
session_precomputed_key,
};
let connection_c = connection.clone();
assert_eq!(connection_c, connection);
}
}