feat: scaffold relay client auth workspace

This commit is contained in:
L
2026-02-23 23:18:56 +00:00
parent b4942e4ab1
commit 050dbc792a
18 changed files with 3724 additions and 0 deletions

11
common/Cargo.toml Normal file
View 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
View 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
View File

@@ -0,0 +1,3 @@
pub mod codec;
pub mod minecraft;
pub mod protocol;

106
common/src/minecraft.rs Normal file
View 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
View 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 },
}