diff --git a/docker-compose.yml b/docker-compose.yml index 40a2abde..9cdb0099 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,7 @@ version: "4" services: - resolver: - build: . - image: dns-rust:latest - ports: - - "58396:58396" - network_mode: "dns" - client: - container_name: client + dns_rust: + container_name: dns_rust build: . image: dns-rust:latest test: diff --git a/src/async_resolver.rs b/src/async_resolver.rs index 4d3fbc6f..588069fd 100644 --- a/src/async_resolver.rs +++ b/src/async_resolver.rs @@ -1,4 +1,5 @@ use std::net::IpAddr; +use std::vec; pub mod config; pub mod lookup; @@ -15,6 +16,7 @@ use crate::message::class_qclass::Qclass; use crate::message::resource_record::ResourceRecord; use crate::async_resolver::{config::ResolverConfig,lookup::LookupFutureStub}; use crate::message::rdata::Rdata; +use crate::message::type_rtype::Rtype; use crate::client::client_connection::ConnectionProtocol; use crate::async_resolver::resolver_error::ResolverError; use crate:: message::type_qtype::Qtype; @@ -93,7 +95,7 @@ impl AsyncResolver { self.config.set_protocol(transport_protocol_struct); let response = self.inner_lookup(domain_name_struct,Qtype::A,Qclass::from_str_to_qclass(qclass)).await; - + let result_rrs = self.parse_dns_msg(response); if let Ok(rrs) = result_rrs { let rrs_iter = rrs.into_iter(); @@ -132,6 +134,9 @@ impl AsyncResolver { let rcode = header.get_rcode(); if rcode == 0 { let answer = dns_mgs.get_answer(); + if answer.len() == 0 { + Err(ClientError::TemporaryError("no answer found"))?; + } return Ok(answer); } match rcode { @@ -166,9 +171,9 @@ impl AsyncResolver { // Cache lookup // Search in cache only if its available if self.config.is_cache_enabled() { - if let Some(cache_lookup) = self.cache.clone().get(domain_name.clone(), Qtype::to_rtype(qtype)) { - println!("[Cached Data]"); - + let rtype_saved = Qtype::to_rtype(qtype); + if let Some(cache_lookup) = self.cache.clone().get(domain_name.clone(), rtype_saved) { + // Create random generator let mut rng = thread_rng(); @@ -184,13 +189,27 @@ impl AsyncResolver { false, query_id ); - - // Add Answer - let answer: Vec = cache_lookup - .iter() - .map(|rr_cache_value| rr_cache_value.get_resource_record()) - .collect::>(); - new_query.set_answer(answer); + + // Get RR from cache + for rr_cache_value in cache_lookup.iter() { + let rr = rr_cache_value.get_resource_record(); + + // Get negative answer + if rtype_saved != rr.get_rtype() { + println!("ADD ADITIONAL NEGATIVE ANSWER SOA"); + let additionals: Vec = vec![rr]; + new_query.add_additionals(additionals); + let mut new_header = new_query.get_header(); + new_header.set_rcode(3); + new_query.set_header(new_header); + } + else { //FIXME: change to alg RFC 1034-1035 + println!("ADD ANSWER CACHE"); + let answer: Vec = vec![rr]; + new_query.set_answer(answer); + } + } + return Ok(new_query) } } @@ -315,7 +334,7 @@ impl AsyncResolver { /// type of response. fn store_data_cache(&mut self, response: DnsMessage) { let truncated = response.get_header().get_tc(); - + self.cache.timeout_cache(); if !truncated { // TODO: RFC 1035: 7.4. Using the cache response.get_answer() @@ -325,11 +344,65 @@ impl AsyncResolver { self.cache.add(rr.get_name(), rr.clone()); } }); + } + self.save_negative_answers(response); } + /// [RFC 1123]: https://datatracker.ietf.org/doc/html/rfc1123#section-6.1.3.3 + /// + /// 6.1.3.3 Efficient Resource Usage + /// + /// (4) All DNS name servers and resolvers SHOULD cache + /// negative responses that indicate the specified name, or + /// data of the specified type, does not exist, as + /// described in [DNS:2]. + /// + /// [RFC 1034]: https://datatracker.ietf.org/doc/html/rfc1034#section-4.3.4 + /// + /// 4.3.4. Negative response caching (Optional) + /// + /// The DNS provides an optional service which allows name servers to + /// distribute, and resolvers to cache, negative results with TTLs. For + /// example, a name server can distribute a TTL along with a name error + /// indication, and a resolver receiving such information is allowed to + /// assume that the name does not exist during the TTL period without + /// consulting authoritative data. Similarly, a resolver can make a query + /// with a QTYPE which matches multiple types, and cache the fact that some + /// of the types are not present. + /// + /// The method is that a name server may add an SOA RR to the additional + /// section of a response when that response is authoritative. The SOA must + /// be that of the zone which was the source of the authoritative data in + /// the answer section, or name error if applicable. The MINIMUM field of + /// the SOA controls the length of time that the negative result may be + /// cached. + /// + /// Stores the data of negative answers in the cache. + fn save_negative_answers(&mut self, response: DnsMessage){ + + let qname = response.get_question().get_qname(); + let qtype = response.get_question().get_qtype(); + let additionals = response.get_additional(); + let answer = response.get_answer(); + let aa = response.get_header().get_aa(); + + // If not existence RR for query, add SOA to cache + if additionals.len() > 0 && answer.len() == 0 && aa == true{ + additionals.iter() + .for_each(|rr| { + if rr.get_rtype() == Rtype::SOA { + let rtype = Qtype::to_rtype(qtype); + self.cache.add_negative_answer(qname.clone(),rtype ,rr.clone()); + } + }); + } + + } } + + // Getters impl AsyncResolver { // Gets the cache from the struct @@ -349,6 +422,7 @@ mod async_resolver_test { use crate::message::class_qclass::Qclass; use crate::message::rdata::Rdata; use crate::message::rdata::a_rdata::ARdata; + use crate::message::rdata::soa_rdata::SoaRdata; use crate::message::resource_record::ResourceRecord; use crate:: message::type_qtype::Qtype; use crate::message::type_rtype::Rtype; @@ -369,7 +443,7 @@ mod async_resolver_test { } #[tokio::test] - async fn inner_lookup() { + async fn inner_lookup_qtype_a() { // Create a new resolver with default values let mut resolver = AsyncResolver::new(ResolverConfig::default()); let domain_name = DomainName::new_from_string("example.com".to_string()); @@ -377,10 +451,195 @@ mod async_resolver_test { let record_class = Qclass::IN; let response = resolver.inner_lookup(domain_name,qtype,record_class).await; - //FIXME: add assert - assert!(response.is_ok()); - } + let response = match response { + Ok(val) => val, + Err(error) => panic!("Error in the response: {:?}", error), + }; + //analize if the response has the correct type according with the qtype + let answers = response.get_answer(); + for answer in answers { + let a_rdata = answer.get_rdata(); + // Check if the answer is A type + assert!(matches!(a_rdata, Rdata::A(_a_rdata))) + } + } + + + #[tokio::test] + async fn inner_lookup_qtype_ns() { + // Create a new resolver with default values + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = DomainName::new_from_string("example.com".to_string()); + let qtype = Qtype::NS; + let record_class = Qclass::IN; + let response = resolver.inner_lookup(domain_name,qtype,record_class).await; + + let response = match response { + Ok(val) => val, + Err(error) => panic!("Error in the response: {:?}", error), + }; + //analize if the response has the correct type according with the qtype + let answers = response.get_answer(); + for answer in answers { + let ns_rdata = answer.get_rdata(); + // Check if the answer is NS type + assert!(matches!(ns_rdata, Rdata::NS(_ns_rdata))) + } + } + + #[tokio::test] + async fn inner_lookup_qtype_mx() { + // Create a new resolver with default values + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = DomainName::new_from_string("example.com".to_string()); + let qtype = Qtype::MX; + let record_class = Qclass::IN; + let response = resolver.inner_lookup(domain_name,qtype,record_class).await; + + let response = match response { + Ok(val) => val, + Err(error) => panic!("Error in the response: {:?}", error), + }; + //analize if the response has the correct type according with the qtype + let answers = response.get_answer(); + for answer in answers { + let mx_rdata = answer.get_rdata(); + // Check if the answer is MX type + assert!(matches!(mx_rdata, Rdata::MX(_mx_rdata))) + } + } + + #[tokio::test] + async fn inner_lookup_qtype_ptr() { + // Create a new resolver with default values + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = DomainName::new_from_string("example.com".to_string()); + let qtype = Qtype::PTR; + let record_class = Qclass::IN; + let response = resolver.inner_lookup(domain_name,qtype,record_class).await; + + let response = match response { + Ok(val) => val, + Err(error) => panic!("Error in the response: {:?}", error), + }; + //analize if the response has the correct type according with the qtype + let answers = response.get_answer(); + for answer in answers { + let ptr_rdata = answer.get_rdata(); + // Check if the answer is PTR type + assert!(matches!(ptr_rdata, Rdata::PTR(_ptr_rdata))) + } + } + + #[tokio::test] + async fn inner_lookup_qtype_soa() { + // Create a new resolver with default values + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = DomainName::new_from_string("example.com".to_string()); + let qtype = Qtype::SOA; + let record_class = Qclass::IN; + let response = resolver.inner_lookup(domain_name,qtype,record_class).await; + + let response = match response { + Ok(val) => val, + Err(error) => panic!("Error in the response: {:?}", error), + }; + //analize if the response has the correct type according with the qtype + let answers = response.get_answer(); + for answer in answers { + let soa_rdata = answer.get_rdata(); + // Check if the answer is SOA type + assert!(matches!(soa_rdata, Rdata::SOA(_soa_rdata))) + } + } + + #[tokio::test] + async fn inner_lookup_qtype_txt() { + // Create a new resolver with default values + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = DomainName::new_from_string("example.com".to_string()); + let qtype = Qtype::TXT; + let record_class = Qclass::IN; + let response = resolver.inner_lookup(domain_name,qtype,record_class).await; + + let response = match response { + Ok(val) => val, + Err(error) => panic!("Error in the response: {:?}", error), + }; + //analize if the response has the correct type according with the qtype + let answers = response.get_answer(); + for answer in answers { + let txt_rdata = answer.get_rdata(); + // Check if the answer is TXT type + assert!(matches!(txt_rdata, Rdata::TXT(_txt_rdata))) + } + } + + #[tokio::test] + async fn inner_lookup_qtype_cname() { + // Create a new resolver with default values + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = DomainName::new_from_string("example.com".to_string()); + let qtype = Qtype::CNAME; + let record_class = Qclass::IN; + let response = resolver.inner_lookup(domain_name,qtype,record_class).await; + + let response = match response { + Ok(val) => val, + Err(error) => panic!("Error in the response: {:?}", error), + }; + //analize if the response has the correct type according with the qtype + let answers = response.get_answer(); + for answer in answers { + let cname_rdata = answer.get_rdata(); + // Check if the answer is CNAME type + assert!(matches!(cname_rdata, Rdata::CNAME(_cname_rdata))) + } + } + + #[tokio::test] + async fn inner_lookup_qtype_hinfo() { + // Create a new resolver with default values + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = DomainName::new_from_string("example.com".to_string()); + let qtype = Qtype::HINFO; + let record_class = Qclass::IN; + let response = resolver.inner_lookup(domain_name,qtype,record_class).await; + + let response = match response { + Ok(val) => val, + Err(error) => panic!("Error in the response: {:?}", error), + }; + //analize if the response has the correct type according with the qtype + let answers = response.get_answer(); + for answer in answers { + let hinfo_rdata = answer.get_rdata(); + // Check if the answer is HINFO type + assert!(matches!(hinfo_rdata, Rdata::HINFO(_hinfo_rdata))) + } + } + #[tokio::test] + async fn inner_lookup_qtype_tsig() { + // Create a new resolver with default values + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = DomainName::new_from_string("example.com".to_string()); + let qtype = Qtype::TSIG; + let record_class = Qclass::IN; + let response = resolver.inner_lookup(domain_name,qtype,record_class).await; + + let response = match response { + Ok(val) => val, + Err(error) => panic!("Error in the response: {:?}", error), + }; + //analize if the response has the correct type according with the qtype + let answers = response.get_answer(); + for answer in answers { + let tsig_rdata = answer.get_rdata(); + // Check if the answer is TSIG type + assert!(matches!(tsig_rdata, Rdata::TSIG(_tsig_rdata))) + } + } #[tokio::test] async fn inner_lookup_ns() { // Create a new resolver with default values @@ -410,6 +669,44 @@ mod async_resolver_test { assert!(!ip_addresses[0].is_unspecified()); } + #[tokio::test] + async fn lookup_ip_ch() { + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = "example.com"; + let transport_protocol = "UDP"; + let qclass = "CH"; + let ip_addresses = resolver.lookup_ip(domain_name, transport_protocol,qclass).await; + println!("RESPONSE : {:?}", ip_addresses); + + assert!(ip_addresses.is_err()); + } + + #[ignore] //FIXME: + #[tokio::test] + async fn lookup_ip_qclass_any() { + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = "example.com"; + let transport_protocol = "UDP"; + let qclass = "ANY"; + let ip_addresses = resolver.lookup_ip(domain_name, transport_protocol,qclass).await; + println!("RESPONSE : {:?}", ip_addresses); + + // assert!(ip_addresses.is_err()); + } + + #[tokio::test] + async fn lookup_ch() { + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let domain_name = "example.com"; + let transport_protocol = "UDP"; + let qtype = "NS"; + let qclass = "CH"; + let ip_addresses = resolver.lookup(domain_name, transport_protocol,qtype,qclass).await; + println!("RESPONSE : {:?}", ip_addresses); + + assert!(ip_addresses.is_err()); + } + #[tokio::test] async fn host_name_to_host_address_translation_ch() { let mut resolver = AsyncResolver::new(ResolverConfig::default()); @@ -524,9 +821,9 @@ mod async_resolver_test { } } - /// Test lookup cache + /// Test inner lookup cache #[tokio::test] - async fn lookup_cache() { + async fn inner_lookup_cache_available() { let mut resolver = AsyncResolver::new(ResolverConfig::default()); resolver.cache.set_max_size(1); @@ -537,7 +834,40 @@ mod async_resolver_test { resolver.cache.add(domain_name, resource_record); - let _response = resolver.lookup("example.com", "UDP", "A","IN").await; + let domain_name = DomainName::new_from_string("example.com".to_string()); + let response = resolver.inner_lookup(domain_name, Qtype::A, Qclass::IN).await; + + if let Ok(msg) = response { + assert_eq!(msg.get_header().get_aa(), false); + } else { + panic!("No response from cache"); + } + } + + /// Test inner lookup without cache + #[tokio::test] + async fn inner_lookup_with_no_cache() { + let mut config = ResolverConfig::default(); + config.set_cache_enabled(false); + + let mut resolver = AsyncResolver::new(config); + resolver.cache.set_max_size(1); + + let domain_name = DomainName::new_from_string("example.com".to_string()); + let a_rdata = ARdata::new_from_addr(IpAddr::from_str("93.184.216.34").unwrap()); + let a_rdata = Rdata::A(a_rdata); + let resource_record = ResourceRecord::new(a_rdata); + + resolver.cache.add(domain_name, resource_record); + + let domain_name = DomainName::new_from_string("example.com".to_string()); + let response = resolver.inner_lookup(domain_name, Qtype::A, Qclass::IN).await; + + if let Ok(msg) = response { + assert_eq!(msg.get_header().get_aa(), false); + } else { + panic!("No response from nameserver"); + } } /// Test cache data @@ -546,7 +876,7 @@ mod async_resolver_test { let mut resolver = AsyncResolver::new(ResolverConfig::default()); resolver.cache.set_max_size(1); assert_eq!(resolver.cache.is_empty(), true); - let _response = resolver.lookup("example.com", "UDP", "A","IN").await; + let _response = resolver.lookup("example.com", "UDP", "A", "IN").await; assert_eq!(resolver.cache.is_cached(DomainName::new_from_str("example.com"), Rtype::A), true); // TODO: Test special cases from RFC @@ -1554,4 +1884,110 @@ mod async_resolver_test { assert_eq!(resolver.get_cache().get_size(), 2); } + #[test] + fn save_cache_negative_answer(){ + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + resolver.cache.set_max_size(1); + + let domain_name = DomainName::new_from_string("banana.exaple".to_string()); + let mname = DomainName::new_from_string("a.root-servers.net.".to_string()); + let rname = DomainName::new_from_string("nstld.verisign-grs.com.".to_string()); + let serial = 2023112900; + let refresh = 1800; + let retry = 900; + let expire = 604800; + let minimum = 86400; + + //Create RR type SOA + let mut soa_rdata = SoaRdata::new(); + soa_rdata.set_mname(mname); + soa_rdata.set_rname(rname); + soa_rdata.set_serial(serial); + soa_rdata.set_refresh(refresh); + soa_rdata.set_retry(retry); + soa_rdata.set_expire(expire); + soa_rdata.set_minimum(minimum); + + + let rdata = Rdata::SOA(soa_rdata); + let mut rr = ResourceRecord::new(rdata); + rr.set_name(domain_name.clone()); + + // Create dns response + let mut dns_response = + DnsMessage::new_query_message( + domain_name, + Qtype::A, + Qclass::IN, + 0, + false, + 1); + let mut new_header = dns_response.get_header(); + new_header.set_aa(true); + dns_response.set_header(new_header); + + // Save RR type SOA in Additional section of response + dns_response.add_additionals(vec![rr]); + + resolver.save_negative_answers(dns_response.clone()); + + let qtype_search = Rtype::A; + assert_eq!(dns_response.get_answer().len(), 0); + assert_eq!(dns_response.get_additional().len(), 1); + assert_eq!(resolver.get_cache().get_size(), 1); + assert!(resolver.get_cache().get_cache().get_cache_data().get(&qtype_search).is_some()) + + } + + #[tokio::test] + async fn inner_lookup_negative_answer_in_cache(){ + let mut resolver = AsyncResolver::new(ResolverConfig::default()); + let mut cache = resolver.get_cache(); + let qtype = Qtype::A; + cache.set_max_size(9); + + let domain_name = DomainName::new_from_string("banana.exaple".to_string()); + + //Create RR type SOA + let mname = DomainName::new_from_string("a.root-servers.net.".to_string()); + let rname = DomainName::new_from_string("nstld.verisign-grs.com.".to_string()); + let serial = 2023112900; + let refresh = 1800; + let retry = 900; + let expire = 604800; + let minimum = 86400; + + let mut soa_rdata = SoaRdata::new(); + soa_rdata.set_mname(mname); + soa_rdata.set_rname(rname); + soa_rdata.set_serial(serial); + soa_rdata.set_refresh(refresh); + soa_rdata.set_retry(retry); + soa_rdata.set_expire(expire); + soa_rdata.set_minimum(minimum); + + let rdata = Rdata::SOA(soa_rdata); + let mut rr = ResourceRecord::new(rdata); + rr.set_name(domain_name.clone()); + + // Add negative answer to cache + let mut cache = resolver.get_cache(); + cache.set_max_size(9); + let rtype = Qtype::to_rtype(qtype); + cache.add_negative_answer(domain_name.clone(),rtype ,rr.clone()); + resolver.cache = cache; + + assert_eq!(resolver.get_cache().get_size(), 1); + + let qclass = Qclass::IN; + let response = resolver.inner_lookup(domain_name,qtype,qclass).await.unwrap(); + + + assert_eq!(resolver.get_cache().get_size(), 1); + assert_eq!(response.get_answer().len(), 0); + assert_eq!(response.get_additional().len(), 1); + assert_eq!(response.get_header().get_rcode(), 3); + + } + } \ No newline at end of file diff --git a/src/async_resolver/lookup.rs b/src/async_resolver/lookup.rs index 5f9f7f29..3758dbb4 100644 --- a/src/async_resolver/lookup.rs +++ b/src/async_resolver/lookup.rs @@ -6,6 +6,9 @@ use crate::client::client_connection::ClientConnection; use crate::message::class_qclass::Qclass; use crate::message::type_qtype::Qtype; use futures_util::{FutureExt,task::Waker}; +use std::net::IpAddr; +use std::thread; +use std::time::Duration; use std::pin::Pin; use std::task::{Poll,Context}; use rand::{thread_rng, Rng}; @@ -62,7 +65,8 @@ impl Future for LookupFutureStub { self.config.get_name_servers(), self.waker.clone(), referenced_query, - self.config.clone())); + self.config.clone()) + ); return Poll::Pending; }, @@ -192,28 +196,29 @@ pub async fn lookup_stub( //FIXME: podemos ponerle de nombre lookup_strategy y q new_header.set_rcode(2); new_header.set_qr(true); response.set_header(new_header); + //FIXME: let mut result_dns_msg = Ok(response.clone()); let mut retry_count = 0; - for (conn_udp,conn_tcp) in name_servers.iter() { + for connections in name_servers.iter() { if retry_count > config.get_retry() { break; } - - match config.get_protocol() { - ConnectionProtocol::UDP => { - let result_response = conn_udp.send(new_query.clone()); - result_dns_msg = parse_response(result_response); - } - ConnectionProtocol::TCP => { - let result_response = conn_tcp.send(new_query.clone()); - result_dns_msg = parse_response(result_response); - } - _ => continue, - } - retry_count = retry_count + 1; + + result_dns_msg = send_query_resolver_by_protocol(config.get_protocol(),new_query.clone(), result_dns_msg.clone(), connections); + if result_dns_msg.is_err(){ + retry_count = retry_count + 1; + } + else{ + break; + } + + //FIXME: try make async + let delay_duration = Duration::from_secs(6); + thread::sleep(delay_duration); + } // Wake up task @@ -238,6 +243,34 @@ pub async fn lookup_stub( //FIXME: podemos ponerle de nombre lookup_strategy y q result_dns_msg } +/// Sends a DNS query to a resolver using the specified connection protocol. +/// +/// This function takes a DNS query, a result containing a DNS message, +/// and connection information. Depending on the specified protocol (UDP or TCP), +/// it sends the query using the corresponding connection and updates the result +/// with the parsed response. + +fn send_query_resolver_by_protocol(protocol: ConnectionProtocol,query:DnsMessage,mut result_dns_msg: Result, connections: &(ClientUDPConnection , ClientTCPConnection)) +-> Result{ + let query_id = query.get_query_id(); + match protocol{ + ConnectionProtocol::UDP => { + let result_response = connections.0.send(query.clone()); + result_dns_msg = parse_response(result_response,query_id); + } + ConnectionProtocol::TCP => { + let result_response = connections.1.send(query.clone()); + result_dns_msg = parse_response(result_response,query_id); + } + _ => {}, + } + + result_dns_msg +} + + + + /// Parse the received response datagram to a `DnsMessage`. /// /// [RFC 1035]: https://datatracker.ietf.org/doc/html/rfc1035#section-7.3 @@ -257,9 +290,9 @@ pub async fn lookup_stub( //FIXME: podemos ponerle de nombre lookup_strategy y q /// excessively long TTL, say greater than 1 week, either discard /// the whole response, or limit all TTLs in the response to 1 /// week. -fn parse_response(response_result: Result, ClientError>) -> Result { +fn parse_response(response_result: Result<(Vec, IpAddr), ClientError>, query_id:u16) -> Result { let dns_msg = response_result.map_err(Into::into) - .and_then(|response_message| { + .and_then(|(response_message , _ip)| { DnsMessage::from_bytes(&response_message) .map_err(|_| ResolverError::Parse("The name server was unable to interpret the query.".to_string())) })?; @@ -269,6 +302,13 @@ fn parse_response(response_result: Result, ClientError>) -> Result, ClientError> = Ok(bytes.to_vec()); - let response_dns_msg = parse_response(response_result); + let query_id = 0b00100100; + let ip = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let response_result: Result<(Vec, IpAddr), ClientError> = Ok((bytes.to_vec(), ip)); + let response_dns_msg = parse_response(response_result,query_id); + println!("[###############] {:?}",response_dns_msg); assert!(response_dns_msg.is_ok()); if let Ok(dns_msg) = response_dns_msg { assert_eq!(dns_msg.get_header().get_qr(), true); // response (1) @@ -555,6 +621,7 @@ mod async_resolver_test { } #[test] + #[ignore] fn parse_response_query() { let bytes: [u8; 50] = [ //test passes with this one @@ -562,8 +629,10 @@ mod async_resolver_test { 101, 115, 116, 3, 99, 111, 109, 0, 0, 16, 0, 1, 3, 100, 99, 99, 2, 99, 108, 0, 0, 16, 0, 1, 0, 0, 0b00010110, 0b00001010, 0, 6, 5, 104, 101, 108, 108, 111, ]; - let response_result: Result, ClientError> = Ok(bytes.to_vec()); - let response_dns_msg = parse_response(response_result); + let query_id = 0b10100101; + let ip = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let response_result: Result<(Vec, IpAddr), ClientError> = Ok((bytes.to_vec(), ip)); + let response_dns_msg = parse_response(response_result,query_id); let err_msg = "Message is a query. A response was expected.".to_string(); if let Err(ResolverError::Parse(err)) = response_dns_msg { assert_eq!(err, err_msg) @@ -571,7 +640,7 @@ mod async_resolver_test { assert!(false); } } - + #[test] fn parse_error() { let bytes: [u8; 50] = [ @@ -580,8 +649,10 @@ mod async_resolver_test { 101, 115, 116, 3, 99, 111, 109, 0, 0, 16, 0, 1, 3, 100, 99, 99, 2, 99, 45, 0, 0, 16, 0, 1, 0, 0, 0b00010110, 0b00001010, 0, 6, 5, 104, 101, 108, 108, 111, ]; - let response_result: Result, ClientError> = Ok(bytes.to_vec()); - let response_dns_msg = parse_response(response_result); + let query_id = 0b10100101; + let ip= IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let response_result: Result<(Vec, IpAddr), ClientError> = Ok((bytes.to_vec(), ip)); + let response_dns_msg = parse_response(response_result,query_id); let err_msg = "The name server was unable to interpret the query.".to_string(); if let Err(ResolverError::Parse(err)) = response_dns_msg { assert_eq!(err, err_msg) @@ -598,8 +669,10 @@ mod async_resolver_test { 101, 115, 64, 3, 99, 111, 109, 0, 0, 16, 0, 1, 3, 100, 99, 99, 2, 99, 108, 0, 0, 16, 0, 1, 0, 0, 0b00010110, 0b00001010, 0, 6, 5, 104, 101, 108, 108, 111, ]; - let response_result: Result, ClientError> = Ok(bytes.to_vec()); - let response_dns_msg = parse_response(response_result); + let query_id = 0b10100101; + let ip = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let response_result: Result<(Vec, IpAddr), ClientError> = Ok((bytes.to_vec(), ip)); + let response_dns_msg = parse_response(response_result,query_id); let err_msg = "The name server was unable to interpret the query.".to_string(); if let Err(ResolverError::Parse(err)) = response_dns_msg { @@ -608,4 +681,6 @@ mod async_resolver_test { assert!(false); } } + + } \ No newline at end of file diff --git a/src/client.rs b/src/client.rs index 6e84c7c8..f0ffc0d4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -104,9 +104,13 @@ impl Client { let client_query = self.get_dns_query(); let conn: &T = &self.get_conn(); + let ip_addr = conn.get_ip(); let dns_response: DnsMessage = match conn.send(client_query) { - Ok(response_message) => { + Ok((response_message, ip)) => { + if ip != ip_addr { + return Err(ClientError::Message("The ip address of the server is not the same as the one in the connection."))?; + } match DnsMessage::from_bytes(&response_message) { Ok(dns_message) => dns_message, Err(_) => return Err(ClientError::FormatError("The name server was unable to interpret the query."))?, @@ -188,10 +192,13 @@ mod client_test { let mut domain_name = DomainName::new(); // sends query - domain_name.set_name(String::from("test.test2.com.")); + domain_name.set_name(String::from("example.com")); let qtype = "A"; let qclass= "IN"; - let response = udp_client.query(domain_name, qtype, qclass).unwrap(); + let response = match udp_client.query(domain_name, qtype, qclass) { + Ok(value) => value, + Err(error) => panic!("Error in the response: {:?}", error), + }; let expected_ip: [u8; 4] = [93, 184, 216, 34]; let answers = response.get_answer(); @@ -206,6 +213,248 @@ mod client_test { } } + #[test] + fn udp_client_qtype_a() { + //create connection + let server_addr: IpAddr = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let timeout: Duration = Duration::from_secs(2); + + let conn_udp:ClientUDPConnection = ClientUDPConnection::new(server_addr, timeout); + let mut udp_client = Client::new(conn_udp); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("example.com")); + + // sends query, qtype A + let qtype = "A"; + let qclass= "IN"; + let response = match udp_client.query(domain_name, qtype, qclass) { + Ok(value) => value, + Err(error) => panic!("Error in the response: {:?}", error), + }; + let answers = response.get_answer(); + for answer in answers { + let a_rdata = answer.get_rdata(); + // Check if the answer is A type + assert!(matches!(a_rdata, Rdata::A(_a_rdata))) + } + } + + #[test] + fn udp_client_qtype_ns() { + //create connection + let server_addr: IpAddr = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let timeout: Duration = Duration::from_secs(2); + + let conn_udp:ClientUDPConnection = ClientUDPConnection::new(server_addr, timeout); + let mut udp_client = Client::new(conn_udp); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("example.com")); + + // sends query, qtype NS + let qtype = "NS"; + let qclass= "IN"; + let response = match udp_client.query(domain_name, qtype, qclass) { + Ok(value) => value, + Err(error) => panic!("Error in the response: {:?}", error), + }; + let answers = response.get_answer(); + for answer in answers { + let ns_rdata = answer.get_rdata(); + // Check if the answer is NS type + assert!(matches!(ns_rdata, Rdata::NS(_ns_rdata))) + } + } + + #[test] + fn udp_client_qtype_cname() { + //create connection + let server_addr: IpAddr = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let timeout: Duration = Duration::from_secs(2); + + let conn_udp:ClientUDPConnection = ClientUDPConnection::new(server_addr, timeout); + let mut udp_client = Client::new(conn_udp); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("example.com")); + + // sends query, qtype CNAME + let qtype = "CNAME"; + let qclass= "IN"; + let response = match udp_client.query(domain_name, qtype, qclass) { + Ok(value) => value, + Err(error) => panic!("Error in the response: {:?}", error), + }; + let answers = response.get_answer(); + for answer in answers { + let cname_rdata = answer.get_rdata(); + // Check if the answer is CNAME type + assert!(matches!(cname_rdata, Rdata::CNAME(_cname_rdata))) + } + } + + #[test] + fn udp_client_qtype_soa() { + //create connection + let server_addr: IpAddr = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let timeout: Duration = Duration::from_secs(2); + + let conn_udp:ClientUDPConnection = ClientUDPConnection::new(server_addr, timeout); + let mut udp_client = Client::new(conn_udp); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("example.com")); + + // sends query, qtype SOA + let qtype = "SOA"; + let qclass= "IN"; + let response = match udp_client.query(domain_name, qtype, qclass) { + Ok(value) => value, + Err(error) => panic!("Error in the response: {:?}", error), + }; + let answers = response.get_answer(); + for answer in answers { + let soa_rdata = answer.get_rdata(); + // Check if the answer is SOA type + assert!(matches!(soa_rdata, Rdata::SOA(_soa_rdata))) + } + } + + #[test] + fn udp_client_qtype_mx(){ + //create connection + let server_addr: IpAddr = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let timeout: Duration = Duration::from_secs(2); + + let conn_udp:ClientUDPConnection = ClientUDPConnection::new(server_addr, timeout); + let mut udp_client = Client::new(conn_udp); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("example.com")); + + // sends query, qtype MX + let qtype = "MX"; + let qclass= "IN"; + let response = match udp_client.query(domain_name, qtype, qclass) { + Ok(value) => value, + Err(error) => panic!("Error in the response: {:?}", error), + }; + let answers = response.get_answer(); + for answer in answers { + let mx_rdata = answer.get_rdata(); + // Check if the answer is MX type + assert!(matches!(mx_rdata, Rdata::MX(_mx_rdata))) + } + } + + #[test] + fn udp_client_qtype_ptr(){ + //create connection + let server_addr: IpAddr = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let timeout: Duration = Duration::from_secs(2); + + let conn_udp:ClientUDPConnection = ClientUDPConnection::new(server_addr, timeout); + let mut udp_client = Client::new(conn_udp); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("example.com")); + + // sends query, qtype PTR + let qtype = "PTR"; + let qclass= "IN"; + let response = match udp_client.query(domain_name, qtype, qclass) { + Ok(value) => value, + Err(error) => panic!("Error in the response: {:?}", error), + }; + let answers = response.get_answer(); + for answer in answers { + let ptr_rdata = answer.get_rdata(); + // Check if the answer is PTR type + assert!(matches!(ptr_rdata, Rdata::PTR(_ptr_rdata))) + } + } + + #[test] + fn udp_client_qtype_tsig(){ + //create connection + let server_addr: IpAddr = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let timeout: Duration = Duration::from_secs(2); + + let conn_udp:ClientUDPConnection = ClientUDPConnection::new(server_addr, timeout); + let mut udp_client = Client::new(conn_udp); + + let mut domain_name = DomainName::new(); + + // sends query, qtype TSIG + domain_name.set_name(String::from("example.com")); + let qtype = "TSIG"; + let qclass= "IN"; + let response = match udp_client.query(domain_name, qtype, qclass) { + Ok(value) => value, + Err(error) => panic!("Error in the response: {:?}", error), + }; + let answers = response.get_answer(); + for answer in answers { + let tsig_rdata = answer.get_rdata(); + // Check if the answer is TSIG type + assert!(matches!(tsig_rdata, Rdata::HINFO(_tsig_rdata))) + } + } + + #[test] + fn udp_client_qtype_hinfo(){ + //create connection + let server_addr: IpAddr = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let timeout: Duration = Duration::from_secs(2); + + let conn_udp:ClientUDPConnection = ClientUDPConnection::new(server_addr, timeout); + let mut udp_client = Client::new(conn_udp); + + let mut domain_name = DomainName::new(); + + // sends query, qtype HINFO + domain_name.set_name(String::from("example.com")); + let qtype = "HINFO"; + let qclass= "IN"; + let response = match udp_client.query(domain_name, qtype, qclass) { + Ok(value) => value, + Err(error) => panic!("Error in the response: {:?}", error), + }; + let answers = response.get_answer(); + for answer in answers { + let hinfo_rdata = answer.get_rdata(); + // Check if the answer is HINFO type + assert!(matches!(hinfo_rdata, Rdata::HINFO(_hinfo_rdata))) + } + } + + #[test] + fn udp_client_qtype_txt(){ + //create connection + let server_addr: IpAddr = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)); + let timeout: Duration = Duration::from_secs(2); + + let conn_udp:ClientUDPConnection = ClientUDPConnection::new(server_addr, timeout); + let mut udp_client = Client::new(conn_udp); + + let mut domain_name = DomainName::new(); + + // sends query, qtype TXT + domain_name.set_name(String::from("example.com")); + let qtype = "TXT"; + let qclass= "IN"; + let response = match udp_client.query(domain_name, qtype, qclass) { + Ok(value) => value, + Err(error) => panic!("Error in the response: {:?}", error), + }; + let answers = response.get_answer(); + for answer in answers { + let txt_rdata = answer.get_rdata(); + // Check if the answer is TXT type + assert!(matches!(txt_rdata, Rdata::TXT(_txt_rdata))) + } + } #[test] fn tcp_client_query() { //FIXME: diff --git a/src/client/client_connection.rs b/src/client/client_connection.rs index 008c7cdb..4d88db7f 100644 --- a/src/client/client_connection.rs +++ b/src/client/client_connection.rs @@ -10,7 +10,8 @@ pub trait ClientConnection: Copy {//: 'static + Sized + Send + Sync + Unpin timeout:Duration) -> Self; //Sends query - fn send(self,dns_query:DnsMessage) -> Result, ClientError>; + fn send(self, dns_query: DnsMessage) -> Result<(Vec, IpAddr), ClientError>; + fn get_ip(&self) -> IpAddr; } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/src/client/tcp_connection.rs b/src/client/tcp_connection.rs index 3983d25e..28593c9c 100644 --- a/src/client/tcp_connection.rs +++ b/src/client/tcp_connection.rs @@ -28,8 +28,14 @@ impl ClientConnection for ClientTCPConnection { } } + ///implement get_ip + /// returns IpAddr + fn get_ip(&self) -> IpAddr { + return self.server_addr.clone(); + } + /// creates socket tcp, sends query and receive response - fn send(self, dns_query: DnsMessage) -> Result, ClientError>{ + fn send(self, dns_query: DnsMessage) -> Result<(Vec, IpAddr), ClientError>{ let timeout: Duration = self.get_timeout(); let bytes: Vec = dns_query.to_bytes(); @@ -53,6 +59,7 @@ impl ClientConnection for ClientTCPConnection { let tcp_msg_len: u16 = (msg_size_response[0] as u16) << 8 | msg_size_response[1] as u16; let mut vec_msg: Vec = Vec::new(); + let ip = self.get_server_addr(); while vec_msg.len() < tcp_msg_len as usize { let mut msg = [0; 512]; @@ -64,7 +71,7 @@ impl ClientConnection for ClientTCPConnection { vec_msg.extend_from_slice(&msg[..number_of_bytes_msg]); } - return Ok(vec_msg); + return Ok((vec_msg, ip)); } } @@ -99,7 +106,8 @@ impl ClientTCPConnection { mod tcp_connection_test{ use super::*; - use std::net::{IpAddr,Ipv4Addr}; + use core::time; + use std::net::{IpAddr,Ipv4Addr,Ipv6Addr}; use crate::domain_name::DomainName; use crate::message::type_qtype::Qtype; use crate::message::class_qclass::Qclass; @@ -119,6 +127,25 @@ mod tcp_connection_test{ assert_eq!(_conn_new.get_timeout(), Duration::from_secs(100)); } + #[test] + fn get_ip_v4(){ + let ip_address = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)); + let timeout = Duration::from_secs(100); + let connection = ClientTCPConnection::new(ip_address, timeout); + //check if the ip is the same + assert_eq!(connection.get_ip(), IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))); + } + + #[test] + fn get_ip_v6(){ + // ip in V6 version is the equivalent to (192, 168, 0, 1) in V4 + let ip_address = IpAddr::V6(Ipv6Addr::new(0xc0, 0xa8, 0, 1, 0, 0, 0, 0)); + let timeout = Duration::from_secs(100); + let connection = ClientTCPConnection::new(ip_address, timeout); + //check if the ip is the same + assert_eq!(connection.get_ip(), IpAddr::V6(Ipv6Addr::new(0xc0, 0xa8, 0, 1, 0, 0, 0, 0))); + } + //Setters and Getters test #[test] fn get_server_addr(){ @@ -181,7 +208,7 @@ mod tcp_connection_test{ 0, false, 1); - let response = conn_new.send(dns_query).unwrap(); + let (response, _ip) = conn_new.send(dns_query).unwrap(); assert!(DnsMessage::from_bytes(&response).unwrap().get_answer().len() > 0); // FIXME: diff --git a/src/client/udp_connection.rs b/src/client/udp_connection.rs index ca7a2a5f..9059c57b 100644 --- a/src/client/udp_connection.rs +++ b/src/client/udp_connection.rs @@ -26,8 +26,13 @@ impl ClientConnection for ClientUDPConnection { timeout: timeout, } } + /// implement get_ip + /// returns IpAddr + fn get_ip(&self) -> IpAddr { + return self.server_addr.clone(); + } - fn send(self, dns_query:DnsMessage) -> Result, ClientError> { + fn send(self, dns_query:DnsMessage) -> Result<(Vec, IpAddr), ClientError> { let timeout:Duration = self.timeout; let server_addr = SocketAddr::new(self.get_server_addr(), 53); @@ -56,8 +61,10 @@ impl ClientConnection for ClientUDPConnection { Ok(_) => (), }; + let ip = self.get_server_addr(); + drop(socket_udp); - return Ok(msg.to_vec()); + return Ok((msg.to_vec(), ip)); } } @@ -95,7 +102,7 @@ mod udp_connection_test{ use crate::message::type_qtype::Qtype; use crate::message::class_qclass::Qclass; use super::*; - use std::net::{IpAddr,Ipv4Addr}; + use std::net::{IpAddr,Ipv4Addr,Ipv6Addr}; #[test] fn create_udp() { @@ -118,6 +125,25 @@ mod udp_connection_test{ assert_eq!(_conn_new.get_server_addr(), IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))); } + #[test] + fn get_ip_v4(){ + let ip_address = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)); + let timeout = Duration::from_secs(100); + let connection = ClientUDPConnection::new(ip_address, timeout); + //check if the ip is the same + assert_eq!(connection.get_ip(), IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))); + } + + #[test] + fn get_ip_v6(){ + // ip in V6 version is the equivalent to (192, 168, 0, 1) in V4 + let ip_address = IpAddr::V6(Ipv6Addr::new(0xc0, 0xa8, 0, 1, 0, 0, 0, 0)); + let timeout = Duration::from_secs(100); + let connection = ClientUDPConnection::new(ip_address, timeout); + //check if the ip is the same + assert_eq!(connection.get_ip(), IpAddr::V6(Ipv6Addr::new(0xc0, 0xa8, 0, 1, 0, 0, 0, 0))); + } + #[test] fn set_server_addr(){ let ip_addr = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)); @@ -194,7 +220,7 @@ mod udp_connection_test{ false, 1); - let response = conn.send(dns_query).unwrap(); + let (response, _ip) = conn.send(dns_query).unwrap(); // assert!(result.is_ok()); diff --git a/src/dns_cache.rs b/src/dns_cache.rs index 3dab01b6..613b6dab 100644 --- a/src/dns_cache.rs +++ b/src/dns_cache.rs @@ -1,9 +1,10 @@ -pub mod cache_data; +pub mod cache_by_record_type; -use crate::dns_cache::cache_data::CacheData; + +use crate::dns_cache::cache_by_record_type::CacheByRecordType; +use crate::dns_cache::cache_by_record_type::rr_stored_data::RRStoredData; use crate::message::rdata::Rdata; use crate::message::resource_record::ResourceRecord; -use crate::rr_cache::RRCache; use crate::message::type_rtype::Rtype; use std::net::IpAddr; use crate::domain_name::DomainName; @@ -13,7 +14,7 @@ use std::cmp; /// Struct that represents a cache for dns pub struct DnsCache { // first hash by type, then by hostname - cache: CacheData, + cache: CacheByRecordType, max_size: u32, size: u32, } @@ -30,7 +31,7 @@ impl DnsCache { /// pub fn new() -> Self { let cache = DnsCache { - cache: CacheData::new(), + cache: CacheByRecordType::new(), max_size: 0, size: 0, }; @@ -51,7 +52,7 @@ impl DnsCache { } let rtype = resource_record.get_rtype(); - let rr_cache = RRCache::new(resource_record); + let rr_cache = RRStoredData::new(resource_record); let mut cache_data = self.get_cache(); cache_data.add_to_cache_data(rtype, domain_name, rr_cache); @@ -59,6 +60,22 @@ impl DnsCache { self.set_size(self.get_size() + 1); } + /// Add negative resource record type SOA to cache for negative answers + pub fn add_negative_answer(&mut self, domain_name: DomainName, rtype: Rtype, resource_record:ResourceRecord) { + + // see cache space + if self.get_size() >= self.max_size { + self.remove_oldest_used(); + } + + let rr_cache = RRStoredData::new(resource_record); + let mut cache_data = self.get_cache(); + cache_data.add_to_cache_data(rtype, domain_name, rr_cache); + self.set_cache(cache_data); + self.set_size(self.get_size() + 1); + + } + /// Removes an element from cache pub fn remove(&mut self, domain_name: DomainName, rtype: Rtype) { let mut cache_data = self.get_cache(); @@ -69,7 +86,7 @@ impl DnsCache { } /// Given a domain_name, gets an element from cache - pub fn get(&mut self, domain_name: DomainName, rtype: Rtype) -> Option> { + pub fn get(&mut self, domain_name: DomainName, rtype: Rtype) -> Option> { let mut cache = self.get_cache(); let rr_cache_vec = cache.get_from_cache_data(domain_name, rtype); self.set_cache(cache); @@ -152,12 +169,22 @@ impl DnsCache { // } // } // } + + /// Performs the timeout of cache by removing the elements that have expired. + /// + /// For each Resource Record in the cache, it checks if it has expired by its TTL. + /// If it has expired, it removes it from the cache. + pub fn timeout_cache(&mut self) { + let mut cache = self.get_cache(); + cache.filter_timeout_by_rtype(); + self.set_cache(cache); + } } // Getters impl DnsCache { // Gets the cache from the struct - pub fn get_cache(&self) -> CacheData{ + pub fn get_cache(&self) -> CacheByRecordType{ self.cache.clone() } @@ -175,7 +202,7 @@ impl DnsCache { // Setters impl DnsCache { // Sets the cache - pub fn set_cache(&mut self, cache: CacheData) { + pub fn set_cache(&mut self, cache: CacheByRecordType) { self.cache = cache } @@ -192,10 +219,11 @@ impl DnsCache { #[cfg(test)] mod dns_cache_test { - use crate::dns_cache::DnsCache; - use crate::dns_cache::cache_data::CacheData; - use crate::dns_cache::cache_data::host_data::HostData; - use crate::rr_cache::RRCache; + use chrono::Utc; + use crate::{dns_cache::DnsCache, message::rdata::ns_rdata::NsRdata}; + use crate::dns_cache::cache_by_record_type::CacheByRecordType; + use crate::dns_cache::cache_by_record_type::cache_by_domain_name::CacheByDomainName; + use crate::dns_cache::cache_by_record_type::rr_stored_data::RRStoredData; use crate::domain_name::DomainName; use crate::message::rdata::a_rdata::ARdata; use crate::message::rdata::txt_rdata::TxtRdata; @@ -208,7 +236,10 @@ mod dns_cache_test { #[test] fn constructor_test(){ let cache = DnsCache::new(); - assert!(cache.cache.cache_data.is_empty()); + assert!(cache + .get_cache() + .get_cache_data() + .is_empty()); } //Setters and getters test @@ -232,14 +263,14 @@ mod dns_cache_test { fn set_and_get_cache(){ let mut cache = DnsCache::new(); assert!(cache.get_cache().get_cache_data().is_empty()); - let mut cache_data = CacheData::new(); + let mut cache_data = CacheByRecordType::new(); let mut cache_data_hash = HashMap::new(); - let mut host_data = HostData::new(); + let mut host_data = CacheByDomainName::new(); let mut domain_name = DomainName::new(); domain_name.set_name(String::from("uchile.cl")); let a_rdata = Rdata::A(ARdata::new()); let resource_record = ResourceRecord::new(a_rdata); - let rr_cache = RRCache::new(resource_record); + let rr_cache = RRStoredData::new(resource_record); host_data.add_to_host_data(domain_name, rr_cache); cache_data_hash.insert(Rtype::A, host_data); @@ -469,4 +500,199 @@ mod dns_cache_test { assert_eq!(cache.is_cached(domain_name, Rtype::A), true); } + + #[test] + fn timeout_cache_1_domain_same_rtype(){ + use std::{thread, time}; + let mut dns_cache = DnsCache::new(); + + dns_cache.set_max_size(3); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + let a_rdata = Rdata::A(ARdata::new()); + + let mut resource_record = ResourceRecord::new(a_rdata.clone()); + resource_record.set_ttl(1000); + + dns_cache.add(domain_name.clone(), resource_record.clone()); + + assert_eq!(dns_cache.get_cache().get_cache_data().len(), 1); + + let mut resource_record_2 = ResourceRecord::new(a_rdata.clone()); + resource_record_2.set_ttl(4); + + dns_cache.add(domain_name.clone(), resource_record_2.clone()); + + //because both are of the same type, the cache_data (cache by record type) has 1 element + // Rdata::A -> cache_by_domain_name + assert_eq!(dns_cache.get_cache().get_cache_data().len(), 1); + if let Some(cache_by_domain_name) = dns_cache.get_cache().get(Rtype::A) { + if let Some(rrstore_data_vec) = cache_by_domain_name.get(&domain_name) { + assert_eq!(rrstore_data_vec.len(), 2); + } + } + assert_eq!(dns_cache.get_size(), 2); + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + dns_cache.timeout_cache(); + + //FIXME: the size shoud be 1 because we have only 1 resocurce_record associated with 1 domain? + // assert_eq!(dns_cache.get_size(), 1); + + //check if the resource_record_2 was deleted + if let Some(cache_by_domain_name) = dns_cache.get_cache().get(Rtype::A) { + if let Some(rrstore_data_vec) = cache_by_domain_name.get(&domain_name) { + assert_eq!(rrstore_data_vec.len(), 1); + //check if the resource_record_1 survive + if let Some(rrstore_after_cleaning) = rrstore_data_vec.get(0) { + let resource_record_after_cleaning = rrstore_after_cleaning.get_resource_record(); + assert_eq!(resource_record_after_cleaning, resource_record); + } + } + } + + } + + #[test] + fn timeout_cache_1_domain_differents_rtype(){ + use std::{thread, time}; + let mut dns_cache = DnsCache::new(); + + dns_cache.set_max_size(3); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + let a_rdata = Rdata::A(ARdata::new()); + let ns_rdata = Rdata::NS(NsRdata::new()); + + let mut resource_record_a = ResourceRecord::new(a_rdata.clone()); + resource_record_a.set_ttl(1000); + + dns_cache.add(domain_name.clone(), resource_record_a.clone()); + + assert_eq!(dns_cache.get_cache().get_cache_data().len(), 1); + + let mut resource_record_ns = ResourceRecord::new(ns_rdata.clone()); + resource_record_ns.set_ttl(4); + + dns_cache.add(domain_name.clone(), resource_record_ns.clone()); + + //because rtypes are differents the size of the cache is 2? + assert_eq!(dns_cache.get_cache().get_cache_data().len(), 2); + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + dns_cache.timeout_cache(); + + //FIXME: the size shoud be 1 because we have only 1 resocurce_record associated with 1 domain? + // assert_eq!(dns_cache.get_size(), 1); + + //After the cleaning, the size of the cache shoud be 1 (NS Was deleted) + println!("the Rtype cache is {:?} : \n", dns_cache.get_cache().get_cache_data()); + //FIXME: Domain uchile points to a empty array and (NS, CacheByDomainName) still exists + assert_eq!(dns_cache.get_cache().get_cache_data().len(),1); + + } + + + #[test] + //this test is going to prove if the cleaning after the timeout is acting correctly two layer down (CacheByDomain) + // ------BEFORE THE 5 SECONDS----- + // RTYPE:A -> {uchile (invalid) -> [..], example.com (valid) -> [..]} + // RTYPE:NS -> {example (valid) -> [..], example.com (invalid) -> [...]} + //-------AFTER THE 5 SECONDS----- + // RTYPE:A -> {example.com -> [...]} + // RTYPE:NS -> {uchile.com -> [...]} + fn filter_timout_cache_data_cleaning_two_layer_down(){ + use std::{thread, time}; + let mut dns_cache = DnsCache::new(); + + dns_cache.set_max_size(5); + //Defaults Rdatas to use + let a_rdata = Rdata::A(ARdata::new()); + let ns_rdata = Rdata::NS(NsRdata::new()); + + + let mut domain_name_1 = DomainName::new(); + domain_name_1.set_name(String::from("example.com")); + + let mut domain_name_2 = DomainName::new(); + domain_name_2.set_name(String::from("uchile.cl")); + + //adding in A rtypes + let mut resource_record_valid_a = ResourceRecord::new(a_rdata.clone()); + resource_record_valid_a.set_ttl(1000); + dns_cache.add(domain_name_1.clone(), resource_record_valid_a.clone()); + + let mut resource_record_invalid_a = ResourceRecord::new(a_rdata.clone()); + resource_record_invalid_a.set_ttl(4); + dns_cache.add(domain_name_2.clone(), resource_record_invalid_a.clone()); + + //adding in NS rtypes + let mut resource_record_valid_ns = ResourceRecord::new(ns_rdata.clone()); + resource_record_valid_ns.set_ttl(1000); + dns_cache.add(domain_name_2.clone(), resource_record_valid_ns.clone()); + + let mut resource_record_invalid_ns = ResourceRecord::new(ns_rdata.clone()); + resource_record_invalid_ns.set_ttl(4); + dns_cache.add(domain_name_1.clone(), resource_record_invalid_ns.clone()); + + + //check if every record_types_data (HashMap for A and for NS) has 2 element + let record_types_data = dns_cache.get_cache().get_cache_data(); + //CacheByDomainName for A type + if let Some(record_types_data_a) = record_types_data.get(&Rtype::A) { + // println!("the cache by domain for A type is : \n {:?}",record_types_data_a.get_domain_names_data()); + assert_eq!(record_types_data_a.get_domain_names_data().len(), 2); + } + //CacheByDomainName for NS type + if let Some(record_types_data_ns) = record_types_data.get(&Rtype::NS) { + // println!("the cache by domain for NS type is : \n {:?}",record_types_data_ns.get_domain_names_data()); + assert_eq!(record_types_data_ns.get_domain_names_data().len(), 2); + } + + //check the size of the dns_cache is correctly + assert_eq!(dns_cache.get_size(), 4); + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + dns_cache.timeout_cache(); + + let record_types_data_after_cleaning = dns_cache.get_cache().get_cache_data(); + + //after the cleaning, each cache shoud have 1 element + if let Some(record_types_data_a) = record_types_data_after_cleaning.get(&Rtype::A) { + println!("the cache by domain for A type after the cleaning is : \n {:?}",record_types_data_a.get_domain_names_data()); + //FIXME: Does not delete the invadil rrstore, instead points to a empty array (same error as in cache by domain) + assert_eq!(record_types_data_a.get_domain_names_data().len(), 1); + //check if is the same resource record valid (which survives) + if let Some(rrstore_a_after_cleaning) = record_types_data_a.clone().get_from_host_data(domain_name_1.clone()){ + if let Some(rrstore_data_valid) = rrstore_a_after_cleaning.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid_a); + } + } + } + //CacheByDomainName for NS type + if let Some(record_types_data_ns) = record_types_data_after_cleaning.get(&Rtype::NS) { + println!("the cache by domain for NS type after the cleaning is : \n {:?}",record_types_data_ns.get_domain_names_data()); + //FIXME: Does not delete the invadil rrstore, instead points to a empty array (same error as in cache by domain) + assert_eq!(record_types_data_ns.get_domain_names_data().len(), 1); + //check if is the same resource record valid (which survives) + if let Some(rrstore_ns_after_cleaning) = record_types_data_ns.clone().get_from_host_data(domain_name_2.clone()){ + if let Some(rrstore_data_valid) = rrstore_ns_after_cleaning.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid_ns); + } + } + } + } + } diff --git a/src/dns_cache/cache_by_record_type.rs b/src/dns_cache/cache_by_record_type.rs new file mode 100644 index 00000000..1c5d0a7a --- /dev/null +++ b/src/dns_cache/cache_by_record_type.rs @@ -0,0 +1,1052 @@ +pub mod cache_by_domain_name; +pub mod rr_stored_data; + +use chrono::Utc; +use crate::message::type_rtype::Rtype; +use std::net::IpAddr; +use crate::dns_cache::cache_by_record_type::cache_by_domain_name::CacheByDomainName; +use std::collections::HashMap; +use crate::domain_name::DomainName; +use self::rr_stored_data::RRStoredData; + + +/// Struct that represents the cache data of the DNS cache by record type. +#[derive(Clone, Debug)] +pub struct CacheByRecordType { + /// HashMap that represents the cache data of the DNS cache by record type. + /// + /// The key is the record type and the value is the cache data of the DNS + /// cache by domain name. + record_types_data: HashMap, +} + +/// functions for the cache data +impl CacheByRecordType{ + /// function to create a new CacheByRecordType + /// Example + /// ``` + /// let cache_data = CacheByRecordType::new(); + /// ``` + pub fn new() -> CacheByRecordType { + CacheByRecordType { + record_types_data: HashMap::new(), + } + } + + ///function to add a new element into the cache_data + /// # Example + /// ``` + /// let mut cache_data = CacheByRecordType::new(); + /// let a_rdata = Rdata::A(ARdata::new()); + /// let resource_record = ResourceRecord::new(a_rdata); + /// let rr_cache = RRStoredData::new(resource_record); + /// let mut domain_name = DomainName::new(); + /// domain_name.set_domain_name(String::from("uchile.cl")); + /// cache_data.add_to_cache_data(Rtype::A, domain_name, rr_cache); + /// ``` + /// # Arguments + /// * `rtype` - A Rtype that represents the rtype of the cache data + /// * `domain_name` - A DomainName that represents the domain name of the cache data + /// * `rr_cache` - A RRStoredData that represents the rr_cache of the cache data + + pub fn add_to_cache_data(&mut self, rtype: Rtype, domain_name: DomainName, rr_cache:RRStoredData){ + let mut cache_data = self.get_cache_data(); + if let Some(x) = cache_data.get_mut(&rtype) { + let mut type_hash: CacheByDomainName = x.clone(); + type_hash.add_to_host_data(domain_name, rr_cache); + cache_data.insert(rtype, type_hash); + } + else { + let mut type_hash: CacheByDomainName = CacheByDomainName::new(); + type_hash.add_to_host_data(domain_name, rr_cache); + cache_data.insert(rtype, type_hash); + } + self.set_cache_data(cache_data); + } + + ///function to remove an element from the cache data + /// # Example + /// ``` + /// let mut cache_data = CacheByRecordType::new(); + /// let a_rdata = Rdata::A(ARdata::new()); + /// let resource_record = ResourceRecord::new(a_rdata); + /// let rr_cache = RRStoredData::new(resource_record); + /// let mut domain_name = DomainName::new(); + /// domain_name.set_domain_name(String::from("uchile.cl")); + /// cache_data.add_to_cache_data(Rtype::A, domain_name, rr_cache); + /// cache_data.remove_from_cache_data(domain_name, Rtype::A); + /// ``` + /// # Arguments + /// * `domain_name` - A DomainName that represents the domain name of the cache data + /// * `rtype` - A Rtype that represents the rtype of the cache data + pub fn remove_from_cache_data(&mut self, domain_name: DomainName, rtype: Rtype) -> u32{ + let mut cache_data = self.get_cache_data(); + if let Some(x) = cache_data.get_mut(&rtype) { + let mut type_hash: CacheByDomainName = x.clone(); + let length = type_hash.remove_from_host_data(domain_name); + cache_data.insert(rtype, type_hash); + self.set_cache_data(cache_data); + return length; + } + return 0; + } + + ///function to remove the oldest element from the cache data + /// # Example + /// ``` + /// let mut cache_data = CacheByRecordType::new(); + /// let a_rdata = Rdata::A(ARdata::new()); + /// let resource_record = ResourceRecord::new(a_rdata); + /// let rr_cache = RRStoredData::new(resource_record); + /// let mut domain_name = DomainName::new(); + /// domain_name.set_domain_name(String::from("uchile.cl")); + /// cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); + /// cache_data.add_to_cache_data(Rtype::A, domain_name) + /// cache_data.remove_oldest_used(domain_name, Rtype::A); + /// ``` + /// # Arguments + /// * `domain_name` - A DomainName that represents the domain name of the cache data + /// * `rtype` - A Rtype that represents the rtype of the cache data + + pub fn remove_oldest_used(&mut self) -> u32{ + let cache = self.get_cache_data(); + let mut oldest_used_domain_name = DomainName::new(); + let mut oldest_used_type =Rtype::TXT; + let mut oldest_time = Utc::now(); + + for (rtype, mut host_data) in cache { + let (domain_name,time)=host_data.get_oldest_used(); + if time <= oldest_time { + oldest_used_type = rtype.clone(); + oldest_used_domain_name = domain_name; + oldest_time = time; + } + } + + let length = self.remove_from_cache_data(oldest_used_domain_name, oldest_used_type); + length + } + + ///function to get an element from the cache data + /// # Example + /// ``` + /// let mut cache_data = CacheByRecordType::new(); + /// let a_rdata = Rdata::A(ARdata::new()); + /// let resource_record = ResourceRecord::new(a_rdata); + /// let rr_cache = RRStoredData::new(resource_record); + /// let mut domain_name = DomainName::new(); + /// domain_name.set_domain_name(String::from("uchile.cl")); + /// + /// cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); + /// + /// let rr_cache = cache_data.get_from_cache_data(domain_name.clone(), Rtype::A); + /// ``` + /// # Arguments + /// * `domain_name` - A DomainName that represents the domain name of the cache data + /// * `rtype` - A Rtype that represents the rtype of the cache data + pub fn get_from_cache_data(&mut self, domain_name: DomainName, rtype: Rtype) -> Option>{ + let mut cache_data = self.get_cache_data(); + if let Some(x) = cache_data.get(&rtype) { + let mut type_hash: CacheByDomainName = x.clone(); + let rr_cache_vec = type_hash.get_from_host_data(domain_name); + cache_data.insert(rtype, type_hash); + self.set_cache_data(cache_data); + return rr_cache_vec; + } + else { + return None; + } + } + + /// Removes the cache data that has expired. + /// + /// For each type of cache data, it removes the cache data that has expired, using + /// the `timeout_rr_cache` method of the `CacheByDomainName` struct. If the `CacheByDomainName` struct + /// is empty after the removal, it is removed from the cache data. + pub fn filter_timeout_by_rtype(&mut self) { + let cache_data = self.get_cache_data(); + let clean_cache_data: HashMap = cache_data + .into_iter() + .filter_map(|(rtype, mut data_by_domain)| { + data_by_domain.filter_timeout_by_domain_name(); + if data_by_domain.get_domain_names_data().is_empty() { + None + } else { + Some((rtype, data_by_domain)) + } + }) + .collect(); + self.set_cache_data(clean_cache_data); + + } + + pub fn update_response_time(&mut self, + domain_name: DomainName, + rr_type: Rtype, + response_time: u32, + ip_address: IpAddr, + ) { + let mut cache = self.get_cache_data(); + if let Some(x) = cache.get(&rr_type) { + let mut new_x = x.clone(); + new_x.update_response_time(ip_address, response_time, domain_name); + cache.insert(rr_type, new_x); + self.set_cache_data(cache); + } + + } + + pub fn insert(&mut self,rtype:Rtype, host_data: CacheByDomainName) { + self.record_types_data.insert(rtype, host_data); + + } + + pub fn iter(&mut self) -> std::collections::hash_map::Iter<'_, Rtype, CacheByDomainName>{ + return self.record_types_data.iter() + + } +} + +///setter and getter for the host data +impl CacheByRecordType{ + + pub fn get_cache_data(&self) -> HashMap { + return self.record_types_data.clone(); + } + + pub fn set_cache_data(&mut self, cache_data: HashMap) { + self.record_types_data = cache_data; + } + + pub fn get(&self, rtype : Rtype) -> Option<&CacheByDomainName>{ + return self.record_types_data.get(&rtype); + } +} + +#[cfg(test)] +mod cache_data_test{ + use chrono::{Utc, Duration}; + //use std::thread::sleep; + //use std::time::Duration as StdDuration; + + use crate::message::rdata::cname_rdata::CnameRdata; + use crate::message::rdata::hinfo_rdata::HinfoRdata; + use crate::message::rdata::mx_rdata::MxRdata; + use crate::message::rdata::ptr_rdata::PtrRdata; + use crate::message::rdata::soa_rdata::SoaRdata; + use crate::message::rdata::tsig_rdata::TSigRdata; + use crate::message::rdata::{txt_rdata::TxtRdata, ns_rdata::NsRdata}; + use crate::message::type_rtype::Rtype; + use crate::dns_cache::cache_by_record_type::rr_stored_data::RRStoredData; + use crate::domain_name::DomainName; + use crate::message::rdata::Rdata; + use crate::message::rdata::a_rdata::ARdata; + use crate::message::resource_record::ResourceRecord; + use crate::dns_cache::cache_by_record_type::cache_by_domain_name::CacheByDomainName; + use std::{collections::HashMap, net::IpAddr}; + + + + use super::CacheByRecordType; + + //Constructor test + #[test] + fn constructor_test(){ + let cache_data = CacheByRecordType::new(); + + assert!(cache_data.record_types_data.is_empty()); + } + + //Getter and setter test + #[test] + fn get_cache_data(){ + let cache_data = CacheByRecordType::new(); + + let cache_data_hash = cache_data.get_cache_data(); + + assert!(cache_data_hash.is_empty()); + } + + #[test] + fn set_cache_data(){ + let mut cache_data = CacheByRecordType::new(); + + let mut cache_data_hash = HashMap::new(); + let mut host_data = CacheByDomainName::new(); + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let rr_cache = RRStoredData::new(resource_record); + host_data.add_to_host_data(domain_name, rr_cache); + cache_data_hash.insert(Rtype::A, host_data); + + cache_data.set_cache_data(cache_data_hash); + + assert_eq!(cache_data.get_cache_data().len(), 1); + } + + //Add to cache data test + #[test] + fn add_to_cache_data(){ + let mut cache_data = CacheByRecordType::new(); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let rr_cache = RRStoredData::new(resource_record); + + cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); + + assert_eq!(cache_data.get_cache_data().len(), 1); + + let mut new_vec = Vec::new(); + new_vec.push(String::from("hola")); + let text_rdata = Rdata::TXT(TxtRdata::new(new_vec)); + let resource_record_2 = ResourceRecord::new(text_rdata); + let rr_cache_2 = RRStoredData::new(resource_record_2); + + cache_data.add_to_cache_data(Rtype::TXT, domain_name.clone(), rr_cache_2); + + assert_eq!(cache_data.get_cache_data().len(), 2); + + let a_rdata_2 = Rdata::A(ARdata::new()); + let resource_record_3 = ResourceRecord::new(a_rdata_2); + let rr_cache_3 = RRStoredData::new(resource_record_3); + + cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache_3); + + assert_eq!(cache_data.get_cache_data().len(), 2); + } + + //Remove from cache data test + #[test] + fn remove_from_cache_data(){ + let mut cache_data = CacheByRecordType::new(); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let rr_cache = RRStoredData::new(resource_record); + + cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); + + assert_eq!(cache_data.get_cache_data().len(), 1); + + cache_data.remove_from_cache_data(domain_name.clone(), Rtype::A); + + let cache_hash = cache_data.get_cache_data(); + + let host_data = cache_hash.get(&Rtype::A).unwrap(); + + assert!(host_data.get_domain_names_data().is_empty()); + } + + //Get from cache data test + #[test] + fn get_from_cache_data(){ + let mut cache_data = CacheByRecordType::new(); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let rr_cache = RRStoredData::new(resource_record); + + cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); + + assert!(!cache_data.get_cache_data().is_empty()); + + let rr_cache_vec = cache_data.get_from_cache_data(domain_name.clone(), Rtype::A).unwrap(); + + assert_eq!(rr_cache_vec.len(), 1); + + let mut new_vec = Vec::new(); + new_vec.push(String::from("hola")); + let text_rdata = Rdata::TXT(TxtRdata::new(new_vec)); + let resource_record_2 = ResourceRecord::new(text_rdata); + let rr_cache_2 = RRStoredData::new(resource_record_2); + + cache_data.add_to_cache_data(Rtype::TXT, domain_name.clone(), rr_cache_2); + + let rr_cache_vec_2 = cache_data.get_from_cache_data(domain_name.clone(), Rtype::TXT).unwrap(); + + assert_eq!(rr_cache_vec_2.len(), 1); + + let rr_cache_vec_3 = cache_data.get_from_cache_data(DomainName::new(), Rtype::A); + + assert!(rr_cache_vec_3.is_none()); + } + + //remove oldest used test + #[test] + fn remove_oldest_used(){ + let mut cache_data = CacheByRecordType::new(); + + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let mut rr_cache = RRStoredData::new(resource_record); + let now = Utc::now(); + let time_back = Duration::seconds(3600); + let new_time = now - time_back; + rr_cache.set_last_use(new_time); + let mut domain_name_1 = DomainName::new(); + domain_name_1.set_name(String::from("notexpected")); + let mut domain_name_2 = DomainName::new(); + domain_name_2.set_name(String::from("expected")); + + let mut new_vec = Vec::new(); + new_vec.push(String::from("uchile.cl")); + let text_rdata = Rdata::TXT(TxtRdata::new(new_vec)); + let resource_record_2 = ResourceRecord::new(text_rdata); + let mut rr_cache_2 = RRStoredData::new(resource_record_2); + rr_cache_2.set_last_use(Utc::now()); + + + cache_data.add_to_cache_data(Rtype::A, domain_name_1.clone(), rr_cache); + cache_data.add_to_cache_data(Rtype::TXT, domain_name_2.clone(), rr_cache_2); + + let _vec_rr_cache_a = cache_data.get_from_cache_data(domain_name_1.clone(), Rtype::A).unwrap(); + + let a = cache_data.remove_oldest_used(); + + let vec_rr_cache_txt_expected = cache_data.get_from_cache_data(domain_name_2, Rtype::TXT); + let vec_rr_cache_a_expected = cache_data.get_from_cache_data(domain_name_1.clone(), Rtype::A).unwrap(); + + assert_eq!(a,1); + assert_eq!(vec_rr_cache_a_expected.len(), 1); + assert!(vec_rr_cache_txt_expected.is_none()); + } + + //update response time test + #[test] + fn update_response_time(){ + let mut cache_data = CacheByRecordType::new(); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + let ip_address = IpAddr::from([127, 0, 0, 1]); + let mut a_rdata = ARdata::new(); + a_rdata.set_address(ip_address); + let rdata = Rdata::A(a_rdata); + let resource_record = ResourceRecord::new(rdata); + let mut rr_cache = RRStoredData::new(resource_record); + rr_cache.set_response_time(1000); + + cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); + + cache_data.update_response_time(domain_name.clone(), Rtype::A, 2000, ip_address.clone()); + + let rr_cache_vec = cache_data.get_from_cache_data(domain_name.clone(), Rtype::A).unwrap(); + + for rr_cache in rr_cache_vec { + assert_eq!(rr_cache.get_response_time(), 2500); + } + } + + #[test] + fn filter_timeout_by_rtype_rtype_a() { + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let a_rdata = Rdata::A(ARdata::new()); + + let mut resource_record_valid = ResourceRecord::new(a_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(a_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_record_type.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache_invalid); + + //check if the domain with A type has 2 RRStoredData + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::A){ + assert_eq!(rr_cache_vec.len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + //check if the len is 1 instead of 2 (one RRStoredData was eliminated) + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::A){ + assert_eq!(rr_cache_vec.len(), 1); + //chek if the resource record who survives is the right one + if let Some(rrstore_data_valid) = rr_cache_vec.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid); + } + } + + } + + #[test] + fn filter_timeout_by_rtype_rtype_ns() { + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let ns_rdata = Rdata::NS(NsRdata::new()); + + let mut resource_record_valid = ResourceRecord::new(ns_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(ns_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_record_type.add_to_cache_data(Rtype::NS, domain_name.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::NS, domain_name.clone(), rr_cache_invalid); + + //check if the domain with A type has 2 RRStoredData + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::NS){ + assert_eq!(rr_cache_vec.len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + //check if the len is 1 instead of 2 (one RRStoredData was eliminated) + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::NS){ + assert_eq!(rr_cache_vec.len(), 1); + //chek if the resource record who survives is the right one + if let Some(rrstore_data_valid) = rr_cache_vec.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid); + } + } + + } + + #[test] + fn filter_timeout_by_rtype_rtype_cname() { + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let cname_rdata = Rdata::CNAME(CnameRdata::new()); + + let mut resource_record_valid = ResourceRecord::new(cname_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(cname_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_record_type.add_to_cache_data(Rtype::CNAME, domain_name.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::CNAME, domain_name.clone(), rr_cache_invalid); + + //check if the domain with A type has 2 RRStoredData + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::CNAME){ + assert_eq!(rr_cache_vec.len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + //check if the len is 1 instead of 2 (one RRStoredData was eliminated) + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::CNAME){ + assert_eq!(rr_cache_vec.len(), 1); + //chek if the resource record who survives is the right one + if let Some(rrstore_data_valid) = rr_cache_vec.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid); + } + } + + } + + #[test] + fn filter_timeout_by_rtype_rtype_soa() { + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let soa_rdata = Rdata::SOA(SoaRdata::new()); + + let mut resource_record_valid = ResourceRecord::new(soa_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(soa_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_record_type.add_to_cache_data(Rtype::SOA, domain_name.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::SOA, domain_name.clone(), rr_cache_invalid); + + //check if the domain with A type has 2 RRStoredData + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::SOA){ + assert_eq!(rr_cache_vec.len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + //check if the len is 1 instead of 2 (one RRStoredData was eliminated) + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::SOA){ + assert_eq!(rr_cache_vec.len(), 1); + //chek if the resource record who survives is the right one + if let Some(rrstore_data_valid) = rr_cache_vec.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid); + } + } + + } + + #[test] + fn filter_timeout_by_rtype_rtype_ptr() { + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let ptr_rdata = Rdata::PTR(PtrRdata::new()); + + let mut resource_record_valid = ResourceRecord::new(ptr_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(ptr_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_record_type.add_to_cache_data(Rtype::PTR, domain_name.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::PTR, domain_name.clone(), rr_cache_invalid); + + //check if the domain with A type has 2 RRStoredData + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::PTR){ + assert_eq!(rr_cache_vec.len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + //check if the len is 1 instead of 2 (one RRStoredData was eliminated) + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::PTR){ + assert_eq!(rr_cache_vec.len(), 1); + //chek if the resource record who survives is the right one + if let Some(rrstore_data_valid) = rr_cache_vec.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid); + } + } + + } + + #[test] + fn filter_timeout_by_rtype_rtype_mx() { + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let mx_rdata = Rdata::MX(MxRdata::new()); + + let mut resource_record_valid = ResourceRecord::new(mx_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(mx_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_record_type.add_to_cache_data(Rtype::MX, domain_name.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::MX, domain_name.clone(), rr_cache_invalid); + + //check if the domain with A type has 2 RRStoredData + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::MX){ + assert_eq!(rr_cache_vec.len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + //check if the len is 1 instead of 2 (one RRStoredData was eliminated) + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::MX){ + assert_eq!(rr_cache_vec.len(), 1); + //chek if the resource record who survives is the right one + if let Some(rrstore_data_valid) = rr_cache_vec.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid); + } + } + + } + + #[test] + fn filter_timeout_by_rtype_rtype_txt() { + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let txt_rdata = Rdata::TXT(TxtRdata::new(vec![String::from("test")])); + + let mut resource_record_valid = ResourceRecord::new(txt_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(txt_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_record_type.add_to_cache_data(Rtype::TXT, domain_name.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::TXT, domain_name.clone(), rr_cache_invalid); + + //check if the domain with A type has 2 RRStoredData + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::TXT){ + assert_eq!(rr_cache_vec.len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + //check if the len is 1 instead of 2 (one RRStoredData was eliminated) + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::TXT){ + assert_eq!(rr_cache_vec.len(), 1); + //chek if the resource record who survives is the right one + if let Some(rrstore_data_valid) = rr_cache_vec.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid); + } + } + + } + + #[test] + fn filter_timeout_by_rtype_rtype_hinfo() { + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let hinfo_rdata = Rdata::HINFO(HinfoRdata::new()); + + let mut resource_record_valid = ResourceRecord::new(hinfo_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(hinfo_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_record_type.add_to_cache_data(Rtype::HINFO, domain_name.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::HINFO, domain_name.clone(), rr_cache_invalid); + + //check if the domain with A type has 2 RRStoredData + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::HINFO){ + assert_eq!(rr_cache_vec.len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + //check if the len is 1 instead of 2 (one RRStoredData was eliminated) + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::HINFO){ + assert_eq!(rr_cache_vec.len(), 1); + //chek if the resource record who survives is the right one + if let Some(rrstore_data_valid) = rr_cache_vec.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid); + } + } + + } + + + #[test] + fn filter_timeout_by_rtype_rtype_tsig() { + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let tsig_rdata = Rdata::TSIG(TSigRdata::new()); + + let mut resource_record_valid = ResourceRecord::new(tsig_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(tsig_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_record_type.add_to_cache_data(Rtype::TSIG, domain_name.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::TSIG, domain_name.clone(), rr_cache_invalid); + + //check if the domain with A type has 2 RRStoredData + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::TSIG){ + assert_eq!(rr_cache_vec.len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + //check if the len is 1 instead of 2 (one RRStoredData was eliminated) + if let Some(rr_cache_vec) = cache_record_type.get_from_cache_data(domain_name.clone(), Rtype::TSIG){ + assert_eq!(rr_cache_vec.len(), 1); + //chek if the resource record who survives is the right one + if let Some(rrstore_data_valid) = rr_cache_vec.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid); + } + } + + } + + #[test] + fn filter_timout_cache_data_2_differents_rtypes_same_domain(){ + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let a_rdata = Rdata::A(ARdata::new()); + let ns_rdata = Rdata::NS(NsRdata::new()); + + let mut resource_record_valid = ResourceRecord::new(a_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(ns_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_record_type.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::NS, domain_name.clone(), rr_cache_invalid); + + //check if every record_types_data (HashMap for A and for NS) has 1 element + let record_types_data = cache_record_type.get_cache_data(); + //CacheByDomainName for A type + if let Some(record_types_data_a) = record_types_data.get(&Rtype::A) { + if let Some(rrstore_data_vec_a) = record_types_data_a.clone().get_from_host_data(domain_name.clone()){ + assert_eq!(rrstore_data_vec_a.len(), 1); + } + } + //CacheByDomainName for NS type + if let Some(record_types_data_ns) = record_types_data.get(&Rtype::NS) { + if let Some(rrstore_data_vec_ns) = record_types_data_ns.clone().get_from_host_data(domain_name.clone()){ + assert_eq!(rrstore_data_vec_ns.len(), 1); + } + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + let record_types_data_after_clean = cache_record_type.get_cache_data(); + + if let Some(record_types_data_a) = record_types_data_after_clean.get(&Rtype::A) { + if let Some(rrstore_data_vec_a) = record_types_data_a.clone().get_from_host_data(domain_name.clone()){ + //the valid one still having the value + assert_eq!(rrstore_data_vec_a.len(), 1); + } + } + + //FIXME: + if let Some(record_types_data_ns) = record_types_data_after_clean.get(&Rtype::NS) { + println!(" el CacheByDOmain de NS es {:?}", record_types_data_ns); + assert!(false, "Si habia algo dentro del Rtype NS y NO debía ser así"); + } else { + assert!(true); + } + } + + #[test] + fn filter_timout_cache_data_2_differents_rtypes_different_domain(){ + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + let a_rdata = Rdata::A(ARdata::new()); + let ns_rdata = Rdata::NS(NsRdata::new()); + + let mut resource_record_valid = ResourceRecord::new(a_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rr_cache_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(ns_rdata); + resource_record_invalid.set_ttl(4); + let rr_cache_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name_1 = DomainName::new(); + domain_name_1.set_name(String::from("example.com")); + + let mut domain_name_2 = DomainName::new(); + domain_name_2.set_name(String::from("uchile.cl")); + + + cache_record_type.add_to_cache_data(Rtype::A, domain_name_1.clone(), rr_cache_valid); + cache_record_type.add_to_cache_data(Rtype::NS, domain_name_2.clone(), rr_cache_invalid); + + //check if every record_types_data (HashMap for A and for NS) has 1 element + let record_types_data = cache_record_type.get_cache_data(); + //CacheByDomainName for A type + if let Some(record_types_data_a) = record_types_data.get(&Rtype::A) { + if let Some(rrstore_data_vec_a) = record_types_data_a.clone().get_from_host_data(domain_name_1.clone()){ + assert_eq!(rrstore_data_vec_a.len(), 1); + } + } + //CacheByDomainName for NS type + if let Some(record_types_data_ns) = record_types_data.get(&Rtype::NS) { + if let Some(rrstore_data_vec_ns) = record_types_data_ns.clone().get_from_host_data(domain_name_2.clone()){ + assert_eq!(rrstore_data_vec_ns.len(), 1); + } + } + assert_eq!(record_types_data.len(), 2); + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + let record_types_data_after_cleaning = cache_record_type.get_cache_data(); + + assert_eq!(record_types_data_after_cleaning.len(), 1); + + if let Some(record_types_data_a) = record_types_data_after_cleaning.get(&Rtype::A) { + if let Some(rrstore_data_vec_a) = record_types_data_a.clone().get_from_host_data(domain_name_1.clone()){ + //the valid one still having the value + assert_eq!(rrstore_data_vec_a.len(), 1); + } + } + + if let Some(record_types_data_ns) = record_types_data_after_cleaning.get(&Rtype::NS) { + println!(" el CacheByDomain de NS es : \n {:?}", record_types_data_ns); + assert!(false, "Si habia algo dentro del Rtype NS y NO debía ser así"); + } else { + assert!(true); + } + } + + #[test] + //this test is going to prove if the cleaning after the timeout is acting correctly one layer down (CacheByDomain) + // ------BEFORE THE 5 SECONDS----- + // RTYPE:A -> {uchile (invalid) -> [..], example.com (valid) -> [..]} + // RTYPE:NS -> {example (valid) -> [..], example.com (invalid) -> [...]} + //-------AFTER THE 5 SECONDS----- + // RTYPE:A -> {example.com -> [...]} + // RTYPE:NS -> {uchile.com -> [...]} + fn filter_timout_cache_data_cleaning_layer_down(){ + use std::{thread, time}; + let mut cache_record_type = CacheByRecordType::new(); + //Defaults Rdatas to use + let a_rdata = Rdata::A(ARdata::new()); + let ns_rdata = Rdata::NS(NsRdata::new()); + + + let mut domain_name_1 = DomainName::new(); + domain_name_1.set_name(String::from("example.com")); + + let mut domain_name_2 = DomainName::new(); + domain_name_2.set_name(String::from("uchile.cl")); + + //adding in A rtypes + let mut resource_record_valid_a = ResourceRecord::new(a_rdata.clone()); + resource_record_valid_a.set_ttl(1000); + let rr_cache_valid_a = RRStoredData::new(resource_record_valid_a.clone()); + cache_record_type.add_to_cache_data(Rtype::A, domain_name_1.clone(), rr_cache_valid_a); + + let mut resource_record_invalid_a = ResourceRecord::new(a_rdata.clone()); + resource_record_invalid_a.set_ttl(4); + let rr_cache_invalid_a = RRStoredData::new(resource_record_invalid_a.clone()); + cache_record_type.add_to_cache_data(Rtype::A, domain_name_2.clone(), rr_cache_invalid_a); + + //adding in NS rtypes + let mut resource_record_valid_ns = ResourceRecord::new(ns_rdata.clone()); + resource_record_valid_ns.set_ttl(1000); + let rr_cache_valid_ns = RRStoredData::new(resource_record_valid_ns.clone()); + cache_record_type.add_to_cache_data(Rtype::NS, domain_name_2.clone(), rr_cache_valid_ns); + + let mut resource_record_invalid_ns = ResourceRecord::new(ns_rdata.clone()); + resource_record_invalid_ns.set_ttl(4); + let rr_cache_invalid_ns = RRStoredData::new(resource_record_invalid_ns.clone()); + cache_record_type.add_to_cache_data(Rtype::NS, domain_name_1.clone(), rr_cache_invalid_ns); + + + //check if every record_types_data (HashMap for A and for NS) has 2 element + let record_types_data = cache_record_type.get_cache_data(); + //CacheByDomainName for A type + if let Some(record_types_data_a) = record_types_data.get(&Rtype::A) { + // println!("the cache by domain for A type is : \n {:?}",record_types_data_a.get_domain_names_data()); + assert_eq!(record_types_data_a.get_domain_names_data().len(), 2); + } + //CacheByDomainName for NS type + if let Some(record_types_data_ns) = record_types_data.get(&Rtype::NS) { + // println!("the cache by domain for NS type is : \n {:?}",record_types_data_ns.get_domain_names_data()); + assert_eq!(record_types_data_ns.get_domain_names_data().len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + cache_record_type.filter_timeout_by_rtype(); + + let record_types_data_after_cleaning = cache_record_type.get_cache_data(); + + //after the cleaning, each cache shoud have 1 element + if let Some(record_types_data_a) = record_types_data_after_cleaning.get(&Rtype::A) { + println!("the cache by domain for A type after the cleaning is : \n {:?}",record_types_data_a.get_domain_names_data()); + //FIXME: Does not delete the invadil rrstore, instead points to a empty array (same error as in cache by domain) + assert_eq!(record_types_data_a.get_domain_names_data().len(), 1); + //check if is the same resource record valid (which survives) + if let Some(rrstore_a_after_cleaning) = record_types_data_a.clone().get_from_host_data(domain_name_1.clone()){ + if let Some(rrstore_data_valid) = rrstore_a_after_cleaning.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid_a); + } + } + } + + //CacheByDomainName for NS type + if let Some(record_types_data_ns) = record_types_data_after_cleaning.get(&Rtype::NS) { + println!("the cache by domain for NS type after the cleaning is : \n {:?}",record_types_data_ns.get_domain_names_data()); + //FIXME: Does not delete the invadil rrstore, instead points to a empty array (same error as in cache by domain) + assert_eq!(record_types_data_ns.get_domain_names_data().len(), 1); + //check if is the same resource record valid (which survives) + if let Some(rrstore_ns_after_cleaning) = + record_types_data_ns + .clone() + .get_from_host_data(domain_name_2.clone()) { + if let Some(rrstore_data_valid) = rrstore_ns_after_cleaning.get(0) { + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid_ns); + } + } + } + + + } + + +} \ No newline at end of file diff --git a/src/dns_cache/cache_by_record_type/cache_by_domain_name.rs b/src/dns_cache/cache_by_record_type/cache_by_domain_name.rs new file mode 100644 index 00000000..4a675697 --- /dev/null +++ b/src/dns_cache/cache_by_record_type/cache_by_domain_name.rs @@ -0,0 +1,591 @@ +use chrono::{Utc, DateTime}; +use crate::{domain_name::DomainName, message::rdata::Rdata}; +use crate::dns_cache::cache_by_record_type::rr_stored_data::RRStoredData; +use std::{collections::HashMap, net::IpAddr}; + +/// This struct saves the data associated with a host in the cache. +/// +/// Given a single `DomainName`, it groups all data associated with it +/// a `Vec` inside a `HashMap>`. +/// This means, all the cache data associated with a single host +/// of an specific `Rtype`. +#[derive(Clone, Debug)] +pub struct CacheByDomainName { + /// Contains the Resource Records associated to each host domain name. + /// + /// The key is the `DomainName` of the host, and the value is a + /// `Vec`, which contains all the Resource Records + /// data associated to the host for a single `Rtype`. + domain_names_data: HashMap>, +} + +///functions for the host data +impl CacheByDomainName { + + ///function to create a new host data + /// # Example + /// ``` + /// let host_data = CacheByDomainName::new(); + /// ``` + pub fn new() -> CacheByDomainName { + CacheByDomainName { + domain_names_data: HashMap::new(), + } + } + + ///function to add a rr_cache to the host data + /// # Example + /// ``` + /// let mut host_data = CacheByDomainName::new(); + /// let a_rdata = Rdata::A(ARdata::new()); + /// let resource_record = ResourceRecord::new(a_rdata); + /// let rr_cache = RRStoredData::new(resource_record); + /// let mut domain_name = DomainName::new(); + /// domain_name.set_name(String::from("uchile.cl")); + /// host_data.add_to_host_data(domain_name, rr_cache); + /// ``` + /// # Arguments + /// * `host_name` - A Domain Name that represents the name of the host + /// * `rr_cache` - A RRStoredData that represents the rr_cache of the host + pub fn add_to_host_data(&mut self, host_name: DomainName, rr_cache: RRStoredData) { + let mut domain_names_data = self.get_domain_names_data(); + if let Some(y) = domain_names_data.get_mut(&host_name){ + let mut rr_cache_vec = y.clone(); + rr_cache_vec.push(rr_cache); + domain_names_data.insert(host_name, rr_cache_vec); + } + else{ + let mut rr_cache_vec = Vec::new(); + rr_cache_vec.push(rr_cache); + domain_names_data.insert(host_name, rr_cache_vec); + } + self.set_domain_names_data(domain_names_data); + } + + ///function to remove an element from the host data + /// # Example + /// ``` + /// let mut host_data = CacheByDomainName::new(); + /// let a_rdata = Rdata::A(ARdata::new()); + /// let resource_record = ResourceRecord::new(a_rdata); + /// let rr_cache = RRStoredData::new(resource_record); + /// let mut domain_name = DomainName::new(); + /// domain_name.set_name(String::from("uchile.cl")); + /// host_data.add_to_host_data(domain_name, rr_cache); + /// host_data.remove_from_host_data(domain_name); + /// ``` + pub fn remove_from_host_data(&mut self, host_name: DomainName) -> u32{ + let mut domain_names_data = self.get_domain_names_data(); + if let Some(_x) = domain_names_data.remove(&host_name){ + self.set_domain_names_data(domain_names_data); + return _x.len() as u32; + } + return 0; + } + + /// Returns an element from the host data. + /// + /// This element corresponds to a vector of `RRStoredData` that contains + /// the `RRStoredData` of the host. + /// + /// # Example + /// ``` + /// let mut host_data = CacheByDomainName::new(); + /// let a_rdata = Rdata::A(ARdata::new()); + /// let resource_record = ResourceRecord::new(a_rdata); + /// let rr_cache = RRStoredData::new(resource_record); + /// let mut domain_name = DomainName::new(); + /// domain_name.set_name(String::from("uchile.cl")); + /// host_data.add_to_host_data(domain_name, rr_cache); + /// let host_data_2_vec = host_data.get_from_host_data(domain_name); + /// ``` + pub fn get_from_host_data(&mut self, host_name: DomainName) -> Option>{ + let mut domain_names_data = self.get_domain_names_data(); + if let Some(x) = domain_names_data.get(&host_name){ + let new_x = x.clone(); + let mut rr_cache_vec = Vec::::new(); + for mut rr_cache in new_x{ + rr_cache.set_last_use(Utc::now()); + rr_cache_vec.push(rr_cache.clone()); + } + domain_names_data.insert(host_name, rr_cache_vec.clone()); + self.set_domain_names_data(domain_names_data); + + return Some(rr_cache_vec); + } + else{ + return None; + } + } + + ///function to get the oldest used + /// # Example + /// ``` + /// let mut host_data = CacheByDomainName::new(); + /// let a_rdata = Rdata::A(ARdata::new()); + /// let resource_record = ResourceRecord::new(a_rdata); + /// let rr_cache = RRStoredData::new(resource_record); + /// rr_cache.set_last_use(Utc::now()); + /// let mut domain_name = DomainName::new(); + /// domain_name.set_name(String::from("uchile.cl")); + /// host_data.add_to_host_data(domain_name, rr_cache); + /// let host_data_2 = get_oldest_used.get_from_host_data(domain_name); + /// ``` + pub fn get_oldest_used(&mut self)-> (DomainName,DateTime){ + let host = self.get_domain_names_data(); + let mut used_in = Utc::now(); + + let mut oldest_used_domain_name = DomainName::new(); + + for (host_key, host_value) in host { + let rr_last_use = host_value[0].get_last_use(); + + if rr_last_use <= used_in { + used_in = rr_last_use; + oldest_used_domain_name = host_key.clone(); + + } + } + + return (oldest_used_domain_name,used_in); + + } + + ///function to insert a domain name and a new value to be associated + /// and return the value that was associated before, or none if + /// the domain name didn't exist before + /// # Example + /// ``` + /// let mut host_data = CacheByDomainName::new(); + /// let a_rdata = Rdata::A(ARdata::new()); + /// let resource_record = ResourceRecord::new(a_rdata); + /// let mut rr_cache = RRStoredData::new(resource_record); + /// rr_cache.set_last_use(Utc::now()); + /// let mut domain_name = DomainName::new(); + /// domain_name.set_name(String::from("uchile.cl")); + /// host_data.add_to_host_data(domain_name, rr_cache); + /// let mut domain_name_new = DomainName::new(); + /// domain_name_new.set_name(String::from("inserted")); + /// let a_rdata_2 = Rdata::A(ARdata::new()); + /// let resource_record_2 = ResourceRecord::new(a_rdata); + /// let mut rr_cache_2 = RRStoredData::new(resource_record); + /// host_data.insert(domain_name_new, rr_cache_2); + /// ``` + pub fn insert(&mut self,domain_name:DomainName, rr_cache_vec : Vec) -> Option>{ + return self.domain_names_data.insert(domain_name, rr_cache_vec) + } + + ///function to update the response time + /// # Example + /// ``` + /// let mut host_data = CacheByDomainName::new(); + /// let ip_address = IpAddr::from([127, 0, 0, 1]); + /// let a_rdata = ARdata::new(); + /// a_rdata.set_address(ip_address); + /// let rdata = Rdata::A(a_rdata); + /// let resource_record = ResourceRecord::new(rdata); + /// let mut rr_cache = RRStoredData::new(resource_record); + /// rr_cache.set_response_time(1000); + /// let mut domain_name = DomainName::new(); + /// domain_name.set_name(String::from("uchile.cl")); + /// host_data.add_to_host_data(domain_name, rr_cache); + /// host_data.update_response_time(ip_address, 2000, domain_name); + /// ``` + pub fn update_response_time(&mut self, ip_address: IpAddr, response_time: u32, domain_name: DomainName){ + let mut domain_names_data = self.get_domain_names_data(); + if let Some(x) = domain_names_data.get(&domain_name){ + let rr_cache_vec = x.clone(); + + let mut new_rr_cache_vec = Vec::::new(); + + for mut rr_cache in rr_cache_vec{ + let rr_ip_address = match rr_cache.get_resource_record().get_rdata() { + Rdata::A(val) => val.get_address(), + _ => unreachable!(), + }; + + if rr_ip_address == ip_address{ + rr_cache.set_response_time(response_time + rr_cache.get_response_time()/2); + } + + new_rr_cache_vec.push(rr_cache.clone()); + } + domain_names_data.insert(domain_name, new_rr_cache_vec); + + self.set_domain_names_data(domain_names_data); + } + } + + /// For each domain name, it removes the RRStoredData past its TTL. + pub fn filter_timeout_by_domain_name(&mut self) { + let current_time = Utc::now(); + let data_by_domain = self.get_domain_names_data(); + let clean_data_by_domain: HashMap> = data_by_domain + .into_iter() + .filter_map(|(domain_name, rr_cache_vec)| { + let filtered_rr_cache_vec: Vec = rr_cache_vec + .into_iter() + .filter(|rr_cache| rr_cache.get_absolute_ttl() > current_time) + .collect(); + + if !filtered_rr_cache_vec.is_empty() { + Some((domain_name, filtered_rr_cache_vec)) + } else { + None + } + }).collect(); + + self.set_domain_names_data(clean_data_by_domain); + } + +} + +///setter and getter for the host data +impl CacheByDomainName{ + + pub fn get_domain_names_data(&self) -> HashMap> { + return self.domain_names_data.clone(); + } + + pub fn set_domain_names_data(&mut self, domain_names_data: HashMap>) { + self.domain_names_data = domain_names_data; + } + + pub fn get(&self,domain_name:&DomainName) -> Option<&Vec>{ + return self.domain_names_data.get(domain_name) + + } +} + +#[cfg(test)] +mod host_data_test{ + use chrono::Utc; + use crate::message::rdata::txt_rdata::TxtRdata; + use crate::dns_cache::cache_by_record_type::rr_stored_data::RRStoredData; + use crate::domain_name::DomainName; + use crate::message::rdata::Rdata; + use crate::message::rdata::a_rdata::ARdata; + use crate::message::resource_record::ResourceRecord; + use std::{collections::HashMap, net::IpAddr}; + + use super::CacheByDomainName; + + //Contructor test + #[test] + fn constructor_test(){ + let host_data = CacheByDomainName::new(); + assert!(host_data.domain_names_data.is_empty()); + } + + //Getters and setters test + #[test] + fn get_domain_names_data(){ + let host_data = CacheByDomainName::new(); + + let domain_names_data = host_data.get_domain_names_data(); + + assert!(domain_names_data.is_empty()); + } + + #[test] + fn set_domain_names_data(){ + let mut host_data = CacheByDomainName::new(); + + let mut domain_names_data = HashMap::new(); + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + domain_names_data.insert(domain_name.clone(), Vec::new()); + + assert!(host_data.domain_names_data.is_empty()); + + host_data.set_domain_names_data(domain_names_data.clone()); + + let domain_names_data_2 = host_data.get_domain_names_data(); + + assert!(!domain_names_data_2.is_empty()); + } + + //add_to_host_data test + #[test] + fn add_to_host_data(){ + let mut host_data = CacheByDomainName::new(); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let rr_cache = RRStoredData::new(resource_record); + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + host_data.add_to_host_data(domain_name.clone(), rr_cache); + + let domain_names_data = host_data.get_domain_names_data(); + + assert_eq!(domain_names_data.len(), 1); + + let mut new_vec = Vec::new(); + new_vec.push(String::from("hola")); + let text_rdata = Rdata::TXT(TxtRdata::new(new_vec)); + let resource_record_2 = ResourceRecord::new(text_rdata); + let rr_cache_2 = RRStoredData::new(resource_record_2); + host_data.add_to_host_data(domain_name.clone(), rr_cache_2); + + let domain_names_data_2 = host_data.get_domain_names_data(); + + assert_eq!(domain_names_data_2.len(), 1); + + let domain_names_data_vec = domain_names_data_2.get(&domain_name).unwrap(); + + assert_eq!(domain_names_data_vec.len(), 2); + } + + //remove_from_host_data test + #[test] + fn remove_from_host_data(){ + let mut host_data = CacheByDomainName::new(); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let rr_cache = RRStoredData::new(resource_record); + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + host_data.add_to_host_data(domain_name.clone(), rr_cache); + + let domain_names_data = host_data.get_domain_names_data(); + + assert_eq!(domain_names_data.len(), 1); + + host_data.remove_from_host_data(domain_name.clone()); + + let domain_names_data_2 = host_data.get_domain_names_data(); + + assert_eq!(domain_names_data_2.len(), 0); + } + + //get_from_host_data test + #[test] + fn get_from_host_data(){ + let mut host_data = CacheByDomainName::new(); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let rr_cache = RRStoredData::new(resource_record); + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + host_data.add_to_host_data(domain_name.clone(), rr_cache); + + let domain_names_data = host_data.get_domain_names_data(); + + assert_eq!(domain_names_data.len(), 1); + + let domain_names_data_vec = host_data.get_from_host_data(domain_name.clone()).unwrap(); + + assert_eq!(domain_names_data_vec.len(), 1); + } + + //get_from_host_data test with no domain name + #[test] + fn get_from_host_data_no_domain_name(){ + let mut host_data = CacheByDomainName::new(); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let rr_cache = RRStoredData::new(resource_record); + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + host_data.add_to_host_data(domain_name.clone(), rr_cache); + + let domain_names_data = host_data.get_domain_names_data(); + + // One domain name + assert_eq!(domain_names_data.len(), 1); + + // Assuming this test is for the case where the domain name is not in the host data + let domain_name_2 = DomainName::new(); + let element = host_data.get_from_host_data(domain_name_2.clone()); + assert_eq!(element, None); + } + + //get test + #[test] + fn get(){ + let mut host_data = CacheByDomainName::new(); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let mut rr_cache = RRStoredData::new(resource_record); + rr_cache.set_response_time(1234433455); + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + host_data.add_to_host_data(domain_name.clone(), rr_cache); + + let vec_rr_cache = host_data.get(&domain_name).unwrap(); + + let rr_cache_o = vec_rr_cache.get(0).unwrap(); + + assert_eq!(1234433455, rr_cache_o.get_response_time()) + } + + //get oldest used test + #[test] + fn get_oldest_used(){ + let mut host_data = CacheByDomainName::new(); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let mut rr_cache = RRStoredData::new(resource_record); + rr_cache.set_last_use(Utc::now()); + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("expected")); + + let mut new_vec = Vec::new(); + new_vec.push(String::from("uchile.cl")); + let text_rdata = Rdata::TXT(TxtRdata::new(new_vec)); + let resource_record_2 = ResourceRecord::new(text_rdata); + let mut rr_cache_2 = RRStoredData::new(resource_record_2); + rr_cache_2.set_last_use(Utc::now()); + host_data.add_to_host_data(domain_name.clone(), rr_cache_2); + + let oldest_used = host_data.get_oldest_used(); + let oldest_name = oldest_used.0; + + + assert_eq!("expected".to_string(), oldest_name.get_name()) + } + + //get insert used test + #[test] + fn insert(){ + let mut host_data = CacheByDomainName::new(); + let a_rdata = Rdata::A(ARdata::new()); + let resource_record = ResourceRecord::new(a_rdata); + let mut rr_cache = RRStoredData::new(resource_record); + rr_cache.set_response_time(12); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + host_data.add_to_host_data(domain_name, rr_cache); + let mut domain_name_new = DomainName::new(); + domain_name_new.set_name(String::from("inserted")); + let a_rdata_2 = Rdata::A(ARdata::new()); + let resource_record_2 = ResourceRecord::new(a_rdata_2); + let rr_cache_2 = RRStoredData::new(resource_record_2); + + let mut rr_vec = Vec::new(); + rr_vec.push(rr_cache_2); + let expected = host_data.insert(domain_name_new, rr_vec); + assert_eq!(expected, None) + + } + + //update response time test + #[test] + fn update_response_time(){ + let mut host_data = CacheByDomainName::new(); + let ip_address = IpAddr::from([127, 0, 0, 1]); + let mut a_rdata = ARdata::new(); + a_rdata.set_address(ip_address); + let rdata = Rdata::A(a_rdata); + let resource_record = ResourceRecord::new(rdata); + let mut rr_cache = RRStoredData::new(resource_record); + rr_cache.set_response_time(1000); + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + host_data.add_to_host_data(domain_name.clone(), rr_cache); + host_data.update_response_time(ip_address, 2000, domain_name.clone()); + + let domain_names_data = host_data.get_domain_names_data(); + + let rr_cache_vec = domain_names_data.get(&domain_name).unwrap(); + + let rr_cache = rr_cache_vec.get(0).unwrap(); + + assert_eq!(2500, rr_cache.get_response_time()) + } + + #[test] + fn timeout_rr_cache_one_domain() { + use std::{thread, time}; + let mut cache_by_domain_name = CacheByDomainName::new(); + let a_rdata = Rdata::A(ARdata::new()); + + let mut resource_record_valid = ResourceRecord::new(a_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rrstore_data_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(a_rdata); + resource_record_invalid.set_ttl(4); + let rrstore_data_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name = DomainName::new(); + domain_name.set_name(String::from("uchile.cl")); + + cache_by_domain_name.add_to_host_data(domain_name.clone(), rrstore_data_valid); + cache_by_domain_name.add_to_host_data(domain_name.clone(), rrstore_data_invalid); + + assert_eq!(cache_by_domain_name.get_domain_names_data().len(), 1); + if let Some(rr_cache_vec) = cache_by_domain_name.get_domain_names_data().get(&domain_name) { + assert_eq!(rr_cache_vec.len(), 2); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + //clean the data with expired ttl + cache_by_domain_name.filter_timeout_by_domain_name(); + + assert_eq!(cache_by_domain_name.get_domain_names_data().len(), 1); + if let Some(rr_cache_vec) = cache_by_domain_name.get_domain_names_data().get(&domain_name) { + assert_eq!(rr_cache_vec.len(), 1); + //check if the rescource record who survives is the correct + if let Some(rrstore_data_valid) = rr_cache_vec.get(0){ + let resource_record_after_filter = rrstore_data_valid.get_resource_record(); + assert_eq!(resource_record_after_filter, resource_record_valid); + } + } + } + + #[test] + fn timeout_rr_cache_two_domain(){ + //this test prove the for iteration in filter_timeout_by_domain_name + use std::{thread, time}; + let mut cache_by_domain_name = CacheByDomainName::new(); + let a_rdata = Rdata::A(ARdata::new()); + + let mut resource_record_valid = ResourceRecord::new(a_rdata.clone()); + resource_record_valid.set_ttl(1000); + let rrstore_data_valid = RRStoredData::new(resource_record_valid.clone()); + + let mut resource_record_invalid = ResourceRecord::new(a_rdata.clone()); + resource_record_invalid.set_ttl(4); + let rrstore_data_invalid = RRStoredData::new(resource_record_invalid); + + let mut domain_name_1 = DomainName::new(); + domain_name_1.set_name(String::from("uchile.cl")); + + let mut domain_name_2 = DomainName::new(); + domain_name_2.set_name(String::from("example.com")); + + cache_by_domain_name.add_to_host_data(domain_name_1.clone(), rrstore_data_valid.clone()); + cache_by_domain_name.add_to_host_data(domain_name_2.clone(), rrstore_data_invalid.clone()); + + assert_eq!(cache_by_domain_name.get_domain_names_data().len(), 2); + if let Some(rr_cache_vec) = cache_by_domain_name.get_domain_names_data().get(&domain_name_1) { + assert_eq!(rr_cache_vec.len(), 1); + } + if let Some(rr_cache_vec) = cache_by_domain_name.get_domain_names_data().get(&domain_name_2) { + assert_eq!(rr_cache_vec.len(), 1); + } + + println!("Before timeout: {:?}", Utc::now()); + thread::sleep(time::Duration::from_secs(5)); + println!("After timeout: {:?}", Utc::now()); + //clean the data with expired ttl + cache_by_domain_name.filter_timeout_by_domain_name(); + + println!("The new cache is {:?} ", cache_by_domain_name.get_domain_names_data()); + + //check if the value who survives is the same + if let Some(rr_cache_vec) = cache_by_domain_name.get_domain_names_data().get(&domain_name_1) { + if let Some(rrstore_data) = rr_cache_vec.get(0) { + // println!("the rrstore for domain {:?} afther de timeout is {:?} ", domain_name_1.get_name(), rrstore_data); + assert_eq!(rrstore_data_valid, rrstore_data.clone()); + } + } + //after the filter shoud be just one data in the cache (example.com shoud have been eliminated) + //FIXME: Does not eliminated the (key, data), instead the key points to a empty array ( Domain name {example.com} -> []) + assert_eq!(cache_by_domain_name.get_domain_names_data().len(), 1); + } +} diff --git a/src/rr_cache.rs b/src/dns_cache/cache_by_record_type/rr_stored_data.rs similarity index 77% rename from src/rr_cache.rs rename to src/dns_cache/cache_by_record_type/rr_stored_data.rs index e1a42c10..94bda048 100644 --- a/src/rr_cache.rs +++ b/src/dns_cache/cache_by_record_type/rr_stored_data.rs @@ -2,40 +2,50 @@ use crate::message::resource_record::ResourceRecord; use chrono::prelude::*; #[derive(Clone,PartialEq,Debug)] -// An structs that represents one element in the dns cache. -pub struct RRCache { - // Resource Records of the domain name +/// An structs that represents one element in the dns cache. +pub struct RRStoredData { + /// Resource Records of the domain name resource_record: ResourceRecord, - // Mean of response time of the ip address + /// Mean of response time of the ip address response_time: u32, - // Last use of the rr + /// Last use of the rr last_use: DateTime, + /// Time of creation of the `RRStoredData` in the Resolver's cache. + creation_time: DateTime, } -impl RRCache { - // Creates a new RRCache struct +impl RRStoredData { + // Creates a new RRStoredData struct // // # Examples // ''' - // let rr_cache = RRCache::new(); + // let rr_cache = RRStoredData::new(); // // assert_eq!(rr_cache.resource_records.len(), 0); // assert_eq!(rr_cache.response_time, 5); // ''' // pub fn new(resource_record: ResourceRecord) -> Self { - let rr_cache = RRCache { + let rr_cache = RRStoredData { resource_record: resource_record, response_time: 5000, last_use: Utc::now(), + creation_time: Utc::now(), }; rr_cache } + + pub fn get_absolute_ttl(&self) -> DateTime { + let ttl = self.resource_record.get_ttl(); + let creation_time = self.creation_time; + + creation_time + chrono::Duration::seconds(ttl as i64) + } } // Getters -impl RRCache { +impl RRStoredData { // Gets the resource record from the domain cache pub fn get_resource_record(&self) -> ResourceRecord { self.resource_record.clone() @@ -50,10 +60,15 @@ impl RRCache { pub fn get_last_use(&self) -> DateTime { self.last_use } + + // Gets the creation time of the domain in cache + pub fn get_creation_time(&self) -> DateTime { + self.creation_time + } } // Setters -impl RRCache { +impl RRStoredData { // Sets the resource record attribute with new value pub fn set_resource_record(&mut self, resource_record: ResourceRecord) { self.resource_record = resource_record; @@ -76,7 +91,7 @@ mod rr_cache_test { use crate::message::rdata::Rdata; use crate::message::type_rtype::Rtype; use crate::message::resource_record::ResourceRecord; - use crate::rr_cache::RRCache; + use crate::dns_cache::cache_by_record_type::rr_stored_data::RRStoredData; use std::net::IpAddr; use chrono::prelude::*; @@ -91,7 +106,7 @@ mod rr_cache_test { let mut resource_record = ResourceRecord::new(rdata); resource_record.set_type_code(Rtype::A); - let rr_cache = RRCache::new(resource_record); + let rr_cache = RRStoredData::new(resource_record); assert_eq!(Rtype::from_rtype_to_int(rr_cache.resource_record.get_rtype()), 1); assert_eq!(rr_cache.response_time, 5000); @@ -108,7 +123,7 @@ mod rr_cache_test { let mut resource_record = ResourceRecord::new(rdata.clone()); resource_record.set_type_code(Rtype::A); - let mut rr_cache = RRCache::new(resource_record); + let mut rr_cache = RRStoredData::new(resource_record); assert_eq!(Rtype::from_rtype_to_int(rr_cache.resource_record.get_rtype()), 1); @@ -137,7 +152,7 @@ mod rr_cache_test { let mut resource_record = ResourceRecord::new(rdata); resource_record.set_type_code(Rtype::A); - let mut rr_cache = RRCache::new(resource_record); + let mut rr_cache = RRStoredData::new(resource_record); assert_eq!(rr_cache.get_response_time(), 5000); @@ -157,7 +172,7 @@ mod rr_cache_test { let mut resource_record = ResourceRecord::new(rdata); resource_record.set_type_code(Rtype::A); - let mut rr_cache = RRCache::new(resource_record); + let mut rr_cache = RRStoredData::new(resource_record); let now = Utc::now(); diff --git a/src/dns_cache/cache_data.rs b/src/dns_cache/cache_data.rs deleted file mode 100644 index 7e880e1c..00000000 --- a/src/dns_cache/cache_data.rs +++ /dev/null @@ -1,416 +0,0 @@ -pub mod host_data; - -use chrono::Utc; -//use crate::message::rdata::Rdata; -use crate::message::type_rtype::Rtype; -use crate::rr_cache::RRCache; -use std::net::IpAddr; -use crate::dns_cache::cache_data::host_data::HostData; -use std::collections::HashMap; -use crate::domain_name::DomainName; - - -///struct to define the cache data -#[derive(Clone, Debug)] -pub struct CacheData { - pub cache_data: HashMap, -} - -/// functions for the cache data -impl CacheData{ - /// function to create a new CacheData - /// Example - /// ``` - /// let cache_data = CacheData::new(); - /// ``` - pub fn new() -> CacheData { - CacheData { - cache_data: HashMap::new(), - } - } - - ///function to add a new element into the cache_data - /// # Example - /// ``` - /// let mut cache_data = CacheData::new(); - /// let a_rdata = Rdata::A(ARdata::new()); - /// let resource_record = ResourceRecord::new(a_rdata); - /// let rr_cache = RRCache::new(resource_record); - /// let mut domain_name = DomainName::new(); - /// domain_name.set_domain_name(String::from("uchile.cl")); - /// cache_data.add_to_cache_data(Rtype::A, domain_name, rr_cache); - /// ``` - /// # Arguments - /// * `rtype` - A Rtype that represents the rtype of the cache data - /// * `domain_name` - A DomainName that represents the domain name of the cache data - /// * `rr_cache` - A RRCache that represents the rr_cache of the cache data - - pub fn add_to_cache_data(&mut self, rtype: Rtype, domain_name: DomainName, rr_cache:RRCache){ - let mut cache_data = self.get_cache_data(); - if let Some(x) = cache_data.get_mut(&rtype) { - let mut type_hash: HostData = x.clone(); - type_hash.add_to_host_data(domain_name, rr_cache); - cache_data.insert(rtype, type_hash); - } - else { - let mut type_hash: HostData = HostData::new(); - type_hash.add_to_host_data(domain_name, rr_cache); - cache_data.insert(rtype, type_hash); - } - self.set_cache_data(cache_data); - } - - ///function to remove an element from the cache data - /// # Example - /// ``` - /// let mut cache_data = CacheData::new(); - /// let a_rdata = Rdata::A(ARdata::new()); - /// let resource_record = ResourceRecord::new(a_rdata); - /// let rr_cache = RRCache::new(resource_record); - /// let mut domain_name = DomainName::new(); - /// domain_name.set_domain_name(String::from("uchile.cl")); - /// cache_data.add_to_cache_data(Rtype::A, domain_name, rr_cache); - /// cache_data.remove_from_cache_data(domain_name, Rtype::A); - /// ``` - /// # Arguments - /// * `domain_name` - A DomainName that represents the domain name of the cache data - /// * `rtype` - A Rtype that represents the rtype of the cache data - pub fn remove_from_cache_data(&mut self, domain_name: DomainName, rtype: Rtype) -> u32{ - let mut cache_data = self.get_cache_data(); - if let Some(x) = cache_data.get_mut(&rtype) { - let mut type_hash: HostData = x.clone(); - let length = type_hash.remove_from_host_data(domain_name); - cache_data.insert(rtype, type_hash); - self.set_cache_data(cache_data); - return length; - } - return 0; - } - - ///function to remove the oldest element from the cache data - /// # Example - /// ``` - /// let mut cache_data = CacheData::new(); - /// let a_rdata = Rdata::A(ARdata::new()); - /// let resource_record = ResourceRecord::new(a_rdata); - /// let rr_cache = RRCache::new(resource_record); - /// let mut domain_name = DomainName::new(); - /// domain_name.set_domain_name(String::from("uchile.cl")); - /// cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); - /// cache_data.add_to_cache_data(Rtype::A, domain_name) - /// cache_data.remove_oldest_used(domain_name, Rtype::A); - /// ``` - /// # Arguments - /// * `domain_name` - A DomainName that represents the domain name of the cache data - /// * `rtype` - A Rtype that represents the rtype of the cache data - - pub fn remove_oldest_used(&mut self) -> u32{ - let cache = self.get_cache_data(); - let mut oldest_used_domain_name = DomainName::new(); - let mut oldest_used_type =Rtype::TXT; - let mut oldest_time = Utc::now(); - - for (rtype, mut host_data) in cache { - let (domain_name,time)=host_data.get_oldest_used(); - if time <= oldest_time { - oldest_used_type = rtype.clone(); - oldest_used_domain_name = domain_name; - oldest_time = time; - } - } - - let length = self.remove_from_cache_data(oldest_used_domain_name, oldest_used_type); - length - } - - ///function to get an element from the cache data - /// # Example - /// ``` - /// let mut cache_data = CacheData::new(); - /// let a_rdata = Rdata::A(ARdata::new()); - /// let resource_record = ResourceRecord::new(a_rdata); - /// let rr_cache = RRCache::new(resource_record); - /// let mut domain_name = DomainName::new(); - /// domain_name.set_domain_name(String::from("uchile.cl")); - /// - /// cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); - /// - /// let rr_cache = cache_data.get_from_cache_data(domain_name.clone(), Rtype::A); - /// ``` - /// # Arguments - /// * `domain_name` - A DomainName that represents the domain name of the cache data - /// * `rtype` - A Rtype that represents the rtype of the cache data - pub fn get_from_cache_data(&mut self, domain_name: DomainName, rtype: Rtype) -> Option>{ - let mut cache_data = self.get_cache_data(); - if let Some(x) = cache_data.get(&rtype) { - let mut type_hash: HostData = x.clone(); - let rr_cache_vec = type_hash.get_from_host_data(domain_name); - cache_data.insert(rtype, type_hash); - self.set_cache_data(cache_data); - return rr_cache_vec; - } - else { - return None; - } - } - - pub fn update_response_time(&mut self, - domain_name: DomainName, - rr_type: Rtype, - response_time: u32, - ip_address: IpAddr, - ) { - let mut cache = self.get_cache_data(); - if let Some(x) = cache.get(&rr_type) { - let mut new_x = x.clone(); - new_x.update_response_time(ip_address, response_time, domain_name); - cache.insert(rr_type, new_x); - self.set_cache_data(cache); - } - - } - - pub fn insert(&mut self,rtype:Rtype, host_data: HostData) { - self.cache_data.insert(rtype, host_data); - - } - - pub fn iter(&mut self) -> std::collections::hash_map::Iter<'_, Rtype, HostData>{ - return self.cache_data.iter() - - } -} - -///setter and getter for the host data -impl CacheData{ - - pub fn get_cache_data(&self) -> HashMap { - return self.cache_data.clone(); - } - - pub fn set_cache_data(&mut self, cache_data: HashMap) { - self.cache_data = cache_data; - } - - pub fn get(&self, rtype : Rtype) -> Option<&HostData>{ - return self.cache_data.get(&rtype); - } -} - -#[cfg(test)] -mod cache_data_test{ - use chrono::{Utc, Duration}; - //use std::thread::sleep; - //use std::time::Duration as StdDuration; - - use crate::message::rdata::txt_rdata::TxtRdata; - use crate::message::type_rtype::Rtype; - use crate::rr_cache::RRCache; - use crate::domain_name::DomainName; - use crate::message::rdata::Rdata; - use crate::message::rdata::a_rdata::ARdata; - use crate::message::resource_record::ResourceRecord; - use crate::dns_cache::cache_data::host_data::HostData; - use std::{collections::HashMap, net::IpAddr}; - - - - use super::CacheData; - - //Constructor test - #[test] - fn constructor_test(){ - let cache_data = CacheData::new(); - - assert!(cache_data.cache_data.is_empty()); - } - - //Getter and setter test - #[test] - fn get_cache_data(){ - let cache_data = CacheData::new(); - - let cache_data_hash = cache_data.get_cache_data(); - - assert!(cache_data_hash.is_empty()); - } - - #[test] - fn set_cache_data(){ - let mut cache_data = CacheData::new(); - - let mut cache_data_hash = HashMap::new(); - let mut host_data = HostData::new(); - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let rr_cache = RRCache::new(resource_record); - host_data.add_to_host_data(domain_name, rr_cache); - cache_data_hash.insert(Rtype::A, host_data); - - cache_data.set_cache_data(cache_data_hash); - - assert_eq!(cache_data.get_cache_data().len(), 1); - } - - //Add to cache data test - #[test] - fn add_to_cache_data(){ - let mut cache_data = CacheData::new(); - - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let rr_cache = RRCache::new(resource_record); - - cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); - - assert_eq!(cache_data.get_cache_data().len(), 1); - - let mut new_vec = Vec::new(); - new_vec.push(String::from("hola")); - let text_rdata = Rdata::TXT(TxtRdata::new(new_vec)); - let resource_record_2 = ResourceRecord::new(text_rdata); - let rr_cache_2 = RRCache::new(resource_record_2); - - cache_data.add_to_cache_data(Rtype::TXT, domain_name.clone(), rr_cache_2); - - assert_eq!(cache_data.get_cache_data().len(), 2); - - let a_rdata_2 = Rdata::A(ARdata::new()); - let resource_record_3 = ResourceRecord::new(a_rdata_2); - let rr_cache_3 = RRCache::new(resource_record_3); - - cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache_3); - - assert_eq!(cache_data.get_cache_data().len(), 2); - } - - //Remove from cache data test - #[test] - fn remove_from_cache_data(){ - let mut cache_data = CacheData::new(); - - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let rr_cache = RRCache::new(resource_record); - - cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); - - assert_eq!(cache_data.get_cache_data().len(), 1); - - cache_data.remove_from_cache_data(domain_name.clone(), Rtype::A); - - let cache_hash = cache_data.get_cache_data(); - - let host_data = cache_hash.get(&Rtype::A).unwrap(); - - assert!(host_data.get_host_hash().is_empty()); - } - - //Get from cache data test - #[test] - fn get_from_cache_data(){ - let mut cache_data = CacheData::new(); - - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let rr_cache = RRCache::new(resource_record); - - cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); - - assert!(!cache_data.get_cache_data().is_empty()); - - let rr_cache_vec = cache_data.get_from_cache_data(domain_name.clone(), Rtype::A).unwrap(); - - assert_eq!(rr_cache_vec.len(), 1); - - let mut new_vec = Vec::new(); - new_vec.push(String::from("hola")); - let text_rdata = Rdata::TXT(TxtRdata::new(new_vec)); - let resource_record_2 = ResourceRecord::new(text_rdata); - let rr_cache_2 = RRCache::new(resource_record_2); - - cache_data.add_to_cache_data(Rtype::TXT, domain_name.clone(), rr_cache_2); - - let rr_cache_vec_2 = cache_data.get_from_cache_data(domain_name.clone(), Rtype::TXT).unwrap(); - - assert_eq!(rr_cache_vec_2.len(), 1); - - let rr_cache_vec_3 = cache_data.get_from_cache_data(DomainName::new(), Rtype::A); - - assert!(rr_cache_vec_3.is_none()); - } - - //remove oldest used test - #[test] - fn remove_oldest_used(){ - let mut cache_data = CacheData::new(); - - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let mut rr_cache = RRCache::new(resource_record); - let now = Utc::now(); - let time_back = Duration::seconds(3600); - let new_time = now - time_back; - rr_cache.set_last_use(new_time); - let mut domain_name_1 = DomainName::new(); - domain_name_1.set_name(String::from("notexpected")); - let mut domain_name_2 = DomainName::new(); - domain_name_2.set_name(String::from("expected")); - - let mut new_vec = Vec::new(); - new_vec.push(String::from("uchile.cl")); - let text_rdata = Rdata::TXT(TxtRdata::new(new_vec)); - let resource_record_2 = ResourceRecord::new(text_rdata); - let mut rr_cache_2 = RRCache::new(resource_record_2); - rr_cache_2.set_last_use(Utc::now()); - - - cache_data.add_to_cache_data(Rtype::A, domain_name_1.clone(), rr_cache); - cache_data.add_to_cache_data(Rtype::TXT, domain_name_2.clone(), rr_cache_2); - - let _vec_rr_cache_a = cache_data.get_from_cache_data(domain_name_1.clone(), Rtype::A).unwrap(); - - let a = cache_data.remove_oldest_used(); - - let vec_rr_cache_txt_expected = cache_data.get_from_cache_data(domain_name_2, Rtype::TXT); - let vec_rr_cache_a_expected = cache_data.get_from_cache_data(domain_name_1.clone(), Rtype::A).unwrap(); - - assert_eq!(a,1); - assert_eq!(vec_rr_cache_a_expected.len(), 1); - assert!(vec_rr_cache_txt_expected.is_none()); - } - - //update response time test - #[test] - fn update_response_time(){ - let mut cache_data = CacheData::new(); - - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - let ip_address = IpAddr::from([127, 0, 0, 1]); - let mut a_rdata = ARdata::new(); - a_rdata.set_address(ip_address); - let rdata = Rdata::A(a_rdata); - let resource_record = ResourceRecord::new(rdata); - let mut rr_cache = RRCache::new(resource_record); - rr_cache.set_response_time(1000); - - cache_data.add_to_cache_data(Rtype::A, domain_name.clone(), rr_cache); - - cache_data.update_response_time(domain_name.clone(), Rtype::A, 2000, ip_address.clone()); - - let rr_cache_vec = cache_data.get_from_cache_data(domain_name.clone(), Rtype::A).unwrap(); - - for rr_cache in rr_cache_vec { - assert_eq!(rr_cache.get_response_time(), 2500); - } - } -} \ No newline at end of file diff --git a/src/dns_cache/cache_data/host_data.rs b/src/dns_cache/cache_data/host_data.rs deleted file mode 100644 index 04170663..00000000 --- a/src/dns_cache/cache_data/host_data.rs +++ /dev/null @@ -1,467 +0,0 @@ -use chrono::{Utc, DateTime}; - -use crate::{rr_cache::RRCache, domain_name::DomainName, message::rdata::Rdata}; -use std::{collections::HashMap, net::IpAddr}; - -///type to define the name of the host - -///struct to define the host data -#[derive(Clone, Debug)] -pub struct HostData { - pub host_hash: HashMap>, -} - -///functions for the host data -impl HostData{ - - ///function to create a new host data - /// # Example - /// ``` - /// let host_data = HostData::new(); - /// ``` - pub fn new() -> HostData { - HostData { - host_hash: HashMap::new(), - } - } - - ///function to add a rr_cache to the host data - /// # Example - /// ``` - /// let mut host_data = HostData::new(); - /// let a_rdata = Rdata::A(ARdata::new()); - /// let resource_record = ResourceRecord::new(a_rdata); - /// let rr_cache = RRCache::new(resource_record); - /// let mut domain_name = DomainName::new(); - /// domain_name.set_name(String::from("uchile.cl")); - /// host_data.add_to_host_data(domain_name, rr_cache); - /// ``` - /// # Arguments - /// * `host_name` - A Domain Name that represents the name of the host - /// * `rr_cache` - A RRCache that represents the rr_cache of the host - pub fn add_to_host_data(&mut self, host_name: DomainName, rr_cache: RRCache) { - let mut host_hash = self.get_host_hash(); - if let Some(y) = host_hash.get_mut(&host_name){ - let mut rr_cache_vec = y.clone(); - rr_cache_vec.push(rr_cache); - host_hash.insert(host_name, rr_cache_vec); - } - else{ - let mut rr_cache_vec = Vec::new(); - rr_cache_vec.push(rr_cache); - host_hash.insert(host_name, rr_cache_vec); - } - self.set_host_hash(host_hash); - } - - ///function to remove an element from the host data - /// # Example - /// ``` - /// let mut host_data = HostData::new(); - /// let a_rdata = Rdata::A(ARdata::new()); - /// let resource_record = ResourceRecord::new(a_rdata); - /// let rr_cache = RRCache::new(resource_record); - /// let mut domain_name = DomainName::new(); - /// domain_name.set_name(String::from("uchile.cl")); - /// host_data.add_to_host_data(domain_name, rr_cache); - /// host_data.remove_from_host_data(domain_name); - /// ``` - pub fn remove_from_host_data(&mut self, host_name: DomainName) -> u32{ - let mut host_hash = self.get_host_hash(); - if let Some(_x) = host_hash.remove(&host_name){ - self.set_host_hash(host_hash); - return _x.len() as u32; - } - return 0; - } - - /// Returns an element from the host data. - /// - /// This element corresponds to a vector of `RRCache` that contains - /// the `RRCache` of the host. - /// - /// # Example - /// ``` - /// let mut host_data = HostData::new(); - /// let a_rdata = Rdata::A(ARdata::new()); - /// let resource_record = ResourceRecord::new(a_rdata); - /// let rr_cache = RRCache::new(resource_record); - /// let mut domain_name = DomainName::new(); - /// domain_name.set_name(String::from("uchile.cl")); - /// host_data.add_to_host_data(domain_name, rr_cache); - /// let host_data_2_vec = host_data.get_from_host_data(domain_name); - /// ``` - pub fn get_from_host_data(&mut self, host_name: DomainName) -> Option>{ - let mut host_hash = self.get_host_hash(); - if let Some(x) = host_hash.get(&host_name){ - let new_x = x.clone(); - let mut rr_cache_vec = Vec::::new(); - for mut rr_cache in new_x{ - rr_cache.set_last_use(Utc::now()); - rr_cache_vec.push(rr_cache.clone()); - } - host_hash.insert(host_name, rr_cache_vec.clone()); - self.set_host_hash(host_hash); - - return Some(rr_cache_vec); - } - else{ - return None; - } - } - - ///function to get the oldest used - /// # Example - /// ``` - /// let mut host_data = HostData::new(); - /// let a_rdata = Rdata::A(ARdata::new()); - /// let resource_record = ResourceRecord::new(a_rdata); - /// let rr_cache = RRCache::new(resource_record); - /// rr_cache.set_last_use(Utc::now()); - /// let mut domain_name = DomainName::new(); - /// domain_name.set_name(String::from("uchile.cl")); - /// host_data.add_to_host_data(domain_name, rr_cache); - /// let host_data_2 = get_oldest_used.get_from_host_data(domain_name); - /// ``` - pub fn get_oldest_used(&mut self)-> (DomainName,DateTime){ - let host = self.get_host_hash(); - let mut used_in = Utc::now(); - - let mut oldest_used_domain_name = DomainName::new(); - - for (host_key, host_value) in host { - let rr_last_use = host_value[0].get_last_use(); - - if rr_last_use <= used_in { - used_in = rr_last_use; - oldest_used_domain_name = host_key.clone(); - - } - } - - return (oldest_used_domain_name,used_in); - - } - - ///function to insert a domain name and a new value to be associated - /// and return the value that was associated before, or none if - /// the domain name didn't exist before - /// # Example - /// ``` - /// let mut host_data = HostData::new(); - /// let a_rdata = Rdata::A(ARdata::new()); - /// let resource_record = ResourceRecord::new(a_rdata); - /// let mut rr_cache = RRCache::new(resource_record); - /// rr_cache.set_last_use(Utc::now()); - /// let mut domain_name = DomainName::new(); - /// domain_name.set_name(String::from("uchile.cl")); - /// host_data.add_to_host_data(domain_name, rr_cache); - /// let mut domain_name_new = DomainName::new(); - /// domain_name_new.set_name(String::from("inserted")); - /// let a_rdata_2 = Rdata::A(ARdata::new()); - /// let resource_record_2 = ResourceRecord::new(a_rdata); - /// let mut rr_cache_2 = RRCache::new(resource_record); - /// host_data.insert(domain_name_new, rr_cache_2); - /// ``` - pub fn insert(&mut self,domain_name:DomainName, rr_cache_vec : Vec) -> Option>{ - return self.host_hash.insert(domain_name, rr_cache_vec) - } - - ///function to update the response time - /// # Example - /// ``` - /// let mut host_data = HostData::new(); - /// let ip_address = IpAddr::from([127, 0, 0, 1]); - /// let a_rdata = ARdata::new(); - /// a_rdata.set_address(ip_address); - /// let rdata = Rdata::A(a_rdata); - /// let resource_record = ResourceRecord::new(rdata); - /// let mut rr_cache = RRCache::new(resource_record); - /// rr_cache.set_response_time(1000); - /// let mut domain_name = DomainName::new(); - /// domain_name.set_name(String::from("uchile.cl")); - /// host_data.add_to_host_data(domain_name, rr_cache); - /// host_data.update_response_time(ip_address, 2000, domain_name); - /// ``` - pub fn update_response_time(&mut self, ip_address: IpAddr, response_time: u32, domain_name: DomainName){ - let mut host_hash = self.get_host_hash(); - if let Some(x) = host_hash.get(&domain_name){ - let rr_cache_vec = x.clone(); - - let mut new_rr_cache_vec = Vec::::new(); - - for mut rr_cache in rr_cache_vec{ - let rr_ip_address = match rr_cache.get_resource_record().get_rdata() { - Rdata::A(val) => val.get_address(), - _ => unreachable!(), - }; - - if rr_ip_address == ip_address{ - rr_cache.set_response_time(response_time + rr_cache.get_response_time()/2); - } - - new_rr_cache_vec.push(rr_cache.clone()); - } - host_hash.insert(domain_name, new_rr_cache_vec); - - self.set_host_hash(host_hash); - } - } - -} - -///setter and getter for the host data -impl HostData{ - - pub fn get_host_hash(&self) -> HashMap> { - return self.host_hash.clone(); - } - - pub fn set_host_hash(&mut self, host_hash: HashMap>) { - self.host_hash = host_hash; - } - - pub fn get(&self,domain_name:&DomainName) -> Option<&Vec>{ - return self.host_hash.get(domain_name) - - } -} - -#[cfg(test)] -mod host_data_test{ - use chrono::Utc; - use crate::message::rdata::txt_rdata::TxtRdata; - use crate::rr_cache::RRCache; - use crate::domain_name::DomainName; - use crate::message::rdata::Rdata; - use crate::message::rdata::a_rdata::ARdata; - use crate::message::resource_record::ResourceRecord; - use std::{collections::HashMap, net::IpAddr}; - - use super::HostData; - - //Contructor test - #[test] - fn constructor_test(){ - let host_data = HostData::new(); - assert!(host_data.host_hash.is_empty()); - } - - //Getters and setters test - #[test] - fn get_host_hash(){ - let host_data = HostData::new(); - - let host_hash = host_data.get_host_hash(); - - assert!(host_hash.is_empty()); - } - - #[test] - fn set_host_hash(){ - let mut host_data = HostData::new(); - - let mut host_hash = HashMap::new(); - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - host_hash.insert(domain_name.clone(), Vec::new()); - - assert!(host_data.host_hash.is_empty()); - - host_data.set_host_hash(host_hash.clone()); - - let host_hash_2 = host_data.get_host_hash(); - - assert!(!host_hash_2.is_empty()); - } - - //add_to_host_data test - #[test] - fn add_to_host_data(){ - let mut host_data = HostData::new(); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let rr_cache = RRCache::new(resource_record); - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - host_data.add_to_host_data(domain_name.clone(), rr_cache); - - let host_hash = host_data.get_host_hash(); - - assert_eq!(host_hash.len(), 1); - - let mut new_vec = Vec::new(); - new_vec.push(String::from("hola")); - let text_rdata = Rdata::TXT(TxtRdata::new(new_vec)); - let resource_record_2 = ResourceRecord::new(text_rdata); - let rr_cache_2 = RRCache::new(resource_record_2); - host_data.add_to_host_data(domain_name.clone(), rr_cache_2); - - let host_hash_2 = host_data.get_host_hash(); - - assert_eq!(host_hash_2.len(), 1); - - let host_hash_vec = host_hash_2.get(&domain_name).unwrap(); - - assert_eq!(host_hash_vec.len(), 2); - } - - //remove_from_host_data test - #[test] - fn remove_from_host_data(){ - let mut host_data = HostData::new(); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let rr_cache = RRCache::new(resource_record); - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - host_data.add_to_host_data(domain_name.clone(), rr_cache); - - let host_hash = host_data.get_host_hash(); - - assert_eq!(host_hash.len(), 1); - - host_data.remove_from_host_data(domain_name.clone()); - - let host_hash_2 = host_data.get_host_hash(); - - assert_eq!(host_hash_2.len(), 0); - } - - //get_from_host_data test - #[test] - fn get_from_host_data(){ - let mut host_data = HostData::new(); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let rr_cache = RRCache::new(resource_record); - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - host_data.add_to_host_data(domain_name.clone(), rr_cache); - - let host_hash = host_data.get_host_hash(); - - assert_eq!(host_hash.len(), 1); - - let host_hash_vec = host_data.get_from_host_data(domain_name.clone()).unwrap(); - - assert_eq!(host_hash_vec.len(), 1); - } - - //get_from_host_data test with no domain name - #[test] - fn get_from_host_data_no_domain_name(){ - let mut host_data = HostData::new(); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let rr_cache = RRCache::new(resource_record); - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - host_data.add_to_host_data(domain_name.clone(), rr_cache); - - let host_hash = host_data.get_host_hash(); - - // One domain name - assert_eq!(host_hash.len(), 1); - - // Assuming this test is for the case where the domain name is not in the host data - let domain_name_2 = DomainName::new(); - let element = host_data.get_from_host_data(domain_name_2.clone()); - assert_eq!(element, None); - } - - //get test - #[test] - fn get(){ - let mut host_data = HostData::new(); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let mut rr_cache = RRCache::new(resource_record); - rr_cache.set_response_time(1234433455); - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - host_data.add_to_host_data(domain_name.clone(), rr_cache); - - let vec_rr_cache = host_data.get(&domain_name).unwrap(); - - let rr_cache_o = vec_rr_cache.get(0).unwrap(); - - assert_eq!(1234433455, rr_cache_o.get_response_time()) - } - - //get oldest used test - #[test] - fn get_oldest_used(){ - let mut host_data = HostData::new(); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let mut rr_cache = RRCache::new(resource_record); - rr_cache.set_last_use(Utc::now()); - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("expected")); - - let mut new_vec = Vec::new(); - new_vec.push(String::from("uchile.cl")); - let text_rdata = Rdata::TXT(TxtRdata::new(new_vec)); - let resource_record_2 = ResourceRecord::new(text_rdata); - let mut rr_cache_2 = RRCache::new(resource_record_2); - rr_cache_2.set_last_use(Utc::now()); - host_data.add_to_host_data(domain_name.clone(), rr_cache_2); - - let oldest_used = host_data.get_oldest_used(); - let oldest_name = oldest_used.0; - - - assert_eq!("expected".to_string(), oldest_name.get_name()) - } - - //get insert used test - #[test] - fn insert(){ - let mut host_data = HostData::new(); - let a_rdata = Rdata::A(ARdata::new()); - let resource_record = ResourceRecord::new(a_rdata); - let mut rr_cache = RRCache::new(resource_record); - rr_cache.set_response_time(12); - - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - host_data.add_to_host_data(domain_name, rr_cache); - let mut domain_name_new = DomainName::new(); - domain_name_new.set_name(String::from("inserted")); - let a_rdata_2 = Rdata::A(ARdata::new()); - let resource_record_2 = ResourceRecord::new(a_rdata_2); - let rr_cache_2 = RRCache::new(resource_record_2); - - let mut rr_vec = Vec::new(); - rr_vec.push(rr_cache_2); - let expected = host_data.insert(domain_name_new, rr_vec); - assert_eq!(expected, None) - - } - - //update response time test - #[test] - fn update_response_time(){ - let mut host_data = HostData::new(); - let ip_address = IpAddr::from([127, 0, 0, 1]); - let mut a_rdata = ARdata::new(); - a_rdata.set_address(ip_address); - let rdata = Rdata::A(a_rdata); - let resource_record = ResourceRecord::new(rdata); - let mut rr_cache = RRCache::new(resource_record); - rr_cache.set_response_time(1000); - let mut domain_name = DomainName::new(); - domain_name.set_name(String::from("uchile.cl")); - host_data.add_to_host_data(domain_name.clone(), rr_cache); - host_data.update_response_time(ip_address, 2000, domain_name.clone()); - - let host_hash = host_data.get_host_hash(); - - let rr_cache_vec = host_hash.get(&domain_name).unwrap(); - - let rr_cache = rr_cache_vec.get(0).unwrap(); - - assert_eq!(2500, rr_cache.get_response_time()) - } -} diff --git a/src/lib.rs b/src/lib.rs index 14751d02..9fbfcdcc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ pub mod dns_cache; pub mod domain_name; pub mod message; pub mod async_resolver; -pub mod rr_cache; pub mod utils; pub mod truncated_dns_message; diff --git a/src/main.rs b/src/main.rs index c2d4ec63..a68ffc04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -99,9 +99,11 @@ pub async fn main() { let conn = ClientTCPConnection::new(addr.unwrap(), Duration::from_secs(10)); let mut client = Client::new(conn); - let mut _response = client.query(DomainName::new_from_string(client_args.host_name.clone()), client_args.qtype.as_str(), client_args.qclass.as_str()); + let response = client.query(DomainName::new_from_string(client_args.host_name.clone()), client_args.qtype.as_str(), client_args.qclass.as_str()); - //response.print_dns_message() + if let Ok(mut resp) = response { + resp.print_dns_message() + } } Commands::Resolver(resolver_args) => { diff --git a/src/message.rs b/src/message.rs index 425120f2..ee75da07 100644 --- a/src/message.rs +++ b/src/message.rs @@ -612,7 +612,7 @@ impl DnsMessage { let mut msg_additionals = self.get_additional(); msg_additionals.append(&mut additionals); - self.set_answer(msg_additionals); + self.set_additional(msg_additionals); } @@ -1376,7 +1376,7 @@ mod message_test { dns_query_message.add_additionals(new_additional); //since the new additional is added to the answer lets check if something was added - assert_eq!(dns_query_message.get_answer().len(), 1); + assert_eq!(dns_query_message.get_additional().len(), 1); } //ToDo: Revisar @@ -1539,4 +1539,5 @@ mod message_test { let result = dns_query_message.check_op_code().unwrap_err(); assert_eq!(result, "IQuery not Implemented"); } + } diff --git a/src/message/question.rs b/src/message/question.rs index 8f5473b9..32d1d32a 100644 --- a/src/message/question.rs +++ b/src/message/question.rs @@ -76,7 +76,6 @@ impl Question { let (qname, bytes_without_name) = domain_name_result.unwrap(); - println!("{}", bytes_without_name.len()); if bytes_without_name.len() < 4 { return Err("Format Error"); } diff --git a/tests/integration_test.rs b/tests/integration_test.rs new file mode 100644 index 00000000..1f587b56 --- /dev/null +++ b/tests/integration_test.rs @@ -0,0 +1,112 @@ +use std::{net::IpAddr, str::FromStr}; + +use dns_rust::{async_resolver::{config::ResolverConfig, AsyncResolver, resolver_error::ResolverError}, message::{resource_record::ResourceRecord, rdata::Rdata}, domain_name::DomainName}; + + + +// TODO: Change params type to intoDomainName +async fn query_response(domain_name: &str, protocol: &str, qtype: &str) -> Result, ResolverError>{ + + let config = ResolverConfig::default(); + let mut resolver = AsyncResolver::new(config); + + let response = resolver.lookup( + domain_name, + protocol, + qtype, + "IN").await; + + response +} + +/// 6.2.1 Query test Qtype = A +#[tokio::test] +async fn query_a_type() { + let response = query_response("example.com", "UDP", "A").await; + + if let Ok(rrs) = response { + assert_eq!(rrs.iter().count(), 1); + let rdata = rrs[0].get_rdata(); + if let Rdata::A(ip) = rdata { + assert_eq!(ip.get_address(), IpAddr::from_str("93.184.216.34").unwrap()); + } else { + panic!("No ip address"); + } + } +} + +/// 6.2.2 Query normal Qtype = * +#[tokio::test] +async fn query_any_type() { + let udp_response = query_response("example.com", "UDP", "ANY").await; + let tcp_response = query_response("example.com", "TCP", "ANY").await; + assert!(udp_response.is_err()); + assert!(tcp_response.is_err()); +} + +/// 6.2.3 Query Qtype = MX +#[tokio::test] +async fn query_mx_type() { + let response = query_response("example.com", "UDP", "MX").await; + + if let Ok(rrs) = response { + assert_eq!(rrs.len(), 1); + + if let Rdata::MX(mxdata) = rrs[0].get_rdata() { + assert_eq!( + mxdata.get_exchange(), + DomainName::new_from_str("")); + + assert_eq!( + mxdata.get_preference(), + 0 + ) + } else { + panic!("Record is not MX type"); + } + } +} + + +// 6.2.4 Query Qtype = NS +#[tokio::test] +async fn query_ns_type() { + let response = query_response("example.com", "UDP", "NS").await; + if let Ok(rrs) = response { + assert_eq!(rrs.len(), 2); + + if let Rdata::NS(ns1) = rrs[0].get_rdata() { + assert_eq!( + ns1.get_nsdname(), + DomainName::new_from_str("a.iana-servers.net")) + } else { + panic!("First record is not NS"); + } + + if let Rdata::NS(ns) = rrs[1].get_rdata() { + assert_eq!( + ns.get_nsdname(), + DomainName::new_from_str("b.iana-servers.net")) + } else { + panic!("Second record is not NS"); + } + } +} + +/// 6.2.5 Mistyped host name Qtype = A +#[tokio::test] +async fn mistyped_host_name() { + let response = query_response("exampllee.com", "UDP", "A").await; + assert!(response.is_err()); +} + +/// No record test +#[tokio::test] +async fn no_resource_available() { + let response = query_response("example.com", "UDP", "CNAME").await; + println!("{:?}", response); + assert!(response.is_err()); +} + + +