use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::player::PlayerId;
use crate::server::ServerId;
use crate::spatial::WorldCoordinate;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransferToken {
pub token_id: String,
pub player_id: PlayerId,
pub source_server: ServerId,
pub target_server: ServerId,
pub target_address: String,
pub created_at_ms: u64,
pub expires_at_ms: u64,
pub signature: String,
}
const DEFAULT_TOKEN_VALIDITY_SECS: u64 = 60;
impl TransferToken {
pub fn new(
player_id: PlayerId,
source_server: ServerId,
target_server: ServerId,
target_address: String,
secret_key: &[u8],
) -> Self {
Self::with_validity(player_id, source_server, target_server, target_address, DEFAULT_TOKEN_VALIDITY_SECS, secret_key)
}
pub fn with_validity(
player_id: PlayerId,
source_server: ServerId,
target_server: ServerId,
target_address: String,
valid_duration_secs: u64,
secret_key: &[u8],
) -> Self {
let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let token_id = format!("txfr-{}-{}", now_ms, Self::rand_component());
let expires_at_ms = now_ms + (valid_duration_secs * 1000);
let mut token = Self {
token_id,
player_id,
source_server,
target_server,
target_address,
created_at_ms: now_ms,
expires_at_ms,
signature: String::new(),
};
token.signature = token.compute_signature(secret_key);
token
}
pub fn verify(&self, secret_key: &[u8]) -> Result<(), TransferError> {
let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
if now_ms > self.expires_at_ms {
return Err(TransferError::TokenExpired);
}
let expected_signature = self.compute_signature(secret_key);
if self.signature != expected_signature {
return Err(TransferError::InvalidSignature);
}
Ok(())
}
fn compute_signature(&self, key: &[u8]) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let data = format!(
"{}:{}:{}:{}:{}",
self.token_id, self.player_id, self.source_server, self.target_server, self.expires_at_ms
);
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
key.hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
fn rand_component() -> u32 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
std::time::Instant::now().hash(&mut hasher);
(hasher.finish() % 1000000) as u32
}
pub fn to_json(&self) -> Result<String, TransferError> {
serde_json::to_string(self)
.map_err(|e| TransferError::SerializationError(e.to_string()))
}
pub fn from_json(json: &str) -> Result<Self, TransferError> {
serde_json::from_str(json)
.map_err(|e| TransferError::SerializationError(e.to_string()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransferRequest {
pub player_id: PlayerId,
pub source_server: ServerId,
pub target_server: ServerId,
pub target_position: WorldCoordinate,
pub reason: TransferReason,
pub priority: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TransferReason {
RegionBoundary,
LoadBalancing,
ServerShutdown,
AdminAction,
Teleport,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransferResult {
pub success: bool,
pub token: Option<TransferToken>,
pub error: Option<TransferError>,
pub duration_ms: u64,
}
impl TransferResult {
pub fn success(token: TransferToken, duration_ms: u64) -> Self {
Self {
success: true,
token: Some(token),
error: None,
duration_ms,
}
}
pub fn failure(error: TransferError) -> Self {
Self {
success: false,
token: None,
error: Some(error),
duration_ms: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
pub enum TransferError {
#[error("Target server unavailable: {0}")]
TargetServerUnavailable(String),
#[error("Player not found: {0}")]
PlayerNotFound(String),
#[error("Transfer token expired")]
TokenExpired,
#[error("Invalid token signature")]
InvalidSignature,
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Transfer already in progress for player")]
TransferInProgress,
#[error("Transfer rejected: {0}")]
TransferRejected(String),
#[error("Network error: {0}")]
NetworkError(String),
#[error("Transfer timeout")]
Timeout,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransferNotification {
pub player_id: PlayerId,
pub target_address: String,
pub token: String,
pub reconnect_delay_ms: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transfer_token_creation_and_verification() {
let player_id = PlayerId::new();
let source = ServerId::new();
let target = ServerId::new();
let secret = b"test_secret_key";
let token = TransferToken::new(
player_id.clone(),
source,
target,
"127.0.0.1:8080".to_string(),
secret
);
assert!(token.verify(secret).is_ok());
}
#[test]
fn test_transfer_token_invalid_signature() {
let player_id = PlayerId::new();
let source = ServerId::new();
let target = ServerId::new();
let token = TransferToken::new(
player_id,
source,
target,
"127.0.0.1:8080".to_string(),
b"key1"
);
assert!(matches!(
token.verify(b"wrong_key"),
Err(TransferError::InvalidSignature)
));
}
}