1use serde::{Deserialize, Serialize};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use crate::player::PlayerId;
10use crate::server::ServerId;
11use crate::spatial::WorldCoordinate;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct TransferToken {
19 pub token_id: String,
21 pub player_id: PlayerId,
23 pub source_server: ServerId,
25 pub target_server: ServerId,
27 pub target_address: String,
29 pub created_at_ms: u64,
31 pub expires_at_ms: u64,
33 pub signature: String,
35}
36
37const DEFAULT_TOKEN_VALIDITY_SECS: u64 = 60;
39
40impl TransferToken {
41 pub fn new(
43 player_id: PlayerId,
44 source_server: ServerId,
45 target_server: ServerId,
46 target_address: String,
47 secret_key: &[u8],
48 ) -> Self {
49 Self::with_validity(player_id, source_server, target_server, target_address, DEFAULT_TOKEN_VALIDITY_SECS, secret_key)
50 }
51
52 pub fn with_validity(
54 player_id: PlayerId,
55 source_server: ServerId,
56 target_server: ServerId,
57 target_address: String,
58 valid_duration_secs: u64,
59 secret_key: &[u8],
60 ) -> Self {
61 let now_ms = SystemTime::now()
62 .duration_since(UNIX_EPOCH)
63 .unwrap_or_default()
64 .as_millis() as u64;
65
66 let token_id = format!("txfr-{}-{}", now_ms, Self::rand_component());
67 let expires_at_ms = now_ms + (valid_duration_secs * 1000);
68
69 let mut token = Self {
70 token_id,
71 player_id,
72 source_server,
73 target_server,
74 target_address,
75 created_at_ms: now_ms,
76 expires_at_ms,
77 signature: String::new(),
78 };
79
80 token.signature = token.compute_signature(secret_key);
81 token
82 }
83
84 pub fn verify(&self, secret_key: &[u8]) -> Result<(), TransferError> {
86 let now_ms = SystemTime::now()
87 .duration_since(UNIX_EPOCH)
88 .unwrap_or_default()
89 .as_millis() as u64;
90
91 if now_ms > self.expires_at_ms {
92 return Err(TransferError::TokenExpired);
93 }
94
95 let expected_signature = self.compute_signature(secret_key);
96 if self.signature != expected_signature {
97 return Err(TransferError::InvalidSignature);
98 }
99
100 Ok(())
101 }
102
103 fn compute_signature(&self, key: &[u8]) -> String {
105 use std::collections::hash_map::DefaultHasher;
106 use std::hash::{Hash, Hasher};
107
108 let data = format!(
109 "{}:{}:{}:{}:{}",
110 self.token_id, self.player_id, self.source_server, self.target_server, self.expires_at_ms
111 );
112
113 let mut hasher = DefaultHasher::new();
114 data.hash(&mut hasher);
115 key.hash(&mut hasher);
116 format!("{:016x}", hasher.finish())
117 }
118
119 fn rand_component() -> u32 {
121 use std::collections::hash_map::DefaultHasher;
122 use std::hash::{Hash, Hasher};
123
124 let mut hasher = DefaultHasher::new();
125 std::time::Instant::now().hash(&mut hasher);
126 (hasher.finish() % 1000000) as u32
127 }
128
129 pub fn to_json(&self) -> Result<String, TransferError> {
131 serde_json::to_string(self)
132 .map_err(|e| TransferError::SerializationError(e.to_string()))
133 }
134
135 pub fn from_json(json: &str) -> Result<Self, TransferError> {
137 serde_json::from_str(json)
138 .map_err(|e| TransferError::SerializationError(e.to_string()))
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct TransferRequest {
145 pub player_id: PlayerId,
147 pub source_server: ServerId,
149 pub target_server: ServerId,
151 pub target_position: WorldCoordinate,
153 pub reason: TransferReason,
155 pub priority: u8,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161#[serde(rename_all = "snake_case")]
162pub enum TransferReason {
163 RegionBoundary,
165 LoadBalancing,
167 ServerShutdown,
169 AdminAction,
171 Teleport,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct TransferResult {
178 pub success: bool,
180 pub token: Option<TransferToken>,
182 pub error: Option<TransferError>,
184 pub duration_ms: u64,
186}
187
188impl TransferResult {
189 pub fn success(token: TransferToken, duration_ms: u64) -> Self {
191 Self {
192 success: true,
193 token: Some(token),
194 error: None,
195 duration_ms,
196 }
197 }
198
199 pub fn failure(error: TransferError) -> Self {
201 Self {
202 success: false,
203 token: None,
204 error: Some(error),
205 duration_ms: 0,
206 }
207 }
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
212pub enum TransferError {
213 #[error("Target server unavailable: {0}")]
215 TargetServerUnavailable(String),
216
217 #[error("Player not found: {0}")]
219 PlayerNotFound(String),
220
221 #[error("Transfer token expired")]
223 TokenExpired,
224
225 #[error("Invalid token signature")]
227 InvalidSignature,
228
229 #[error("Serialization error: {0}")]
231 SerializationError(String),
232
233 #[error("Transfer already in progress for player")]
235 TransferInProgress,
236
237 #[error("Transfer rejected: {0}")]
239 TransferRejected(String),
240
241 #[error("Network error: {0}")]
243 NetworkError(String),
244
245 #[error("Transfer timeout")]
247 Timeout,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct TransferNotification {
253 pub player_id: PlayerId,
255 pub target_address: String,
257 pub token: String,
259 pub reconnect_delay_ms: u64,
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_transfer_token_creation_and_verification() {
269 let player_id = PlayerId::new();
270 let source = ServerId::new();
271 let target = ServerId::new();
272 let secret = b"test_secret_key";
273
274 let token = TransferToken::new(
275 player_id.clone(),
276 source,
277 target,
278 "127.0.0.1:8080".to_string(),
279 secret
280 );
281 assert!(token.verify(secret).is_ok());
282 }
283
284 #[test]
285 fn test_transfer_token_invalid_signature() {
286 let player_id = PlayerId::new();
287 let source = ServerId::new();
288 let target = ServerId::new();
289
290 let token = TransferToken::new(
291 player_id,
292 source,
293 target,
294 "127.0.0.1:8080".to_string(),
295 b"key1"
296 );
297 assert!(matches!(
298 token.verify(b"wrong_key"),
299 Err(TransferError::InvalidSignature)
300 ));
301 }
302}