Communities

This commit is contained in:
Alex Emmet 2025-10-26 11:17:52 +01:00
commit 1c08956387
26 changed files with 1988 additions and 338 deletions

126
Cargo.lock generated
View file

@ -6,6 +6,7 @@ version = 4
name = "Iota"
version = "0.1.0"
dependencies = [
"aes-gcm",
"axum",
"base64",
"bytes",
@ -13,8 +14,11 @@ dependencies = [
"color-eyre",
"crossterm 0.29.0",
"der",
"futures",
"futures-util",
"hex",
"hkdf",
"hmac",
"json",
"once_cell",
"pkcs8",
@ -29,6 +33,7 @@ dependencies = [
"sysinfo",
"tokio",
"tokio-tungstenite",
"tungstenite",
"uuid",
"walkdir",
"x448",
@ -50,6 +55,41 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aes-gcm"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
@ -235,6 +275,16 @@ dependencies = [
"windows-link 0.2.0",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "cmake"
version = "0.1.54"
@ -409,9 +459,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"rand_core 0.6.4",
"typenum",
]
[[package]]
name = "ctr"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [
"cipher",
]
[[package]]
name = "darling"
version = "0.20.11"
@ -501,6 +561,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
@ -751,6 +812,16 @@ dependencies = [
"wasi 0.14.4+wasi-0.2.4",
]
[[package]]
name = "ghash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
dependencies = [
"opaque-debug",
"polyval",
]
[[package]]
name = "gimli"
version = "0.31.1"
@ -799,6 +870,24 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hkdf"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "http"
version = "1.3.1"
@ -1085,6 +1174,15 @@ version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array",
]
[[package]]
name = "instability"
version = "0.3.9"
@ -1321,6 +1419,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
version = "0.10.73"
@ -1434,6 +1538,18 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "polyval"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
dependencies = [
"cfg-if",
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "potential_utf"
version = "0.1.3"
@ -2332,6 +2448,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
"crypto-common",
"subtle",
]
[[package]]
name = "untrusted"
version = "0.9.0"

View file

@ -35,3 +35,8 @@ x509 = "*"
sha2 = "*"
der = "*"
pkcs8 = { version = "*", features = ["alloc"] }
tungstenite = "*"
futures = "*"
aes-gcm = "*"
hkdf = "*"
hmac = "*"

138
src/auth/crypto_helper.rs Normal file
View file

@ -0,0 +1,138 @@
use aes_gcm::{
Aes256Gcm, // AES256 GCM
Nonce,
aead::{Aead, KeyInit, OsRng},
};
use base64::{Engine as _, engine::general_purpose::STANDARD};
use base64::{decode as b64_decode, encode as b64_encode};
use hex;
use json::JsonValue;
use once_cell::sync::Lazy;
use rand::Rng;
use rand_core::RngCore;
use sha2::{Digest, Sha256};
use std::io;
use std::sync::Mutex;
use uuid::Uuid;
use x448::{PublicKey, Secret, SharedSecret}; // from the `x448` crate
/// Errors for crypto operations
#[derive(Debug)]
pub enum CryptoError {
Base64Decode(base64::DecodeError),
InvalidKey,
AgreementError,
EncryptionError(aes_gcm::Error),
DecryptionError(aes_gcm::Error),
}
impl From<base64::DecodeError> for CryptoError {
fn from(err: base64::DecodeError) -> Self {
CryptoError::Base64Decode(err)
}
}
pub struct KeyPair {
pub secret: Secret,
pub public: PublicKey,
}
pub fn generate_keypair() -> KeyPair {
let mut buf = [0u8; 56];
let mut rng = OsRng;
rng.fill_bytes(&mut buf);
let secret = Secret::from_bytes(&buf).unwrap();
let public = PublicKey::from(&secret);
KeyPair { secret, public }
}
pub fn public_key_to_base64(pubkey: &PublicKey) -> String {
b64_encode(pubkey.as_bytes().as_ref())
}
pub fn secret_key_to_base64(secret: &Secret) -> String {
b64_encode(secret.as_bytes().as_ref())
}
pub fn load_public_key(base64_pub: &str) -> Option<PublicKey> {
let bytes = b64_decode(base64_pub).unwrap();
PublicKey::from_bytes(&bytes)
}
pub fn load_secret_key(base64_secret: &str) -> Option<Secret> {
let bytes = b64_decode(base64_secret).unwrap();
Secret::from_bytes(&bytes)
}
fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(shared.as_bytes());
let result = hasher.finalize();
let mut key = [0u8; 32];
key.copy_from_slice(&result[..32]);
key
}
pub fn encrypt(
base64_secret: &str,
base64_peer_pub: &str,
plaintext: &str,
) -> Result<String, CryptoError> {
let secret = load_secret_key(base64_secret).unwrap();
let peer_pub = load_public_key(base64_peer_pub).unwrap();
let shared = secret
.to_diffie_hellman(&peer_pub)
.ok_or(CryptoError::AgreementError)?;
let key_bytes = derive_aes_key(&shared);
let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct");
let mut nonce_bytes = [0u8; 12];
OsRng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher
.encrypt(nonce, plaintext.as_bytes())
.map_err(CryptoError::EncryptionError)?;
// prefix nonce to ciphertext
let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
out.extend_from_slice(&nonce_bytes);
out.extend_from_slice(&ciphertext);
Ok(b64_encode(&out))
}
pub fn decrypt(
base64_secret: &str,
base64_peer_pub: &str,
encrypted_base64: &str,
) -> Result<String, CryptoError> {
let secret = load_secret_key(base64_secret).unwrap();
let peer_pub = load_public_key(base64_peer_pub).unwrap();
let shared = secret
.to_diffie_hellman(&peer_pub)
.ok_or(CryptoError::AgreementError)?;
let key_bytes = derive_aes_key(&shared);
let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct");
let encrypted = b64_decode(encrypted_base64)?;
if encrypted.len() < 12 {
return Err(CryptoError::DecryptionError(aes_gcm::Error));
}
let nonce_bytes = &encrypted[..12];
let ciphertext = &encrypted[12..];
let nonce = Nonce::from_slice(nonce_bytes);
let plaintext_bytes = cipher
.decrypt(nonce, ciphertext)
.map_err(CryptoError::DecryptionError)?;
let plaintext = String::from_utf8(plaintext_bytes)
.map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?;
Ok(plaintext)
}
pub fn hash_it(input: &str) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(input.as_bytes());
hasher.finalize().to_vec()
}
pub fn hex_hash(input: &str) -> String {
let digest = hash_it(input);
digest.iter().map(|b| format!("{:02x}", b)).collect()
}

View file

@ -1 +1,2 @@
pub mod auth_connector;
pub mod crypto_helper;

View file

@ -0,0 +1,280 @@
use crate::communities::interactables::category::Category;
use crate::communities::interactables::registry;
use crate::communities::interactables::text_chat::TextChat;
use crate::communities::interactables::voice_chat::VoiceChat;
use crate::communities::{
community_connection::CommunityConnection, interactables::interactable::Interactable,
};
use crate::data::communication::{CommunicationType, CommunicationValue};
use crate::util::file_util;
use base64::{Engine as _, engine::general_purpose::STANDARD};
use json::JsonValue;
use json::object::Object;
use rand::RngCore;
use rand_core::OsRng;
use ratatui::text;
use serde::de::value::StringDeserializer;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;
use x448::{PublicKey, Secret};
/// Permissions
// uuid -> interactable/path/like/this/interactable_name:permission
// -> role
/// Roles
// rolename -> interactable/path/like/this/interactable_name:permission
// -> other/path/like/this/interactable_name:permission
pub struct Community {
name: String,
owner_id: Uuid,
members: Vec<Uuid>,
permissions: HashMap<Uuid, Vec<String>>,
roles: HashMap<String, Vec<String>>,
private_key: Secret,
public_key: PublicKey,
pub interactables: Arc<RwLock<Vec<Arc<Box<dyn Interactable>>>>>,
pub connections: Arc<RwLock<HashMap<Uuid, Vec<Arc<CommunityConnection>>>>>,
}
impl Community {
pub fn new() -> Self {
let mut buf = [0u8; 56];
let mut rng = OsRng;
rng.fill_bytes(&mut buf);
let private_key = Secret::from_bytes(&buf).unwrap();
let public_key = PublicKey::from(&private_key);
Community {
name: String::new(),
owner_id: Uuid::new_v4(),
members: Vec::new(),
permissions: HashMap::new(),
roles: HashMap::new(),
private_key,
public_key,
interactables: Arc::new(RwLock::new(Vec::new())),
connections: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn add_member(&mut self, member_id: Uuid) {
self.members.push(member_id);
}
pub fn remove_member(&mut self, member_id: Uuid) {
self.members.retain(|id| *id != member_id);
}
pub fn get_name(&self) -> &str {
&self.name
}
pub fn get_private_key(&self) -> Secret {
Secret::from_bytes(self.private_key.as_bytes()).unwrap()
}
pub fn get_public_key(&self) -> &PublicKey {
&self.public_key
}
pub async fn add_connection(self: &Arc<Self>, other: Arc<CommunityConnection>) {
let mut vec = self
.connections
.read()
.await
.get(&other.get_user_id().await.unwrap())
.cloned()
.unwrap_or_default();
vec.push(other.clone());
self.connections
.write()
.await
.insert(other.get_user_id().await.unwrap(), vec);
}
pub async fn get_connections(&self) -> HashMap<Uuid, Vec<Arc<CommunityConnection>>> {
self.connections.read().await.clone()
}
pub async fn get_connections_for_user(&self, user_id: Uuid) -> Vec<Arc<CommunityConnection>> {
self.connections
.read()
.await
.get(&user_id)
.cloned()
.unwrap_or_default()
}
pub async fn get_interactables(
&self,
user_id: Uuid,
) -> Vec<Arc<Box<dyn Interactable + 'static>>> {
self.interactables.read().await.clone()
}
pub async fn add_interactable(self: &mut Arc<Self>, interactable: Arc<Box<dyn Interactable>>) {
self.interactables.write().await.push(interactable);
}
pub async fn remove_interactable(
self: &mut Arc<Self>,
interactable: Arc<Box<dyn Interactable>>,
) {
self.interactables
.write()
.await
.retain(|i| !Arc::ptr_eq(i, &interactable));
}
pub async fn run_function(
self: &mut Arc<Self>,
user_id: Uuid,
name: &str,
path: &str,
function: &str,
cv: &CommunicationValue,
) -> CommunicationValue {
if path.is_empty() {
let mut target_interactables = &self.interactables.read().await.clone();
for interactable in target_interactables.iter() {
if interactable.get_name() == name {
if interactable.get_codec() == "category" {
return CommunicationValue::new(CommunicationType::error);
} else {
return interactable.run_function(cv.clone());
}
}
}
} else {
let target_interactables = &self.interactables.read().await.clone();
for interactable in target_interactables.iter() {
if interactable.get_name() == name {
if interactable.get_codec() == "category" {
let category: &Category =
interactable.as_any().downcast_ref::<Category>().unwrap();
return category
.get_child(path.to_string(), name.to_string())
.unwrap()
.run_function(cv.clone());
} else {
return CommunicationValue::new(CommunicationType::error);
}
}
}
}
CommunicationValue::new(CommunicationType::add_chat)
}
pub async fn save(&self) {
let mut json = Object::new();
json.insert("name", JsonValue::String(self.name.clone()));
json.insert("owner_id", JsonValue::String(self.owner_id.to_string()));
json.insert(
"private_key",
JsonValue::String(STANDARD.encode(&self.private_key.as_bytes())),
);
json.insert(
"public_key",
JsonValue::String(STANDARD.encode(&self.public_key.as_bytes())),
);
file_util::save_file(
&format!("communities/{}/", self.name),
"config.json",
&json.dump(),
);
let mut user_data = Object::new();
for user in self.members.iter() {
let mut data = JsonValue::new_object();
let mut permissions = JsonValue::new_array();
for perm in self.permissions.get(user).unwrap() {
permissions.push(perm.to_string());
}
data.insert("permissions", permissions);
user_data.insert(&user.to_string(), data);
}
file_util::save_file(
&format!("communities/{}/", self.name),
"users.json",
&user_data.dump(),
);
for interactable in self.interactables.read().await.clone().iter() {
registry::save(interactable).await;
}
}
}
pub async fn load(name: &String) -> Option<Arc<Community>> {
let file_contents = file_util::load_file(&format!("communities/{}/", name), "config.json");
let json_content = json::parse(&file_contents).unwrap();
let user_data = file_util::load_file(&format!("communities/{}/", name), "users.json");
let user_json: JsonValue = json::parse(&user_data).unwrap();
let mut users = Vec::new();
let mut permissions: HashMap<Uuid, Vec<String>> = HashMap::new();
for user in user_json.entries() {
let (str, json): (&str, &JsonValue) = user;
let perms_j = &json["permissions"];
let perms = Vec::new();
for i in perms_j.entries() {
// let perm_j = i.as_str().unwrap();
// perms.push(perm_j.to_string());
}
let user_id = Uuid::parse_str(str).unwrap();
users.push(user_id);
permissions.insert(user_id, perms);
}
let role_data = file_util::load_file(&format!("communities/{}/", name), "roles.json");
if let Ok(user_json) = json::parse(&role_data) {
} else {
return None;
};
let mut roles: HashMap<String, Vec<String>> = HashMap::new();
let community = Community {
name: json_content["name"].as_str().unwrap().to_string(),
owner_id: Uuid::parse_str(json_content["owner_id"].as_str().unwrap()).unwrap(),
members: users,
roles,
permissions,
private_key: Secret::from_bytes(
&STANDARD
.decode(json_content["private_key"].as_str().unwrap())
.unwrap(),
)
.unwrap(),
public_key: PublicKey::from(
&Secret::from_bytes(
&STANDARD
.decode(json_content["private_key"].as_str().unwrap())
.unwrap(),
)
.unwrap(),
),
interactables: Arc::new(RwLock::new(Vec::new())),
connections: Arc::new(RwLock::new(HashMap::new())),
};
let mut comarc = Arc::new(community);
let interactable_files: Vec<String> =
file_util::get_children(&format!("communities/{}/interactables/", name));
for file in interactable_files {
if file.contains(".json") {
let name = file.split('.').next().unwrap().to_string();
let interactable: Box<dyn Interactable> =
registry::load(comarc.clone(), String::new(), name).await;
comarc.add_interactable(Arc::new(interactable)).await;
}
}
let mut text_chat: TextChat = TextChat::new();
text_chat.load(
comarc.clone(),
String::new(),
String::from("a"),
&JsonValue::Null,
);
comarc.add_interactable(Arc::new(Box::new(text_chat))).await;
Some(comarc)
}

View file

@ -0,0 +1,415 @@
use crate::auth::auth_connector::AuthUser;
use crate::auth::auth_connector::get_user;
use crate::communities::community::Community;
use crate::communities::interactables::interactable::Interactable;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use aes_gcm::{Aes256Gcm, KeyInit, Nonce, aead::Aead};
use base64::{Engine as _, engine::general_purpose::STANDARD};
use futures::SinkExt;
use hkdf::Hkdf;
use json::JsonValue;
use json::object::Object;
use rand::{Rng, distributions::Alphanumeric};
use sha2::Sha256;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::sync::RwLock;
use tokio_tungstenite::{WebSocketStream, tungstenite::Message};
use uuid::Uuid;
use x448::PublicKey;
pub struct CommunityConnection {
pub session: Arc<Mutex<WebSocketStream<tokio::net::TcpStream>>>,
pub user_id: Arc<RwLock<Option<Uuid>>>,
pub community: Arc<RwLock<Option<Arc<Community>>>>,
identified: Arc<RwLock<bool>>,
challenged: Arc<RwLock<bool>>,
challenge: Arc<RwLock<String>>,
auth: Arc<RwLock<Option<AuthUser>>>,
pub ping: Arc<RwLock<i64>>,
}
impl CommunityConnection {
pub fn new(
session: WebSocketStream<tokio::net::TcpStream>,
community: Arc<Community>,
) -> Arc<Self> {
Arc::new(Self {
session: Arc::new(Mutex::new(session)),
user_id: Arc::new(RwLock::new(None)),
community: Arc::new(RwLock::new(Some(community))),
identified: Arc::new(RwLock::new(false)),
challenged: Arc::new(RwLock::new(false)),
challenge: Arc::new(RwLock::new(String::new())),
auth: Arc::new(RwLock::new(None)),
ping: Arc::new(RwLock::new(-1)),
})
}
pub async fn send_message(&self, message: &CommunicationValue) {
let mut session = self.session.lock().await;
session
.send(Message::Text(message.to_json().to_string()))
.await
.unwrap();
}
pub async fn get_community(&self) -> Option<Arc<Community>> {
self.community.read().await.clone()
}
pub async fn get_user_id(&self) -> Option<Uuid> {
*self.user_id.read().await
}
pub async fn is_identified(&self) -> bool {
*self.identified.read().await && *self.challenged.read().await
}
pub async fn handle_message(self: Arc<Self>, message: String) {
let cv = CommunicationValue::from_json(&message);
if cv.is_type(CommunicationType::identification) && !self.is_identified().await {
self.handle_identification(cv).await;
return;
}
if cv.is_type(CommunicationType::challenge_response) && !self.is_identified().await {
self.handle_challenge_response(cv).await;
return;
}
if !self.is_identified().await {
return;
}
if cv.is_type(CommunicationType::ping) {
self.handle_ping(cv).await;
return;
}
if cv.is_type(CommunicationType::client_changed) {
//self.handle_client_changed(cv).await;
return;
}
if cv.is_type(CommunicationType::function) {
self.handle_function(cv).await;
return;
}
}
async fn handle_function(&self, cv: CommunicationValue) {
/*
* {
* "type": "function",
* "id": "<uuid>",
* "log": {
* "log_level": 0,
* "message": "running function"
* },
* "data": {
* "codec": "<channel_codec>", // optional
* "name": "<channel_name>",
* "path": "<overlord_category_name>/<overlord_category_name>...",
* "function": "<function_to_execute>",
* "payload": {
* "<ARG_1>": "<VAL_1>",
* ...
* }
* }
* }
*/
let name = cv.get_data(DataTypes::name).unwrap().as_str().unwrap();
let path = cv.get_data(DataTypes::path).unwrap().as_str().unwrap();
let function = cv.get_data(DataTypes::function).unwrap().as_str().unwrap();
let result = self
.get_community()
.await
.unwrap()
.run_function(self.get_user_id().await.unwrap(), name, path, function, &cv)
.await;
self.send_message(&result).await;
}
async fn handle_identification(&self, cv: CommunicationValue) {
let user_id = match cv.get_data(DataTypes::user_id) {
Some(id_str) => match Uuid::parse_str(&id_str.to_string()) {
Ok(id) => id,
Err(_) => {
self.send_error_response(
&cv.get_id(),
CommunicationType::error_invalid_user_id,
)
.await;
return;
}
},
None => {
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
.await;
return;
}
};
let Some(user) = get_user(user_id).await else {
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
.await;
return;
};
{
let mut auth_guard = self.auth.write().await;
*auth_guard = Some(user.clone());
let mut user_id_guard = self.user_id.write().await;
*user_id_guard = Some(user_id);
let mut identified_guard = self.identified.write().await;
*identified_guard = true;
}
let challenge_str: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(32)
.map(char::from)
.collect();
{
let mut challenge_guard = self.challenge.write().await;
*challenge_guard = challenge_str.clone();
}
let user_public_key_bytes = match STANDARD.decode(&user.public_key) {
Ok(bytes) => bytes,
Err(_) => {
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
.await;
return;
}
};
let user_pub_key: PublicKey = match PublicKey::from_bytes(&user_public_key_bytes) {
Some(key) => key,
None => {
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
.await;
return;
}
};
let Some(community) = self.community.read().await.clone() else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let community_private_key = community.get_private_key();
let community_public_key = community.get_public_key();
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
Some(secret) => secret,
None => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let aes_key = {
let hk = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut key_bytes = [0u8; 32];
hk.expand(b"challenge", &mut key_bytes)
.expect("HKDF failure");
key_bytes
};
let cipher = Aes256Gcm::new_from_slice(&aes_key).expect("AES init failed");
let nonce_bytes: [u8; 12] = rand::random();
let nonce = Nonce::from_slice(&nonce_bytes);
let encrypted_challenge = match cipher.encrypt(nonce, challenge_str.as_bytes()) {
Ok(data) => data,
Err(_) => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let mut encrypted_out = nonce_bytes.to_vec();
encrypted_out.extend(encrypted_challenge);
let response = CommunicationValue::new(CommunicationType::challenge)
.add_data_str(
DataTypes::public_key,
STANDARD.encode(community_public_key.as_bytes()),
)
.add_data_str(DataTypes::challenge, STANDARD.encode(&encrypted_out))
.with_id(cv.get_id());
self.send_message(&response).await;
}
async fn handle_challenge_response(self: Arc<Self>, cv: CommunicationValue) {
let client_challenge_response_b64 = match cv.get_data(DataTypes::challenge) {
Some(data) => data.to_string(),
None => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let challenge_response_bytes = match STANDARD.decode(&client_challenge_response_b64) {
Ok(bytes) => bytes,
Err(_) => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
if challenge_response_bytes.len() < 12 {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
let Some(user) = self.auth.read().await.clone() else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let Some(user_pub_bytes) = STANDARD.decode(&user.public_key).ok() else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let Some(user_pub_key) = PublicKey::from_bytes(&user_pub_bytes) else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let Some(community) = self.community.read().await.clone() else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let community_private_key = community.get_private_key();
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
Some(secret) => secret,
None => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let aes_key = {
use hkdf::Hkdf;
use sha2::Sha256;
let hk = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut key_bytes = [0u8; 32];
hk.expand(b"challenge", &mut key_bytes)
.expect("HKDF failure");
key_bytes
};
let (nonce_bytes, ciphertext) = challenge_response_bytes.split_at(12);
let nonce = Nonce::from_slice(nonce_bytes);
let cipher = Aes256Gcm::new_from_slice(&aes_key).expect("AES init failed");
let decrypted_bytes = match cipher.decrypt(nonce, ciphertext) {
Ok(pt) => pt,
Err(_) => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let client_response = match String::from_utf8(decrypted_bytes) {
Ok(str) => str,
Err(_) => {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
}
};
let expected_challenge = self.challenge.read().await.clone();
if client_response != expected_challenge {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
self.close().await;
return;
}
{
let mut authenticated_guard = self.challenged.write().await;
*authenticated_guard = true;
}
let Some(community) = self.community.read().await.clone() else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
let arc = Arc::new(community);
let Some(user_id) = self.get_user_id().await else {
self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
};
arc.add_connection(self.clone()).await;
let response = CommunicationValue::new(CommunicationType::identification_response)
.add_data(DataTypes::interactables, {
let a: Vec<Arc<Box<dyn Interactable>>> = arc.get_interactables(user_id).await;
let mut c: JsonValue = JsonValue::new_object();
for b in a {
let mut subject = JsonValue::new_object();
subject["codec"] = JsonValue::String(b.get_codec());
subject["data"] = b.get_data();
c[b.get_name()] = subject;
}
c
})
.with_id(cv.get_id());
self.send_message(&response).await;
}
async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) {
let error = CommunicationValue::new(error_type).with_id(*message_id);
self.send_message(&error).await;
}
pub async fn close(&self) {
let mut session = self.session.lock().await;
let _ = session.close(None).await;
}
pub async fn handle_close(&self) {
if self.is_identified().await {
if let Some(user_id) = self.get_user_id().await {
todo!();
}
}
}
async fn handle_ping(&self, cv: CommunicationValue) {
if let Some(last_ping) = cv.get_data(DataTypes::last_ping) {
if let Ok(ping_val) = last_ping.to_string().parse::<i64>() {
let mut ping_guard = self.ping.write().await;
*ping_guard = ping_val;
}
}
let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id());
self.send_message(&response).await;
}
}

View file

@ -0,0 +1,45 @@
use crate::communities::community::{self, Community};
use crate::gui::log_panel;
use crate::util::file_util;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use uuid::Uuid;
pub static COMMUNITY_REGISTRY: Lazy<Arc<Mutex<HashMap<String, Arc<Community>>>>> =
Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
pub async fn add_community(community: Arc<Community>) {
COMMUNITY_REGISTRY
.lock()
.await
.insert(community.get_name().to_string(), community);
}
pub async fn get_community(name: &str) -> Option<Arc<Community>> {
if let Some(c) = COMMUNITY_REGISTRY.lock().await.get(name) {
Some(c.clone())
} else {
None
}
}
pub async fn load_communities() {
let community_names = file_util::get_children("communities");
for name in community_names {
if let Some(community) = community::load(&name).await {
add_community(community).await;
} else {
log_panel::log_message(format!("failed to load the {} community", &name));
}
}
}
pub async fn save_communities() {
for community in COMMUNITY_REGISTRY.lock().await.values() {
community.save().await;
}
}
pub async fn get_communities() -> Vec<Arc<Community>> {
COMMUNITY_REGISTRY.lock().await.values().cloned().collect()
}

View file

@ -0,0 +1,67 @@
use crate::communities::{
community::Community, community_connection::CommunityConnection, community_manager,
};
use futures::StreamExt;
use std::sync::Arc;
use tokio::net::TcpListener;
use tokio_tungstenite::accept_hdr_async;
use tungstenite::handshake::server::{Request, Response};
pub async fn start(port: u16) -> bool {
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
if let Err(_) = listener {
return false;
}
let listener = listener.unwrap();
tokio::spawn(async move {
while let Ok((stream, _)) = listener.accept().await {
tokio::spawn(async move {
let mut path: String = "/".to_string();
let callback = |req: &Request, response: Response| {
path = format!("{}", &req.uri().path());
Ok(response)
};
let ws_stream = match accept_hdr_async(stream, callback).await {
Ok(ws) => ws,
Err(e) => {
return;
}
};
if path.starts_with("/community/") {
let community_id = path.split("/").nth(2).unwrap();
if let Some(community) = community_manager::get_community(community_id).await {
let community_conn: Arc<CommunityConnection> =
Arc::from(CommunityConnection::new(ws_stream, community));
loop {
let msg_result = {
let mut session_lock = community_conn.session.lock().await;
session_lock.next().await
};
match msg_result {
Some(Ok(msg)) => {
if msg.is_text() {
let text = msg.into_text().unwrap();
community_conn.clone().handle_message(text).await;
} else if msg.is_close() {
community_conn.handle_close().await;
return;
}
}
Some(Err(e)) => {
community_conn.handle_close().await;
return;
}
None => {
community_conn.handle_close().await;
return;
}
}
}
}
}
});
}
});
true
}

View file

@ -0,0 +1,108 @@
use crate::{
communities::{community::Community, interactables::interactable::Interactable},
data::communication::{CommunicationType, CommunicationValue},
};
use axum::Json;
use json::JsonValue;
use std::any::Any;
use std::sync::Arc;
pub struct Category {
name: String,
path: String,
community: Arc<Community>,
children: Vec<Arc<Box<dyn Interactable>>>,
}
impl Category {
pub fn new() -> Category {
Category {
name: String::new(),
path: String::new(),
community: Arc::new(Community::new()),
children: Vec::new(),
}
}
pub fn get_child(&self, path: String, name: String) -> Option<Arc<Box<dyn Interactable>>> {
if path.is_empty() {
self.children
.iter()
.find(|child| child.get_name() == &name)
.cloned()
} else {
let sub_module = path.split("/").next().unwrap();
let next = self
.children
.iter()
.find(|child| child.get_name() == sub_module)
.unwrap();
if next.get_codec() == "category" {
let next_cat = next.as_any().downcast_ref::<Category>().unwrap();
next_cat.get_child(path, name)
} else {
Some(next.clone())
}
}
}
pub fn get_children(&self) -> Vec<Arc<Box<dyn Interactable>>> {
self.children.iter().map(|child| child.clone()).collect()
}
}
impl Interactable for Category {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn get_codec(&self) -> String {
"category".to_string()
}
fn set_name(&mut self, name: String) {
self.name = name;
}
fn set_path(&mut self, path: String) {
self.path = path;
}
fn get_community(&self) -> &Arc<Community> {
&self.community
}
fn set_community(&mut self, community: Arc<Community>) {
self.community = community;
}
fn get_name(&self) -> &String {
&self.name
}
fn get_path(&self) -> &String {
&self.path
}
fn get_total_path(&self) -> String {
String::new() + &self.path + "/" + &self.name
}
fn get_data(&self) -> JsonValue {
let mut v = JsonValue::new_object();
for child in &self.children {
let mut subject = JsonValue::new_object();
subject["codec"] = JsonValue::String(child.get_codec());
subject["data"] = child.get_data();
v[child.get_name()] = subject;
}
v
}
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
CommunicationValue::new(CommunicationType::error)
}
fn to_json(&self) -> JsonValue {
let mut v = JsonValue::new_object();
v["children"] = JsonValue::new_array();
for child in &self.children {
v["children"].push(child.to_json());
}
v
}
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
self.community = community;
self.name = name;
self.path = path;
}
}

View file

@ -0,0 +1,27 @@
use crate::{
communities::community::Community,
data::communication::{CommunicationType, CommunicationValue},
};
use axum::Json;
use json::JsonValue;
use std::any::Any;
use std::sync::Arc;
pub type InteractableFactory = fn() -> Box<dyn Interactable>;
pub trait Interactable: Send + Sync + Any {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn get_codec(&self) -> String;
fn get_name(&self) -> &String;
fn get_path(&self) -> &String;
fn get_total_path(&self) -> String;
fn set_name(&mut self, name: String);
fn set_path(&mut self, path: String);
fn get_community(&self) -> &Arc<Community>;
fn set_community(&mut self, community: Arc<Community>);
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue;
fn get_data(&self) -> JsonValue;
fn to_json(&self) -> JsonValue;
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue);
}

View file

@ -0,0 +1,73 @@
use crate::communities::community::Community;
use crate::communities::interactables::category::Category;
use crate::communities::interactables::interactable::{Interactable, InteractableFactory};
use crate::communities::interactables::text_chat::TextChat;
use crate::communities::interactables::voice_chat::VoiceChat;
use crate::gui::log_panel;
use crate::util::file_util;
use json::JsonValue;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
pub static INTERACTABLE_REGISTRY: Lazy<Arc<Mutex<HashMap<String, InteractableFactory>>>> =
Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
pub async fn load_interactables() {
INTERACTABLE_REGISTRY
.lock()
.await
.insert(TextChat::new().get_codec(), || {
Box::new(TextChat::new()) as Box<dyn Interactable>
});
INTERACTABLE_REGISTRY
.lock()
.await
.insert(VoiceChat::new().get_codec(), || {
Box::new(VoiceChat::new()) as Box<dyn Interactable>
});
INTERACTABLE_REGISTRY
.lock()
.await
.insert(Category::new().get_codec(), || {
Box::new(Category::new()) as Box<dyn Interactable>
});
}
pub async fn register_interactable(name: String, interactable: InteractableFactory) {
INTERACTABLE_REGISTRY
.lock()
.await
.insert(name.to_string(), interactable)
.unwrap();
}
pub async fn get_interactable(name: &str) -> Box<dyn Interactable> {
INTERACTABLE_REGISTRY.lock().await.get(name).unwrap()()
}
pub async fn save(interactable: &Arc<Box<dyn Interactable>>) {
let mut json_object: JsonValue = interactable.to_json().clone();
json_object["codec"] = JsonValue::String(interactable.get_codec());
file_util::save_file(
&format!(
"communities/{}/interactables/{}",
interactable.get_community().get_name(),
interactable.get_path()
),
&format!("{}.json", interactable.get_name()),
&json_object.to_string(),
);
}
pub async fn load(
c: Arc<Community>,
path: String,
name: String,
) -> Box<dyn Interactable + 'static> {
let s = file_util::load_file(
&format!("communities/{}/interactables/{}", c.get_name(), path),
&format!("{}.json", name),
);
let json_object: JsonValue = json::parse(&s).unwrap();
let codec: String = json_object["codec"].as_str().unwrap().to_string();
let mut interactable = get_interactable(&codec).await;
interactable.load(c, path, name, &json_object);
interactable
}

View file

@ -0,0 +1,225 @@
use crate::{
communities::{community::Community, interactables::interactable::Interactable},
data::communication::{CommunicationType, CommunicationValue, DataTypes},
gui::log_panel::log_message,
util::file_util::{get_children, load_file, save_file},
};
use aes_gcm::aead::Payload;
use axum::Json;
use json::{JsonValue, array, object};
use std::any::Any;
use std::fs::{self, File};
use std::sync::Arc;
use uuid::Uuid;
pub struct TextChat {
name: String,
path: String,
community: Arc<Community>,
}
impl TextChat {
pub fn new() -> TextChat {
TextChat {
name: String::new(),
path: String::new(),
community: Arc::new(Community::new()),
}
}
pub fn add_message(&self, send_time: u128, sender: Uuid, message: &str) {
let user_dir = &format!(
"communities/{}/interactables/{}/{}",
self.get_community().get_name(),
self.get_path(),
self.get_name()
);
if let Err(e) = fs::create_dir_all(user_dir) {
log_message(format!("Failed to create chat directory: {}", e));
return;
}
let mut chunk_index = 0;
let mut message_chunk = array![];
// find latest chunk not full (max 800 msgs)
loop {
let file_name = format!("msgs_{}.json", chunk_index);
let file_content = load_file(&user_dir, &file_name);
if !file_content.is_empty() {
if let Ok(current_chunk) = json::parse(&file_content) {
if current_chunk.is_array() && current_chunk.len() < 800 {
message_chunk = current_chunk;
break;
}
} else {
log_message(format!("Failed to parse existing JSON file: {}", file_name));
}
} else {
// New file, use empty array
break;
}
chunk_index += 1;
if chunk_index > 1000 {
log_message(format!("Too many message chunks. Aborting add."));
return;
}
}
let json_obj = object! {
"timestamp" => send_time as i64,
"content" => message,
"sender" => sender.to_string(),
};
if let Err(e) = message_chunk.push(json_obj) {
log_message(format!("Failed to push new message into JSON array: {}", e));
return;
}
let file_name = format!("msgs_{}.json", chunk_index);
log_message(format!("Saving message to {}/{}", user_dir, file_name));
save_file(&user_dir, &file_name, &message_chunk.dump());
}
pub fn get_messages(&self, loaded_messages: i64, amount: i64) -> JsonValue {
let mut messages = array![];
let mut latest_chunk_index: i32 = -1;
let files = get_children(&format!(
"communities/{}/interactables/{}/{}",
self.get_community().get_name(),
self.get_path(),
self.get_name()
));
for entry in files {
if let Some(num) = {
entry
.strip_prefix("msgs_")
.and_then(|s| s.strip_suffix(".json"))
} {
if let Ok(index) = num.parse::<i32>() {
if index > latest_chunk_index {
latest_chunk_index = index;
}
}
}
}
if latest_chunk_index == -1 {
return messages;
}
let mut to_skip = loaded_messages;
let mut needed = amount;
for chunk_index in (0..=latest_chunk_index).rev() {
if needed == 0 {
break;
}
let file_name = format!("msgs_{}.json", chunk_index);
let file_content = load_file(
&format!(
"communities/{}/interactables/{}/{}",
self.get_community().get_name(),
self.get_path(),
self.get_name()
),
&file_name,
);
if file_content.is_empty() {
continue;
}
if let Ok(chunk) = json::parse(&file_content) {
for i in (0..chunk.len()).rev() {
if needed == 0 {
break;
}
if to_skip > 0 {
to_skip -= 1;
continue;
}
messages.push(chunk[i].clone()).unwrap();
needed -= 1;
}
}
}
messages
}
}
impl Interactable for TextChat {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn get_codec(&self) -> String {
"text".to_string()
}
fn set_name(&mut self, name: String) {
self.name = name;
}
fn set_path(&mut self, path: String) {
self.path = path;
}
fn get_community(&self) -> &Arc<Community> {
&self.community
}
fn set_community(&mut self, community: Arc<Community>) {
self.community = community;
}
fn get_name(&self) -> &String {
&self.name
}
fn get_path(&self) -> &String {
&self.path
}
fn get_total_path(&self) -> String {
String::new() + &self.path + "/" + &self.name
}
fn get_data(&self) -> JsonValue {
JsonValue::new_object()
}
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
let payload = cv.get_data(DataTypes::payload).unwrap();
if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "get_messages" {
let amount = payload["amount"].as_i64().unwrap();
let loaded_messages = payload["loaded_messages"].as_i64().unwrap();
let messages = self.get_messages(loaded_messages, amount);
let mut payload = JsonValue::new_object();
payload["messages"] = messages;
return CommunicationValue::new(CommunicationType::function)
.with_id(cv.get_id())
.add_data_str(DataTypes::name, self.name.clone())
.add_data_str(DataTypes::path, self.path.clone())
.add_data_str(DataTypes::result, "message_chunk".to_string())
.add_data(DataTypes::payload, payload);
}
if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "send_message" {
let message = payload["message"].as_str().unwrap();
let milliseconds_timestamp: u128 = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis();
self.add_message(milliseconds_timestamp, cv.get_sender().unwrap(), message);
return CommunicationValue::new(CommunicationType::function)
.with_id(cv.get_id())
.add_data_str(DataTypes::name, self.name.clone())
.add_data_str(DataTypes::path, self.path.clone())
.add_data_str(DataTypes::result, "message_received".to_string())
.add_data(DataTypes::payload, JsonValue::new_object());
}
CommunicationValue::new(CommunicationType::error).with_id(cv.get_id())
}
fn to_json(&self) -> JsonValue {
let mut v = JsonValue::new_object();
v
}
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
self.community = community;
self.name = name;
self.path = path;
}
}

View file

@ -0,0 +1,83 @@
use crate::{
communities::{community::Community, interactables::interactable::Interactable},
data::communication::{CommunicationType, CommunicationValue},
};
use json::JsonValue;
use std::any::Any;
use std::sync::Arc;
use uuid::Uuid;
pub enum CallUserState {
Active,
Muted,
Deafed,
}
pub struct CallUser {
user_id: Uuid,
user_state: CallUserState,
streaming: bool,
}
pub struct VoiceChat {
name: String,
path: String,
community: Arc<Community>,
users: Vec<CallUser>,
}
impl VoiceChat {
pub fn new() -> VoiceChat {
VoiceChat {
name: String::new(),
path: String::new(),
community: Arc::new(Community::new()),
users: Vec::new(),
}
}
}
impl Interactable for VoiceChat {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn get_codec(&self) -> String {
"voice".to_string()
}
fn set_name(&mut self, name: String) {
self.name = name;
}
fn set_path(&mut self, path: String) {
self.path = path;
}
fn get_community(&self) -> &Arc<Community> {
&self.community
}
fn set_community(&mut self, community: Arc<Community>) {
self.community = community;
}
fn get_name(&self) -> &String {
&self.name
}
fn get_path(&self) -> &String {
&self.path
}
fn get_total_path(&self) -> String {
String::new() + &self.path + "/" + &self.name
}
fn get_data(&self) -> JsonValue {
JsonValue::new_object()
}
fn run_function(&self, cv: CommunicationValue) -> CommunicationValue {
CommunicationValue::new(CommunicationType::error)
}
fn to_json(&self) -> JsonValue {
let mut v = JsonValue::new_object();
v
}
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
self.community = community;
self.name = name;
self.path = path;
}
}

11
src/communities/mod.rs Normal file
View file

@ -0,0 +1,11 @@
pub mod community_manager;
pub mod community_socket;
pub mod interactables {
pub mod category;
pub mod interactable;
pub mod registry;
pub mod text_chat;
pub mod voice_chat;
}
pub mod community;
pub mod community_connection;

View file

@ -1,9 +1,6 @@
use axum::Json;
use json::number::Number;
use json::{Array, JsonValue, object, parse, stringify};
use std::any::Any;
use json::{Array, JsonValue, object, parse};
use std::collections::HashMap;
use std::env::VarsOs;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
use uuid::Uuid;
@ -157,6 +154,9 @@ impl DataTypes {
#[derive(PartialEq, Clone, Debug)]
pub enum CommunicationType {
error,
error_invalid_user_id,
error_not_found,
error_invalid_challenge,
success,
message,
message_send,
@ -213,7 +213,7 @@ impl CommunicationType {
"messageotheriota" => CommunicationType::message_other_iota,
"messagechunk" => CommunicationType::message_chunk,
"messagesget" => CommunicationType::messages_get,
"message_send" => CommunicationType::message_send,
"messagesend" => CommunicationType::message_send,
"changeconfirm" => CommunicationType::change_confirm,
"confirmreceive" => CommunicationType::confirm_receive,
"confirmread" => CommunicationType::confirm_read,
@ -259,8 +259,8 @@ impl CommunicationType {
pub struct CommunicationValue {
pub id: Uuid,
pub comm_type: CommunicationType,
pub sender: Uuid,
pub receiver: Uuid,
pub sender: Option<Uuid>,
pub receiver: Option<Uuid>,
pub data: HashMap<DataTypes, JsonValue>,
}
@ -269,8 +269,8 @@ impl CommunicationValue {
Self {
id: Uuid::new_v4(),
comm_type,
sender: Uuid::new_v4(),
receiver: Uuid::new_v4(),
sender: None,
receiver: None,
data: HashMap::new(),
}
}
@ -282,17 +282,17 @@ impl CommunicationValue {
self.id.clone()
}
pub fn with_sender(mut self, sender: Uuid) -> Self {
self.sender = sender;
self.sender = Some(sender);
self
}
pub fn get_sender(&self) -> Uuid {
pub fn get_sender(&self) -> Option<Uuid> {
self.sender.clone()
}
pub fn with_receiver(mut self, receiver: Uuid) -> Self {
self.receiver = receiver;
self.receiver = Some(receiver);
self
}
pub fn get_receiver(&self) -> Uuid {
pub fn get_receiver(&self) -> Option<Uuid> {
self.receiver.clone()
}
pub fn add_data_num(mut self, key: DataTypes, value: Number) -> Self {
@ -323,29 +323,59 @@ impl CommunicationValue {
for (k, v) in &self.data {
jdata[&format!("{:?}", k)] = JsonValue::from(v.clone());
}
if self.sender.is_some() && self.receiver.is_some() {
object! {
id: self.id.to_string(),
type: format!("{:?}", self.comm_type),
sender: self.sender.to_string(),
receiver: self.receiver.to_string(),
sender: self.sender.unwrap().to_string(),
receiver: self.receiver.unwrap().to_string(),
data: jdata
}
} else if self.sender.is_some() {
object! {
id: self.id.to_string(),
type: format!("{:?}", self.comm_type),
sender: self.sender.unwrap().to_string(),
data: jdata
}
} else if self.receiver.is_some() {
object! {
id: self.id.to_string(),
type: format!("{:?}", self.comm_type),
receiver: self.receiver.unwrap().to_string(),
data: jdata
}
} else {
object! {
id: self.id.to_string(),
type: format!("{:?}", self.comm_type),
data: jdata
}
}
}
pub fn from_json(json_str: &str) -> Self {
let parsed = parse(json_str).unwrap();
let comm_type = CommunicationType::parse(parsed["type"].to_string());
let sender: Uuid = parsed["sender"]
let mut sender: Option<Uuid> = None;
if parsed.has_key("sender") {
sender = Some(
parsed["sender"]
.as_str()
.and_then(|s| Uuid::parse_str(s).ok())
.unwrap_or(Uuid::new_v4());
let receiver: Uuid = parsed["receiver"]
.unwrap_or(Uuid::new_v4()),
);
}
let mut receiver: Option<Uuid> = None;
if parsed.has_key("receiver") {
receiver = Some(
parsed["receiver"]
.as_str()
.and_then(|s| Uuid::parse_str(s).ok())
.unwrap_or(Uuid::new_v4());
.unwrap_or(Uuid::new_v4()),
);
}
let uuid = Uuid::parse_str(parsed["id"].as_str().unwrap_or("")).unwrap_or(Uuid::new_v4());
let mut data = HashMap::new();
@ -386,17 +416,22 @@ impl CommunicationValue {
.unwrap()
.as_millis() as i64;
let cv = CommunicationValue::new(CommunicationType::message_other_iota)
let sender = original.get_sender();
CommunicationValue::new(CommunicationType::message_other_iota)
.with_id(original.get_id())
.with_receiver(receiver.unwrap())
.add_data( DataTypes::receiver_id,
JsonValue::String(receiver.unwrap().to_string()),
)
.with_sender(sender.unwrap())
.add_data(DataTypes::send_time, JsonValue::String(now_ms.to_string()))
.add_data(
DataTypes::sender_id,
JsonValue::String(sender.unwrap().to_string()),
)
.add_data(
DataTypes::content,
JsonValue::String(original.get_data(DataTypes::content).unwrap().to_string()),
);
// include sender_id if the original had one
let sender = original.get_sender();
cv.add_data(DataTypes::sender_id, JsonValue::String(sender.to_string()))
)
}
}

View file

@ -29,7 +29,18 @@ pub fn create_languages() {
general_texts.insert("user_id", "USER ID: {}");
general_texts.insert("user_ids", "USER IDS: {}");
general_texts.insert("setup_completed", "Launched");
general_texts.insert(
"community_active",
"Communities active on ws://0.0.0.0:{}/community/...",
);
general_texts.insert(
"community_start_error",
"Failed to start community socket on port {}!",
);
general_texts.insert(
"community_start_error_admin",
"Failed to start community socket on port {}! Run with admin privileges",
);
// DEBUG
debug_messages.insert("", "");
save_file(

View file

@ -1,5 +1,5 @@
use crate::util::file_util::{self, load_file};
use json::{JsonValue, parse};
use crate::util::file_util::{self};
use json::parse;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Mutex;

View file

@ -1,13 +1,12 @@
use json::JsonValue::String;
use json::{self};
use json::{self, JsonValue::String};
use std::sync::Arc;
use std::sync::LazyLock;
use std::sync::Mutex;
use sysinfo::User;
use tokio::time::{Duration, sleep};
use uuid::Uuid;
mod auth;
mod communities;
mod data;
mod eula;
mod gui;
@ -16,13 +15,16 @@ mod omikron;
mod users;
mod util;
use crate::communities::interactables::registry;
use crate::communities::{community_manager, community_socket};
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::gui::app_state::AppState;
use crate::gui::log_panel::{log_message, log_message_trans};
use crate::gui::{log_panel, ratatui_interface};
use crate::langu::language_creator;
use crate::langu::language_manager::format;
use crate::omikron::omikron_connection::OmikronConnection;
use crate::users::user_manager::UserManager;
use crate::users::user_manager;
use crate::util::config_util::CONFIG;
pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
@ -50,9 +52,9 @@ async fn main() {
}
// USER MANAGEMENT
UserManager::load_users().await;
user_manager::load_users().await;
let mut sb = "".to_string();
for up in UserManager::get_users() {
for up in user_manager::get_users() {
sb = sb + "," + &up.user_id.to_string().as_str();
}
@ -72,6 +74,32 @@ async fn main() {
.unwrap()
));
log_message(format!("User IDS: {}", sb));
// COMMUNITY MANAGEMENT
registry::load_interactables().await;
community_manager::load_communities().await;
community_manager::save_communities().await;
let mut sb1 = "".to_string();
for cp in community_manager::get_communities().await {
sb1 = sb1 + "," + &cp.get_name().to_string().as_str();
}
if !sb1.is_empty() {
sb1.remove(0);
sb1 = sb1 + ",";
}
log_message(format!("Community IDS: {}", sb1));
let port = CONFIG.lock().unwrap().get_port();
if community_socket::start(port).await {
log_message(format("community_active", &[&port.to_string()]));
} else {
if port < 1024 {
log_message(format("community_start_error_admin", &[&port.to_string()]));
} else {
log_message(format("community_start_error", &[&port.to_string()]));
}
}
loop {
let omikron: OmikronConnection = OmikronConnection::new();
omikron.connect().await;

View file

@ -1,9 +1,8 @@
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::gui::log_panel::log_message_trans;
use crate::gui::log_panel::{log_cv, log_message};
use crate::users::contact::Contact;
use crate::users::user_community_util::UserCommunityUtil;
use crate::util::chat_files::ChatFiles;
use crate::util::chat_files;
use crate::util::chats_util::{get_user, get_users, mod_user};
use futures_util::{SinkExt, StreamExt};
use json::JsonValue;
@ -33,6 +32,7 @@ pub struct OmikronConnection {
>,
waiting: Arc<Mutex<HashMap<Uuid, Box<dyn Fn(CommunicationValue) + Send + Sync>>>>, // waiting for responses
pingpong: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>, // ping-pong handler
pub last_ping: Arc<Mutex<i64>>,
pub message_send_times: Arc<Mutex<HashMap<Uuid, Instant>>>,
pub is_connected: Arc<Mutex<bool>>,
}
@ -43,6 +43,7 @@ impl OmikronConnection {
writer: Arc::new(Mutex::new(None)),
waiting: Arc::new(Mutex::new(HashMap::new())),
pingpong: Arc::new(Mutex::new(None)),
last_ping: Arc::new(Mutex::new(-1)),
message_send_times: Arc::new(Mutex::new(HashMap::new())),
is_connected: Arc::new(Mutex::new(false)),
}
@ -112,14 +113,14 @@ impl OmikronConnection {
// ************************************************ //
log_cv(&cv);
if cv.is_type(CommunicationType::message_other_iota) {
let sender_id = &cv.get_sender();
let receiver_id = &cv.get_receiver();
let sender_id = &cv.get_sender().unwrap();
let receiver_id = &cv.get_receiver().unwrap();
ChatFiles::add_message(
chat_files::add_message(
cv.get_data(DataTypes::send_time)
.unwrap()
.as_i64()
.unwrap_or(-1),
.unwrap_or(0) as u128,
false,
*receiver_id,
*sender_id,
@ -127,7 +128,7 @@ impl OmikronConnection {
);
let response = CommunicationValue::new(CommunicationType::message_live)
.with_id(cv.get_id())
.with_receiver(cv.get_receiver())
.with_receiver(cv.get_receiver().unwrap())
.add_data(
DataTypes::send_time,
cv.get_data(DataTypes::send_time).unwrap().clone(),
@ -138,7 +139,7 @@ impl OmikronConnection {
)
.add_data(
DataTypes::sender_id,
JsonValue::String(cv.get_sender().clone().to_string()),
JsonValue::String(cv.get_sender().unwrap().to_string()),
);
Self::send_message_static(
&writer.clone(),
@ -161,16 +162,16 @@ impl OmikronConnection {
],
"content": "<enc markdown>"
*/
let my_id = cv.get_sender();
let my_id = cv.get_sender().unwrap();
let other_id = Uuid::from_str(
&*cv.get_data(DataTypes::receiver_id).unwrap().to_string(),
)
.unwrap();
ChatFiles::add_message(
chat_files::add_message(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as i64,
.as_millis() as u128,
true,
my_id,
other_id,
@ -189,7 +190,7 @@ impl OmikronConnection {
}
if cv.is_type(CommunicationType::messages_get) {
let my_id = cv.get_sender();
let my_id = cv.get_sender().unwrap();
let partner_id = Uuid::from_str(
&*cv.get_data(DataTypes::user_id).unwrap().to_string(),
)
@ -207,7 +208,7 @@ impl OmikronConnection {
.parse::<i64>()
.unwrap_or(0);
let messages =
ChatFiles::get_messages(my_id, partner_id, offset, amount);
chat_files::get_messages(my_id, partner_id, offset, amount);
let resp = CommunicationValue::new(CommunicationType::messages_get)
.with_id(cv.get_id())
.with_receiver(my_id)
@ -219,7 +220,7 @@ impl OmikronConnection {
}
if cv.is_type(CommunicationType::get_chats) {
let user_id = cv.get_sender();
let user_id = cv.get_sender().unwrap();
let users = get_users(user_id);
let resp = CommunicationValue::new(CommunicationType::get_chats)
.with_id(cv.get_id())
@ -231,7 +232,7 @@ impl OmikronConnection {
}
if cv.is_type(CommunicationType::add_chat) {
let user_id = cv.get_sender();
let user_id = cv.get_sender().unwrap();
let other_id = Uuid::from_str(
&*cv.get_data(DataTypes::user_id).unwrap().to_string(),
)
@ -255,7 +256,7 @@ impl OmikronConnection {
if cv.is_type(CommunicationType::add_community) {
UserCommunityUtil::add_community(
cv.get_sender(),
cv.get_sender().unwrap(),
cv.get_data(DataTypes::community_address)
.unwrap()
.to_string(),
@ -264,7 +265,7 @@ impl OmikronConnection {
);
let resp = CommunicationValue::new(CommunicationType::add_community)
.with_id(cv.get_id())
.with_receiver(cv.get_sender());
.with_receiver(cv.get_sender().unwrap());
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
.await;
continue;
@ -273,11 +274,11 @@ impl OmikronConnection {
if cv.is_type(CommunicationType::get_communities) {
let resp = CommunicationValue::new(CommunicationType::get_communities)
.with_id(cv.get_id())
.with_receiver(cv.get_sender())
.with_receiver(cv.get_sender().unwrap())
.add_array(
DataTypes::communities,
UserCommunityUtil::get_communities(cv.get_sender()),
); // needs UserCommunityUtil
UserCommunityUtil::get_communities(cv.get_sender().unwrap()),
);
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
.await;
continue;
@ -285,14 +286,14 @@ impl OmikronConnection {
if cv.is_type(CommunicationType::remove_community) {
UserCommunityUtil::remove_community(
cv.get_sender(),
cv.get_sender().unwrap(),
cv.get_data(DataTypes::community_address)
.unwrap()
.to_string(),
); // needs UserCommunityUtil
let resp = CommunicationValue::new(CommunicationType::remove_community)
.with_id(cv.get_id())
.with_receiver(cv.get_sender());
.with_receiver(cv.get_sender().unwrap());
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
.await;
continue;

View file

@ -1,7 +1,9 @@
use crate::APP_STATE;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::omikron::omikron_connection::OmikronConnection;
use json::JsonValue;
use json::number::Number;
use std::sync::Arc;
use tokio::time::Instant;
use uuid::Uuid;
@ -17,7 +19,10 @@ impl OmikronConnection {
pub async fn send_ping_message(&self, uuid: Uuid) {
let ping_message = CommunicationValue::new(CommunicationType::ping)
.with_id(uuid)
.add_data_num(DataTypes::last_ping, Number::from(2))
.add_data_num(
DataTypes::last_ping,
Number::from(*self.last_ping.lock().await),
)
.to_json()
.to_string();
@ -33,11 +38,13 @@ impl OmikronConnection {
};
if let Some(send_time) = send_time_opt {
let ping = Instant::now().duration_since(send_time).as_millis() as f64;
let ping = Instant::now().duration_since(send_time).as_millis() as i64;
self.message_send_times.lock().await.remove(&id);
*self.last_ping.lock().await = ping as i64;
if log {
APP_STATE.lock().unwrap().push_ping_val(ping);
APP_STATE.lock().unwrap().push_ping_val(ping as f64);
}
}
}

View file

@ -3,32 +3,23 @@ use crate::users::user_profile::UserProfile;
use crate::users::user_profile_full::UserProfileFull;
use crate::util::config_util::CONFIG;
use crate::util::file_util::{load_file, save_file};
use base64::{Engine as _, engine::general_purpose};
use der::DerOrd;
use base64::{Engine as _, engine::general_purpose::STANDARD};
use hex;
use json::JsonValue;
use once_cell::sync::Lazy;
use pkcs8::EncodePublicKey;
use pkcs8::spki::SubjectPublicKeyInfoOwned;
use pkcs8::{ObjectIdentifier, PrivateKeyInfo, SubjectPublicKeyInfo, der::Encode};
use rand::Rng;
use rand_core::OsRng;
use rand_core::RngCore;
use rustls::pki_types::SubjectPublicKeyInfoDer;
use sha2::{Digest, Sha256};
use std::io;
use std::sync::Mutex;
use uuid::Uuid;
use x448::{PublicKey, Secret};
use x509::AlgorithmIdentifier;
pub struct UserManager;
static USERS: Lazy<Mutex<Vec<UserProfile>>> = Lazy::new(|| Mutex::new(Vec::new()));
static UNIQUE: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
impl UserManager {
pub async fn create_user(username: &str) -> Option<UserProfileFull> {
pub async fn create_user(username: &str) -> Option<UserProfileFull> {
let user_id = auth_connector::get_register().await.unwrap();
let mut buf = [0u8; 56];
let mut rng = OsRng;
@ -37,76 +28,67 @@ impl UserManager {
let public_key = PublicKey::from(&private_key);
let mut hasher = Sha256::new();
hasher.update(
&general_purpose::STANDARD
.encode(&private_key.as_bytes())
.as_bytes(),
);
hasher.update(&STANDARD.encode(&private_key.as_bytes()).as_bytes());
let result = hasher.finalize();
let private_key_hash = hex::encode(result);
let mut bytes = [0u8; 192];
OsRng.fill(bytes.as_mut());
let reset_token = general_purpose::STANDARD.encode(&bytes);
let reset_token = STANDARD.encode(&bytes);
let up = UserProfile::new(
user_id,
username.to_string(),
None,
general_purpose::STANDARD.encode(&public_key.as_bytes()),
STANDARD.encode(&public_key.as_bytes()),
private_key_hash,
reset_token,
);
let up_full = UserProfileFull {
user_profile: up.clone(),
private_key: general_purpose::STANDARD.encode(&private_key.as_bytes()),
private_key: STANDARD.encode(&private_key.as_bytes()),
};
auth_connector::complete_register(&up, &CONFIG.lock().unwrap().get_iota_id().to_string())
.await;
auth_connector::complete_register(&up, &CONFIG.lock().unwrap().get_iota_id().to_string()).await;
save_file(
"",
&format!("{}.tu", username),
&format!(
"{}::{}",
user_id,
general_purpose::STANDARD.encode(&private_key.as_bytes())
),
&format!("{}::{}", user_id, STANDARD.encode(&private_key.as_bytes())),
);
USERS.lock().unwrap().push(up);
Self::save_users().ok();
save_users().ok();
Some(up_full)
}
}
pub fn get_user(user_id: Uuid) -> Option<UserProfile> {
pub fn get_user(user_id: Uuid) -> Option<UserProfile> {
USERS
.lock()
.unwrap()
.iter()
.cloned()
.find(|u| u.user_id == user_id)
}
}
pub fn get_users() -> Vec<UserProfile> {
pub fn get_users() -> Vec<UserProfile> {
USERS.lock().unwrap().clone()
}
}
pub fn add_user(up: UserProfile) {
pub fn add_user(up: UserProfile) {
let mut users = USERS.lock().unwrap();
users.retain(|u| u.user_id != up.user_id);
users.push(up);
*UNIQUE.lock().unwrap() = true;
}
}
pub fn remove_user(user_id: Uuid) {
pub fn remove_user(user_id: Uuid) {
let mut users = USERS.lock().unwrap();
users.retain(|u| u.user_id != user_id);
*UNIQUE.lock().unwrap() = true;
}
}
pub fn save_users() -> io::Result<()> {
pub fn save_users() -> io::Result<()> {
*UNIQUE.lock().unwrap() = false;
let users = USERS.lock().unwrap();
let arr: Vec<JsonValue> = users.iter().map(|u| u.to_json()).collect();
@ -114,16 +96,16 @@ impl UserManager {
save_file("", "users.json", &json_str);
Ok(())
}
}
pub async fn load_users() -> io::Result<()> {
pub async fn load_users() -> io::Result<()> {
let content = load_file("", "users.json");
if content.trim().is_empty() {
return Ok(());
}
let parsed = json::parse(&content)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
let parsed =
json::parse(&content).map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
if let JsonValue::Array(arr) = parsed {
let mut users = USERS.lock().unwrap();
for j in arr.iter() {
@ -133,12 +115,11 @@ impl UserManager {
}
}
if *UNIQUE.lock().unwrap() {
Self::save_users().ok();
save_users().ok();
}
Ok(())
}
}
pub fn set_unique(val: bool) {
pub fn set_unique(val: bool) {
*UNIQUE.lock().unwrap() = val;
}
}

View file

@ -1,15 +1,10 @@
use crate::auth::auth_connector;
use crate::gui::log_panel::log_message;
use crate::users::user_manager::UserManager;
use crate::users::user_manager;
use base64::{Engine as _, engine::general_purpose};
use json::{JsonValue, object, stringify};
use json::{JsonValue, object};
use rand::Rng;
use rand::rngs::OsRng;
use std::collections::HashMap;
use std::fs;
use std::io;
use std::path::Path;
use std::sync::Mutex;
use uuid::Uuid;
// --- UserProfile ---
@ -81,7 +76,7 @@ impl UserProfile {
{
if auth_connector::migrate_user(&mut up).await {
log_message(format!("[INFO] Migration triggered for {}", up.username));
UserManager::set_unique(true);
user_manager::set_unique(true);
}
}

View file

@ -1,14 +1,10 @@
use crate::util::file_util::{get_children, load_file, save_file};
use json::{self, JsonValue, array, object};
use sha2::digest::typenum::Add1;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::fs::{self};
use std::path::Path;
use sys_info::loadavg;
use uuid::Uuid;
use crate::gui::log_panel::log_message;
use crate::langu::language_manager::format;
pub enum MessageState {
Read,
@ -28,16 +24,13 @@ impl MessageState {
}
}
pub struct ChatFiles;
impl ChatFiles {
pub fn add_message(
send_time: i64,
pub fn add_message(
send_time: u128,
storage_owner_is_sender: bool,
storage_owner: Uuid,
external_user: Uuid,
message: &str,
) {
) {
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
if let Err(e) = fs::create_dir_all(&user_dir) {
@ -75,9 +68,9 @@ impl ChatFiles {
}
let json_obj = object! {
"message_time" => send_time,
"message_content" => message,
"sender_is_me" => storage_owner_is_sender,
"timestamp" => send_time as i64,
"content" => message,
"sent_by_self" => storage_owner_is_sender,
"message_state" => MessageState::Sending.as_str()
};
@ -89,13 +82,13 @@ impl ChatFiles {
let file_name = format!("msgs_{}.json", chunk_index);
log_message(format!("Saving message to {}/{}", user_dir, file_name));
save_file(&user_dir, &file_name, &message_chunk.dump());
}
pub fn change_message_state(
}
pub fn change_message_state(
storage_owner: Uuid,
external_user: Uuid,
timestamp: i64,
new_state: MessageState,
) -> std::io::Result<()> {
) -> std::io::Result<()> {
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
let path = Path::new(&user_dir);
@ -133,14 +126,14 @@ impl ChatFiles {
}
}
Ok(())
}
}
pub fn get_messages(
pub fn get_messages(
storage_owner: Uuid,
external_user: Uuid,
loaded_messages: i64,
amount: i64,
) -> JsonValue {
) -> JsonValue {
let mut messages = array![];
let mut latest_chunk_index: i32 = -1;
@ -195,5 +188,4 @@ impl ChatFiles {
}
messages
}
}

View file

@ -1,7 +1,4 @@
use axum::Json;
use json::{self, JsonValue, array};
use std::path::Path;
use std::string::String;
use uuid::Uuid;
use crate::users::contact::Contact;

View file

@ -1,8 +1,6 @@
use crate::util::file_util::{load_file, save_file};
use json::JsonValue;
use once_cell::sync::Lazy;
use std::fs::{self, File};
use std::path::Path;
use std::sync::Mutex;
use uuid::Uuid;
@ -36,6 +34,10 @@ impl ConfigUtil {
.unwrap_or_default()
}
pub fn get_port(&self) -> u16 {
self.config["port"].as_u16().unwrap_or(1984)
}
pub fn change(&mut self, key: &str, value: Uuid) {
self.config[key] = JsonValue::String(value.to_string());
self.unique = true;

View file

@ -1,10 +1,7 @@
use std::ffi::OsStr;
use std::fmt::Write as FmtWrite;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process;
use std::time::SystemTime;
use sysinfo::System;
use uuid::Uuid;
use walkdir::WalkDir;