Оно работает!

Почищу код позже...
This commit is contained in:
Shiroyasha 2024-05-17 11:00:37 +03:00
parent 3fd49300db
commit b280da2742
Signed by: shiroyashik
GPG key ID: E4953D3940D7860A
14 changed files with 659 additions and 114 deletions

View file

@ -1,6 +1,8 @@
use axum::{extract::{Query, State}, routing::get, Router, debug_handler};
use axum::{async_trait, debug_handler, extract::{FromRequestParts, Query, State}, http::{request::Parts, StatusCode}, response::{IntoResponse, Response}, routing::get, Router};
use log::debug;
use serde::Deserialize;
use ring::digest::{self, digest};
use crate::utils::*;
use crate::AppState;
@ -10,16 +12,18 @@ pub fn router() -> Router<AppState> {
.route("/verify", get(verify))
}
// Веб функции
#[derive(Deserialize)]
struct Id {username: String}
#[debug_handler]
async fn id(
async fn id( // 1 этап аутентификации
Query(query): Query<Id>,
State(state): State<AppState>,
) -> String {
let server_id = bytes_into_string(&digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, &rand()).as_ref()[0 .. 20]);
let state = state.pending.lock().expect("Mutex poisoned!");
let state = state.pending.lock().await;
state.insert(server_id.clone(), query.username);
server_id
}
@ -28,36 +32,67 @@ async fn id(
struct Verify {id: String}
#[debug_handler]
async fn verify(
async fn verify( // 2 этап аутентификации
Query(query): Query<Verify>,
State(state): State<AppState>,
) -> String {
let server_id = query.id.clone();
let username = state.pending.lock().expect("Mutex poisoned!").remove(&server_id).unwrap().1;
if !elyby_api::has_joined(&server_id, &username).await.unwrap() {
let username = state.pending.lock().await.remove(&server_id).unwrap().1;
if let Some(uuid) = elyby_api::has_joined(&server_id, &username).await.unwrap() {
let authenticated = state.authenticated.lock().await;
let link = state.authenticated_link.lock().await;
authenticated.insert(server_id.clone(), crate::Userinfo { username, uuid });
link.insert(uuid, crate::AuthenticatedLink(server_id.clone()));
return format!("{server_id}")
} else {
return String::from("failed to verify")
}
let authenticated = state.authenticated.lock().expect("Mutex poisoned!");
authenticated.insert(server_id.clone(), username);
format!("{server_id}")
}
fn rand() -> [u8; 50] {
use rand::{Rng, thread_rng};
let mut rng = thread_rng();
let distr = rand::distributions::Uniform::new_inclusive(0, 255);
let mut nums: [u8; 50] = [0u8; 50];
for x in &mut nums {
*x = rng.sample(distr);
pub async fn status(
Token(token): Token,
State(state): State<AppState>,
) -> Response {
match token {
Some(token) => {
if state.authenticated.lock().await.contains_key(&token) {
// format!("ok") // 200
(StatusCode::OK, format!("ok")).into_response()
} else {
// format!("unauthorized") // 401
(StatusCode::UNAUTHORIZED, format!("unauthorized")).into_response()
}
},
None => {
// format!("bad request") // 400
(StatusCode::BAD_REQUEST, format!("bad request")).into_response()
},
}
nums
}
// Конец веб функций
pub fn bytes_into_string(code: &[u8]) -> String {
use std::fmt::Write;
let mut result = String::new();
for byte in code {
write!(result, "{:02x}", byte).unwrap();
// Это экстрактор достающий из Заголовка зовущегося токен, соответственно ТОКЕН.
#[derive(PartialEq, Debug)]
pub struct Token(pub Option<String>);
#[async_trait]
impl<S> FromRequestParts<S> for Token
where
S: Send + Sync,
{
type Rejection = StatusCode;
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
let token = parts
.headers
.get("token")
.and_then(|value| value.to_str().ok());
debug!("[Extractor Token] Данные: {token:?}");
match token {
Some(token) => Ok(Self(Some(token.to_string()))),
None => Ok(Self(None)),
}
}
result
}
}
// Конец экстрактора

30
src/info.rs Normal file
View file

@ -0,0 +1,30 @@
use axum::Json;
use serde_json::{json, Value};
pub async fn version() -> Json<Value> {
Json(json!({
"release": "1.7.1",
"prerelease": "1.7.2"
}))
}
pub async fn limits() -> Json<Value> {
Json(json!({
"rate": {
"pingSize": 1024,
"pingRate": 32, // TODO: Проверить
"equip": 1,
"download": 50,
"upload": 1
},
"limits": {
"maxAvatarSize": 100000,
"maxAvatars": 10,
"allowedBadges": {
"special": [0,0,0,0,0,0],
"pride": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
}
}
}))
}

View file

@ -1,33 +1,53 @@
use anyhow::Result;
use axum::{
extract::Path,
routing::{delete, get, post, put},
Router,
middleware::from_extractor, routing::{delete, get, post, put}, Router
};
use chrono::prelude::*;
use dashmap::DashMap;
use fern::colors::{Color, ColoredLevelConfig};
use log::info;
use std::sync::{Arc, Mutex};
use uuid::Uuid;
use std::sync::Arc;
use tokio::sync::{broadcast, Mutex};
use tower_http::trace::TraceLayer;
// WebSocket worker
mod ws;
use ws::handler;
// API
// API: Auth
mod auth;
use auth as api_auth;
// API: Server info
mod info;
use info as api_info;
// API: Profile
mod profile;
use profile as api_profile;
// Utils
mod utils;
#[derive(Debug, Clone)]
pub struct Userinfo {
id: usize
username: String,
uuid: Uuid,
}
#[derive(Debug, Clone)]
pub struct AuthenticatedLink(String);
#[derive(Debug, Clone)]
pub struct AppState {
authenticated: Arc<Mutex<DashMap<String, String>>>, // <SHA1, USERNAME>
pending: Arc<Mutex<DashMap<String, String>>>
// Пользователи с незаконченной аутентификацией
pending: Arc<Mutex<DashMap<String, String>>>, // <SHA1 serverId, USERNAME>
// Аутентифицированные пользователи
authenticated: Arc<Mutex<DashMap<String, Userinfo>>>, // <SHA1 serverId, Userinfo> NOTE: В будущем попробовать в отдельной ветке LockRw
authenticated_link: Arc<Mutex<DashMap<Uuid, AuthenticatedLink>>>, // Получаем токен из Uuid
// Трансляции Ping'ов для WebSocket соединений
broadcasts: Arc<Mutex<DashMap<Uuid, broadcast::Sender<Vec<u8>>>>>,
}
#[tokio::main]
@ -54,13 +74,17 @@ async fn main() -> Result<()> {
.chain(fern::log_file("output.log")?)
.apply()?;
// Config init here
// Конфиг
// TODO: Сделать Config.toml для установки настроек сервера
let listen = "0.0.0.0:6665";
// State init here
// Состояние
// TODO: Сделать usersStorage.toml как "временная" замена базе данных.
let state = AppState {
pending: Arc::new(Mutex::new(DashMap::new())),
authenticated: Arc::new(Mutex::new(DashMap::new())),
pending: Arc::new(Mutex::new(DashMap::new()))
authenticated_link: Arc::new(Mutex::new(DashMap::new())),
broadcasts: Arc::new(Mutex::new(DashMap::new())),
};
let api = Router::new()
@ -70,11 +94,11 @@ async fn main() -> Result<()> {
) // check Auth; return 200 OK if token valid
.route(
"/limits",
get(|| async { "@toomanylimits" })
get(api_info::limits)
) // Need more info :( TODO:
.route(
"/version",
get(|| async { "{\"release\":\"2.7.1\",\"prerelease\":\"2.7.1\"}" }),
get(api_info::version),
)
.route(
"/motd",
@ -82,28 +106,32 @@ async fn main() -> Result<()> {
)
.route(
"/equip",
post(|| async { "Do it! NOW!" })
) // set Equipped; TODO:
post(api_profile::equip_avatar)
)
.route(
"/:owner/:id",
get(|Path((owner, id)): Path<(String, String)>| async move {
format!("getting user {id}, owner {owner}")
}),
) // get Avatar
"/:uuid",
get(api_profile::user_info),
)
.route(
"/:avatar",
put(|Path(avatar): Path<String>| async move { format!("put {avatar}") }),
) // put Avatar
"/:uuid/avatar",
get(api_profile::download_avatar),
)
.route(
"/:avatar",
delete(|Path(avatar): Path<String>| async move { format!("delete {avatar}") }),
"/avatar",
put(api_profile::upload_avatar),
)
.route(
"/avatar",
delete(api_profile::delete_avatar),
); // delete Avatar
let app = Router::new()
.nest("/api", api)
.route("/api/", get(api_auth::status))
.route("/ws", get(handler))
.layer(TraceLayer::new_for_http().on_request(()))
.with_state(state);
.route_layer(from_extractor::<api_auth::Token>())
.with_state(state)
.layer(TraceLayer::new_for_http().on_request(()));
let listener = tokio::net::TcpListener::bind(listen).await?;
info!("Listening on {}", listener.local_addr()?);

117
src/profile.rs Normal file
View file

@ -0,0 +1,117 @@
use anyhow_http::{http_error_ret, response::Result};
use axum::{body::Bytes, debug_handler, extract::{Path, State}, Json};
use serde_json::{json, Value};
use tokio::{fs, io::{AsyncReadExt, BufWriter, self}};
use uuid::Uuid;
use crate::{utils::{calculate_file_sha256, format_uuid}, auth::Token, AppState};
#[debug_handler]
pub async fn user_info(
Path(uuid): Path<Uuid>,
State(_state): State<AppState>, // FIXME: Variable doesn't using!
) -> Json<Value> {
log::info!("Получение информации для {}",uuid);
let formatted_uuid = format_uuid(uuid);
let avatar_file = format!("avatars/{}.moon", formatted_uuid);
let mut user_info_response = json!({
"uuid": &formatted_uuid,
"rank": "default",
"equipped": [],
"lastUsed": "2024-05-11T22:20:48.884Z",
"equippedBadges": {
"special": [1,1,1,1,1,1],
"pride": [0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
},
"version": "0.1.4+1.20.1",
"banned": false
});
if fs::metadata(&avatar_file).await.is_ok() {
if let Some(equipped) = user_info_response.get_mut("equipped").and_then(Value::as_array_mut){
match calculate_file_sha256(&avatar_file){
Ok(hash) => {
equipped.push(json!({
"id": "avatar",
"owner": &formatted_uuid,
"hash": hash
}))
}
Err(_e) => {}
}
}
}
Json(user_info_response)
}
#[debug_handler]
pub async fn download_avatar(
Path(uuid): Path<Uuid>,
) -> Result<Vec<u8>> {
let uuid = format_uuid(uuid);
log::info!("Запрашиваем аватар: {}", uuid);
let mut file = if let Ok(file) = fs::File::open(format!("avatars/{}.moon", uuid)).await {
file
} else {
http_error_ret!(NOT_FOUND, "Ошибка! Данный аватар не существует!");
};
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).await?;
//match Body::from_file("avatars/74cf2ba3-f346-4dfe-b3b5-f453b9f5cc5e.moon").await {
// match Body::from_file(format!("avatars/{}.moon",uuid)).await {
// Ok(body) => Ok(Response::builder(StatusCode::Ok).body(body).build()),
// Err(e) => Err(e.into()),
// }
Ok(buffer)
}
#[debug_handler]
pub async fn upload_avatar(
Token(token): Token,
State(state): State<AppState>,
body: Bytes,
) -> Result<String> {
let request_data = body;
let token = match token {
Some(t) => t,
None => http_error_ret!(UNAUTHORIZED, "Ошибка аутентификации!"),
};
let userinfos = state.authenticated.lock().await;
if let Some(user_info) = userinfos.get(token.as_str()) {
log::info!("{} ({}) пытается загрузить аватар",user_info.uuid,user_info.username);
let avatar_file = format!("avatars/{}.moon",user_info.uuid);
let mut file = BufWriter::new(fs::File::create(&avatar_file).await?);
io::copy(&mut request_data.as_ref(), &mut file).await?;
}
Ok(format!("ok"))
}
pub async fn equip_avatar() -> String {
format!("ok")
}
pub async fn delete_avatar(
Token(token): Token,
State(state): State<AppState>,
) -> Result<String> {
let token = match token {
Some(t) => t,
None => http_error_ret!(UNAUTHORIZED, "Ошибка аутентификации!"),
};
let userinfos = state.authenticated.lock().await;
if let Some(user_info) = userinfos.get(token.as_str()) {
log::info!("{} ({}) пытается удалить аватар",user_info.uuid,user_info.username);
let avatar_file = format!("avatars/{}.moon",user_info.uuid);
fs::remove_file(avatar_file).await?;
}
// let avatar_file = format!("avatars/{}.moon",user_info.uuid);
Ok(format!("ok"))
}

59
src/utils.rs Normal file
View file

@ -0,0 +1,59 @@
use std::{fs::File, io::Read};
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use ring::digest::{self, digest};
use uuid::Uuid;
use base64::prelude::*;
// Кор функции
pub fn rand() -> [u8; 50] {
let mut rng = thread_rng();
let distr = rand::distributions::Uniform::new_inclusive(0, 255);
let mut nums: [u8; 50] = [0u8; 50];
for x in &mut nums {
*x = rng.sample(distr);
}
nums
}
pub fn bytes_into_string(code: &[u8]) -> String {
code.iter().map(|byte| format!("{:02x}", byte)).collect::<String>()
}
// Конец кор функций
pub fn _generate_hex_string(length: usize) -> String { // FIXME: Variable doesn't using!
let rng = thread_rng();
let random_bytes: Vec<u8> = rng
.sample_iter(&Alphanumeric)
.take(length / 2)
.collect();
hex::encode(random_bytes)
}
pub fn format_uuid(uuid: Uuid) -> String {
// let uuid = Uuid::parse_str(&uuid)?; TODO: Вероятно format_uuid стоит убрать
// .map_err(|_| tide::Error::from_str(StatusCode::InternalServerError, "Failed to parse UUID"))?;
uuid.as_hyphenated().to_string()
}
pub fn calculate_file_sha256(file_path: &str) -> Result<String, std::io::Error> {
// Read the file content
let mut file = File::open(file_path)?;
let mut content = Vec::new();
file.read_to_end(&mut content)?;
// Convert the content to base64
let base64_content = BASE64_STANDARD.encode(&content);
// Calculate the SHA-256 hash of the base64 string
let binding = digest(&digest::SHA256, base64_content.as_bytes());
let hash = binding.as_ref();
// Convert the hash to a hexadecimal string
let hex_hash = bytes_into_string(hash);
Ok(hex_hash)
}

View file

@ -1,3 +1,5 @@
use uuid::Uuid;
use super::MessageLoadError;
use std::convert::{TryFrom, TryInto};
@ -6,8 +8,8 @@ use std::convert::{TryFrom, TryInto};
pub enum C2SMessage<'a> {
Token(&'a [u8]) = 0,
Ping(u32, bool, &'a [u8]) = 1,
Sub(u128) = 2, // owo
Unsub(u128) = 3,
Sub(Uuid) = 2, // owo
Unsub(Uuid) = 3,
}
// 6 - 6
impl<'a> TryFrom<&'a [u8]> for C2SMessage<'a> {
@ -36,7 +38,7 @@ impl<'a> TryFrom<&'a [u8]> for C2SMessage<'a> {
}
2 => {
if buf.len() == 17 {
Ok(C2SMessage::Sub(u128::from_be_bytes(
Ok(C2SMessage::Sub(Uuid::from_bytes(
(&buf[1..]).try_into().unwrap(),
)))
} else {
@ -50,7 +52,7 @@ impl<'a> TryFrom<&'a [u8]> for C2SMessage<'a> {
}
3 => {
if buf.len() == 17 {
Ok(C2SMessage::Unsub(u128::from_be_bytes(
Ok(C2SMessage::Unsub(Uuid::from_bytes(
(&buf[1..]).try_into().unwrap(),
)))
} else {
@ -81,8 +83,23 @@ impl<'a> Into<Box<[u8]>> for C2SMessage<'a> {
.chain(iter::once(s.into()))
.chain(d.into_iter().copied())
.collect(),
C2SMessage::Sub(s) => iter::once(2).chain(s.to_be_bytes()).collect(),
C2SMessage::Unsub(s) => iter::once(3).chain(s.to_be_bytes()).collect(),
C2SMessage::Sub(s) => iter::once(2).chain(s.into_bytes()).collect(),
C2SMessage::Unsub(s) => iter::once(3).chain(s.into_bytes()).collect(),
};
a
}
}
impl<'a> C2SMessage<'a> {
pub fn ping_data(self) -> Box<[u8]> {
use std::iter;
let a: Box<[u8]> = match self {
C2SMessage::Ping(p, s, d) => iter::empty()
.chain(p.to_be_bytes())
.chain(iter::once(s.into()))
.chain(d.into_iter().copied())
.collect(),
_ => todo!() // FIXME: Это всё нихеровых размеров костыль!
};
a
}

View file

@ -1,51 +1,219 @@
use axum::{extract::{ws::{Message, WebSocket}, WebSocketUpgrade}, response::Response};
use log::{error, info, warn};
use std::sync::Arc;
use crate::ws::C2SMessage;
use axum::{extract::{ws::{Message, WebSocket}, State, WebSocketUpgrade}, response::Response};
use dashmap::DashMap;
use log::{debug, error, info, log, warn};
use tokio::sync::{broadcast::{self, Receiver}, mpsc, Notify};
use uuid::Uuid;
pub async fn handler(ws: WebSocketUpgrade) -> Response {
ws.on_upgrade(handle_socket)
use crate::{ws::{C2SMessage, S2CMessage}, AppState};
pub async fn handler(
ws: WebSocketUpgrade,
State(state): State<AppState>,
) -> Response {
ws.on_upgrade(|socket| handle_socket(socket, state))
}
async fn handle_socket(mut socket: WebSocket) {
while let Some(msg) = socket.recv().await {
info!("{msg:?}");
let mut msg = if let Ok(msg) = msg {
msg
#[derive(Debug, Clone)]
struct WSOwner(Option<WSUser>);
#[derive(Debug, Clone)]
struct WSUser {
username: String,
token: String,
uuid: Uuid,
}
impl WSOwner {
fn name(&self) -> String {
if let Some(user) = &self.0 {
format!(" ({})", user.username)
} else {
// if reached here - client disconnected
warn!("ws disconnected!");
return;
};
// Work with code here
let msg_array = msg.clone().into_data();
let msg_array = msg_array.as_slice();
let newmsg = match C2SMessage::try_from(msg_array) {
Ok(data) => data,
Err(e) => {
error!("MessageLoadError: {e:?}");
return;
},
};
match newmsg {
C2SMessage::Token(token) => {
// TODO: Authenticated check
msg = Message::Binary(vec![0])
},
// C2SMessage::Ping(_, _, _) => todo!(),
// C2SMessage::Sub(_) => todo!(),
// C2SMessage::Unsub(_) => todo!(),
_ => ()
}
info!("{newmsg:?}");
if socket.send(msg).await.is_err() {
// if reached here - client disconnected
warn!("ws disconnected!");
return;
String::new()
}
}
}
async fn handle_socket(mut socket: WebSocket, state: AppState) {
let mut owner = WSOwner(None);
let cutoff: DashMap<Uuid, Arc<Notify>> = DashMap::new();
let (mtx, mut mrx) = mpsc::channel(64);
// let (bctx, mut _bcrx) = broadcast::channel(64);
let mut bctx: Option<broadcast::Sender<Vec<u8>>> = None;
loop {
tokio::select! {
Some(msg) = socket.recv() => {
debug!("[WebSocket{}] Raw: {msg:?}", owner.name());
let mut msg = if let Ok(msg) = msg {
if let Message::Close(_) = msg {
info!("[WebSocket{}] Соединение удачно закрыто!", owner.name());
if let Some(u) = owner.0 {
remove_broadcast(state.broadcasts.clone(), u.uuid).await;
}
return;
}
msg
} else {
// если попали сюда, значит вероятнее всего клиент отключился
warn!("[WebSocket{}] Ошибка получения! Соединение разорвано!", owner.name());
if let Some(u) = owner.0 {
remove_broadcast(state.broadcasts.clone(), u.uuid).await;
}
return;
};
// Далее код для обработки msg
let msg_vec = msg.clone().into_data();
let msg_array = msg_vec.as_slice();
let newmsg = match C2SMessage::try_from(msg_array) {
Ok(data) => data,
Err(e) => {
error!("[WebSocket{}] Это сообщение не от Figura! {e:?}", owner.name());
if let Some(u) = owner.0 {
remove_broadcast(state.broadcasts.clone(), u.uuid).await;
}
return;
},
};
info!("[WebSocket{}] Данные: {newmsg:?}", owner.name());
match newmsg {
C2SMessage::Token(token) => { // FIXME: Написать переменную спомощью которой бужет проверяться авторизовался ли пользователь или нет
info!("[WebSocket{}] Token", owner.name());
let token = String::from_utf8(token.to_vec()).unwrap();
let authenticated = state.authenticated.lock().await;
match authenticated.get(&token) { // Принцип прост: если токена в authenticated нет, значит это trash
Some(t) => {
//username = t.username.clone();
owner.0 = Some(WSUser { username: t.username.clone(), token, uuid: t.uuid });
msg = Message::Binary(S2CMessage::Auth.to_vec());
let bcs = state.broadcasts.lock().await;
match bcs.get(&t.uuid) {
Some(tx) => {
bctx = Some(tx.to_owned());
},
None => {
let (tx, _rx) = broadcast::channel(64);
bcs.insert(t.uuid, tx.clone());
bctx = Some(tx.to_owned());
},
};
},
None => {
warn!("[WebSocket] Ошибка авторизации! Соединение разорвано! {token}");
if let Some(u) = owner.0 {
remove_broadcast(state.broadcasts.clone(), u.uuid).await;
}
return; // TODO: Прописать код отключения
},
};
},
C2SMessage::Ping(_, _, _) => {
info!("[WebSocket{}] Ping", owner.name());
let data = into_s2c_ping(msg_vec, owner.0.clone().unwrap().uuid);
info!("Im gotcha homie! {:?}", data);
match bctx.clone().unwrap().send(data) {
Ok(_) => (),
Err(_) => error!("[WebSocket{}] Не удалось отправить Пинг!", owner.name()),
};
continue;
},
C2SMessage::Sub(uuid) => { // FIXME: Исключить возможность использования SUB без авторизации
info!("[WebSocket{}] Sub", owner.name());
// Отбрасываю Sub на самого себя
if uuid == owner.0.clone().unwrap().uuid {
continue;
};
let broadcast = state.broadcasts.lock().await;
let rx = match broadcast.get(&uuid) {
Some(rx) => rx.to_owned().subscribe(),
None => {
warn!("Внимание! Необходимый UUID для подписки не найден!");
let (tx, rx) = broadcast::channel(64);
broadcast.insert(uuid, tx);
rx
},
};
// .to_owned().subscribe();
let shutdown = Arc::new(Notify::new());
tokio::spawn(subscribe(mtx.clone(), rx, shutdown.clone()));
cutoff.insert(uuid, shutdown);
continue;
},
C2SMessage::Unsub(uuid) => {
info!("[WebSocket{}] Unsub", owner.name());
// Отбрасываю Unsub на самого себя
if uuid == owner.0.clone().unwrap().uuid {
continue;
};
let shutdown = cutoff.remove(&uuid).unwrap().1;
shutdown.notify_one();
continue;
},
// _ => continue
}
// Отправка сообщения
warn!("[WebSocket{}] Отвечаю: {msg:?}", owner.name());
if socket.send(msg).await.is_err() {
// если попали сюда, значит вероятнее всего клиент отключился
warn!("[WebSocket{}] Ошибка отправки! Соединение разорвано!", owner.name());
if let Some(u) = owner.0 {
remove_broadcast(state.broadcasts.clone(), u.uuid).await;
}
return;
}
}
msg = mrx.recv() => {
match socket.send(Message::Binary(msg.clone().unwrap())).await {
Ok(_) => {
warn!("[WebSocketSubscribe{}] Отвечаю: {}", owner.name(), hex::encode(msg.unwrap()));
}
Err(_) => {
// если попали сюда, значит вероятнее всего клиент отключился
warn!("[WebSocketSubscriber{}] Ошибка отправки! Соединение разорвано!", owner.name());
if let Some(u) = owner.0 {
remove_broadcast(state.broadcasts.clone(), u.uuid).await;
}
return;
}
}
}
}
}
}
async fn subscribe(socket: mpsc::Sender<Vec<u8>>, mut rx: Receiver<Vec<u8>>, shutdown: Arc<Notify>) {
loop {
tokio::select! {
_ = shutdown.notified() => {
debug!("Unsubscribing!");
return;
}
msg = rx.recv() => {
socket.send(msg.unwrap()).await.unwrap();
}
}
}
}
fn into_s2c_ping(buf: Vec<u8>, uuid: Uuid) -> Vec<u8> {
use std::iter::once;
// let mut vec = Vec::new();
// vec
//let uuid = uuid.as_u128();
//let uuid = uuid.into_bytes();
// info!("UUID {} UUID BE {}", hex::encode(uuid.into_bytes()), hex::encode(uuid128.to_be_bytes()));
let res: Vec<u8> = once(1).chain(uuid.into_bytes().iter().copied()).chain(buf.as_slice()[1..].iter().copied()).collect();
debug!("Sending ping: {}", hex::encode(res.clone()));
res
// vec
}
async fn remove_broadcast(broadcasts: Arc<tokio::sync::Mutex<DashMap<Uuid, broadcast::Sender<Vec<u8>>>>>, uuid: Uuid) {
let map = broadcasts.lock().await;
map.remove(&uuid);
}

View file

@ -1,3 +1,6 @@
use log::debug;
use uuid::Uuid;
use super::MessageLoadError;
use std::convert::{TryFrom, TryInto};
@ -5,8 +8,8 @@ use std::convert::{TryFrom, TryInto};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum S2CMessage<'a> {
Auth = 0,
Ping(u128, u32, bool, &'a [u8]) = 1,
Event(u128) = 2,
Ping(Uuid, u32, bool, &'a [u8]) = 1,
Event(Uuid) = 2, // UUID Обновляет аватар других игроков
Toast(u8, &'a str, Option<&'a str>) = 3,
Chat(&'a str) = 4,
Notice(u8) = 5,
@ -30,7 +33,7 @@ impl<'a> TryFrom<&'a [u8]> for S2CMessage<'a> {
1 => {
if buf.len() >= 22 {
Ok(Ping(
u128::from_be_bytes((&buf[1..17]).try_into().unwrap()),
Uuid::from_bytes((&buf[1..17]).try_into().unwrap()),
u32::from_be_bytes((&buf[17..21]).try_into().unwrap()),
buf[21] != 0,
&buf[22..],
@ -41,7 +44,7 @@ impl<'a> TryFrom<&'a [u8]> for S2CMessage<'a> {
}
2 => {
if buf.len() == 17 {
Ok(Event(u128::from_be_bytes(
Ok(Event(Uuid::from_bytes(
(&buf[1..17]).try_into().unwrap(),
)))
} else {
@ -63,12 +66,12 @@ impl<'a> Into<Box<[u8]>> for S2CMessage<'a> {
match self {
Auth => Box::new([0]),
Ping(u, i, s, d) => once(1)
.chain(u.to_be_bytes().iter().copied())
.chain(u.into_bytes().iter().copied())
.chain(i.to_be_bytes().iter().copied())
.chain(once(if s { 1 } else { 0 }))
.chain(d.into_iter().copied())
.collect(),
Event(u) => once(2).chain(u.to_be_bytes().iter().copied()).collect(),
Event(u) => once(2).chain(u.into_bytes().iter().copied()).collect(),
Toast(t, h, d) => once(3)
.chain(once(t))
.chain(h.as_bytes().into_iter().copied())
@ -82,4 +85,23 @@ impl<'a> Into<Box<[u8]>> for S2CMessage<'a> {
Notice(t) => Box::new([5, t]),
}
}
}
impl<'a> S2CMessage<'a> {
pub fn to_s2c_ping(uuid: Uuid, buf: &'a [u8]) -> S2CMessage<'a> {
use S2CMessage::Ping;
debug!("!!! {buf:?}");
Ping(
uuid,
u32::from_be_bytes((&buf[1..5]).try_into().unwrap()),
buf[5] != 0, // Ping может быть короче чем ожидалось
&buf[6..],
)
}
pub fn to_array(self) -> Box<[u8]> {
<S2CMessage as Into<Box<[u8]>>>::into(self)
}
pub fn to_vec(self) -> Vec<u8> {
self.to_array().to_vec()
}
}