feat:移除了弹窗,服务器添加sls
This commit is contained in:
248
packages/ariadne/src/ids.rs
Normal file
248
packages/ariadne/src/ids.rs
Normal file
@ -0,0 +1,248 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// Generates a random 64 bit integer that is exactly `n` characters
|
||||
/// long when encoded as base62.
|
||||
///
|
||||
/// Uses `rand`'s thread rng on every call.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method panics if `n` is 0 or greater than 11, since a `u64`
|
||||
/// can only represent up to 11 character base62 strings
|
||||
#[inline]
|
||||
pub fn random_base62(n: usize) -> u64 {
|
||||
random_base62_rng(&mut rand::thread_rng(), n)
|
||||
}
|
||||
|
||||
/// Generates a random 64 bit integer that is exactly `n` characters
|
||||
/// long when encoded as base62, using the given rng.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method panics if `n` is 0 or greater than 11, since a `u64`
|
||||
/// can only represent up to 11 character base62 strings
|
||||
pub fn random_base62_rng<R: rand::RngCore>(rng: &mut R, n: usize) -> u64 {
|
||||
random_base62_rng_range(rng, n, n)
|
||||
}
|
||||
|
||||
pub fn random_base62_rng_range<R: rand::RngCore>(
|
||||
rng: &mut R,
|
||||
n_min: usize,
|
||||
n_max: usize,
|
||||
) -> u64 {
|
||||
use rand::Rng;
|
||||
assert!(n_min > 0 && n_max <= 11 && n_min <= n_max);
|
||||
// random_range is [low, high): max value is `MULTIPLES[n] - 1`,
|
||||
// which is n characters long when encoded
|
||||
rng.gen_range(MULTIPLES[n_min - 1]..MULTIPLES[n_max])
|
||||
}
|
||||
|
||||
const MULTIPLES: [u64; 12] = [
|
||||
1,
|
||||
62,
|
||||
62 * 62,
|
||||
62 * 62 * 62,
|
||||
62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
|
||||
u64::MAX,
|
||||
];
|
||||
|
||||
/// An ID encoded as base62 for use in the API.
|
||||
///
|
||||
/// All ids should be random and encode to 8-10 character base62 strings,
|
||||
/// to avoid enumeration and other attacks.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct Base62Id(pub u64);
|
||||
|
||||
impl utoipa::PartialSchema for Base62Id {
|
||||
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
|
||||
utoipa::openapi::ObjectBuilder::new()
|
||||
.schema_type(utoipa::openapi::schema::Type::String)
|
||||
.min_length(Some(8))
|
||||
.max_length(Some(8))
|
||||
.pattern(Some("^[A-Za-z0-9]{8}$"))
|
||||
.examples([serde_json::json!("ABcd1234")])
|
||||
.build()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl utoipa::ToSchema for Base62Id {}
|
||||
|
||||
/// An error decoding a number from base62.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DecodingError {
|
||||
/// Encountered a non-base62 character in a base62 string
|
||||
#[error("Invalid character {0:?} in base62 encoding")]
|
||||
InvalidBase62(char),
|
||||
/// Encountered integer overflow when decoding a base62 id.
|
||||
#[error("Base62 decoding overflowed")]
|
||||
Overflow,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_base62_display {
|
||||
($struct:ty) => {
|
||||
impl std::fmt::Display for $struct {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&$crate::ids::base62_impl::to_base62(self.0))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_base62_display!(Base62Id);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! base62_id {
|
||||
($struct:ident) => {
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
Debug,
|
||||
Hash,
|
||||
)]
|
||||
#[serde(from = "ariadne::ids::Base62Id")]
|
||||
#[serde(into = "ariadne::ids::Base62Id")]
|
||||
pub struct $struct(pub u64);
|
||||
|
||||
impl utoipa::PartialSchema for $struct {
|
||||
fn schema()
|
||||
-> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
|
||||
<$crate::ids::Base62Id as utoipa::PartialSchema>::schema()
|
||||
}
|
||||
}
|
||||
|
||||
impl utoipa::ToSchema for $struct {}
|
||||
|
||||
$crate::ids::impl_base62_display!($struct);
|
||||
|
||||
impl From<$crate::ids::Base62Id> for $struct {
|
||||
fn from(id: $crate::ids::Base62Id) -> Self {
|
||||
Self(id.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$struct> for $crate::ids::Base62Id {
|
||||
fn from(id: $struct) -> Self {
|
||||
Self(id.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
use crate as ariadne; // Hack because serde(from) and serde(into) don't work with $crate
|
||||
base62_id!(UserId);
|
||||
|
||||
pub use {base62_id, impl_base62_display};
|
||||
|
||||
pub mod base62_impl {
|
||||
use serde::de::{self, Deserializer, Visitor};
|
||||
use serde::ser::Serializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Base62Id, DecodingError};
|
||||
|
||||
impl<'de> Deserialize<'de> for Base62Id {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct Base62Visitor;
|
||||
|
||||
impl Visitor<'_> for Base62Visitor {
|
||||
type Value = Base62Id;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut std::fmt::Formatter,
|
||||
) -> std::fmt::Result {
|
||||
formatter.write_str("a base62 string id")
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(Base62Id(v))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, string: &str) -> Result<Base62Id, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
parse_base62(string).map(Base62Id).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
if deserializer.is_human_readable() {
|
||||
deserializer.deserialize_str(Base62Visitor)
|
||||
} else {
|
||||
deserializer.deserialize_u64(Base62Visitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Base62Id {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if serializer.is_human_readable() {
|
||||
serializer.serialize_str(&to_base62(self.0))
|
||||
} else {
|
||||
serializer.serialize_u64(self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BASE62_CHARS: [u8; 62] =
|
||||
*b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
pub fn to_base62(mut num: u64) -> String {
|
||||
let length = (num as f64).log(62.0).ceil() as usize;
|
||||
let mut output = String::with_capacity(length);
|
||||
|
||||
while num > 0 {
|
||||
// Could be done more efficiently, but requires byte
|
||||
// manipulation of strings & Vec<u8> -> String conversion
|
||||
output.insert(0, BASE62_CHARS[(num % 62) as usize] as char);
|
||||
num /= 62;
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub fn parse_base62(string: &str) -> Result<u64, DecodingError> {
|
||||
let mut num: u64 = 0;
|
||||
for c in string.chars() {
|
||||
let next_digit;
|
||||
if c.is_ascii_digit() {
|
||||
next_digit = (c as u8 - b'0') as u64;
|
||||
} else if c.is_ascii_uppercase() {
|
||||
next_digit = 10 + (c as u8 - b'A') as u64;
|
||||
} else if c.is_ascii_lowercase() {
|
||||
next_digit = 36 + (c as u8 - b'a') as u64;
|
||||
} else {
|
||||
return Err(DecodingError::InvalidBase62(c));
|
||||
}
|
||||
|
||||
// We don't want this panicking or wrapping on integer overflow
|
||||
if let Some(n) =
|
||||
num.checked_mul(62).and_then(|n| n.checked_add(next_digit))
|
||||
{
|
||||
num = n;
|
||||
} else {
|
||||
return Err(DecodingError::Overflow);
|
||||
}
|
||||
}
|
||||
Ok(num)
|
||||
}
|
||||
}
|
||||
4
packages/ariadne/src/lib.rs
Normal file
4
packages/ariadne/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod ids;
|
||||
pub mod networking;
|
||||
pub mod users;
|
||||
pub mod versions;
|
||||
65
packages/ariadne/src/networking/message.rs
Normal file
65
packages/ariadne/src/networking/message.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use crate::ids::UserId;
|
||||
use crate::users::UserStatus;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ClientToServerMessage {
|
||||
StatusUpdate {
|
||||
profile_name: Option<String>,
|
||||
},
|
||||
|
||||
SocketListen {
|
||||
socket: Uuid,
|
||||
},
|
||||
SocketClose {
|
||||
socket: Uuid,
|
||||
},
|
||||
SocketSend {
|
||||
socket: Uuid,
|
||||
#[serde(with = "serde_bytes")]
|
||||
data: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ServerToClientMessage {
|
||||
StatusUpdate {
|
||||
status: UserStatus,
|
||||
},
|
||||
UserOffline {
|
||||
id: UserId,
|
||||
},
|
||||
FriendStatuses {
|
||||
statuses: Vec<UserStatus>,
|
||||
},
|
||||
FriendRequest {
|
||||
from: UserId,
|
||||
},
|
||||
FriendRequestRejected {
|
||||
from: UserId,
|
||||
},
|
||||
|
||||
FriendSocketListening {
|
||||
user: UserId,
|
||||
socket: Uuid,
|
||||
},
|
||||
FriendSocketStoppedListening {
|
||||
user: UserId,
|
||||
},
|
||||
|
||||
SocketConnected {
|
||||
to_socket: Uuid,
|
||||
new_socket: Uuid,
|
||||
},
|
||||
SocketClosed {
|
||||
socket: Uuid,
|
||||
},
|
||||
SocketData {
|
||||
socket: Uuid,
|
||||
#[serde(with = "serde_bytes")]
|
||||
data: Vec<u8>,
|
||||
},
|
||||
}
|
||||
2
packages/ariadne/src/networking/mod.rs
Normal file
2
packages/ariadne/src/networking/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod message;
|
||||
pub mod serialization;
|
||||
56
packages/ariadne/src/networking/serialization.rs
Normal file
56
packages/ariadne/src/networking/serialization.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use super::message::{ClientToServerMessage, ServerToClientMessage};
|
||||
use either::Either;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SerializationError {
|
||||
#[error("Failed to (de)serialize message: {0}")]
|
||||
SerializationFailed(#[from] serde_json::Error),
|
||||
|
||||
#[error("Failed to (de)serialize binary message: {0}")]
|
||||
BinarySerializationFailed(#[from] serde_cbor::Error),
|
||||
}
|
||||
|
||||
macro_rules! message_serialization {
|
||||
($message_enum:ty $(,$binary_pattern:pat_param)* $(,)?) => {
|
||||
impl $message_enum {
|
||||
pub fn is_binary(&self) -> bool {
|
||||
match self {
|
||||
$(
|
||||
$binary_pattern => true,
|
||||
)*
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize(
|
||||
&self,
|
||||
) -> Result<Either<String, Vec<u8>>, SerializationError> {
|
||||
Ok(match self {
|
||||
$(
|
||||
$binary_pattern => Either::Right(serde_cbor::to_vec(self)?),
|
||||
)*
|
||||
_ => Either::Left(serde_json::to_string(self)?),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deserialize(
|
||||
msg: Either<&str, &[u8]>,
|
||||
) -> Result<Self, SerializationError> {
|
||||
Ok(match msg {
|
||||
Either::Left(text) => serde_json::from_str(&text)?,
|
||||
Either::Right(bytes) => serde_cbor::from_slice(&bytes)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
message_serialization!(
|
||||
ClientToServerMessage,
|
||||
ClientToServerMessage::SocketSend { .. },
|
||||
);
|
||||
message_serialization!(
|
||||
ServerToClientMessage,
|
||||
ServerToClientMessage::SocketData { .. },
|
||||
);
|
||||
10
packages/ariadne/src/users.rs
Normal file
10
packages/ariadne/src/users.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use super::ids::UserId;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct UserStatus {
|
||||
pub user_id: UserId,
|
||||
pub profile_name: Option<String>,
|
||||
pub last_update: DateTime<Utc>,
|
||||
}
|
||||
44
packages/ariadne/src/versions.rs
Normal file
44
packages/ariadne/src/versions.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use std::{collections::HashMap, sync::LazyLock};
|
||||
|
||||
static SPECIAL_PARENTS: LazyLock<HashMap<&'static str, &'static str>> =
|
||||
LazyLock::new(|| {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("15w14a", "1.8.3");
|
||||
m.insert("1.RV-Pre1", "1.9.2");
|
||||
m.insert("3D Shareware v1.34", "19w13b");
|
||||
m.insert("20w14infinite", "20w13b");
|
||||
m.insert("22w13oneblockatatime", "1.18.2");
|
||||
m.insert("23w13a_or_b", "23w13a");
|
||||
m.insert("24w14potato", "24w12a");
|
||||
m
|
||||
});
|
||||
|
||||
pub fn is_feature_supported_in(
|
||||
version: &str,
|
||||
first_release: &str,
|
||||
first_snapshot: &str,
|
||||
) -> bool {
|
||||
let version = SPECIAL_PARENTS.get(version).copied().unwrap_or(version);
|
||||
if version.contains('w') && version.len() == 6 {
|
||||
return version >= first_snapshot;
|
||||
}
|
||||
if version == first_release {
|
||||
return true;
|
||||
}
|
||||
let parts_version = version.split('.');
|
||||
let parts_first_release = first_release.split('.');
|
||||
for (part_version, part_first_release) in
|
||||
parts_version.zip(parts_first_release)
|
||||
{
|
||||
if part_version == part_first_release {
|
||||
continue;
|
||||
}
|
||||
if let Ok(part_version) = part_version.parse::<u32>()
|
||||
&& let Ok(part_first_release) = part_first_release.parse::<u32>()
|
||||
&& part_version > part_first_release
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
Reference in New Issue
Block a user