Horizon_Network_Common/
transfer.rs

1//! Player transfer types for seamless server-to-server migration.
2//!
3//! These types handle the secure transfer of players between Horizon instances
4//! when they move across region boundaries.
5
6use 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/// Transfer token that authorizes a player to connect to a new server.
15///
16/// This token is generated by Atlas and must be presented by the player
17/// when connecting to the target server to prove they are authorized.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct TransferToken {
20    /// Unique token identifier
21    pub token_id: Uuid,
22    /// Player being transferred
23    pub player_id: PlayerId,
24    /// Source server
25    pub source_server: ServerId,
26    /// Target server
27    pub target_server: ServerId,
28    /// When this token was created
29    pub created_at: DateTime<Utc>,
30    /// When this token expires
31    pub expires_at: DateTime<Utc>,
32    /// Cryptographic signature (HMAC or similar)
33    pub signature: String,
34    /// Serialized player state
35    pub player_state: String,
36}
37
38impl TransferToken {
39    /// Creates a new transfer token.
40    ///
41    /// The token is valid for the specified duration.
42    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        // Create signature from token data
58        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    /// Verifies the token signature and expiration.
77    pub fn verify(&self, secret_key: &[u8]) -> Result<(), TransferError> {
78        // Check expiration
79        if Utc::now() > self.expires_at {
80            return Err(TransferError::TokenExpired);
81        }
82
83        // Verify signature
84        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    /// Extracts the player state from the token.
99    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    /// Simple signature computation (in production, use proper HMAC-SHA256).
105    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/// Request to initiate a player transfer.
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct TransferRequest {
119    /// Player to transfer
120    pub player_id: PlayerId,
121    /// Current server
122    pub source_server: ServerId,
123    /// Target server
124    pub target_server: ServerId,
125    /// Target position in world coordinates
126    pub target_position: WorldCoordinate,
127    /// Reason for transfer
128    pub reason: TransferReason,
129    /// Priority (higher = more urgent)
130    pub priority: u8,
131}
132
133/// Reason for initiating a transfer.
134#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(rename_all = "snake_case")]
136pub enum TransferReason {
137    /// Player crossed region boundary
138    RegionBoundary,
139    /// Load balancing decision
140    LoadBalancing,
141    /// Server is shutting down
142    ServerShutdown,
143    /// Manual admin action
144    AdminAction,
145    /// Player teleport
146    Teleport,
147}
148
149/// Result of a transfer operation.
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct TransferResult {
152    /// Whether transfer succeeded
153    pub success: bool,
154    /// Transfer token if successful
155    pub token: Option<TransferToken>,
156    /// Error if failed
157    pub error: Option<TransferError>,
158    /// Time taken for transfer in milliseconds
159    pub duration_ms: u64,
160}
161
162impl TransferResult {
163    /// Creates a successful transfer result.
164    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    /// Creates a failed transfer result.
174    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/// Errors that can occur during transfer.
185#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
186pub enum TransferError {
187    /// Target server is not available
188    #[error("Target server unavailable: {0}")]
189    TargetServerUnavailable(String),
190    
191    /// Player not found
192    #[error("Player not found: {0}")]
193    PlayerNotFound(String),
194    
195    /// Transfer token expired
196    #[error("Transfer token expired")]
197    TokenExpired,
198    
199    /// Invalid token signature
200    #[error("Invalid token signature")]
201    InvalidSignature,
202    
203    /// Serialization error
204    #[error("Serialization error: {0}")]
205    SerializationError(String),
206    
207    /// Transfer already in progress
208    #[error("Transfer already in progress for player")]
209    TransferInProgress,
210    
211    /// Target server rejected transfer
212    #[error("Transfer rejected: {0}")]
213    TransferRejected(String),
214    
215    /// Network error during transfer
216    #[error("Network error: {0}")]
217    NetworkError(String),
218    
219    /// Timeout waiting for transfer
220    #[error("Transfer timeout")]
221    Timeout,
222}
223
224/// Transfer notification sent to clients.
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct TransferNotification {
227    /// Player being transferred
228    pub player_id: PlayerId,
229    /// New server address to connect to
230    pub target_address: String,
231    /// Transfer token to present
232    pub token: String,
233    /// Suggested reconnect delay in milliseconds
234    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}