use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::misc::{get_present_formatted_timestamp, AccountError, CNACMetadata};
use crate::prelude::ConnectionInfo;
use multimap::MultiMap;
use citadel_crypt::endpoint_crypto_container::PeerSessionCrypto;
use citadel_crypt::ratchets::Ratchet;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::fmt::Formatter;
use crate::auth::proposed_credentials::ProposedCredentials;
use crate::auth::DeclaredAuthenticationMode;
use crate::serialization::SyncIO;
use citadel_crypt::prelude::Toolset;
use citadel_crypt::ratchets::mono::MonoRatchet;
use citadel_crypt::ratchets::stacked::StackedRatchet;
use citadel_types::crypto::SecBuffer;
use citadel_types::user::MutualPeer;
use std::collections::HashMap;
use std::marker::PhantomData;
pub const HYPERLAN_IDX: u64 = 0;
#[derive(Serialize, Deserialize)]
pub struct AccountState {
#[cfg(feature = "google-services")]
pub client_rtdb_config: Option<crate::external_services::rtdb::RtdbClientConfig>,
#[cfg(not(feature = "google-services"))]
pub client_rtdb_config: Option<()>,
pub mutuals: MultiMap<u64, MutualPeer>,
pub byte_map: HashMap<u64, HashMap<String, HashMap<String, Vec<u8>>>>,
}
#[derive(Serialize, Deserialize)]
pub struct ClientNetworkAccount<R: Ratchet = StackedRatchet, Fcm: Ratchet = MonoRatchet> {
#[serde(bound = "")]
inner: Arc<ClientNetworkAccountInner<R, Fcm>>,
}
#[derive(Serialize, Deserialize)]
struct ClientNetworkAccountInner<R: Ratchet = StackedRatchet, Fcm: Ratchet = MonoRatchet> {
cid: u64,
is_personal: bool,
is_transient: bool,
pub creation_date: String,
pub adjacent_nac: ConnectionInfo,
#[serde(bound = "")]
pub crypto_session_state: Option<PeerSessionCrypto<R>>,
pub auth_store: DeclaredAuthenticationMode,
peer_state: RwLock<AccountState>,
_phantom: PhantomData<Fcm>,
}
impl<R: Ratchet, Fcm: Ratchet> ClientNetworkAccount<R, Fcm> {
#[allow(unused_results)]
pub async fn new(
valid_cid: u64,
is_personal: bool,
adjacent_nac: ConnectionInfo,
auth_store: DeclaredAuthenticationMode,
crypto_session_state: Option<PeerSessionCrypto<R>>,
) -> Result<Self, AccountError> {
if valid_cid == 0 && crypto_session_state.is_some() {
return Err(citadel_io::error!(citadel_io::ErrorCode::CnacCidZero));
}
log::trace!(target: "citadel", "Creating CNAC w/valid cid: {valid_cid:?}");
let creation_date = get_present_formatted_timestamp();
let peer_state = AccountState {
mutuals: MultiMap::new(),
byte_map: HashMap::default(),
client_rtdb_config: None,
};
let is_transient = auth_store.is_transient();
Ok(Self {
inner: Arc::new(ClientNetworkAccountInner {
creation_date,
cid: valid_cid,
auth_store,
adjacent_nac,
is_personal,
is_transient,
crypto_session_state,
peer_state: RwLock::new(peer_state),
_phantom: PhantomData,
}),
})
}
pub fn get_session_crypto(&self) -> &PeerSessionCrypto<R> {
self.inner.crypto_session_state.as_ref().expect("Unauthorized access to the zero CID. Raising to panic. Access to the zero CID is prohibited.")
}
#[allow(unused_results)]
pub fn refresh_static_ratchet(&self) -> R {
self.get_session_crypto().refresh_state();
let write = self.get_session_crypto().toolset().write();
write.verify_init_state();
write.get_static_auxiliary_ratchet().clone()
}
#[cfg(feature = "google-services")]
pub fn store_rtdb_config(&self, cfg: crate::external_services::rtdb::RtdbClientConfig) {
self.write().client_rtdb_config = Some(cfg);
}
pub fn auth_store(&self) -> &DeclaredAuthenticationMode {
&self.inner.auth_store
}
pub fn is_personal(&self) -> bool {
self.inner.is_personal
}
pub async fn new_from_network_personal(
valid_cid: u64,
session_crypto_state: Option<PeerSessionCrypto<R>>,
auth_store: DeclaredAuthenticationMode,
conn_info: ConnectionInfo,
) -> Result<Self, AccountError> {
const IS_PERSONAL: bool = true;
Self::new(
valid_cid,
IS_PERSONAL,
conn_info,
auth_store,
session_crypto_state,
)
.await
}
pub fn get_username(&self) -> String {
self.inner.auth_store.username().to_string()
}
pub async fn validate_credentials(
&self,
creds: ProposedCredentials,
) -> Result<(), AccountError> {
let argon_container = {
let username = self.inner.auth_store.username();
if !creds.compare_username(username.as_bytes()) {
return Err(AccountError::account_invalid_username());
}
match &self.inner.auth_store {
DeclaredAuthenticationMode::Argon { argon, .. } => argon.clone(),
DeclaredAuthenticationMode::Transient { .. } => return Ok(()),
}
};
creds.validate_credentials(argon_container).await
}
pub async fn generate_connect_credentials(
&self,
password_raw: SecBuffer,
) -> Result<ProposedCredentials, AccountError> {
let (settings, full_name, username) = {
match &self.inner.auth_store {
DeclaredAuthenticationMode::Argon {
argon,
full_name,
username,
} => (
argon.settings().clone(),
full_name.clone(),
username.clone(),
),
DeclaredAuthenticationMode::Transient { username, .. } => {
return Ok(ProposedCredentials::transient(username.clone()))
}
}
};
ProposedCredentials::new_connect(full_name, username, password_raw, settings).await
}
pub fn on_session_init(&self, toolset: Toolset<R>) {
self.get_session_crypto().replace_toolset_and_reset(toolset);
}
pub fn get_static_auxiliary_ratchet(&self) -> R {
self.get_session_crypto()
.toolset()
.read()
.get_static_auxiliary_ratchet()
.clone()
}
pub fn read(&self) -> RwLockReadGuard<'_, AccountState> {
self.inner.peer_state.read()
}
pub fn write(&self) -> RwLockWriteGuard<'_, AccountState> {
self.inner.peer_state.write()
}
pub(crate) fn get_hyperlan_peer_list(&self) -> Option<Vec<u64>> {
let this = self.read();
let hyperlan_peers = this.mutuals.get_vec(&HYPERLAN_IDX)?;
Some(
hyperlan_peers
.iter()
.map(|peer| peer.cid)
.collect::<Vec<u64>>(),
)
}
pub(crate) fn get_hyperlan_peer_mutuals(&self) -> Option<Vec<MutualPeer>> {
let this = self.read();
this.mutuals.get_vec(&HYPERLAN_IDX).cloned()
}
#[allow(dead_code)]
pub(crate) fn get_hyperwan_peer_list(&self, icid: u64) -> Option<Vec<u64>> {
let this = self.read();
let hyperwan_peers = this.mutuals.get_vec(&icid)?;
Some(
hyperwan_peers
.iter()
.map(|peer| peer.cid)
.collect::<Vec<u64>>(),
)
}
pub(crate) fn get_hyperlan_peer(&self, cid: u64) -> Option<MutualPeer> {
let read = self.read();
let hyperlan_peers = read.mutuals.get_vec(&HYPERLAN_IDX)?;
hyperlan_peers.iter().find(|peer| peer.cid == cid).cloned()
}
pub(crate) fn get_hyperlan_peers(&self, peers: impl AsRef<[u64]>) -> Option<Vec<MutualPeer>> {
let read = self.read();
let peers = peers.as_ref();
let hyperlan_peers = read.mutuals.get_vec(&HYPERLAN_IDX)?;
Some(
peers
.iter()
.filter_map(|peer_wanted| {
hyperlan_peers
.iter()
.find(|peer| peer.cid == *peer_wanted)
.cloned()
})
.collect(),
)
}
#[allow(unused_results)]
pub(crate) fn register_hyperlan_p2p_as_server(
&self,
other_orig: &ClientNetworkAccount<R, Fcm>,
) -> Result<(), AccountError> {
let this_cid = self.inner.cid;
let other_cid = other_orig.inner.cid;
let this_username = self.inner.auth_store.username().to_string();
let other_username = other_orig.inner.auth_store.username().to_string();
let mut this = self.write();
let mut other = other_orig.write();
this.mutuals.insert(
HYPERLAN_IDX,
MutualPeer {
parent_icid: HYPERLAN_IDX,
cid: other_cid,
username: Some(other_username),
},
);
other.mutuals.insert(
HYPERLAN_IDX,
MutualPeer {
parent_icid: HYPERLAN_IDX,
cid: this_cid,
username: Some(this_username),
},
);
Ok(())
}
#[allow(unused_results)]
pub(crate) fn deregister_hyperlan_p2p_as_server(
&self,
other: &ClientNetworkAccount<R, Fcm>,
) -> Result<(), AccountError> {
self.remove_hyperlan_peer(other.get_cid())
.ok_or_else(|| AccountError::account_client_non_exists(other.get_cid()))?;
other
.remove_hyperlan_peer(self.get_cid())
.ok_or_else(|| citadel_io::error!(citadel_io::ErrorCode::PeerRemoveSelfFailed))?;
Ok(())
}
pub(crate) fn hyperlan_peers_exist(&self, peers: impl AsRef<[u64]>) -> Vec<bool> {
let read = self.read();
let peers = peers.as_ref();
if let Some(hyperlan_peers) = read.mutuals.get_vec(&HYPERLAN_IDX) {
peers
.iter()
.map(|peer| {
hyperlan_peers
.iter()
.any(|hyperlan_peer| hyperlan_peer.cid == *peer)
})
.collect()
} else {
log::warn!(target: "citadel", "Attempted to check hyperlan list, but it does not exists");
peers.iter().map(|_| false).collect()
}
}
pub(crate) fn synchronize_hyperlan_peer_list(&self, peers: Vec<MutualPeer>) {
let mut this = self.write();
let AccountState { mutuals, .. } = &mut *this;
let _ = mutuals.remove(&HYPERLAN_IDX);
mutuals.insert_many(HYPERLAN_IDX, peers);
}
pub(crate) fn insert_hyperlan_peer<T: Into<String>>(&self, cid: u64, username: T) {
let mut write = self.write();
let username = Some(username.into());
write.mutuals.insert(
HYPERLAN_IDX,
MutualPeer {
username,
parent_icid: HYPERLAN_IDX,
cid,
},
);
}
#[allow(unused_results)]
pub(crate) fn remove_hyperlan_peer(&self, cid: u64) -> Option<MutualPeer> {
log::trace!(target: "citadel", "[remove peer] session_cid: {} | peer_cid: {}", self.get_cid(), cid);
let mut write = self.write();
if let Some(hyperlan_peers) = write.mutuals.get_vec_mut(&HYPERLAN_IDX) {
if let Some(idx) = hyperlan_peers.iter().position(|peer| peer.cid == cid) {
let removed_peer = hyperlan_peers.remove(idx);
return Some(removed_peer);
} else {
log::warn!(target: "citadel", "Peer {} not found within cnac {}", cid, self.inner.cid);
}
}
None
}
pub fn generate_proper_bytes(&self) -> Result<Vec<u8>, AccountError>
where
Self: SyncIO,
{
self.serialize_to_vector()
}
pub(crate) fn get_metadata(&self) -> CNACMetadata {
let read = &self.inner;
let cid = read.cid;
let username = read.auth_store.username().to_string();
let full_name = read.auth_store.full_name().to_string();
let is_personal = read.is_personal;
let creation_date = read.creation_date.clone();
CNACMetadata {
cid,
username,
full_name,
is_personal,
creation_date,
}
}
pub fn get_connect_info(&self) -> ConnectionInfo {
self.inner.adjacent_nac.clone()
}
pub fn get_cid(&self) -> u64 {
self.inner.cid
}
pub fn is_transient(&self) -> bool {
self.inner.is_transient
}
}
impl<R: Ratchet, Fcm: Ratchet> std::fmt::Debug for ClientNetworkAccount<R, Fcm> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "CNAC | CID: {}", self.inner.cid)
}
}
impl<R: Ratchet, Fcm: Ratchet> std::fmt::Display for ClientNetworkAccount<R, Fcm> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"{}\t\t{}\t\t{}\t\t{}",
self.inner.cid,
self.inner.auth_store.username(),
self.inner.auth_store.full_name(),
self.inner.is_personal
)
}
}
impl<R: Ratchet, Fcm: Ratchet> Clone for ClientNetworkAccount<R, Fcm> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}