1use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8use chrono::{DateTime, Utc, Duration};
9
10use crate::player::{PlayerId, PlayerState};
11use crate::server::ServerId;
12use crate::spatial::WorldCoordinate;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct TransferToken {
20 pub token_id: Uuid,
22 pub player_id: PlayerId,
24 pub source_server: ServerId,
26 pub target_server: ServerId,
28 pub created_at: DateTime<Utc>,
30 pub expires_at: DateTime<Utc>,
32 pub signature: String,
34 pub player_state: String,
36}
37
38impl TransferToken {
39 pub fn new(
43 player_id: PlayerId,
44 source_server: ServerId,
45 target_server: ServerId,
46 player_state: PlayerState,
47 valid_duration_secs: i64,
48 secret_key: &[u8],
49 ) -> Result<Self, TransferError> {
50 let token_id = Uuid::new_v4();
51 let created_at = Utc::now();
52 let expires_at = created_at + Duration::seconds(valid_duration_secs);
53
54 let player_state_json = serde_json::to_string(&player_state)
55 .map_err(|e| TransferError::SerializationError(e.to_string()))?;
56
57 let sign_data = format!(
59 "{}:{}:{}:{}:{}",
60 token_id, player_id, source_server, target_server, expires_at.timestamp()
61 );
62 let signature = Self::compute_signature(&sign_data, secret_key);
63
64 Ok(Self {
65 token_id,
66 player_id,
67 source_server,
68 target_server,
69 created_at,
70 expires_at,
71 signature,
72 player_state: player_state_json,
73 })
74 }
75
76 pub fn verify(&self, secret_key: &[u8]) -> Result<(), TransferError> {
78 if Utc::now() > self.expires_at {
80 return Err(TransferError::TokenExpired);
81 }
82
83 let sign_data = format!(
85 "{}:{}:{}:{}:{}",
86 self.token_id, self.player_id, self.source_server, self.target_server,
87 self.expires_at.timestamp()
88 );
89 let expected_signature = Self::compute_signature(&sign_data, secret_key);
90
91 if self.signature != expected_signature {
92 return Err(TransferError::InvalidSignature);
93 }
94
95 Ok(())
96 }
97
98 pub fn extract_player_state(&self) -> Result<PlayerState, TransferError> {
100 serde_json::from_str(&self.player_state)
101 .map_err(|e| TransferError::SerializationError(e.to_string()))
102 }
103
104 fn compute_signature(data: &str, key: &[u8]) -> String {
106 use std::collections::hash_map::DefaultHasher;
107 use std::hash::{Hash, Hasher};
108
109 let mut hasher = DefaultHasher::new();
110 data.hash(&mut hasher);
111 key.hash(&mut hasher);
112 format!("{:016x}", hasher.finish())
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct TransferRequest {
119 pub player_id: PlayerId,
121 pub source_server: ServerId,
123 pub target_server: ServerId,
125 pub target_position: WorldCoordinate,
127 pub reason: TransferReason,
129 pub priority: u8,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(rename_all = "snake_case")]
136pub enum TransferReason {
137 RegionBoundary,
139 LoadBalancing,
141 ServerShutdown,
143 AdminAction,
145 Teleport,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct TransferResult {
152 pub success: bool,
154 pub token: Option<TransferToken>,
156 pub error: Option<TransferError>,
158 pub duration_ms: u64,
160}
161
162impl TransferResult {
163 pub fn success(token: TransferToken, duration_ms: u64) -> Self {
165 Self {
166 success: true,
167 token: Some(token),
168 error: None,
169 duration_ms,
170 }
171 }
172
173 pub fn failure(error: TransferError) -> Self {
175 Self {
176 success: false,
177 token: None,
178 error: Some(error),
179 duration_ms: 0,
180 }
181 }
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
186pub enum TransferError {
187 #[error("Target server unavailable: {0}")]
189 TargetServerUnavailable(String),
190
191 #[error("Player not found: {0}")]
193 PlayerNotFound(String),
194
195 #[error("Transfer token expired")]
197 TokenExpired,
198
199 #[error("Invalid token signature")]
201 InvalidSignature,
202
203 #[error("Serialization error: {0}")]
205 SerializationError(String),
206
207 #[error("Transfer already in progress for player")]
209 TransferInProgress,
210
211 #[error("Transfer rejected: {0}")]
213 TransferRejected(String),
214
215 #[error("Network error: {0}")]
217 NetworkError(String),
218
219 #[error("Transfer timeout")]
221 Timeout,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct TransferNotification {
227 pub player_id: PlayerId,
229 pub target_address: String,
231 pub token: String,
233 pub reconnect_delay_ms: u64,
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::player::PlayerInfo;
241
242 #[test]
243 fn test_transfer_token_creation_and_verification() {
244 let player_id = PlayerId::new();
245 let source = ServerId::new();
246 let target = ServerId::new();
247 let state = PlayerState::new(PlayerInfo::new(player_id, "TestPlayer".to_string()));
248 let secret = b"test_secret_key";
249
250 let token = TransferToken::new(player_id, source, target, state, 300, secret).unwrap();
251 assert!(token.verify(secret).is_ok());
252 }
253
254 #[test]
255 fn test_transfer_token_invalid_signature() {
256 let player_id = PlayerId::new();
257 let source = ServerId::new();
258 let target = ServerId::new();
259 let state = PlayerState::new(PlayerInfo::new(player_id, "TestPlayer".to_string()));
260
261 let token = TransferToken::new(player_id, source, target, state, 300, b"key1").unwrap();
262 assert!(matches!(
263 token.verify(b"wrong_key"),
264 Err(TransferError::InvalidSignature)
265 ));
266 }
267}