use crate::kv; use domain::base::question::Question; use domain::base::rdata::UnknownRecordData; use domain::base::{Dname, Record}; use js_sys::Date; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize)] struct DnsCacheMetadata { created_ts: u64, // seconds ttl: u32, } pub struct DnsCache { store: kv::KvNamespace, } impl DnsCache { pub fn new() -> DnsCache { DnsCache { store: kv::get_dns_cache(), } } pub async fn put_cache( &self, record: &Record>, UnknownRecordData>>, ) -> Result<(), String> { let ttl = record.ttl(); self.store .put_buf_ttl_metadata( &Self::record_to_key(record), record.data().data(), ttl as u64, DnsCacheMetadata { created_ts: (Date::now() / 1000f64) as u64, ttl, }, ) .await } pub async fn get_cache( &self, question: &Question>>, ) -> Option>, UnknownRecordData>>>> { // One question can have multiple cached records; so we list by prefix // Note that list_prefix returns 1000 records at maximum by default // We don't expect one question to have that many answers, so it // should be fine let keys = self .store .list_prefix(&Self::question_to_key_prefix(question)) .await .ok()? .keys; if keys.len() == 0 { return None; } // If there are keys available, then return all of the cached records let mut ret = Vec::new(); for k in keys { let (value, metadata): (Option>, Option) = self.store.get_buf_metadata(&k.name).await; if value.is_none() || metadata.is_none() { continue; } let (value, metadata) = (value.unwrap(), metadata.unwrap()); let elapsed_since_creation = (Date::now() / 1000f64) as u64 - metadata.created_ts; // Calculate the remaining TTL correctly // don't just return the original TTL blindly let remaining_ttl = if elapsed_since_creation > metadata.ttl as u64 { 0 } else { metadata.ttl as u64 - elapsed_since_creation }; ret.push(Record::new( question.qname().to_owned(), question.qclass(), remaining_ttl as u32, UnknownRecordData::from_octets(question.qtype(), value), )); } Some(ret) } fn record_to_key(record: &Record>, UnknownRecordData>>) -> String { format!( "{};{};{};{}", record.owner(), record.rtype(), record.class(), // We need to append the hash of the record data to the key // because one question might have multiple answers // When reading, we need to list the keys first crate::util::hash_buf(record.data().data()) ) } fn question_to_key_prefix(question: &Question>>) -> String { format!( "{};{};{};", question.qname(), question.qtype(), question.qclass() ) } }