tnnl 0.1.17

tnnl gives you full control over whether and when your IoT devices should be reachable from the internet
//! # Client library for [tnnl](https://tnnl.de)
//!
//! This library is for writing rust clients that are using [tnnl](https://tnnl.de).
//!
//! tnnl ("tunnel") gives you full control over whether and when your IoT devices should be reachable from the internet.
//! It provides secure access from distant networks - anywhere in the world.
//!
//! ## You can use [tnnl_main] to start tnnl with configuration from [envionment variables](tnnl_main).
//! ```rust,no_run
//! use tnnl::tnnl_main;
//! tnnl_main();
//! ```
//!
//! ## Or you can use [builder::Builder] if you want to have more control over the tnnl connection.
//! ```rust,no_run
//! use tnnl::builder::*;
//! use tnnl::rules::*;
//! # use tnnl::apperror::*;
//! # #[tokio::main]
//! # async fn main() -> Result<(), AppError> {
//! Builder::new(&"MY_SECRET_TOKEN")
//!     .with_client_name(&"MY_CLIENT_NAME")
//!     .with_client_id(&"MY_CLIENT_ID")
//!     .with_rule(Rule::parse("*:*-*:*")?)
//!     .connect().await?;
//! # Ok(())
//! # }
//! ```

use apperror::AppError;
use rules::Rule;
use std::{env, process::ExitCode, thread::sleep, time::Duration};
use tokio::runtime::Runtime;
pub mod apperror;
pub mod builder;
mod channel;
mod channel_handler;
mod command_channel;
mod model;
mod rpc_helper;
pub mod rules;
pub mod session;
mod tcp_channel;

async fn run() -> Result<(), AppError> {
    let token = env::var("TOKEN").map_err(|_| AppError::new("failed to read TOKEN from env"))?;
    let client_name = env::var("CLIENT_NAME").ok();
    let client_id = env::var("CLIENT_ID").ok();

    let rule = if let Ok(rule) = env::var("RULE") {
        Rule::parse(&rule)?
    } else {
        Rule::unrestricted()
    };
    log::info!("using rule:\n{:}", rule);

    loop {
        let builder = crate::builder::Builder::new(&token)
            .with_client_name_opt(&client_name)
            .with_client_id_opt(&client_id)
            .with_rule(rule.clone())
            .with_tls(true);

        #[cfg(feature = "no-tls")]
        let builder = builder.with_tls(false);

        match builder.connect().await {
            Ok(_) => continue,
            Err(e) => {
                log::error!("error: {:?}", e);
                sleep(Duration::from_secs(5));
            }
        }
    }
}

/// Starts the tnnl client without special configuration.
/// The tnnl client will use environment variables for the configuration and block until an fatal error occurs.
///
/// # Example
/// ```rust,no_run
/// use tnnl::tnnl_main;
/// tnnl_main();
/// ```
/// ## Used environment variables
///
/// |Variable|Description|Requirement|Reference|
/// |---|---|---|---|
/// |`TOKEN`|The tnnl login token|mandatory|[builder::Builder::new]|
/// |`CLIENT_NAME`|The name of the client application|optional|[builder::Builder::with_client_name]|
/// |`CLIENT_ID`|An id that uniquely identifies the device|optional|[builder::Builder::with_client_id]|
pub fn tnnl_main() -> ExitCode {
    #[cfg(feature = "log-colog")]
    colog::init();

    #[cfg(feature = "init-rustls")]
    {
        if Result::is_err(&rustls::crypto::aws_lc_rs::default_provider().install_default()) {
            log::error!(
                "failed to install crypto provider; if there is one already installed disable the init-rustls feature"
            );
            return ExitCode::FAILURE;
        }
    }

    let Ok(rt) = Runtime::new() else {
        log::error!("failed to start runtime");
        return ExitCode::FAILURE;
    };

    rt.block_on(async {
        match run().await {
            Ok(_) => ExitCode::SUCCESS,
            Err(e) => {
                log::error!("{:}", e);
                ExitCode::FAILURE
            }
        }
    })
}

#[unsafe(export_name = "tnnl_main")]
pub extern "C" fn tnnl_main_extern() -> u8 {
    let exit_code = tnnl_main();

    match exit_code {
        ExitCode::SUCCESS => 0,
        _ => 1,
    }
}