Creation
This commit is contained in:
parent
5149a2d7d1
commit
8a93f91e69
19 changed files with 7090 additions and 1 deletions
454
src/data/communication.rs
Normal file
454
src/data/communication.rs
Normal file
|
|
@ -0,0 +1,454 @@
|
|||
use json::number::Number;
|
||||
use json::{Array, JsonValue, object, parse};
|
||||
use std::collections::HashMap;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Eq, Hash, PartialEq, Clone, Debug)]
|
||||
#[allow(non_camel_case_types, dead_code)]
|
||||
pub enum DataTypes {
|
||||
error_type,
|
||||
accepted_ids,
|
||||
uuid,
|
||||
settings,
|
||||
settings_name,
|
||||
chat_partner_id,
|
||||
iota_id,
|
||||
user_id,
|
||||
user_ids,
|
||||
user_state,
|
||||
user_states,
|
||||
user_pings,
|
||||
call_state,
|
||||
screen_share,
|
||||
private_key_hash,
|
||||
accepted,
|
||||
accepted_profiles,
|
||||
denied_profiles,
|
||||
content,
|
||||
messages,
|
||||
send_time,
|
||||
get_time,
|
||||
get_variant,
|
||||
shared_secret_own,
|
||||
shared_secret_other,
|
||||
shared_secret_sign,
|
||||
shared_secret,
|
||||
call_id,
|
||||
call_token,
|
||||
start_date,
|
||||
end_date,
|
||||
receiver_id,
|
||||
sender_id,
|
||||
signature,
|
||||
signed,
|
||||
message,
|
||||
last_ping,
|
||||
ping_iota,
|
||||
ping_clients,
|
||||
matches,
|
||||
omikron,
|
||||
offset,
|
||||
amount,
|
||||
position,
|
||||
name,
|
||||
path,
|
||||
codec,
|
||||
function,
|
||||
payload,
|
||||
result,
|
||||
interactables,
|
||||
want_to_watch,
|
||||
watcher,
|
||||
created_at,
|
||||
username,
|
||||
display,
|
||||
avatar,
|
||||
about,
|
||||
status,
|
||||
public_key,
|
||||
sub_level,
|
||||
sub_end,
|
||||
community_address,
|
||||
challenge,
|
||||
community_title,
|
||||
communities,
|
||||
|
||||
user,
|
||||
}
|
||||
|
||||
impl DataTypes {
|
||||
pub fn parse(p0: String) -> DataTypes {
|
||||
let normalized = p0.to_lowercase().replace('_', "");
|
||||
|
||||
match normalized.as_str() {
|
||||
"errortype" => DataTypes::error_type,
|
||||
"chatpartnerid" => DataTypes::chat_partner_id,
|
||||
"uuid" => DataTypes::uuid,
|
||||
"settings" => DataTypes::settings,
|
||||
"settingsname" => DataTypes::settings_name,
|
||||
"iotaid" => DataTypes::iota_id,
|
||||
"userid" => DataTypes::user_id,
|
||||
"userids" => DataTypes::user_ids,
|
||||
"userstate" => DataTypes::user_state,
|
||||
"userstates" => DataTypes::user_states,
|
||||
"userpings" => DataTypes::user_pings,
|
||||
"callstate" => DataTypes::call_state,
|
||||
"screenshare" => DataTypes::screen_share,
|
||||
"privatekeyhash" => DataTypes::private_key_hash,
|
||||
"accepted" => DataTypes::accepted,
|
||||
"acceptedprofiles" => DataTypes::accepted_profiles,
|
||||
"deniedprofiles" => DataTypes::denied_profiles,
|
||||
"content" => DataTypes::content,
|
||||
"messages" => DataTypes::messages,
|
||||
"sendtime" => DataTypes::send_time,
|
||||
"gettime" => DataTypes::get_time,
|
||||
"getvariant" => DataTypes::get_variant,
|
||||
"sharedsecretown" => DataTypes::shared_secret_own,
|
||||
"sharedsecretother" => DataTypes::shared_secret_other,
|
||||
"sharedsecretsign" => DataTypes::shared_secret_sign,
|
||||
"sharedsecret" => DataTypes::shared_secret,
|
||||
"callid" => DataTypes::call_id,
|
||||
"calltoken" => DataTypes::call_token,
|
||||
"startdate" => DataTypes::start_date,
|
||||
"enddate" => DataTypes::end_date,
|
||||
"receiverid" => DataTypes::receiver_id,
|
||||
"senderid" => DataTypes::sender_id,
|
||||
"signature" => DataTypes::signature,
|
||||
"signed" => DataTypes::signed,
|
||||
"message" => DataTypes::message,
|
||||
"lastping" => DataTypes::last_ping,
|
||||
"pingiota" => DataTypes::ping_iota,
|
||||
"pingclients" => DataTypes::ping_clients,
|
||||
"matches" => DataTypes::matches,
|
||||
"omikron" => DataTypes::omikron,
|
||||
"offset" => DataTypes::offset,
|
||||
"amount" => DataTypes::amount,
|
||||
"position" => DataTypes::position,
|
||||
"name" => DataTypes::name,
|
||||
"path" => DataTypes::path,
|
||||
"codec" => DataTypes::codec,
|
||||
"function" => DataTypes::function,
|
||||
"payload" => DataTypes::payload,
|
||||
"result" => DataTypes::result,
|
||||
"interactables" => DataTypes::interactables,
|
||||
"wanttowatch" => DataTypes::want_to_watch,
|
||||
"watcher" => DataTypes::watcher,
|
||||
"createdat" => DataTypes::created_at,
|
||||
"username" => DataTypes::username,
|
||||
"display" => DataTypes::display,
|
||||
"avatar" => DataTypes::avatar,
|
||||
"about" => DataTypes::about,
|
||||
"status" => DataTypes::status,
|
||||
"publickey" => DataTypes::public_key,
|
||||
"sublevel" => DataTypes::sub_level,
|
||||
"subend" => DataTypes::sub_end,
|
||||
"communityaddress" => DataTypes::community_address,
|
||||
"challenge" => DataTypes::challenge,
|
||||
"communitytitle" => DataTypes::community_title,
|
||||
"communities" => DataTypes::communities,
|
||||
|
||||
"user" => DataTypes::user,
|
||||
_ => DataTypes::error_type, // fallback if unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
#[allow(non_camel_case_types, dead_code)]
|
||||
pub enum CommunicationType {
|
||||
error,
|
||||
error_invalid_user_id,
|
||||
error_not_found,
|
||||
error_no_iota,
|
||||
error_invalid_challenge,
|
||||
error_invalid_secret,
|
||||
error_invalid_private_key,
|
||||
error_no_user_id,
|
||||
error_no_call_id,
|
||||
error_invalid_call_id,
|
||||
success,
|
||||
settings_save,
|
||||
settings_load,
|
||||
settings_list,
|
||||
message,
|
||||
message_send,
|
||||
message_live,
|
||||
message_other_iota,
|
||||
message_chunk,
|
||||
messages_get,
|
||||
change_confirm,
|
||||
confirm_receive,
|
||||
confirm_read,
|
||||
get_chats,
|
||||
get_states,
|
||||
add_community,
|
||||
remove_community,
|
||||
get_communities,
|
||||
challenge,
|
||||
challenge_response,
|
||||
register,
|
||||
register_response,
|
||||
identification,
|
||||
identification_response,
|
||||
ping,
|
||||
pong,
|
||||
add_chat,
|
||||
send_chat,
|
||||
iota_connected,
|
||||
iota_closed,
|
||||
client_changed,
|
||||
client_connected,
|
||||
client_disconnected,
|
||||
client_closed,
|
||||
public_key,
|
||||
private_key,
|
||||
webrtc_sdp,
|
||||
webrtc_ice,
|
||||
start_stream,
|
||||
end_stream,
|
||||
watch_stream,
|
||||
call_token,
|
||||
call_invite,
|
||||
end_call,
|
||||
function,
|
||||
update,
|
||||
create_user,
|
||||
}
|
||||
impl CommunicationType {
|
||||
pub fn parse(p0: String) -> CommunicationType {
|
||||
let normalized = p0.to_lowercase().replace('_', "");
|
||||
|
||||
match normalized.as_str() {
|
||||
"watchstream" => CommunicationType::watch_stream,
|
||||
"calltoken" => CommunicationType::call_token,
|
||||
"callinvite" => CommunicationType::call_invite,
|
||||
"endcall" => CommunicationType::end_call,
|
||||
"function" => CommunicationType::function,
|
||||
"update" => CommunicationType::update,
|
||||
"createuser" => CommunicationType::create_user,
|
||||
"errorinvaliduserid" => CommunicationType::error_invalid_user_id,
|
||||
"errornotfound" => CommunicationType::error_not_found,
|
||||
"errornoiota" => CommunicationType::error_no_iota,
|
||||
"errorinvalidchallenge" => CommunicationType::error_invalid_challenge,
|
||||
"errorinvalidsecret" => CommunicationType::error_invalid_secret,
|
||||
"errorinvalidprivatekey" => CommunicationType::error_invalid_private_key,
|
||||
"errornouserid" => CommunicationType::error_no_user_id,
|
||||
"errornocallid" => CommunicationType::error_no_call_id,
|
||||
"errorinvalidcallid" => CommunicationType::error_invalid_call_id,
|
||||
"success" => CommunicationType::success,
|
||||
"settingssave" => CommunicationType::settings_save,
|
||||
"settingsload" => CommunicationType::settings_load,
|
||||
"settingslist" => CommunicationType::settings_list,
|
||||
"message" => CommunicationType::message,
|
||||
"messagesend" => CommunicationType::message_send,
|
||||
"messagelive" => CommunicationType::message_live,
|
||||
"messageother_iota" => CommunicationType::message_other_iota,
|
||||
"messagechunk" => CommunicationType::message_chunk,
|
||||
"messagesget" => CommunicationType::messages_get,
|
||||
"changeconfirm" => CommunicationType::change_confirm,
|
||||
"confirmreceive" => CommunicationType::confirm_receive,
|
||||
"confirmread" => CommunicationType::confirm_read,
|
||||
"getchats" => CommunicationType::get_chats,
|
||||
"getstates" => CommunicationType::get_states,
|
||||
"addcommunity" => CommunicationType::add_community,
|
||||
"removecommunity" => CommunicationType::remove_community,
|
||||
"getcommunities" => CommunicationType::get_communities,
|
||||
"challenge" => CommunicationType::challenge,
|
||||
"challengeresponse" => CommunicationType::challenge_response,
|
||||
"register" => CommunicationType::register,
|
||||
"registerresponse" => CommunicationType::register_response,
|
||||
"identification" => CommunicationType::identification,
|
||||
"identificationresponse" => CommunicationType::identification_response,
|
||||
"ping" => CommunicationType::ping,
|
||||
"pong" => CommunicationType::pong,
|
||||
"addchat" => CommunicationType::add_chat,
|
||||
"sendchat" => CommunicationType::send_chat,
|
||||
"iotaconnected" => CommunicationType::iota_connected,
|
||||
"iotaclosed" => CommunicationType::iota_closed,
|
||||
"clientchanged" => CommunicationType::client_changed,
|
||||
"clientconnected" => CommunicationType::client_connected,
|
||||
"clientdisconnected" => CommunicationType::client_disconnected,
|
||||
"clientclosed" => CommunicationType::client_closed,
|
||||
"publickey" => CommunicationType::public_key,
|
||||
"privatekey" => CommunicationType::private_key,
|
||||
"webrtcsdp" => CommunicationType::webrtc_sdp,
|
||||
"webrtcice" => CommunicationType::webrtc_ice,
|
||||
"startstream" => CommunicationType::start_stream,
|
||||
"endstream" => CommunicationType::end_stream,
|
||||
|
||||
_ => CommunicationType::error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommunicationValue {
|
||||
pub id: Uuid,
|
||||
pub comm_type: CommunicationType,
|
||||
pub sender: i64,
|
||||
pub receiver: i64,
|
||||
pub data: HashMap<DataTypes, JsonValue>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl CommunicationValue {
|
||||
pub fn new(comm_type: CommunicationType) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
comm_type,
|
||||
sender: 0,
|
||||
receiver: 0,
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn with_id(mut self, p0: Uuid) -> Self {
|
||||
self.id = p0;
|
||||
self
|
||||
}
|
||||
pub fn get_id(&self) -> Uuid {
|
||||
self.id.clone()
|
||||
}
|
||||
pub fn with_sender(mut self, sender: i64) -> Self {
|
||||
self.sender = sender;
|
||||
self
|
||||
}
|
||||
pub fn get_sender(&self) -> i64 {
|
||||
self.sender.clone()
|
||||
}
|
||||
pub fn with_receiver(mut self, receiver: i64) -> Self {
|
||||
self.receiver = receiver;
|
||||
self
|
||||
}
|
||||
pub fn get_receiver(&self) -> i64 {
|
||||
self.receiver.clone()
|
||||
}
|
||||
pub fn add_data_num(mut self, key: DataTypes, value: Number) -> Self {
|
||||
self.data.insert(key, JsonValue::Number(value));
|
||||
self
|
||||
}
|
||||
pub fn add_data_str(mut self, key: DataTypes, value: String) -> Self {
|
||||
self.data.insert(key, JsonValue::String(value));
|
||||
self
|
||||
}
|
||||
pub fn add_data(mut self, key: DataTypes, value: JsonValue) -> Self {
|
||||
self.data.insert(key, value);
|
||||
self
|
||||
}
|
||||
pub fn add_array(mut self, key: DataTypes, value: Array) -> Self {
|
||||
self.data.insert(key, JsonValue::Array(value));
|
||||
self
|
||||
}
|
||||
pub fn get_data(&self, key: DataTypes) -> Option<&JsonValue> {
|
||||
self.data.get(&key)
|
||||
}
|
||||
|
||||
pub(crate) fn is_type(&self, p0: CommunicationType) -> bool {
|
||||
self.comm_type == p0
|
||||
}
|
||||
pub fn to_json(&self) -> JsonValue {
|
||||
let mut jdata = object! {};
|
||||
for (k, v) in &self.data {
|
||||
jdata[&format!("{:?}", k)] = JsonValue::from(v.clone());
|
||||
}
|
||||
if self.sender > 0 && self.receiver > 0 {
|
||||
object! {
|
||||
id: self.id.to_string(),
|
||||
type: format!("{:?}", self.comm_type),
|
||||
sender: self.sender,
|
||||
receiver: self.receiver,
|
||||
data: jdata
|
||||
}
|
||||
} else if self.sender > 0 {
|
||||
object! {
|
||||
id: self.id.to_string(),
|
||||
type: format!("{:?}", self.comm_type),
|
||||
sender: self.sender,
|
||||
data: jdata
|
||||
}
|
||||
} else if self.receiver > 0 {
|
||||
object! {
|
||||
id: self.id.to_string(),
|
||||
type: format!("{:?}", self.comm_type),
|
||||
receiver: self.receiver,
|
||||
data: jdata
|
||||
}
|
||||
} else {
|
||||
object! {
|
||||
id: self.id.to_string(),
|
||||
type: format!("{:?}", self.comm_type),
|
||||
data: jdata
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json(json_str: &str) -> Self {
|
||||
if let Ok(parsed) = parse(json_str) {
|
||||
let comm_type = CommunicationType::parse(parsed["type"].to_string());
|
||||
let mut sender: i64 = 0;
|
||||
if parsed.has_key("sender") {
|
||||
sender = parsed["sender"].as_i64().unwrap_or(0);
|
||||
}
|
||||
let mut receiver: i64 = 0;
|
||||
if parsed.has_key("receiver") {
|
||||
receiver = parsed["receiver"].as_i64().unwrap_or(0);
|
||||
}
|
||||
|
||||
let uuid =
|
||||
Uuid::parse_str(parsed["id"].as_str().unwrap_or("")).unwrap_or(Uuid::new_v4());
|
||||
let mut data = HashMap::new();
|
||||
if parsed["data"].is_object() {
|
||||
for (k, v) in parsed["data"].entries() {
|
||||
data.insert(DataTypes::parse(k.to_string()), v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
id: uuid,
|
||||
comm_type,
|
||||
sender,
|
||||
receiver,
|
||||
data,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
comm_type: CommunicationType::error,
|
||||
sender: 0,
|
||||
receiver: 0,
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn forward_to_other_iota(original: &mut CommunicationValue) -> CommunicationValue {
|
||||
let receiver = original
|
||||
.get_data(DataTypes::receiver_id)
|
||||
.unwrap_or(&JsonValue::Number(Number::from(0)))
|
||||
.as_i64()
|
||||
.unwrap_or(0);
|
||||
|
||||
let now_ms = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as i64;
|
||||
|
||||
let sender = original.get_sender();
|
||||
CommunicationValue::new(CommunicationType::message_other_iota)
|
||||
.with_id(original.get_id())
|
||||
.with_receiver(receiver)
|
||||
.add_data(
|
||||
DataTypes::receiver_id,
|
||||
JsonValue::Number(Number::from(receiver)),
|
||||
)
|
||||
.with_sender(sender)
|
||||
.add_data(DataTypes::send_time, JsonValue::String(now_ms.to_string()))
|
||||
.add_data(
|
||||
DataTypes::sender_id,
|
||||
JsonValue::Number(Number::from(sender)),
|
||||
)
|
||||
.add_data(
|
||||
DataTypes::content,
|
||||
JsonValue::String(original.get_data(DataTypes::content).unwrap().to_string()),
|
||||
)
|
||||
}
|
||||
}
|
||||
1
src/data/mod.rs
Normal file
1
src/data/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod communication;
|
||||
44
src/main.rs
Normal file
44
src/main.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
mod data;
|
||||
mod server;
|
||||
mod sql;
|
||||
mod util;
|
||||
|
||||
use crate::sql::sql::initialize_db;
|
||||
use crate::sql::sql::print_users;
|
||||
use crate::util::crypto_helper::load_public_key;
|
||||
use crate::util::crypto_helper::load_secret_key;
|
||||
use crate::util::logger::startup;
|
||||
use dotenv::dotenv;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::env;
|
||||
|
||||
static PRIVATE_KEY: Lazy<String> = Lazy::new(|| env::var("PRIVATE_KEY").unwrap());
|
||||
pub fn get_private_key() -> x448::Secret {
|
||||
load_secret_key(&*PRIVATE_KEY).unwrap()
|
||||
}
|
||||
static PUBLIC_KEY: Lazy<String> = Lazy::new(|| env::var("PUBLIC_KEY").unwrap());
|
||||
pub fn get_public_key() -> x448::PublicKey {
|
||||
load_public_key(&*PUBLIC_KEY).unwrap()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenv().ok();
|
||||
startup();
|
||||
log!("Started");
|
||||
log_in!("Incoming messages");
|
||||
log_out!("Outgoing messages");
|
||||
log!(" .env");
|
||||
if let Err(e) = initialize_db().await {
|
||||
log!("[FATAL] Database initialization failed: {}", e);
|
||||
log!(
|
||||
"[FATAL] Please ensure the database is running and the .env file is configured correctly."
|
||||
);
|
||||
return;
|
||||
}
|
||||
let _ = print_users().await;
|
||||
log!(" DB");
|
||||
server::server::start(9187).await;
|
||||
log!(" Server");
|
||||
loop {}
|
||||
}
|
||||
177
src/server/api.rs
Normal file
177
src/server/api.rs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
use crate::{get_public_key, log};
|
||||
use crate::{
|
||||
sql::{
|
||||
iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota},
|
||||
sql::{
|
||||
change_about, change_avatar, change_display_name, change_iota_id, change_iota_key,
|
||||
change_keys, change_status, change_username, get_by_id, get_by_username,
|
||||
get_iota_by_id, get_omikron_by_id, get_random_omikron, get_register_id,
|
||||
register_complete_iota, register_complete_user,
|
||||
},
|
||||
},
|
||||
util::crypto_helper::public_key_to_base64,
|
||||
};
|
||||
use axum::http::HeaderValue;
|
||||
use http_body_util::Full;
|
||||
use hyper::body::Bytes;
|
||||
use hyper::{HeaderMap, Response as HttpResponse, StatusCode};
|
||||
use json::JsonValue;
|
||||
|
||||
pub async fn handle(
|
||||
path: &str,
|
||||
headers: HeaderMap<HeaderValue>,
|
||||
body_string: Option<String>,
|
||||
) -> HttpResponse<Full<Bytes>> {
|
||||
let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect();
|
||||
|
||||
let body: Option<JsonValue> = if body_string.is_some() {
|
||||
if let Ok(body_json) = json::parse(&body_string.unwrap()) {
|
||||
Some(body_json)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// api/
|
||||
// get/
|
||||
// omikron/
|
||||
// id/
|
||||
// register/
|
||||
// innit/
|
||||
// complete
|
||||
log!("{}", path);
|
||||
log!("{:?} .len = {}", path_parts, path_parts.len());
|
||||
let (status, content, body_text) = if path_parts.len() >= 2 {
|
||||
match path_parts[1] {
|
||||
"get" => match path_parts[2] {
|
||||
// api/get/omikron -> any omikron
|
||||
// api/get/omikron/<id> -> omikron for id (user / iota / omikron)
|
||||
"omikron" => {
|
||||
if path_parts.len() == 3 {
|
||||
if let Ok((id, public_key, ip_address)) = get_random_omikron().await {
|
||||
(
|
||||
StatusCode::OK,
|
||||
"application/json",
|
||||
format!(
|
||||
"{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}",
|
||||
id, public_key, ip_address
|
||||
),
|
||||
)
|
||||
} else {
|
||||
not_found()
|
||||
}
|
||||
} else if path_parts.len() == 4 {
|
||||
let id = path_parts[3].parse::<i64>().unwrap_or(0);
|
||||
if id == 0 {
|
||||
not_found()
|
||||
} else if let Ok((omikron_id, public_key, ip_address)) =
|
||||
get_omikron_by_id(id).await
|
||||
{
|
||||
(
|
||||
StatusCode::OK,
|
||||
"application/json",
|
||||
format!(
|
||||
"{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}",
|
||||
omikron_id, public_key, ip_address
|
||||
),
|
||||
)
|
||||
} else if let Some(omikron_id) = get_omikron_for_iota(id).await {
|
||||
if let Ok((omikron_id, public_key, ip_address)) =
|
||||
get_omikron_by_id(omikron_id).await
|
||||
{
|
||||
(
|
||||
StatusCode::OK,
|
||||
"application/json",
|
||||
format!(
|
||||
"{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}",
|
||||
omikron_id, public_key, ip_address
|
||||
),
|
||||
)
|
||||
} else {
|
||||
not_found()
|
||||
}
|
||||
} else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) =
|
||||
get_by_id(id).await
|
||||
{
|
||||
if let Some(omikron_id) = get_omikron_for_iota(iota_id).await {
|
||||
if let Ok((omikron_id, public_key, ip_address)) =
|
||||
get_omikron_by_id(omikron_id).await
|
||||
{
|
||||
(
|
||||
StatusCode::OK,
|
||||
"application/json",
|
||||
format!(
|
||||
"{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}",
|
||||
omikron_id, public_key, ip_address
|
||||
),
|
||||
)
|
||||
} else {
|
||||
not_found()
|
||||
}
|
||||
} else {
|
||||
not_found()
|
||||
}
|
||||
} else {
|
||||
not_found()
|
||||
}
|
||||
} else {
|
||||
bad_request()
|
||||
}
|
||||
}
|
||||
// get/id/<username>
|
||||
"id" => {
|
||||
let username = path_parts[3];
|
||||
bad_request()
|
||||
}
|
||||
"public_key" => (
|
||||
StatusCode::OK,
|
||||
"application/json",
|
||||
public_key_to_base64(&get_public_key()),
|
||||
),
|
||||
_ => {
|
||||
let id = path_parts[2];
|
||||
let id: i64 = id.parse().unwrap_or(0);
|
||||
if id == 0 {
|
||||
bad_request()
|
||||
} else {
|
||||
bad_request()
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => not_found(),
|
||||
}
|
||||
} else {
|
||||
not_found()
|
||||
};
|
||||
let body = Full::new(Bytes::from(body_text.to_string()));
|
||||
HttpResponse::builder().status(status).body(body).unwrap()
|
||||
}
|
||||
pub fn bad_request() -> (StatusCode, &'static str, String) {
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"text/text",
|
||||
"400 Bad Request".to_string(),
|
||||
)
|
||||
}
|
||||
pub fn unauthorized() -> (StatusCode, &'static str, String) {
|
||||
(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"text/text",
|
||||
"401 Unauthorized".to_string(),
|
||||
)
|
||||
}
|
||||
pub fn forbidden() -> (StatusCode, &'static str, String) {
|
||||
(
|
||||
StatusCode::FORBIDDEN,
|
||||
"text/text",
|
||||
"403 Forbidden".to_string(),
|
||||
)
|
||||
}
|
||||
pub fn not_found() -> (StatusCode, &'static str, String) {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
"text/text",
|
||||
"404 Not Found".to_string(),
|
||||
)
|
||||
}
|
||||
5
src/server/mod.rs
Normal file
5
src/server/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod api;
|
||||
pub mod omikron_connection;
|
||||
pub mod omikron_manager;
|
||||
pub mod server;
|
||||
pub mod socket;
|
||||
258
src/server/omikron_connection.rs
Normal file
258
src/server/omikron_connection.rs
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
|
||||
use crate::get_public_key;
|
||||
use crate::sql::sql::get_omikron_by_id;
|
||||
use crate::util::crypto_helper::encrypt;
|
||||
use crate::{get_private_key, log_in_from, log_out_from};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use dashmap::DashMap;
|
||||
use futures::SinkExt;
|
||||
use futures::stream::SplitSink;
|
||||
use futures::stream::SplitStream;
|
||||
use hyper::upgrade::Upgraded;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use json::JsonValue;
|
||||
use rand::Rng;
|
||||
use rand::distributions::Alphanumeric;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use tungstenite::Message;
|
||||
use tungstenite::Utf8Bytes;
|
||||
use uuid::Uuid;
|
||||
use x448::PublicKey;
|
||||
|
||||
pub struct OmikronConnection {
|
||||
pub sender: Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>,
|
||||
pub receiver: Arc<RwLock<SplitStream<WebSocketStream<TokioIo<Upgraded>>>>>,
|
||||
pub omikron_id: Arc<RwLock<i64>>,
|
||||
pub pub_key: Arc<RwLock<Option<Vec<u8>>>>,
|
||||
identified: Arc<RwLock<bool>>,
|
||||
challenged: Arc<RwLock<bool>>,
|
||||
challenge: Arc<RwLock<String>>,
|
||||
pub ping: Arc<RwLock<i64>>,
|
||||
waiting_tasks: DashMap<
|
||||
Uuid,
|
||||
Box<dyn Fn(Arc<OmikronConnection>, CommunicationValue) -> bool + Send + Sync>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl OmikronConnection {
|
||||
pub fn new(
|
||||
sender: SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>,
|
||||
receiver: SplitStream<WebSocketStream<TokioIo<Upgraded>>>,
|
||||
) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
sender: Arc::new(RwLock::new(sender)),
|
||||
receiver: Arc::new(RwLock::new(receiver)),
|
||||
omikron_id: Arc::new(RwLock::new(0)),
|
||||
pub_key: Arc::new(RwLock::new(None)),
|
||||
identified: Arc::new(RwLock::new(false)),
|
||||
challenged: Arc::new(RwLock::new(false)),
|
||||
challenge: Arc::new(RwLock::new(String::new())),
|
||||
ping: Arc::new(RwLock::new(-1)),
|
||||
waiting_tasks: DashMap::new(),
|
||||
})
|
||||
}
|
||||
pub async fn send_message(&self, message: &CommunicationValue) {
|
||||
let mut sender = self.sender.write().await;
|
||||
let message_text = Message::Text(Utf8Bytes::from(message.to_json().to_string()));
|
||||
log_out_from!(*self.omikron_id.read().await, "{}", message_text);
|
||||
sender.send(message_text).await.unwrap();
|
||||
}
|
||||
pub async fn get_user_id(&self) -> i64 {
|
||||
*self.omikron_id.read().await
|
||||
}
|
||||
pub async fn is_identified(&self) -> bool {
|
||||
*self.identified.read().await && *self.challenged.read().await
|
||||
}
|
||||
pub async fn get_public_key(&self) -> PublicKey {
|
||||
PublicKey::from_bytes(self.pub_key.read().await.as_ref().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
pub async fn handle_message(self: Arc<Self>, message: String) {
|
||||
let cv = CommunicationValue::from_json(&message);
|
||||
if cv.is_type(CommunicationType::ping) {
|
||||
self.handle_ping(cv).await;
|
||||
return;
|
||||
}
|
||||
|
||||
log_in_from!(*self.omikron_id.read().await, "{}", message);
|
||||
|
||||
if !*self.identified.read().await && cv.is_type(CommunicationType::identification) {
|
||||
let omikron_id = cv
|
||||
.get_data(DataTypes::omikron)
|
||||
.unwrap_or(&JsonValue::Null)
|
||||
.as_i64()
|
||||
.unwrap_or(0);
|
||||
if let Ok((_, public_key, _)) = get_omikron_by_id(omikron_id).await {
|
||||
// Generate Challenge, encrypt it and send it to the omikron
|
||||
*self.omikron_id.write().await = omikron_id;
|
||||
*self.identified.write().await = true;
|
||||
|
||||
let challenge_str: String = rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(32)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
*self.challenge.write().await = challenge_str.clone();
|
||||
|
||||
let user_public_key_bytes = match STANDARD.decode(&public_key) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(_) => {
|
||||
self.send_error_response(
|
||||
&cv.get_id(),
|
||||
CommunicationType::error_invalid_user_id,
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
*self.pub_key.write().await = Some(user_public_key_bytes.clone());
|
||||
|
||||
let omikron_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 encrypted_challenge =
|
||||
encrypt(get_private_key(), omikron_pub_key, &challenge_str)
|
||||
.unwrap_or("".to_string());
|
||||
|
||||
let response = CommunicationValue::new(CommunicationType::challenge)
|
||||
.add_data_str(
|
||||
DataTypes::public_key,
|
||||
STANDARD.encode(get_public_key().as_bytes()),
|
||||
)
|
||||
.add_data_str(DataTypes::challenge, encrypted_challenge)
|
||||
.with_id(cv.get_id());
|
||||
|
||||
self.send_message(&response).await;
|
||||
// prepare Challenge Response handling
|
||||
self.waiting_tasks.insert(
|
||||
cv.get_id(),
|
||||
Box::new(
|
||||
|selfc: Arc<OmikronConnection>, cv: CommunicationValue| -> bool {
|
||||
tokio::spawn(async move {
|
||||
let client_challenge_response_b64 =
|
||||
match cv.get_data(DataTypes::challenge) {
|
||||
Some(data) => data.to_string(),
|
||||
None => {
|
||||
selfc
|
||||
.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(_) => {
|
||||
selfc
|
||||
.send_error_response(
|
||||
&cv.get_id(),
|
||||
CommunicationType::error,
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if challenge_response_bytes.len() < 12 {
|
||||
selfc
|
||||
.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let client_response = cv
|
||||
.get_data(DataTypes::challenge)
|
||||
.unwrap_or(&JsonValue::Null)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
|
||||
let expected_challenge = selfc.challenge.read().await.clone();
|
||||
|
||||
if client_response != expected_challenge {
|
||||
selfc
|
||||
.send_error_response(
|
||||
&cv.get_id(),
|
||||
CommunicationType::error_invalid_challenge,
|
||||
)
|
||||
.await;
|
||||
selfc.close().await;
|
||||
return;
|
||||
}
|
||||
|
||||
*selfc.challenged.write().await = true;
|
||||
|
||||
let response = CommunicationValue::new(
|
||||
CommunicationType::identification_response,
|
||||
)
|
||||
.with_id(cv.get_id());
|
||||
|
||||
selfc.send_message(&response).await;
|
||||
return;
|
||||
});
|
||||
return true;
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error_not_found)
|
||||
.await;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if self.waiting_tasks.contains_key(&cv.get_id()) {
|
||||
let (_, task) = self.waiting_tasks.remove(&cv.get_id()).unwrap();
|
||||
let _ = task(self.clone(), cv.clone());
|
||||
}
|
||||
|
||||
if !self.is_identified().await {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error_not_found)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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 sender = self.sender.write().await;
|
||||
let _ = sender.close().await;
|
||||
}
|
||||
pub async fn handle_close(self: Arc<Self>) {
|
||||
if self.is_identified().await {
|
||||
if self.get_user_id().await != 0 {}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
0
src/server/omikron_manager.rs
Normal file
0
src/server/omikron_manager.rs
Normal file
475
src/server/server.rs
Normal file
475
src/server/server.rs
Normal file
|
|
@ -0,0 +1,475 @@
|
|||
use crate::log;
|
||||
use crate::server::api;
|
||||
use crate::server::omikron_connection::OmikronConnection;
|
||||
use crate::util::file_util::load_file_buf;
|
||||
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
use futures_util::TryFutureExt;
|
||||
use http_body_util::BodyExt;
|
||||
use http_body_util::Full;
|
||||
use hyper::{Method, StatusCode};
|
||||
use hyper::{
|
||||
Request as HttpRequest, Response as HttpResponse, body::Incoming, server::conn::http1, upgrade,
|
||||
};
|
||||
use hyper_util::rt::tokio::TokioIo;
|
||||
use hyper_util::service::TowerToHyperService;
|
||||
use pnet::datalink::NetworkInterface;
|
||||
use rustls::ServerConfig;
|
||||
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::error::Error;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::{self, BufReader};
|
||||
use std::net::SocketAddr;
|
||||
use std::result::Result::Ok;
|
||||
use std::sync::Arc;
|
||||
use std::{future::Future, pin::Pin, time::Duration};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
use tokio_tungstenite::{WebSocketStream, accept_async};
|
||||
use tower::Service;
|
||||
use tungstenite::Message;
|
||||
#[derive(Clone)]
|
||||
struct HttpService {
|
||||
peer_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl Service<HttpRequest<Incoming>> for HttpService {
|
||||
type Response = HttpResponse<Full<Bytes>>;
|
||||
type Error = io::Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
std::task::Poll::Ready(std::io::Result::Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: HttpRequest<Incoming>) -> Self::Future {
|
||||
let _peer_ip = self.peer_addr.ip();
|
||||
|
||||
let (parts, body) = req.into_parts();
|
||||
|
||||
let method = parts.method.clone();
|
||||
let path = parts.uri.path().to_string();
|
||||
let headers = parts.headers.clone();
|
||||
|
||||
let fut = async move {
|
||||
let is_websocket_upgrade = path == "/ws/omikron"
|
||||
&& method == Method::GET
|
||||
&& headers
|
||||
.get("connection")
|
||||
.map(|v| {
|
||||
v.to_str()
|
||||
.unwrap_or("")
|
||||
.split(',')
|
||||
.any(|s| s.trim().eq_ignore_ascii_case("upgrade"))
|
||||
})
|
||||
.unwrap_or(false)
|
||||
&& headers
|
||||
.get("upgrade")
|
||||
.map(|v| v.to_str().unwrap_or("").eq_ignore_ascii_case("websocket"))
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_websocket_upgrade {
|
||||
log!("Attempting WebSocket upgrade on {}", path);
|
||||
|
||||
if let Some(sec_websocket_key) = headers.get("sec-websocket-key") {
|
||||
let sec_websocket_key = sec_websocket_key.to_str().unwrap_or("").to_string();
|
||||
let sec_websocket_accept = calculate_accept_key(&sec_websocket_key);
|
||||
|
||||
let response = HttpResponse::builder()
|
||||
.status(StatusCode::SWITCHING_PROTOCOLS)
|
||||
.header("Upgrade", "websocket")
|
||||
.header("Connection", "Upgrade")
|
||||
.header("Sec-WebSocket-Accept", sec_websocket_accept)
|
||||
.body(Full::new(Bytes::from("")))
|
||||
.unwrap();
|
||||
let req_for_upgrade = HttpRequest::from_parts(parts, body);
|
||||
let upgrades = upgrade::on(req_for_upgrade);
|
||||
log!("Handling WebSocket upgrade");
|
||||
|
||||
// Spawn upgrade handling to avoid blocking the service call
|
||||
tokio::spawn(async move {
|
||||
match upgrades.await {
|
||||
Ok(upgraded_stream) => {
|
||||
log!("Valid WebSocket upgrade");
|
||||
let raw_stream = TokioIo::new(upgraded_stream);
|
||||
|
||||
let ws_stream = WebSocketStream::from_raw_socket(
|
||||
raw_stream,
|
||||
tungstenite::protocol::Role::Server,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
log!(
|
||||
"WebSocket handshake successful, handling connection for Omikron"
|
||||
);
|
||||
|
||||
// Split stream for OmikronConnection
|
||||
let (writer, reader) = ws_stream.split();
|
||||
|
||||
// INTEGRATION START
|
||||
// Erstelle die Connection und starte den Handler
|
||||
let connection = OmikronConnection::new(writer, reader);
|
||||
|
||||
// Handler in separatem Task starten
|
||||
start_omikron_handler(connection).await;
|
||||
// INTEGRATION END
|
||||
}
|
||||
Err(e) => {
|
||||
log!("WebSocket upgrade failed after response: {:?}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
log!("Handled WebSocket connection initiation");
|
||||
Ok(response)
|
||||
} else {
|
||||
log!("No Sec-WebSocket-Key found in request headers");
|
||||
let response = HttpResponse::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Full::new(Bytes::from("Missing Sec-WebSocket-Key")))
|
||||
.unwrap();
|
||||
Ok(response)
|
||||
}
|
||||
} else if path.starts_with("/api") {
|
||||
let whole_body = match body.collect().await {
|
||||
Ok(collected) => collected,
|
||||
Err(e) => {
|
||||
log!("Error collecting body: {}", e);
|
||||
return Ok(HttpResponse::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(Full::new(Bytes::from(format!(
|
||||
"Failed to read body: {}",
|
||||
e
|
||||
))))
|
||||
.unwrap());
|
||||
}
|
||||
};
|
||||
let bytes = whole_body.to_bytes();
|
||||
|
||||
let body_string: Option<String> = match String::from_utf8(bytes.to_vec()) {
|
||||
Ok(s) => Some(s),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
Ok(api::handle(&path, headers.clone(), body_string).await)
|
||||
} else {
|
||||
let response = HttpResponse::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Full::new(Bytes::from("No path provided")))
|
||||
.unwrap();
|
||||
Ok(response)
|
||||
}
|
||||
};
|
||||
|
||||
Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Error in request handling: {}", err),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_http_server(port: u16) -> bool {
|
||||
let ip = "0.0.0.0".to_string();
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
|
||||
if let Err(e) = listener {
|
||||
log!("Failed to bind to port {}: {:?}", port, e);
|
||||
return false;
|
||||
}
|
||||
let listener = listener.unwrap();
|
||||
log!(
|
||||
"Standard Server listening for HTTP and WS on {}:{}",
|
||||
ip,
|
||||
port
|
||||
);
|
||||
|
||||
// Create a broadcast channel for graceful shutdown signal
|
||||
let (shutdown_tx, _) = broadcast::channel::<()>(1);
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
|
||||
accepted = listener.accept() => {
|
||||
match accepted {
|
||||
std::result::Result::Ok((stream, addr)) => {
|
||||
let service = HttpService { peer_addr: addr };
|
||||
let io = TokioIo::new(stream);
|
||||
|
||||
// Subscribe to the shutdown signal for this specific connection
|
||||
let mut rx = shutdown_tx.subscribe();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Prepare the connection future
|
||||
let conn = http1::Builder::new()
|
||||
.preserve_header_case(true)
|
||||
.title_case_headers(true)
|
||||
.serve_connection(io, TowerToHyperService::new(service))
|
||||
.with_upgrades();
|
||||
|
||||
// Wait for either the connection to finish naturally OR the shutdown signal
|
||||
tokio::select! {
|
||||
res = conn => {
|
||||
if let Err(err) = res {
|
||||
if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::<io::Error>()) {
|
||||
if io_err.kind() != io::ErrorKind::ConnectionReset
|
||||
&& io_err.kind() != io::ErrorKind::BrokenPipe
|
||||
{
|
||||
log!("Error serving connection: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = rx.recv() => {
|
||||
// Shutdown signal received.
|
||||
// Dropping the 'conn' future here closes the socket immediately.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
log!("Error accepting connection: {:?}", e);
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Runs the encrypted HTTPS/WSS server loop using the provided TLS config.
|
||||
async fn run_tls_server(port: u16, tls_config: Arc<ServerConfig>) -> bool {
|
||||
let mut ip = "0.0.0.0".to_string();
|
||||
for iface in pnet::datalink::interfaces() {
|
||||
let iface: NetworkInterface = iface;
|
||||
let ipsv = format!("{}", iface.ips[0]);
|
||||
let ips: &str = ipsv.split('/').next().unwrap();
|
||||
log!("{}", ips.to_string());
|
||||
if format!("{}", ips).starts_with("10.") {
|
||||
ip = ips.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
let acceptor = TlsAcceptor::from(tls_config);
|
||||
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
|
||||
if let Err(e) = listener {
|
||||
log!("Failed to bind to port {}: {:?}", port, e);
|
||||
return false;
|
||||
}
|
||||
let listener = listener.unwrap();
|
||||
log!(
|
||||
"Encrypted Server listening for HTTPS and WSS on {}:{}",
|
||||
ip,
|
||||
port
|
||||
);
|
||||
|
||||
// Create a broadcast channel for graceful shutdown signal
|
||||
let (shutdown_tx, _) = broadcast::channel::<()>(1);
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
// Monitor for shutdown signal
|
||||
_ = async {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
} => {
|
||||
log!("Encrypted Server received shutdown signal.");
|
||||
// Send kill signal to all active connection tasks
|
||||
let _ = shutdown_tx.send(());
|
||||
break;
|
||||
}
|
||||
|
||||
// Accept new connections
|
||||
accepted = listener.accept() => {
|
||||
match accepted {
|
||||
std::result::Result::Ok((stream, addr)) => {
|
||||
let service = HttpService { peer_addr: addr };
|
||||
let acceptor = acceptor.clone();
|
||||
|
||||
// Subscribe to the shutdown signal for this specific connection
|
||||
let mut rx = shutdown_tx.subscribe();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Perform TLS handshake
|
||||
let tls_stream = match acceptor.accept(stream).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
if e.kind() != io::ErrorKind::Interrupted {
|
||||
log!("TLS Handshake error: {:?}", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
let io = TokioIo::new(tls_stream);
|
||||
|
||||
// Prepare connection future
|
||||
let conn = http1::Builder::new()
|
||||
.preserve_header_case(true)
|
||||
.title_case_headers(true)
|
||||
.serve_connection(io, TowerToHyperService::new(service))
|
||||
.with_upgrades();
|
||||
|
||||
// Wait for either the connection to finish naturally OR the shutdown signal
|
||||
tokio::select! {
|
||||
res = conn => {
|
||||
if let Err(err) = res {
|
||||
if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::<io::Error>()) {
|
||||
if io_err.kind() != io::ErrorKind::ConnectionReset
|
||||
&& io_err.kind() != io::ErrorKind::BrokenPipe
|
||||
{
|
||||
log!("Error serving connection: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = rx.recv() => {
|
||||
// Shutdown signal received.
|
||||
// Dropping the 'conn' future here closes the socket immediately.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
log!("Error accepting connection: {:?}", e);
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log!("Encrypted Server shutdown complete.");
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn start(port: u16) -> bool {
|
||||
let tls_result = load_tls_config();
|
||||
|
||||
match tls_result {
|
||||
Ok(Some(tls_config)) => run_tls_server(port, tls_config).await,
|
||||
Ok(None) => run_http_server(port).await,
|
||||
Err(e) => {
|
||||
log!("Fatal error during TLS config load: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
fn calculate_accept_key(key: &str) -> String {
|
||||
let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
let mut sha1 = Sha1::new();
|
||||
sha1.update(key.as_bytes());
|
||||
sha1.update(websocket_guid.as_bytes());
|
||||
let result = sha1.finalize();
|
||||
STANDARD.encode(result) // Base64 encode the result
|
||||
}
|
||||
|
||||
/// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails.
|
||||
fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn Error>> {
|
||||
let cert_file_res = load_file_buf("certs", "cert.pem");
|
||||
let key_file_res = load_file_buf("certs", "cert.key");
|
||||
|
||||
// Check if certificate files are present. If not, return None.
|
||||
let cert_file_buf = match cert_file_res {
|
||||
Ok(b) => b,
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||
log!("TLS certificate 'certs/cert.pem' not found.");
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => return Err(e.into()), // Other IO error
|
||||
};
|
||||
|
||||
let key_file_buf = match key_file_res {
|
||||
Ok(b) => b,
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||
log!("TLS key 'certs/cert.key' not found.");
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => return Err(e.into()), // Other IO error
|
||||
};
|
||||
|
||||
// Continue with configuration if both files were found
|
||||
let mut cert_reader = BufReader::new(cert_file_buf);
|
||||
let cert_ders = rustls_pemfile::certs(&mut cert_reader)
|
||||
.collect::<Result<Vec<CertificateDer>, io::Error>>()?;
|
||||
|
||||
// PKCS8
|
||||
let mut key_reader = BufReader::new(key_file_buf);
|
||||
let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader)
|
||||
.map(|r| r.map(Into::into)) // Explicit conversion
|
||||
.collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?;
|
||||
|
||||
if key_ders.is_empty() {
|
||||
// RSA
|
||||
key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file
|
||||
key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader)
|
||||
.map(|r| r.map(Into::into))
|
||||
.collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?;
|
||||
}
|
||||
|
||||
if key_ders.is_empty() {
|
||||
// EC
|
||||
key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file
|
||||
key_ders = rustls_pemfile::ec_private_keys(&mut key_reader)
|
||||
.map(|r| r.map(Into::into))
|
||||
.collect::<Result<Vec<PrivateKeyDer>, io::Error>>()?;
|
||||
}
|
||||
|
||||
if key_ders.is_empty() {
|
||||
return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into());
|
||||
}
|
||||
|
||||
let config = rustls::ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(cert_ders, key_ders.remove(0))
|
||||
.map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))?;
|
||||
|
||||
Ok(Some(Arc::new(config)))
|
||||
}
|
||||
// In deiner Server-Logik, wo OmikronConnection initialisiert wird:
|
||||
|
||||
pub async fn start_omikron_handler(connection: Arc<OmikronConnection>) {
|
||||
loop {
|
||||
let msg = match {
|
||||
let mut receiver = connection.receiver.write().await;
|
||||
receiver.next().await
|
||||
} {
|
||||
Some(Ok(msg)) => msg,
|
||||
Some(Err(e)) => {
|
||||
log!("WS Error: {}", e);
|
||||
break;
|
||||
}
|
||||
None => break, // Stream ended
|
||||
};
|
||||
|
||||
match msg {
|
||||
Message::Text(text) => {
|
||||
let conn_clone = connection.clone();
|
||||
tokio::spawn(async move {
|
||||
conn_clone.handle_message(text.to_string()).await;
|
||||
});
|
||||
}
|
||||
Message::Close(_) => {
|
||||
break;
|
||||
}
|
||||
// Other message types like Binary, Ping, Pong are ignored.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
connection.handle_close().await;
|
||||
}
|
||||
58
src/server/socket.rs
Normal file
58
src/server/socket.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use futures::StreamExt;
|
||||
use futures::stream::SplitSink;
|
||||
use futures::stream::SplitStream;
|
||||
use hyper::upgrade::Upgraded;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use tungstenite::Message;
|
||||
|
||||
use crate::log;
|
||||
use crate::server::omikron_connection::OmikronConnection;
|
||||
|
||||
pub fn handle(
|
||||
path: String,
|
||||
writer: SplitSink<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>, Message>,
|
||||
reader: SplitStream<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>>,
|
||||
) {
|
||||
log!("handling");
|
||||
tokio::spawn(async move {
|
||||
if path.starts_with("/ws/phi/") {
|
||||
} else if path.starts_with("/ws/omikron/") {
|
||||
let community_conn: Arc<OmikronConnection> =
|
||||
Arc::from(OmikronConnection::new(writer, reader));
|
||||
loop {
|
||||
let msg_result: Option<Result<_, _>> = {
|
||||
let mut session_lock = community_conn.receiver.write().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.to_string())
|
||||
.await;
|
||||
} else if msg.is_close() {
|
||||
log!("Closing: {}", msg);
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
log!("Closing ERR: {}", e);
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
log!("Closed Session me!");
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
22
src/sql/iota_omikron_tracker.rs
Normal file
22
src/sql/iota_omikron_tracker.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
static IOTA_OMIKRON_MAP: Lazy<Arc<RwLock<HashMap<i64, i64>>>> =
|
||||
Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
|
||||
|
||||
pub async fn track_iota_omikron(iota: i64, omikron: i64) {
|
||||
let mut c = IOTA_OMIKRON_MAP.write().await;
|
||||
c.insert(iota, omikron);
|
||||
}
|
||||
|
||||
pub async fn get_omikron_for_iota(iota: i64) -> Option<i64> {
|
||||
let c = IOTA_OMIKRON_MAP.read().await;
|
||||
c.get(&iota).cloned()
|
||||
}
|
||||
|
||||
pub async fn untrack_iota(iota: i64) {
|
||||
let mut c = IOTA_OMIKRON_MAP.write().await;
|
||||
c.remove(&iota);
|
||||
}
|
||||
2
src/sql/mod.rs
Normal file
2
src/sql/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod iota_omikron_tracker;
|
||||
pub mod sql;
|
||||
450
src/sql/sql.rs
Normal file
450
src/sql/sql.rs
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
use crate::log;
|
||||
use once_cell::sync::Lazy;
|
||||
use sqlx::{MySql, Pool, mysql::MySqlPoolOptions};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::{
|
||||
env,
|
||||
sync::Arc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
/*
|
||||
use crate::sql::{
|
||||
iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota},
|
||||
sql::{
|
||||
change_about, change_avatar, change_display_name, change_iota_id, change_iota_key,
|
||||
change_keys, change_status, change_username, get_by_id, get_by_username, get_iota_by_id,
|
||||
get_register_id, register_complete_iota, register_complete_user,
|
||||
},
|
||||
};
|
||||
*/
|
||||
|
||||
static SQL_DB: Lazy<Arc<RwLock<Option<Pool<MySql>>>>> = Lazy::new(|| Arc::new(RwLock::new(None)));
|
||||
|
||||
pub async fn connect() -> Result<Pool<MySql>, sqlx::Error> {
|
||||
let user = env::var("DB_USERNAME").expect("DB_USERNAME is not set");
|
||||
let passwd = env::var("DB_PASSWD").expect("DB_PASSWD is not set");
|
||||
let table = env::var("DB_TABLE").expect("DB_TABLE is not set");
|
||||
|
||||
MySqlPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&format!(
|
||||
"mysql://{}:{}@127.0.0.1:3306/{}",
|
||||
user, passwd, table
|
||||
))
|
||||
.await
|
||||
}
|
||||
// Omega
|
||||
// - Omikron
|
||||
// - Iota
|
||||
// - User
|
||||
// - User
|
||||
// - Iota
|
||||
// - User
|
||||
// - User
|
||||
// - Omikron
|
||||
// - Iota
|
||||
// - User
|
||||
// - User
|
||||
// - Iota
|
||||
// - User
|
||||
// - User
|
||||
pub async fn initialize_db() -> Result<(), sqlx::Error> {
|
||||
let pool = connect().await?;
|
||||
let mut db_lock = SQL_DB.write().await;
|
||||
// create tables
|
||||
// with indexes
|
||||
let _ = sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS
|
||||
users (
|
||||
id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
|
||||
username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin,
|
||||
display VARCHAR(15) COLLATE utf8mb4_bin,
|
||||
status VARCHAR(15) COLLATE utf8mb4_bin,
|
||||
about VARCHAR(200) COLLATE utf8mb4_bin,
|
||||
avatar MEDIUMBLOB,
|
||||
sub_level INT(11) NOT NULL DEFAULT 0,
|
||||
sub_end BIGINT(20) NOT NULL,
|
||||
public_key TEXT NOT NULL COLLATE utf8mb4_bin,
|
||||
private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin,
|
||||
iota_id BIGINT UNSIGNED NOT NULL,
|
||||
token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin
|
||||
)",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
let _ = sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS
|
||||
iotas (
|
||||
id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
|
||||
public_key TEXT NOT NULL COLLATE utf8mb4_bin
|
||||
)",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
let _ = sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS
|
||||
omikrons (
|
||||
id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
|
||||
is_active INT(1) NOT NULL DEFAULT 0,
|
||||
public_key TEXT NOT NULL COLLATE utf8mb4_bin,
|
||||
location TEXT NOT NULL COLLATE utf8mb4_bin,
|
||||
ip_address TEXT NOT NULL COLLATE utf8mb4_bin
|
||||
)",
|
||||
)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
|
||||
*db_lock = Some(pool);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ==========================================================================================
|
||||
// REGISTER
|
||||
// ==========================================================================================
|
||||
pub static CURRENT_MILLI_USED: Lazy<Arc<AtomicU64>> = Lazy::new(|| Arc::new(AtomicU64::new(0)));
|
||||
pub static CURRENT_REGISTER_PROCESS: Lazy<Arc<RwLock<Vec<u64>>>> =
|
||||
Lazy::new(|| Arc::new(RwLock::new(Vec::new())));
|
||||
|
||||
pub async fn get_register_id() -> u64 {
|
||||
let mut current_time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as u64;
|
||||
|
||||
loop {
|
||||
let current_locked = CURRENT_MILLI_USED.load(Ordering::SeqCst);
|
||||
|
||||
if current_locked < current_time {
|
||||
let result = CURRENT_MILLI_USED.compare_exchange(
|
||||
current_locked, // expected value
|
||||
current_time, // new value
|
||||
Ordering::SeqCst, // acquire/release ordering
|
||||
Ordering::SeqCst, // failure ordering
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
CURRENT_REGISTER_PROCESS.write().await.push(current_time);
|
||||
return current_time;
|
||||
}
|
||||
Err(_) => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
current_time = current_locked + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================================
|
||||
// USERS
|
||||
// ==========================================================================================
|
||||
|
||||
pub async fn get_by_username(
|
||||
username: &str,
|
||||
) -> Result<
|
||||
(
|
||||
i64,
|
||||
i64,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
i32,
|
||||
i64,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
),
|
||||
sqlx::Error,
|
||||
> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>(
|
||||
"SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?",
|
||||
)
|
||||
.bind(username)
|
||||
.fetch_optional(pool)
|
||||
.await?.ok_or_else(|| sqlx::Error::RowNotFound)
|
||||
}
|
||||
|
||||
pub async fn get_by_id(
|
||||
id: i64,
|
||||
) -> Result<
|
||||
(
|
||||
i64,
|
||||
i64,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
i32,
|
||||
i64,
|
||||
String,
|
||||
String,
|
||||
String,
|
||||
),
|
||||
sqlx::Error,
|
||||
> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>(
|
||||
"SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?",
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_optional(pool)
|
||||
.await?.ok_or_else(|| sqlx::Error::RowNotFound)
|
||||
}
|
||||
|
||||
pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("UPDATE users SET username = ? WHERE id = ?")
|
||||
.bind(new_username)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn change_display_name(id: i64, new_display_name: String) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("UPDATE users SET display_name = ? WHERE id = ?")
|
||||
.bind(new_display_name)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("UPDATE users SET avatar = ? WHERE id = ?")
|
||||
.bind(new_avatar)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("UPDATE users SET about = ? WHERE id = ?")
|
||||
.bind(new_about)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("UPDATE users SET status = ? WHERE id = ?")
|
||||
.bind(new_status)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?")
|
||||
.bind(new_iota_id)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn change_keys(
|
||||
id: i64,
|
||||
new_public_key: String,
|
||||
new_private_key_hash: String,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?")
|
||||
.bind(new_public_key)
|
||||
.bind(new_private_key_hash)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub async fn register_complete_user(
|
||||
id: i64,
|
||||
username: String,
|
||||
public_key: String,
|
||||
private_key_hash: String,
|
||||
iota_id: i64,
|
||||
reset_token: String,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, reset_token) VALUES (?, ?, ?, ?, ?, ?)")
|
||||
.bind(id)
|
||||
.bind(username)
|
||||
.bind(public_key)
|
||||
.bind(private_key_hash)
|
||||
.bind(iota_id)
|
||||
.bind(reset_token)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub async fn print_users() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
log!("Printing users...");
|
||||
for (id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token)
|
||||
in sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>(
|
||||
"SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users",
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?
|
||||
.iter()
|
||||
{
|
||||
log!(
|
||||
"User: {:?}",
|
||||
(id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ==========================================================================================
|
||||
// IOTA
|
||||
// ==========================================================================================
|
||||
pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)")
|
||||
.bind(id)
|
||||
.bind(public_key)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("DELETE FROM users WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query_as::<_, (i64, String)>("SELECT id, public_key FROM iotas WHERE id = ?")
|
||||
.bind(id)
|
||||
.fetch_optional(pool)
|
||||
.await?
|
||||
.ok_or_else(|| sqlx::Error::RowNotFound)
|
||||
}
|
||||
|
||||
pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?")
|
||||
.bind(new_key)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query("DELETE FROM iotas WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ==========================================================================================
|
||||
// OMIKRONS
|
||||
// ==========================================================================================
|
||||
|
||||
pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
let a = sqlx::query_as("SELECT id, public_key, ip_address FROM omikrons WHERE is_active = 1 ORDER BY RAND() LIMIT 1")
|
||||
.fetch_optional(pool)
|
||||
.await?
|
||||
.ok_or_else(|| sqlx::Error::RowNotFound);
|
||||
// this log isn't printing
|
||||
log!("Random Omikron: {:?}", a);
|
||||
a
|
||||
}
|
||||
|
||||
pub async fn get_omikron_by_id(id: i64) -> Result<(i64, String, String), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
sqlx::query_as("SELECT id, public_key, ip_address FROM omikrons WHERE id = ?")
|
||||
.bind(id)
|
||||
.fetch_optional(pool)
|
||||
.await?
|
||||
.ok_or_else(|| sqlx::Error::RowNotFound)
|
||||
}
|
||||
|
||||
pub async fn set_omikron_active(id: i64, active: bool) -> Result<(), sqlx::Error> {
|
||||
let db_lock = SQL_DB.read().await;
|
||||
let pool = db_lock.as_ref().expect("Database pool is not initialized");
|
||||
|
||||
let active = if active { 1 } else { 0 };
|
||||
|
||||
sqlx::query("UPDATE omikrons SET active = ? WHERE id = ?")
|
||||
.bind(active)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
143
src/util/crypto_helper.rs
Normal file
143
src/util/crypto_helper.rs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
use aes_gcm::{
|
||||
Aes256Gcm, Nonce,
|
||||
aead::{Aead, KeyInit, OsRng},
|
||||
};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use rand_core::RngCore;
|
||||
use sha2::{Digest, Sha256};
|
||||
use x448::{PublicKey, Secret, SharedSecret};
|
||||
|
||||
/// 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 {
|
||||
STANDARD.encode(pubkey.as_bytes().as_ref())
|
||||
}
|
||||
|
||||
pub fn secret_key_to_base64(secret: &Secret) -> String {
|
||||
STANDARD.encode(secret.as_bytes().as_ref())
|
||||
}
|
||||
|
||||
pub fn load_public_key(base64_pub: &str) -> Option<PublicKey> {
|
||||
let bytes = STANDARD.decode(base64_pub).unwrap();
|
||||
PublicKey::from_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub fn load_secret_key(base64_secret: &str) -> Option<Secret> {
|
||||
let bytes = STANDARD.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_b64(
|
||||
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();
|
||||
encrypt(secret, peer_pub, plaintext)
|
||||
}
|
||||
pub fn encrypt(
|
||||
secret: Secret,
|
||||
peer_pub: PublicKey,
|
||||
plaintext: &str,
|
||||
) -> Result<String, CryptoError> {
|
||||
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(STANDARD.encode(&out))
|
||||
}
|
||||
|
||||
pub fn decrypt_b64(
|
||||
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();
|
||||
decrypt(secret, peer_pub, encrypted_base64)
|
||||
}
|
||||
pub fn decrypt(
|
||||
secret: Secret,
|
||||
peer_pub: PublicKey,
|
||||
encrypted_base64: &str,
|
||||
) -> Result<String, CryptoError> {
|
||||
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 = STANDARD.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()
|
||||
}
|
||||
350
src/util/file_util.rs
Normal file
350
src/util/file_util.rs
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
use crate::log;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, BufReader, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use sysinfo::System;
|
||||
use uuid::Uuid;
|
||||
use walkdir::WalkDir;
|
||||
use zip::ZipArchive;
|
||||
|
||||
pub fn delete_file(path: &str, name: &str) -> bool {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file = dir.join(name);
|
||||
if !file.exists() {
|
||||
return false;
|
||||
}
|
||||
fs::remove_file(file).is_ok()
|
||||
}
|
||||
|
||||
pub fn delete_directory(path: &str) -> bool {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
delete_dir_recursive(&dir)
|
||||
}
|
||||
|
||||
fn delete_dir_recursive(directory: &Path) -> bool {
|
||||
if !directory.exists() {
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = fs::remove_dir_all(directory) {
|
||||
log!(
|
||||
"[IMPORTANT] Couldn't delete directory {}: {}",
|
||||
directory.display(),
|
||||
e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn load_file_buf(path: &str, name: &str) -> io::Result<BufReader<File>> {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file_path = dir.join(name);
|
||||
|
||||
// Ensure the directory exists, create if necessary
|
||||
if !dir.exists() {
|
||||
if let Err(_) = fs::create_dir_all(&dir) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"Directory creation failed",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Create the file if it doesn't exist
|
||||
if !file_path.exists() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"File creation failed",
|
||||
));
|
||||
}
|
||||
|
||||
// Open the file and return a BufReader for efficient reading
|
||||
let file = File::open(&file_path)?;
|
||||
Ok(BufReader::new(file))
|
||||
}
|
||||
pub fn has_file(path: &str, name: &str) -> bool {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file_path = dir.join(name);
|
||||
|
||||
if !dir.exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !file_path.exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
pub fn has_dir(path: &str) -> bool {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
|
||||
if !dir.exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn load_file(path: &str, name: &str) -> String {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file_path = dir.join(name);
|
||||
|
||||
if !dir.exists() {
|
||||
if let Err(e) = fs::create_dir_all(&dir) {
|
||||
log!("[IMPORTANT] Couldn't create directories: {}", e);
|
||||
return String::new();
|
||||
}
|
||||
return String::new();
|
||||
}
|
||||
|
||||
if !file_path.exists() {
|
||||
if let Err(e) = File::create(&file_path) {
|
||||
log!("[IMPORTANT] Couldn't create file: {}", e);
|
||||
}
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let mut content = String::new();
|
||||
if let Ok(mut f) = File::open(&file_path) {
|
||||
let _ = f.read_to_string(&mut content);
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
pub fn load_file_vec(path: &str, name: &str) -> Vec<u8> {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file_path = dir.join(name);
|
||||
|
||||
if !dir.exists() {
|
||||
if let Err(e) = fs::create_dir_all(&dir) {
|
||||
log!("[IMPORTANT] Couldn't create directories: {}", e);
|
||||
return Vec::new();
|
||||
}
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
if !file_path.exists() {
|
||||
if let Err(e) = File::create(&file_path) {
|
||||
log!("[IMPORTANT] Couldn't create file: {}", e);
|
||||
}
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut content = Vec::new();
|
||||
if let Ok(mut f) = File::open(&file_path) {
|
||||
let _ = f.read_to_end(&mut content);
|
||||
}
|
||||
content
|
||||
}
|
||||
pub fn save_file(path: &str, name: &str, value: &str) {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file_path = dir.join(name);
|
||||
|
||||
if !dir.exists() {
|
||||
if let Err(e) = fs::create_dir_all(&dir) {
|
||||
log!("[IMPORTANT] Couldn't create directories: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = fs::write(&file_path, value) {
|
||||
log!(
|
||||
"[IMPORTANT] Couldn't write file {}: {}",
|
||||
file_path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_children(path: &str) -> Vec<String> {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let mut children = Vec::new();
|
||||
if let Ok(entries) = fs::read_dir(&dir) {
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
children.push(entry.file_name().to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
children
|
||||
}
|
||||
|
||||
pub fn get_directory() -> String {
|
||||
let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("."));
|
||||
exe.parent()
|
||||
.unwrap_or(Path::new("."))
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn used_space() -> u64 {
|
||||
get_directory_size(&PathBuf::from(get_directory()))
|
||||
}
|
||||
pub fn used_dir_space(path: &str) -> u64 {
|
||||
get_directory_size(&PathBuf::from(format!("{}/{}", get_directory(), path)))
|
||||
}
|
||||
|
||||
pub fn get_directory_size(directory: &Path) -> u64 {
|
||||
let mut size = 0;
|
||||
for entry in WalkDir::new(directory).into_iter().filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
if let Ok(metadata) = path.metadata() {
|
||||
size += path.file_name().unwrap_or(OsStr::new("")).len() as u64;
|
||||
size += metadata.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
pub fn get_designed_storage(user_id: Uuid) -> String {
|
||||
let user_dir = Path::new(&get_directory())
|
||||
.join("users")
|
||||
.join(user_id.to_string());
|
||||
design_byte(get_directory_size(&user_dir))
|
||||
}
|
||||
|
||||
pub fn design_byte(bytes: u64) -> String {
|
||||
let mut hr_size = format!("{:.2}B", bytes as f64);
|
||||
let k = bytes as f64 / 1024.0;
|
||||
let m = k / 1024.0;
|
||||
let g = m / 1024.0;
|
||||
let t = g / 1024.0;
|
||||
|
||||
if t >= 1.0 {
|
||||
hr_size = format!("{:.2}TB", t);
|
||||
} else if g >= 1.0 {
|
||||
hr_size = format!("{:.2}GB", g);
|
||||
} else if m >= 1.0 {
|
||||
hr_size = format!("{:.2}MB", m);
|
||||
} else if k >= 1.0 {
|
||||
hr_size = format!("{:.2}KB", k);
|
||||
}
|
||||
hr_size
|
||||
}
|
||||
|
||||
pub fn get_used_ram() -> String {
|
||||
let mut sys = System::new_all();
|
||||
sys.refresh_all();
|
||||
let used = sys.used_memory() * 1024; // kB to bytes
|
||||
let total = sys.total_memory() * 1024;
|
||||
format!("{}/{}", design_byte(used), design_byte(total))
|
||||
}
|
||||
|
||||
// Helper to download the zip file content to a file on disk
|
||||
async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let response = reqwest::get(url).await?;
|
||||
|
||||
// Check for successful response status
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Failed to download file: Status {}", response.status()).into());
|
||||
}
|
||||
|
||||
let mut zip_file = File::create(as_name)?;
|
||||
let body = response.bytes().await?;
|
||||
io::copy(&mut &*body, &mut zip_file)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_zip_contents_to_folder(
|
||||
zip_path: &Path,
|
||||
target_dir: &Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let file = File::open(zip_path)?;
|
||||
let mut archive = ZipArchive::new(file)?;
|
||||
|
||||
let staging_dir = target_dir.with_extension("staging");
|
||||
|
||||
let _ = fs::remove_dir_all(&staging_dir);
|
||||
fs::create_dir_all(&staging_dir)?;
|
||||
|
||||
let mut first_item_name: Option<PathBuf> = None;
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i)?;
|
||||
let entry_path = staging_dir.join(file.sanitized_name());
|
||||
|
||||
if i == 0 {
|
||||
if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 {
|
||||
first_item_name = Some(file.sanitized_name());
|
||||
}
|
||||
}
|
||||
|
||||
if file.name().ends_with('/') {
|
||||
fs::create_dir_all(&entry_path)?;
|
||||
} else {
|
||||
if let Some(parent) = entry_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let mut out_file = File::create(entry_path)?;
|
||||
io::copy(&mut file, &mut out_file)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(root_path) = first_item_name {
|
||||
let root_dir = staging_dir.join(&root_path);
|
||||
|
||||
if root_dir.is_dir() {
|
||||
let root_contents_count = fs::read_dir(&staging_dir)?.count();
|
||||
|
||||
if root_contents_count == 1
|
||||
|| (root_contents_count > 1 && fs::metadata(&root_dir).is_ok())
|
||||
{
|
||||
let _ = fs::remove_dir_all(target_dir);
|
||||
fs::create_dir_all(target_dir)?;
|
||||
|
||||
for entry in fs::read_dir(root_dir)? {
|
||||
let entry = entry?;
|
||||
let src = entry.path();
|
||||
let dest = target_dir.join(entry.file_name());
|
||||
|
||||
if let Err(_) = fs::rename(&src, &dest) {
|
||||
if src.is_file() {
|
||||
fs::copy(&src, &dest)?;
|
||||
} else {
|
||||
if entry.path().is_dir() {
|
||||
fs::rename(&src, &dest)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = fs::remove_dir_all(&staging_dir);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log!("Extracting directly (no single root folder detected).");
|
||||
let _ = fs::remove_dir_all(target_dir);
|
||||
fs::rename(&staging_dir, target_dir)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn download_and_extract_zip(url: &str, as_name: &str) {
|
||||
let base_dir = PathBuf::from(get_directory());
|
||||
let zip_filename = format!("{}.zip", Uuid::new_v4()); // Use a unique name for the downloaded ZIP file
|
||||
let zip_path = base_dir.join(&zip_filename);
|
||||
let target_dir = base_dir.join(as_name);
|
||||
|
||||
// Step 1: Download the ZIP file
|
||||
if let Err(e) = download_zip(url, &zip_path).await {
|
||||
log!("Error downloading file: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: Extract and flatten the ZIP file contents into the target directory
|
||||
if let Err(e) = extract_zip_contents_to_folder(&zip_path, &target_dir) {
|
||||
log!("Error extracting ZIP file contents: {}", e);
|
||||
}
|
||||
|
||||
// Step 3: Clean up the downloaded ZIP file
|
||||
if let Err(e) = fs::remove_file(&zip_path) {
|
||||
log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e);
|
||||
}
|
||||
}
|
||||
135
src/util/logger.rs
Normal file
135
src/util/logger.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
use std::{
|
||||
fs::{self, OpenOptions},
|
||||
io::Write,
|
||||
path::Path,
|
||||
sync::{OnceLock, mpsc},
|
||||
thread,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
static LOGGER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
|
||||
|
||||
struct LogMessage {
|
||||
timestamp_ms: u128,
|
||||
sender: Option<i64>,
|
||||
message: String,
|
||||
}
|
||||
|
||||
/// Initialize logger (call once)
|
||||
pub fn startup() {
|
||||
let (tx, rx) = mpsc::channel::<LogMessage>();
|
||||
|
||||
LOGGER.set(tx).expect("Logger already initialized");
|
||||
|
||||
thread::spawn(move || {
|
||||
// Prepare log directory
|
||||
let log_dir = Path::new("logs");
|
||||
fs::create_dir_all(log_dir).expect("Failed to create log directory");
|
||||
|
||||
let start_ts = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
let path = log_dir.join(format!("log_{}.txt", start_ts));
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(path)
|
||||
.expect("Failed to open log file");
|
||||
|
||||
// Dedicated logging loop
|
||||
for msg in rx {
|
||||
let timestamp_box = fixed_box(&msg.timestamp_ms.to_string(), 13);
|
||||
|
||||
let sender_box = match msg.sender {
|
||||
Some(id) => fixed_box(&format!("{}", id), 19),
|
||||
None => fixed_box("", 19),
|
||||
};
|
||||
|
||||
let line = format!("{} {} {}", timestamp_box, sender_box, msg.message);
|
||||
|
||||
println!("{}", line);
|
||||
let _ = writeln!(file, "{}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
fn fixed_box(content: &str, width: usize) -> String {
|
||||
let s = content.chars().take(width).collect::<String>();
|
||||
let len = s.chars().count();
|
||||
if len < width {
|
||||
let mut a = " ".repeat(width - len);
|
||||
a.push_str(&s);
|
||||
format!("[{}]", a)
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
/// Internal function (sync + async safe)
|
||||
pub fn log_internal(sender: Option<i64>, message: String) {
|
||||
if let Some(tx) = LOGGER.get() {
|
||||
let _ = tx.send(LogMessage {
|
||||
timestamp_ms: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis(),
|
||||
sender,
|
||||
message,
|
||||
});
|
||||
} else {
|
||||
println!("{}", message);
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($($arg:tt)*) => {
|
||||
$crate::util::logger::log_internal(None, format!($($arg)*))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_from {
|
||||
($sender:expr, $($arg:tt)*) => {
|
||||
$crate::util::logger::log_internal(Some($sender), format!($($arg)*))
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! log_in {
|
||||
($($arg:tt)*) => {
|
||||
$crate::util::logger::log_internal(
|
||||
None,
|
||||
format!("> {}", format!($($arg)*))
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_out {
|
||||
($($arg:tt)*) => {
|
||||
$crate::util::logger::log_internal(
|
||||
None,
|
||||
format!("< {}", format!($($arg)*))
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_in_from {
|
||||
($sender:expr, $($arg:tt)*) => {
|
||||
$crate::util::logger::log_internal(
|
||||
Some($sender),
|
||||
format!("> {}", format!($($arg)*))
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_out_from {
|
||||
($sender:expr, $($arg:tt)*) => {
|
||||
$crate::util::logger::log_internal(
|
||||
Some($sender),
|
||||
format!("< {}", format!($($arg)*))
|
||||
)
|
||||
};
|
||||
}
|
||||
3
src/util/mod.rs
Normal file
3
src/util/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod crypto_helper;
|
||||
pub mod file_util;
|
||||
pub mod logger;
|
||||
Loading…
Reference in a new issue