feat: scaffold relay client auth workspace
This commit is contained in:
11
common/Cargo.toml
Normal file
11
common/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "common"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tokio.workspace = true
|
||||
thiserror.workspace = true
|
||||
uuid.workspace = true
|
||||
42
common/src/codec.rs
Normal file
42
common/src/codec.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::io;
|
||||
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
|
||||
const MAX_FRAME_SIZE: usize = 1024 * 1024;
|
||||
|
||||
pub async fn write_frame<W, T>(writer: &mut W, value: &T) -> io::Result<()>
|
||||
where
|
||||
W: AsyncWrite + Unpin,
|
||||
T: Serialize,
|
||||
{
|
||||
let payload = serde_json::to_vec(value)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
|
||||
if payload.len() > MAX_FRAME_SIZE {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"frame exceeds max size",
|
||||
));
|
||||
}
|
||||
writer.write_u32(payload.len() as u32).await?;
|
||||
writer.write_all(&payload).await?;
|
||||
writer.flush().await
|
||||
}
|
||||
|
||||
pub async fn read_frame<R, T>(reader: &mut R) -> io::Result<T>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let len = reader.read_u32().await? as usize;
|
||||
if len > MAX_FRAME_SIZE {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"frame too large",
|
||||
));
|
||||
}
|
||||
let mut payload = vec![0u8; len];
|
||||
reader.read_exact(&mut payload).await?;
|
||||
serde_json::from_slice(&payload)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))
|
||||
}
|
||||
3
common/src/lib.rs
Normal file
3
common/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod codec;
|
||||
pub mod minecraft;
|
||||
pub mod protocol;
|
||||
106
common/src/minecraft.rs
Normal file
106
common/src/minecraft.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use std::io;
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
|
||||
pub async fn read_handshake_hostname<R>(reader: &mut R) -> io::Result<String>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
{
|
||||
let (hostname, _) = read_handshake_hostname_and_bytes(reader).await?;
|
||||
Ok(hostname)
|
||||
}
|
||||
|
||||
pub async fn read_handshake_hostname_and_bytes<R>(reader: &mut R) -> io::Result<(String, Vec<u8>)>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
{
|
||||
let (packet_len, packet_len_bytes) = read_varint_async_with_bytes(reader).await?;
|
||||
let packet_len = packet_len as usize;
|
||||
if packet_len == 0 || packet_len > 2048 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"invalid minecraft handshake packet length",
|
||||
));
|
||||
}
|
||||
|
||||
let mut buf = vec![0u8; packet_len];
|
||||
reader.read_exact(&mut buf).await?;
|
||||
let mut cur = &buf[..];
|
||||
|
||||
let packet_id = read_varint_slice(&mut cur)?;
|
||||
if packet_id != 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"expected handshake packet id 0",
|
||||
));
|
||||
}
|
||||
|
||||
let _protocol_version = read_varint_slice(&mut cur)?;
|
||||
let host = read_string_slice(&mut cur, 255)?;
|
||||
if cur.len() < 2 {
|
||||
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "missing port"));
|
||||
}
|
||||
let _port = u16::from_be_bytes([cur[0], cur[1]]);
|
||||
cur = &cur[2..];
|
||||
let _next_state = read_varint_slice(&mut cur)?;
|
||||
|
||||
let mut raw = packet_len_bytes;
|
||||
raw.extend_from_slice(&buf);
|
||||
Ok((host, raw))
|
||||
}
|
||||
|
||||
async fn read_varint_async_with_bytes<R>(reader: &mut R) -> io::Result<(i32, Vec<u8>)>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
{
|
||||
let mut num_read = 0;
|
||||
let mut result = 0i32;
|
||||
let mut raw = Vec::with_capacity(5);
|
||||
loop {
|
||||
let byte = reader.read_u8().await?;
|
||||
raw.push(byte);
|
||||
let value = (byte & 0x7F) as i32;
|
||||
result |= value << (7 * num_read);
|
||||
num_read += 1;
|
||||
if num_read > 5 {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "varint too big"));
|
||||
}
|
||||
if (byte & 0x80) == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok((result, raw))
|
||||
}
|
||||
|
||||
fn read_varint_slice(input: &mut &[u8]) -> io::Result<i32> {
|
||||
let mut num_read = 0;
|
||||
let mut result = 0i32;
|
||||
loop {
|
||||
if input.is_empty() {
|
||||
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "eof"));
|
||||
}
|
||||
let byte = input[0];
|
||||
*input = &input[1..];
|
||||
let value = (byte & 0x7F) as i32;
|
||||
result |= value << (7 * num_read);
|
||||
num_read += 1;
|
||||
if num_read > 5 {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "varint too big"));
|
||||
}
|
||||
if (byte & 0x80) == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn read_string_slice(input: &mut &[u8], max_len: usize) -> io::Result<String> {
|
||||
let len = read_varint_slice(input)? as usize;
|
||||
if len > max_len || input.len() < len {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "bad string len"));
|
||||
}
|
||||
let bytes = &input[..len];
|
||||
*input = &input[len..];
|
||||
String::from_utf8(bytes.to_vec())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))
|
||||
}
|
||||
69
common/src/protocol.rs
Normal file
69
common/src/protocol.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RegisterRequest {
|
||||
pub token: String,
|
||||
pub region: String,
|
||||
pub requested_subdomain: Option<String>,
|
||||
pub local_addr: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RegisterAccepted {
|
||||
pub session_id: String,
|
||||
pub fqdn: String,
|
||||
pub heartbeat_interval_secs: u64,
|
||||
pub owner_instance_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Heartbeat {
|
||||
pub session_id: String,
|
||||
pub active_streams: u32,
|
||||
pub bytes_in: u64,
|
||||
pub bytes_out: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IncomingTcp {
|
||||
pub stream_id: String,
|
||||
pub session_id: String,
|
||||
pub peer_addr: String,
|
||||
pub hostname: String,
|
||||
pub initial_data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StreamData {
|
||||
pub stream_id: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StreamClosed {
|
||||
pub stream_id: String,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum ClientFrame {
|
||||
Register(RegisterRequest),
|
||||
Heartbeat(Heartbeat),
|
||||
StreamData(StreamData),
|
||||
StreamClosed(StreamClosed),
|
||||
Pong,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum ServerFrame {
|
||||
RegisterAccepted(RegisterAccepted),
|
||||
RegisterRejected { reason: String },
|
||||
IncomingTcp(IncomingTcp),
|
||||
StreamData(StreamData),
|
||||
StreamClosed(StreamClosed),
|
||||
Ping,
|
||||
DrainNotice { retry_after_ms: u64, reason: String },
|
||||
Error { message: String },
|
||||
}
|
||||
Reference in New Issue
Block a user