cdns-rs 1.2.2

A native Sync/Async Rust implementation of client DNS resolver.
Documentation
/*-
 * cdns-rs - a simple sync/async DNS query library
 * 
 * Copyright (C) 2020  Aleksandr Morozov
 * 
 * Copyright (C) 2025 Aleksandr Morozov
 * 
 * The syslog-rs crate can be redistributed and/or modified
 * under the terms of either of the following licenses:
 *
 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
 *                     
 *   2. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
 */

/// This file contains configuration caches.

use std::path::Path;
use std::sync::{Arc, LazyLock, Mutex};
use std::time::SystemTime;
use std::fs;


use crate::cfg_host_parser::HostConfig;
use crate::cfg_resolv_parser::ResolveConfig;
use crate::{error::*, internal_error, internal_error_map, write_error};

use super::cfg_parsers::*;

pub static CACHE: LazyLock<CachesController> = LazyLock::new(|| { CachesController::new() });

pub trait CacheOperations
{
    fn is_reload_allowed(&self) -> bool;
}

#[derive(Clone, Debug)]
struct CacheTime<'cache>
{
    path: &'cache Path,
    last_modif: SystemTime,
}

impl<'cache> CacheTime<'cache>
{
    fn new(path: &'cache Path) -> CDnsResult<Self>
    {
        return Ok(Self { path: path, last_modif: Self::get_last_modified(path)? });
    }

    fn fake(path: &'cache Path) -> Self
    {
        return Self { path: path, last_modif: SystemTime::now() };
    }

    fn get_last_modified(path: &'cache Path) -> CDnsResult<SystemTime>
    {
        let metadata = fs::metadata(path).map_err(|e| 
            internal_error_map!(CDnsErrorType::InternalError, "fs::metadata::modified() not supported on this platform: '{}'", e)
        )?;

        let last_modif = 
            metadata.modified().map_err(|e| 
                internal_error_map!(CDnsErrorType::InternalError, "fs::metadata::modified() not supported on this platform: '{}'", e)
            )?;

        return Ok(last_modif);
    }

    fn check_modified(&mut self) -> CDnsResult<bool>
    {
        // in case if get_last_modified() will return Err, return what was cached prevously
        let last_modif = 
            match Self::get_last_modified(self.path)
            {
                Ok(t) => t,
                Err(e) => internal_error!(CDnsErrorType::InternalError, "{}", e),
            };

        return Ok(self.last_modif != last_modif);
        /*{
            // reload
            match ResolveConfEntry::parse_resolve_cfg()
            {
                Ok(r) => 
                    self.cache = Arc::new(r),
                Err(e) =>
                    write_error!("{}", e),
            }
             
        }
        
        return;*/
    }
}

#[derive(Clone, Debug)]
pub struct CacheInstance<T: Default + ConfigParser<T> + CacheOperations>
{
    last_modif: CacheTime<'static>,
    cache: Arc<T>,
}

impl<T: Default + ConfigParser<T> + CacheOperations> Default for CacheInstance<T>
{
    fn default() -> Self 
    {
        return 
            Self 
            {
                last_modif: CacheTime::fake(T::get_file_path()), 
                cache: Arc::new(T::default()),
              //  f: PhantomData,
            };
    }
}

impl<T: Default + ConfigParser<T> + CacheOperations> CacheInstance<T>
{
    fn new() -> CDnsResult<Self>
    {
        return Ok(
            Self 
            {
                last_modif: CacheTime::new(T::get_file_path())?, 
                cache: Arc::new(T::parse_config()?),
                //f: PhantomData,
            }
        );
    }

    fn read_config() -> CDnsResult<Arc<T>>
    {
        return Ok(Arc::new(T::parse_config()?));
    }

    fn check_modified(&mut self) -> CDnsResult<()>
    {
        // in case if get_last_modified() will return Err, save what was cached prevously
        if self.cache.is_reload_allowed() == false
        {
            // just return OK
            return Ok(());
        }

        if self.last_modif.check_modified()? == true
        {
            // reload
            self.cache = Self::read_config()?;
        }
        
        return Ok(());
    }

    fn clone_cache(&self) -> Arc<T>
    {
        return self.cache.clone();
    }
}

pub struct CachesController
{
    resolv_cache: Mutex<CacheInstance<ResolveConfig>>,
    host_cache: Mutex<CacheInstance<HostConfig>>
}

unsafe impl Sync for CachesController{}
unsafe impl Send for CachesController{}

impl CachesController
{
    /// Spawns instance and reads the configuration.
    fn new() -> Self
    {
        let resolve_cache_res = CacheInstance::<ResolveConfig>::new();

        let host_cache_res = CacheInstance::<HostConfig>::new();

        let resolve_cache = 
            match resolve_cache_res
            {
                Ok(r) => r,
                Err(e) =>
                {
                    write_error!(e);

                    CacheInstance::default()
                }
            };
        
        let host_cache = 
            match host_cache_res
            {
                Ok(r) => r,
                Err(e) =>
                {
                    write_error!(e);

                    CacheInstance::default()
                }
            };
    

        return 
            CachesController
            { 
                resolv_cache: Mutex::new(resolve_cache),
                host_cache: Mutex::new(host_cache),
            };
    }

    pub 
    fn is_resolve_cached(&self) -> bool
    {
        return !self.resolv_cache.lock().unwrap().cache.is_default();
    }

    pub 
    fn is_host_cached(&self) -> bool
    {
        return !self.host_cache.lock().unwrap().cache.is_default();
    }

    fn try_cache_resolve(&self) -> CDnsResult<Arc<ResolveConfig>>
    {
        let mut mutx_resolv = self.resolv_cache.lock().unwrap();
        mutx_resolv.check_modified()?;

        return Ok(mutx_resolv.clone_cache());
    }

    /// Clones the [ResolveConfig] [Arc] (atomic reference counter) reference.
    pub 
    fn clone_resolve_list(&self) -> CDnsResult<Arc<ResolveConfig>>
    {
        // clone from cache and/or reload cache
        match self.try_cache_resolve()
        {
            Ok(r) => 
                return Ok(r),
            Err(e) => 
            {
                write_error!(e);

                // try to read directly, this ignores all reload restrictions
                return CacheInstance::<ResolveConfig>::read_config();
            }
        }
    }

    fn try_cache_host(&self) -> CDnsResult<Arc<HostConfig>>
    {
        let mut mutx_host = self.host_cache.lock().unwrap();
        mutx_host.check_modified()?;

        return Ok(mutx_host.clone_cache());
    }

    /// Clones the [HostConfig] [Arc] (atomic reference counter) reference.
    pub 
    fn clone_host_list(&self) -> CDnsResult<Arc<HostConfig>>
    {
        match self.try_cache_host()
        {
            Ok(r) => 
                return Ok(r),
            Err(e) =>
            {
                write_error!(e);

                // try to read directly, this ignores all reload restrictions
                return CacheInstance::<HostConfig>::read_config();
            }
        }
    }
}



#[cfg(test)]
mod tests
{
    use crate::sync::caches::CACHE;

    #[test]
    fn test_init()
    {
        let res = CACHE.clone_resolve_list();
        assert_eq!(res.is_ok(), true, "{}", res.err().unwrap());

        let res = res.unwrap();

        println!("{:?}", res);

        let res = CACHE.clone_host_list();
        assert_eq!(res.is_ok(), true, "{}", res.err().unwrap());

        let res = res.unwrap();

        println!("{:?}", res);
    }
}