blob: 6d7c6172c66b2bba3a8808a5b1ccae74404c510f [file] [log] [blame]
// Copyright 2015-2019 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::cmp::Ordering;
use std::sync::RwLock;
use std::time::Instant;
use proto::op::Edns;
pub struct NameServerState(RwLock<NameServerStateInner>);
/// State of a connection with a remote NameServer.
#[derive(Debug)]
enum NameServerStateInner {
/// Initial state, if Edns is not none, then Edns will be requested
Init { send_edns: Option<Edns> },
/// There has been successful communication with the remote.
/// if no Edns is associated, then the remote does not support Edns
Established { remote_edns: Option<Edns> },
/// For some reason the connection failed. For UDP this would generally be a timeout
/// for TCP this could be either Connection could never be established, or it
/// failed at some point after. The Failed state should *not* be entered due to an
/// error contained in a Message received from the server. In All cases to reestablish
/// a new connection will need to be created.
Failed { when: Instant },
}
impl NameServerStateInner {
/// used for ordering purposes. The highest priority is placed on open connections
fn to_usize(&self) -> usize {
match *self {
NameServerStateInner::Init { .. } => 2,
NameServerStateInner::Established { .. } => 3,
NameServerStateInner::Failed { .. } => 1,
}
}
}
impl NameServerState {
/// Set at the new Init state
///
/// If send_dns is some, this will be sent on the first request when it is established
pub fn init(send_edns: Option<Edns>) -> Self {
NameServerState(RwLock::new(NameServerStateInner::Init { send_edns }))
}
/// Transition to the Established state
///
/// If remote_edns is Some, then it will be used to effect things like buffer sizes based on
/// the remote's support.
pub fn establish(&self, remote_edns: Option<Edns>) {
let mut state = self.0.write().expect("poisoned lock");
*state = NameServerStateInner::Established { remote_edns };
}
/// transition to the Failed state
///
/// when is the time of the failure
pub fn fail(&self, when: Instant) {
let mut state = self.0.write().expect("poisoned lock");
*state = NameServerStateInner::Failed { when };
}
/// True if this is in the Failed state
pub(crate) fn is_failed(&self) -> bool {
if let NameServerStateInner::Failed { .. } = *self.0.read().expect("poisoned lock") {
true
} else {
false
}
}
}
impl Ord for NameServerStateInner {
fn cmp(&self, other: &Self) -> Ordering {
let (self_num, other_num) = (self.to_usize(), other.to_usize());
match self_num.cmp(&other_num) {
Ordering::Equal => match (self, other) {
(
NameServerStateInner::Failed {
when: ref self_when,
},
NameServerStateInner::Failed {
when: ref other_when,
},
) => {
// We reverse, because we want the "older" failures to be tried first...
self_when.cmp(other_when).reverse()
}
_ => Ordering::Equal,
},
cmp => cmp,
}
}
}
impl PartialOrd for NameServerStateInner {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for NameServerStateInner {
fn eq(&self, other: &Self) -> bool {
self.to_usize() == other.to_usize()
}
}
impl Eq for NameServerStateInner {}
impl Ord for NameServerState {
fn cmp(&self, other: &Self) -> Ordering {
let other = other.0.read().expect("other poisoned");
self.0.read().expect("self poisoned").cmp(&*other)
}
}
impl PartialOrd for NameServerState {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for NameServerState {
fn eq(&self, other: &Self) -> bool {
self.0.read().expect("self poisoned").to_usize()
== other.0.read().expect("self poisoned").to_usize()
}
}
impl Eq for NameServerState {}
#[cfg(test)]
mod tests {
use super::*;
use crate::name_server::NameServerState;
#[test]
fn test_state_cmp() {
let init = NameServerState::init(None);
let established = NameServerState(RwLock::new(NameServerStateInner::Established {
remote_edns: None,
}));
let failed = NameServerState(RwLock::new(NameServerStateInner::Failed {
when: Instant::now(),
}));
assert_eq!(init.cmp(&init), Ordering::Equal);
assert_eq!(init.cmp(&established), Ordering::Less);
assert_eq!(init.cmp(&failed), Ordering::Greater);
assert_eq!(established.cmp(&established), Ordering::Equal);
assert_eq!(established.cmp(&failed), Ordering::Greater);
assert_eq!(failed.cmp(&failed), Ordering::Equal);
}
}