use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc, Duration};
use crate::player::{PlayerId, PlayerState};
use crate::server::ServerId;
use crate::spatial::WorldCoordinate;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransferToken {
pub token_id: Uuid,
pub player_id: PlayerId,
pub source_server: ServerId,
pub target_server: ServerId,
pub created_at: DateTime<Utc>,
pub expires_at: DateTime<Utc>,
pub signature: String,
pub player_state: String,
}
impl TransferToken {
pub fn new(
player_id: PlayerId,
source_server: ServerId,
target_server: ServerId,
player_state: PlayerState,
valid_duration_secs: i64,
secret_key: &[u8],
) -> Result<Self, TransferError> {
let token_id = Uuid::new_v4();
let created_at = Utc::now();
let expires_at = created_at + Duration::seconds(valid_duration_secs);
let player_state_json = serde_json::to_string(&player_state)
.map_err(|e| TransferError::SerializationError(e.to_string()))?;
let sign_data = format!(
"{}:{}:{}:{}:{}",
token_id, player_id, source_server, target_server, expires_at.timestamp()
);
let signature = Self::compute_signature(&sign_data, secret_key);
Ok(Self {
token_id,
player_id,
source_server,
target_server,
created_at,
expires_at,
signature,
player_state: player_state_json,
})
}
pub fn verify(&self, secret_key: &[u8]) -> Result<(), TransferError> {
if Utc::now() > self.expires_at {
return Err(TransferError::TokenExpired);
}
let sign_data = format!(
"{}:{}:{}:{}:{}",
self.token_id, self.player_id, self.source_server, self.target_server,
self.expires_at.timestamp()
);
let expected_signature = Self::compute_signature(&sign_data, secret_key);
if self.signature != expected_signature {
return Err(TransferError::InvalidSignature);
}
Ok(())
}
pub fn extract_player_state(&self) -> Result<PlayerState, TransferError> {
serde_json::from_str(&self.player_state)
.map_err(|e| TransferError::SerializationError(e.to_string()))
}
fn compute_signature(data: &str, key: &[u8]) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
key.hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
}
#[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::*;
use crate::player::PlayerInfo;
#[test]
fn test_transfer_token_creation_and_verification() {
let player_id = PlayerId::new();
let source = ServerId::new();
let target = ServerId::new();
let state = PlayerState::new(PlayerInfo::new(player_id, "TestPlayer".to_string()));
let secret = b"test_secret_key";
let token = TransferToken::new(player_id, source, target, state, 300, secret).unwrap();
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 state = PlayerState::new(PlayerInfo::new(player_id, "TestPlayer".to_string()));
let token = TransferToken::new(player_id, source, target, state, 300, b"key1").unwrap();
assert!(matches!(
token.verify(b"wrong_key"),
Err(TransferError::InvalidSignature)
));
}
}