//! Compound Request integration tests //! Tests SMB2 compound request processing (CREATE + READ, CREATE + WRITE + CLOSE, etc.) use smb_server::wire::header::{Command, SMB2_FLAGS_RELATED_OPERATIONS}; use smb_server::ntstatus; /// Test compound request header flags #[test] fn test_compound_header_flags() { // First request: no RELATED flag let first_flags: u32 = 0; assert_eq!(first_flags & SMB2_FLAGS_RELATED_OPERATIONS, 0); // Second request: RELATED flag set let second_flags = SMB2_FLAGS_RELATED_OPERATIONS; assert_ne!(second_flags & SMB2_FLAGS_RELATED_OPERATIONS, 0); // Verify RELATED flag value (MS-SMB2 §2.2.1.2) assert_eq!(SMB2_FLAGS_RELATED_OPERATIONS, 0x0000_0004); } /// Test compound request NextCommand offset #[test] fn test_next_command_offset() { // NextCommand = 0 indicates last request in chain let last_next_command: u32 = 0; assert_eq!(last_next_command, 0); // NextCommand > 0 indicates next request offset let next_command: u32 = 128; assert!(next_command > 64); // Must be > SMB2_HEADER_LEN (64 bytes) } /// Test compound request chain validation #[test] fn test_compound_chain_validation() { // Valid chain: CREATE -> READ -> CLOSE let commands = vec![ Command::Create, Command::Read, Command::Close, ]; assert_eq!(commands.len(), 3); assert_eq!(commands[0], Command::Create); assert_eq!(commands[1], Command::Read); assert_eq!(commands[2], Command::Close); // Invalid chain: READ -> CREATE (CREATE must be first for file operations) let invalid_commands = vec![ Command::Read, Command::Create, ]; // This should be rejected by server (READ needs file_id from CREATE) assert_ne!(invalid_commands[0], Command::Create); } /// Test compound request file_id inheritance #[test] fn test_file_id_inheritance() { // FileId from CREATE response (16 bytes) let file_id: [u8; 16] = [ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, ]; // Verify file_id format (persistent + volatile parts) assert_eq!(file_id.len(), 16); // Split into persistent (first 8 bytes) and volatile (last 8 bytes) let persistent: [u8; 8] = file_id[0..8].try_into().unwrap(); let volatile: [u8; 8] = file_id[8..16].try_into().unwrap(); assert_ne!(persistent, volatile); } /// Test compound response stitching #[test] fn test_response_stitching() { // Simulate two responses let response1_len = 128; let response2_len = 96; // Calculate aligned lengths (8-byte alignment) let aligned1 = (response1_len + 7) & !7; let aligned2 = response2_len; // Last response, no padding assert_eq!(aligned1, 128); // Already aligned assert_eq!(aligned2, 96); // Total compound response length let total = aligned1 + aligned2; assert_eq!(total, 224); } /// Test compound request error handling #[test] fn test_compound_error_handling() { // MS-SMB2 §3.3.5.2.7: If any request fails, subsequent requests should not be processed // However, all responses should still be returned (with error status) // Simulate error in second request (READ after CREATE failed) let error_status: u32 = ntstatus::STATUS_OBJECT_NAME_NOT_FOUND; // Verify error status assert_ne!(error_status, ntstatus::STATUS_SUCCESS); // Error response should still be included in compound chain // (with STATUS_PENDING for subsequent requests) }