mockforge_tcp/
server.rs

1//! TCP server implementation
2
3use crate::{TcpConfig, TcpSpecRegistry};
4use mockforge_core::Result;
5use std::net::SocketAddr;
6use std::sync::Arc;
7use tokio::io::{AsyncReadExt, AsyncWriteExt};
8use tokio::net::{TcpListener, TcpStream};
9use tokio::time::{sleep, timeout, Duration};
10use tracing::{debug, error, info, warn};
11
12/// TCP server
13pub struct TcpServer {
14    config: TcpConfig,
15    spec_registry: Arc<TcpSpecRegistry>,
16}
17
18impl TcpServer {
19    /// Create a new TCP server
20    pub fn new(config: TcpConfig, spec_registry: Arc<TcpSpecRegistry>) -> Result<Self> {
21        Ok(Self {
22            config,
23            spec_registry,
24        })
25    }
26
27    /// Start the TCP server
28    pub async fn start(&self) -> Result<()> {
29        let addr = format!("{}:{}", self.config.host, self.config.port);
30        let listener = TcpListener::bind(&addr).await?;
31
32        info!("TCP server listening on {}", addr);
33
34        loop {
35            match listener.accept().await {
36                Ok((stream, peer_addr)) => {
37                    debug!("New TCP connection from {}", peer_addr);
38
39                    let registry = self.spec_registry.clone();
40                    let config = self.config.clone();
41
42                    tokio::spawn(async move {
43                        if let Err(e) =
44                            handle_tcp_connection(stream, peer_addr, registry, config).await
45                        {
46                            error!("TCP connection error from {}: {}", peer_addr, e);
47                        }
48                    });
49                }
50                Err(e) => {
51                    error!("Failed to accept TCP connection: {}", e);
52                }
53            }
54        }
55    }
56}
57
58/// Handle a single TCP connection
59async fn handle_tcp_connection(
60    mut stream: TcpStream,
61    peer_addr: SocketAddr,
62    registry: Arc<TcpSpecRegistry>,
63    config: TcpConfig,
64) -> Result<()> {
65    debug!("Handling TCP connection from {}", peer_addr);
66
67    let mut buffer = vec![0u8; config.read_buffer_size];
68    let mut accumulated_data = Vec::new();
69
70    loop {
71        // Set read timeout
72        let read_timeout = Duration::from_secs(config.timeout_secs);
73
74        match timeout(read_timeout, stream.read(&mut buffer)).await {
75            Ok(Ok(0)) => {
76                // Connection closed by client
77                debug!("TCP connection closed by client: {}", peer_addr);
78                break;
79            }
80            Ok(Ok(n)) => {
81                let received_data = &buffer[..n];
82                accumulated_data.extend_from_slice(received_data);
83
84                debug!("Received {} bytes from {}", n, peer_addr);
85
86                // Try to find matching fixture
87                let response_data =
88                    if let Some(fixture) = registry.find_matching_fixture(&accumulated_data) {
89                        debug!("Found matching fixture: {}", fixture.identifier);
90
91                        // Apply delay if configured
92                        if fixture.response.delay_ms > 0 {
93                            sleep(Duration::from_millis(fixture.response.delay_ms)).await;
94                        }
95
96                        // Generate response data
97                        generate_response_data(&fixture.response)?
98                    } else if config.echo_mode {
99                        // Echo mode: echo back received data
100                        debug!("No fixture match, echoing data back");
101                        accumulated_data.clone()
102                    } else {
103                        // No match and echo mode disabled - close connection
104                        warn!("No fixture match and echo mode disabled, closing connection");
105                        break;
106                    };
107
108                // Send response
109                if !response_data.is_empty() {
110                    if let Err(e) = stream.write_all(&response_data).await {
111                        error!("Failed to write response to {}: {}", peer_addr, e);
112                        break;
113                    }
114
115                    if let Err(e) = stream.flush().await {
116                        error!("Failed to flush response to {}: {}", peer_addr, e);
117                        break;
118                    }
119                }
120
121                // Check if we should close after response
122                if let Some(fixture) = registry.find_matching_fixture(&accumulated_data) {
123                    if fixture.response.close_after_response {
124                        debug!("Closing connection after response as configured");
125                        break;
126                    }
127
128                    if !fixture.response.keep_alive {
129                        debug!("Closing connection (keep_alive=false)");
130                        break;
131                    }
132                } else if !config.echo_mode {
133                    // Close if echo mode disabled and no fixture matched
134                    break;
135                }
136
137                // If delimiter is configured, check if we've received complete message
138                if let Some(ref delimiter) = config.delimiter {
139                    if accumulated_data.ends_with(delimiter) {
140                        debug!("Received complete message (matched delimiter), resetting buffer");
141                        accumulated_data.clear();
142                    }
143                } else {
144                    // Stream mode: reset buffer for next read
145                    accumulated_data.clear();
146                }
147            }
148            Ok(Err(e)) => {
149                error!("TCP read error from {}: {}", peer_addr, e);
150                break;
151            }
152            Err(_) => {
153                warn!("TCP read timeout from {}", peer_addr);
154                break;
155            }
156        }
157    }
158
159    debug!("TCP connection handler finished for {}", peer_addr);
160    Ok(())
161}
162
163/// Generate response data from fixture configuration
164fn generate_response_data(response: &crate::fixtures::TcpResponse) -> Result<Vec<u8>> {
165    match response.encoding.as_str() {
166        "hex" => hex::decode(&response.data)
167            .map_err(|e| mockforge_core::Error::generic(format!("Invalid hex data: {}", e))),
168        "base64" => base64::decode(&response.data)
169            .map_err(|e| mockforge_core::Error::generic(format!("Invalid base64 data: {}", e))),
170        "text" => Ok(response.data.as_bytes().to_vec()),
171        "file" => {
172            let file_path = response.file_path.as_ref().ok_or_else(|| {
173                mockforge_core::Error::generic("file_path not specified for file encoding")
174            })?;
175
176            std::fs::read(file_path).map_err(|e| {
177                mockforge_core::Error::generic(format!(
178                    "Failed to read file {:?}: {}",
179                    file_path, e
180                ))
181            })
182        }
183        _ => Err(mockforge_core::Error::generic(format!(
184            "Unknown encoding: {}. Supported: hex, base64, text, file",
185            response.encoding
186        ))),
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193    use crate::fixtures::TcpResponse;
194    use std::io::Write;
195    use std::path::PathBuf;
196
197    fn create_test_response(data: &str, encoding: &str) -> TcpResponse {
198        TcpResponse {
199            data: data.to_string(),
200            encoding: encoding.to_string(),
201            file_path: None,
202            delay_ms: 0,
203            close_after_response: false,
204            keep_alive: true,
205        }
206    }
207
208    #[test]
209    fn test_tcp_server_new() {
210        let config = TcpConfig::default();
211        let registry = Arc::new(TcpSpecRegistry::new());
212
213        let server = TcpServer::new(config.clone(), registry.clone());
214        assert!(server.is_ok());
215
216        let server = server.unwrap();
217        assert_eq!(server.config.port, config.port);
218        assert_eq!(server.config.host, config.host);
219    }
220
221    #[test]
222    fn test_tcp_server_new_with_custom_config() {
223        let config = TcpConfig {
224            port: 8080,
225            host: "127.0.0.1".to_string(),
226            timeout_secs: 60,
227            echo_mode: false,
228            ..Default::default()
229        };
230        let registry = Arc::new(TcpSpecRegistry::new());
231
232        let server = TcpServer::new(config.clone(), registry).unwrap();
233        assert_eq!(server.config.port, 8080);
234        assert_eq!(server.config.host, "127.0.0.1");
235        assert_eq!(server.config.timeout_secs, 60);
236        assert!(!server.config.echo_mode);
237    }
238
239    #[test]
240    fn test_generate_response_data_text_encoding() {
241        let response = create_test_response("Hello, World!", "text");
242        let result = generate_response_data(&response);
243
244        assert!(result.is_ok());
245        let data = result.unwrap();
246        assert_eq!(data, b"Hello, World!");
247        assert_eq!(String::from_utf8(data).unwrap(), "Hello, World!");
248    }
249
250    #[test]
251    fn test_generate_response_data_text_encoding_empty() {
252        let response = create_test_response("", "text");
253        let result = generate_response_data(&response);
254
255        assert!(result.is_ok());
256        assert_eq!(result.unwrap(), b"");
257    }
258
259    #[test]
260    fn test_generate_response_data_text_encoding_unicode() {
261        let response = create_test_response("Hello δΈ–η•Œ 🌍", "text");
262        let result = generate_response_data(&response);
263
264        assert!(result.is_ok());
265        let data = result.unwrap();
266        assert_eq!(String::from_utf8(data).unwrap(), "Hello δΈ–η•Œ 🌍");
267    }
268
269    #[test]
270    fn test_generate_response_data_hex_encoding() {
271        let response = create_test_response("48656c6c6f", "hex"); // "Hello" in hex
272        let result = generate_response_data(&response);
273
274        assert!(result.is_ok());
275        let data = result.unwrap();
276        assert_eq!(data, b"Hello");
277    }
278
279    #[test]
280    fn test_generate_response_data_hex_encoding_uppercase() {
281        let response = create_test_response("48656C6C6F", "hex"); // "Hello" in hex (uppercase)
282        let result = generate_response_data(&response);
283
284        assert!(result.is_ok());
285        let data = result.unwrap();
286        assert_eq!(data, b"Hello");
287    }
288
289    #[test]
290    fn test_generate_response_data_hex_encoding_mixed_case() {
291        let response = create_test_response("48656c6C6f", "hex"); // "Hello" in hex (mixed case)
292        let result = generate_response_data(&response);
293
294        assert!(result.is_ok());
295        let data = result.unwrap();
296        assert_eq!(data, b"Hello");
297    }
298
299    #[test]
300    fn test_generate_response_data_hex_encoding_invalid() {
301        let response = create_test_response("GGGG", "hex"); // Invalid hex
302        let result = generate_response_data(&response);
303
304        assert!(result.is_err());
305        let error = result.unwrap_err();
306        assert!(error.to_string().contains("Invalid hex data"));
307    }
308
309    #[test]
310    fn test_generate_response_data_hex_encoding_odd_length() {
311        let response = create_test_response("123", "hex"); // Odd length hex string
312        let result = generate_response_data(&response);
313
314        assert!(result.is_err());
315        let error = result.unwrap_err();
316        assert!(error.to_string().contains("Invalid hex data"));
317    }
318
319    #[test]
320    fn test_generate_response_data_base64_encoding() {
321        let response = create_test_response("SGVsbG8gV29ybGQ=", "base64"); // "Hello World" in base64
322        let result = generate_response_data(&response);
323
324        assert!(result.is_ok());
325        let data = result.unwrap();
326        assert_eq!(data, b"Hello World");
327    }
328
329    #[test]
330    fn test_generate_response_data_base64_encoding_with_padding() {
331        let response = create_test_response("SGVsbG8=", "base64"); // "Hello" in base64 with padding
332        let result = generate_response_data(&response);
333
334        assert!(result.is_ok());
335        let data = result.unwrap();
336        assert_eq!(data, b"Hello");
337    }
338
339    #[test]
340    fn test_generate_response_data_base64_url_safe() {
341        let response = create_test_response("PEJPRA==", "base64"); // base64 standard encoding
342        let result = generate_response_data(&response);
343
344        assert!(result.is_ok());
345        assert!(!result.unwrap().is_empty());
346    }
347
348    #[test]
349    fn test_generate_response_data_base64_encoding_invalid() {
350        let response = create_test_response("!!!invalid@@@", "base64"); // Invalid base64
351        let result = generate_response_data(&response);
352
353        assert!(result.is_err());
354        let error = result.unwrap_err();
355        assert!(error.to_string().contains("Invalid base64 data"));
356    }
357
358    #[test]
359    fn test_generate_response_data_file_encoding() {
360        // Create a temporary file
361        let mut temp_file = tempfile::NamedTempFile::new().unwrap();
362        temp_file.write_all(b"File content").unwrap();
363        temp_file.flush().unwrap();
364
365        let mut response = create_test_response("", "file");
366        response.file_path = Some(temp_file.path().to_path_buf());
367
368        let result = generate_response_data(&response);
369
370        assert!(result.is_ok());
371        let data = result.unwrap();
372        assert_eq!(data, b"File content");
373    }
374
375    #[test]
376    fn test_generate_response_data_file_encoding_binary() {
377        // Create a temporary file with binary data
378        let mut temp_file = tempfile::NamedTempFile::new().unwrap();
379        let binary_data = vec![0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD];
380        temp_file.write_all(&binary_data).unwrap();
381        temp_file.flush().unwrap();
382
383        let mut response = create_test_response("", "file");
384        response.file_path = Some(temp_file.path().to_path_buf());
385
386        let result = generate_response_data(&response);
387
388        assert!(result.is_ok());
389        let data = result.unwrap();
390        assert_eq!(data, binary_data);
391    }
392
393    #[test]
394    fn test_generate_response_data_file_encoding_no_path() {
395        let response = create_test_response("", "file");
396        // file_path is None
397
398        let result = generate_response_data(&response);
399
400        assert!(result.is_err());
401        let error = result.unwrap_err();
402        assert!(error.to_string().contains("file_path not specified"));
403    }
404
405    #[test]
406    fn test_generate_response_data_file_encoding_nonexistent_file() {
407        let mut response = create_test_response("", "file");
408        response.file_path = Some(PathBuf::from("/nonexistent/path/to/file.txt"));
409
410        let result = generate_response_data(&response);
411
412        assert!(result.is_err());
413        let error = result.unwrap_err();
414        assert!(error.to_string().contains("Failed to read file"));
415    }
416
417    #[test]
418    fn test_generate_response_data_unknown_encoding() {
419        let response = create_test_response("data", "unknown");
420        let result = generate_response_data(&response);
421
422        assert!(result.is_err());
423        let error = result.unwrap_err();
424        assert!(error.to_string().contains("Unknown encoding: unknown"));
425        assert!(error.to_string().contains("Supported: hex, base64, text, file"));
426    }
427
428    #[test]
429    fn test_generate_response_data_case_sensitive_encoding() {
430        // Test that encoding is case-sensitive
431        let response = create_test_response("SGVsbG8=", "BASE64"); // uppercase encoding
432        let result = generate_response_data(&response);
433
434        assert!(result.is_err());
435        assert!(result.unwrap_err().to_string().contains("Unknown encoding"));
436    }
437
438    #[test]
439    fn test_generate_response_data_text_with_special_chars() {
440        let response = create_test_response("Line1\nLine2\r\nLine3\t\0End", "text");
441        let result = generate_response_data(&response);
442
443        assert!(result.is_ok());
444        let data = result.unwrap();
445        assert_eq!(data, b"Line1\nLine2\r\nLine3\t\0End");
446    }
447
448    #[test]
449    fn test_generate_response_data_hex_empty() {
450        let response = create_test_response("", "hex");
451        let result = generate_response_data(&response);
452
453        assert!(result.is_ok());
454        assert_eq!(result.unwrap(), b"");
455    }
456
457    #[test]
458    fn test_generate_response_data_base64_empty() {
459        let response = create_test_response("", "base64");
460        let result = generate_response_data(&response);
461
462        assert!(result.is_ok());
463        assert_eq!(result.unwrap(), b"");
464    }
465
466    #[test]
467    fn test_generate_response_data_hex_with_spaces() {
468        // Hex decoder doesn't handle spaces, should fail
469        let response = create_test_response("48 65 6c 6c 6f", "hex");
470        let result = generate_response_data(&response);
471
472        assert!(result.is_err());
473    }
474
475    #[test]
476    fn test_tcp_server_config_fields() {
477        let config = TcpConfig {
478            port: 9000,
479            host: "localhost".to_string(),
480            fixtures_dir: Some(PathBuf::from("/tmp/fixtures")),
481            timeout_secs: 120,
482            max_connections: 50,
483            read_buffer_size: 4096,
484            write_buffer_size: 4096,
485            enable_tls: true,
486            tls_cert_path: Some(PathBuf::from("/path/to/cert.pem")),
487            tls_key_path: Some(PathBuf::from("/path/to/key.pem")),
488            echo_mode: false,
489            delimiter: Some(b"\r\n".to_vec()),
490        };
491
492        let registry = Arc::new(TcpSpecRegistry::new());
493        let server = TcpServer::new(config, registry).unwrap();
494
495        assert_eq!(server.config.port, 9000);
496        assert_eq!(server.config.host, "localhost");
497        assert_eq!(server.config.timeout_secs, 120);
498        assert_eq!(server.config.max_connections, 50);
499        assert_eq!(server.config.read_buffer_size, 4096);
500        assert_eq!(server.config.write_buffer_size, 4096);
501        assert!(server.config.enable_tls);
502        assert!(!server.config.echo_mode);
503        assert_eq!(server.config.delimiter, Some(b"\r\n".to_vec()));
504    }
505
506    #[test]
507    fn test_tcp_response_with_delay() {
508        let response = TcpResponse {
509            data: "delayed".to_string(),
510            encoding: "text".to_string(),
511            file_path: None,
512            delay_ms: 500,
513            close_after_response: true,
514            keep_alive: false,
515        };
516
517        let result = generate_response_data(&response);
518        assert!(result.is_ok());
519        assert_eq!(result.unwrap(), b"delayed");
520        // Note: delay is applied in handle_tcp_connection, not in generate_response_data
521    }
522
523    #[test]
524    fn test_tcp_response_close_after_response() {
525        let response = TcpResponse {
526            data: "close me".to_string(),
527            encoding: "text".to_string(),
528            file_path: None,
529            delay_ms: 0,
530            close_after_response: true,
531            keep_alive: false,
532        };
533
534        assert!(response.close_after_response);
535        assert!(!response.keep_alive);
536
537        let result = generate_response_data(&response);
538        assert!(result.is_ok());
539    }
540
541    #[test]
542    fn test_generate_response_data_large_text() {
543        let large_text = "x".repeat(100_000);
544        let response = create_test_response(&large_text, "text");
545        let result = generate_response_data(&response);
546
547        assert!(result.is_ok());
548        let data = result.unwrap();
549        assert_eq!(data.len(), 100_000);
550        assert_eq!(data, large_text.as_bytes());
551    }
552
553    #[test]
554    fn test_generate_response_data_large_hex() {
555        // Generate 10000 bytes of hex data (20000 hex chars)
556        let hex_data = "00".repeat(10_000);
557        let response = create_test_response(&hex_data, "hex");
558        let result = generate_response_data(&response);
559
560        assert!(result.is_ok());
561        let data = result.unwrap();
562        assert_eq!(data.len(), 10_000);
563        assert!(data.iter().all(|&b| b == 0));
564    }
565
566    #[test]
567    fn test_file_encoding_empty_file() {
568        let temp_file = tempfile::NamedTempFile::new().unwrap();
569        // Don't write anything, leave it empty
570
571        let mut response = create_test_response("", "file");
572        response.file_path = Some(temp_file.path().to_path_buf());
573
574        let result = generate_response_data(&response);
575
576        assert!(result.is_ok());
577        assert_eq!(result.unwrap(), b"");
578    }
579
580    #[test]
581    fn test_file_encoding_large_file() {
582        let mut temp_file = tempfile::NamedTempFile::new().unwrap();
583        let large_data = vec![0xAB; 50_000]; // 50KB of data
584        temp_file.write_all(&large_data).unwrap();
585        temp_file.flush().unwrap();
586
587        let mut response = create_test_response("", "file");
588        response.file_path = Some(temp_file.path().to_path_buf());
589
590        let result = generate_response_data(&response);
591
592        assert!(result.is_ok());
593        let data = result.unwrap();
594        assert_eq!(data.len(), 50_000);
595        assert_eq!(data, large_data);
596    }
597}