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
 */

use std::marker::PhantomData;
/// This file contains configuration caches.

use std::path::Path;
use std::sync::Arc;
use std::time::SystemTime;


use crate::a_sync::interface::{AsyncMutex, AsyncMutexGuard, MutexedCaches, UnifiedFs};
#[cfg(feature = "built_in_async")]
use crate::a_sync::{IoInterf};
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, FS: UnifiedFs>
{
    path: &'cache Path,
    last_modif: SystemTime,
    _fs: PhantomData<FS>
}

impl<'cache, FS: UnifiedFs> CacheTime<'cache, FS>
{
    async 
    fn new(path: &'cache Path) -> CDnsResult<CacheTime<'cache, FS>>
    {
        return Ok(
            CacheTime 
            { 
                path: path, 
                last_modif: Self::get_last_modified(path).await?,
                _fs: PhantomData
            }
        );
    }

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

    async 
    fn get_last_modified(path: &'cache Path) -> CDnsResult<SystemTime>
    {
        let metadata = 
            FS::metadata(path)
                .await
                .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);
    }

    async 
    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).await
            {
                Ok(t) => t,
                Err(e) => internal_error!(CDnsErrorType::InternalError, "{}", e),
            };

        return Ok(self.last_modif != last_modif);
    }
}

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

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

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

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

    async 
    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().await? == true
        {
            // reload
            self.cache = Self::read_config().await?;
        }
        
        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>>
}
    */

#[cfg(feature = "built_in_async")]
#[derive(Debug)]
pub struct CachesController<MC: MutexedCaches = IoInterf>
{
    resolv_cache: MC::ResolveCache,
    host_cache: MC::HostCahae,
    _p: PhantomData<MC>,
}

#[cfg(feature = "built_in_async")]
impl CachesController<IoInterf>
{
    pub async 
    fn new() -> CDnsResult<Self>
    {
         return Ok(
            CachesController
            { 
                resolv_cache: 
                    <IoInterf as MutexedCaches>::ResolveCache::a_new(CacheInstance::new().await?),//resolve_cache),
                host_cache: 
                    <IoInterf as MutexedCaches>::HostCahae::a_new(CacheInstance::new().await?),//host_cache),
                _p: 
                    PhantomData
            }
        );
    }
}

#[cfg(not(feature = "built_in_async"))]
#[derive(Debug)]
pub struct CachesController<MC: MutexedCaches>
{
    resolv_cache: MC::ResolveCache,
    host_cache: MC::HostCahae,
    _p: PhantomData<MC>,
}

unsafe impl<MC: MutexedCaches> Sync for CachesController<MC>{}
unsafe impl<MC: MutexedCaches> Send for CachesController<MC>{}

impl<MC: MutexedCaches> CachesController<MC>
{
    /// Just creates insrance, but does not read anything from disk. 
    pub async 
    fn new_custom() -> CDnsResult<Self>
    {
        /*let resolve_cache_res = CacheInstance::<ResolveConfig>::new().await;

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

        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 Ok(
            CachesController
            { 
                resolv_cache: MC::ResolveCache::a_new(CacheInstance::new().await?),//resolve_cache),
                host_cache: MC::HostCahae::a_new(CacheInstance::new().await?),//host_cache),
                _p: PhantomData
            }
        );
    }

    pub async 
    fn is_resolve_cached(&self) -> bool
    {
        return !self.resolv_cache.a_lock().await.guard().cache.is_default();
    }

    pub async 
    fn is_host_cached(&self) -> bool
    {
        return !self.host_cache.a_lock().await.guard().cache.is_default();
    }

    async 
    fn try_cache_resolve(&self) -> CDnsResult<Arc<ResolveConfig>>
    {
        let mut mutx_resolv = self.resolv_cache.a_lock().await;
        mutx_resolv.guard_mut().check_modified().await?;

        return Ok(mutx_resolv.guard().clone_cache());
    }

    pub async 
    fn clone_resolve_list(&self) -> CDnsResult<Arc<ResolveConfig>>
    {
        // clone from cache and/or reload cache
        match self.try_cache_resolve().await
        {
            Ok(r) => 
                return Ok(r),
            Err(e) => 
            {
                write_error!(e);

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

    async 
    fn try_cache_host(&self) -> CDnsResult<Arc<HostConfig>>
    {
        let mut mutx_host = self.host_cache.a_lock().await;
        mutx_host.guard_mut().check_modified().await?;

        return Ok(mutx_host.guard().clone_cache());
    }

    pub async 
    fn clone_host_list(&self) -> CDnsResult<Arc<HostConfig>>
    {
        match self.try_cache_host().await
        {
            Ok(r) => return Ok(r),
            Err(e) =>
            {
                write_error!(e);

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


#[cfg(feature = "use_async_tokio_tls")]
#[cfg(test)]
mod tests
{
    use std::sync::Arc;

    use crate::a_sync::CachesController;


    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
    async fn test_init()
    {
        let cache = Arc::new(CachesController::new().await.unwrap());

        let res = cache.clone_resolve_list().await;
        assert_eq!(res.is_ok(), true, "{}", res.err().unwrap());

        let res = res.unwrap();

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

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

        let res = res.unwrap();

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