//! RPC commands and related functionality for authentication.
//!
//! In Arti's RPC system, authentication is a kind of method that can be invoked
//! on the special "connection" object, which gives you an RPC _session_ as a
//! result.  The RPC session is the root for all other capabilities.

use std::sync::Arc;

use super::Connection;
use tor_rpcbase as rpc;

/*
    TODO RPC: This is disabled because the design isn't really useful.
    If we're going to provide something here, it should probably
    contain a list of protocol elements/aspects, and it should be designed
    to enable compatibility, with a clear view of what applications are
    supposed to do about it.

/// Declare the get_rpc_protocol method.
mod get_rpc_protocol {
    use super::Connection;
    use std::sync::Arc;
    use tor_rpcbase as rpc;

    /// Method to inquire about the RPC protocol.
    #[derive(Debug, serde::Deserialize)]
    struct GetRpcProtocol {}

    /// Reply to the [`GetRpcProtocol`] method
    #[derive(Debug, serde::Serialize)]
    struct GetProtocolReply {
        /// The version of the RPC protocol that this server speaks.
        // TODO RPC: Should this be a list?
        version: RpcProtocolId,
    }

    /// Identifier for a version of this RPC meta-protocol.
    #[derive(Debug, Copy, Clone, serde::Serialize)]
    enum RpcProtocolId {
        /// Alpha version of the protocol.  Things might break between here and the
        /// stable protocol.
        ///
        /// TODO RPC: CHange this to v0.
        #[serde(rename = "alpha")]
        Alpha,
    }
    rpc::decl_method! {"auth:get_rpc_protocol" => GetRpcProtocol}
    impl rpc::Method for GetRpcProtocol {
        type Output = GetProtocolReply;
        type Update = rpc::NoUpdates;
    }

    /// Describe which version of the RPC protocol our connection implements.
    async fn conn_get_rpc_protocol(
        _conn: Arc<Connection>,
        _method: Box<GetRpcProtocol>,
        _ctx: Box<dyn rpc::Context>,
    ) -> Result<GetProtocolReply, rpc::RpcError> {
        Ok(GetProtocolReply {
            version: RpcProtocolId::Alpha,
        })
    }
    rpc::rpc_invoke_fn! {
        conn_get_rpc_protocol(Connection, GetRpcProtocol);
    }
}
*/

/// The authentication scheme as enumerated in the spec.
///
/// Conceptually, an authentication scheme answers the question "How can the
/// Arti process know you have permissions to use or administer it?"
///
/// TODO RPC: The only supported one for now is "inherent:unix_path"
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
enum AuthenticationScheme {
    /// Inherent authority based on the ability to access an AF_UNIX address.
    #[serde(rename = "inherent:unix_path")]
    InherentUnixPath,
}

/// Method to ask which authentication methods are supported.
#[derive(Debug, serde::Deserialize)]
struct AuthQuery {}

/// A list of supported authentication schemes and their parameters.
#[derive(Debug, serde::Serialize)]
struct SupportedAuth {
    /// A list of the supported authentication schemes.
    ///
    /// TODO RPC: Actually, this should be able to contain strings _or_ maps,
    /// where the maps are additional information about the parameters needed
    /// for a particular scheme.  But I think that's a change we can make later
    /// once we have a scheme that takes parameters.
    ///
    /// TODO RPC: Should we indicate which schemes get you additional privileges?
    schemes: Vec<AuthenticationScheme>,
}

rpc::decl_method! {"auth:query" => AuthQuery}
impl rpc::Method for AuthQuery {
    type Output = SupportedAuth;
    type Update = rpc::NoUpdates;
}
/// Implement `auth:AuthQuery` on a connection.
async fn conn_authquery(
    _conn: Arc<Connection>,
    _query: Box<AuthQuery>,
    _ctx: Box<dyn rpc::Context>,
) -> Result<SupportedAuth, rpc::RpcError> {
    // Right now, every connection supports the same scheme.
    Ok(SupportedAuth {
        schemes: vec![AuthenticationScheme::InherentUnixPath],
    })
}
rpc::rpc_invoke_fn! {
    conn_authquery(Connection, AuthQuery);
}

/// Method to implement basic authentication.  Right now only "I connected to
/// you so I must have permission!" is supported.
#[derive(Debug, serde::Deserialize)]
struct Authenticate {
    /// The authentication scheme as enumerated in the spec.
    ///
    /// TODO RPC: The only supported one for now is "inherent:unix_path"
    scheme: AuthenticationScheme,
}

/// A reply from the `Authenticate` method.
#[derive(Debug, serde::Serialize)]
struct AuthenticateReply {
    /// An owned reference to a `Session` object.
    session: rpc::ObjectId,
}

rpc::decl_method! {"auth:authenticate" => Authenticate}
impl rpc::Method for Authenticate {
    type Output = AuthenticateReply;
    type Update = rpc::NoUpdates;
}

/// An error during authentication.
#[derive(Debug, Clone, thiserror::Error, serde::Serialize)]
enum AuthenticationFailure {}

impl tor_error::HasKind for AuthenticationFailure {
    fn kind(&self) -> tor_error::ErrorKind {
        // TODO RPC not right.
        tor_error::ErrorKind::LocalProtocolViolation
    }
}

/// Invoke the "authenticate" method on a connection.
///
/// TODO RPC: This behavior is wrong; we'll need to fix it to be all
/// capabilities-like.
async fn authenticate_connection(
    unauth: Arc<Connection>,
    method: Box<Authenticate>,
    ctx: Box<dyn rpc::Context>,
) -> Result<AuthenticateReply, rpc::RpcError> {
    match method.scheme {
        // For now, we only support AF_UNIX connections, and we assume that if
        // you have permission to open such a connection to us, you have
        // permission to use Arti. We will refine this later on!
        AuthenticationScheme::InherentUnixPath => {}
    }

    // TODO RPC: I'm actually not totally sure about the semantics of creating a
    // new session object here, since it will _look_ separate from other
    // sessions, but in fact they will all share the same object map.
    //
    // Perhaps we need to think more about the semantics of authenticating more
    // then once on the same connection.
    let client = unauth.inner.lock().expect("lock poisoned").client.clone();
    let session = crate::session::Session::new(client);
    let session = ctx.register_owned(session);
    Ok(AuthenticateReply { session })
}
rpc::rpc_invoke_fn! {
    authenticate_connection(Connection, Authenticate);
}
