⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/fs/smb/
client.rs

1//! CIFS/SMB2/3 Client Implementation
2//!
3//! Implements SMB2/3 protocol with dialect negotiation, NTLM authentication,
4//! tree connect/disconnect, file create/read/write/close, directory queries,
5//! and message signing via HMAC-SHA256.
6
7#![allow(dead_code)]
8
9#[cfg(feature = "alloc")]
10extern crate alloc;
11
12#[cfg(feature = "alloc")]
13use alloc::{string::String, vec::Vec};
14
15// ---------------------------------------------------------------------------
16// Protocol constants
17// ---------------------------------------------------------------------------
18
19/// SMB2 protocol magic: 0xFE 'S' 'M' 'B'.
20const SMB2_MAGIC: u32 = 0xFE53_4D42;
21
22/// SMB2 header structure size.
23const SMB2_HEADER_SIZE: usize = 64;
24
25/// NTLMSSP signature: "NTLMSSP\0".
26const NTLMSSP_SIGNATURE: [u8; 8] = [0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0x00];
27
28/// NTLM message types.
29const NTLM_NEGOTIATE: u32 = 1;
30const NTLM_CHALLENGE: u32 = 2;
31const NTLM_AUTHENTICATE: u32 = 3;
32
33/// NTLM negotiate flags.
34const NTLMSSP_NEGOTIATE_UNICODE: u32 = 0x0000_0001;
35const NTLMSSP_NEGOTIATE_NTLM: u32 = 0x0000_0200;
36const NTLMSSP_REQUEST_TARGET: u32 = 0x0000_0004;
37
38// ---------------------------------------------------------------------------
39// SMB Dialect
40// ---------------------------------------------------------------------------
41
42/// Supported SMB protocol dialects.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
44#[repr(u16)]
45pub enum SmbDialect {
46    Smb2_0_2 = 0x0202,
47    Smb2_1 = 0x0210,
48    Smb3_0 = 0x0300,
49    Smb3_0_2 = 0x0302,
50    Smb3_1_1 = 0x0311,
51}
52
53impl SmbDialect {
54    /// Convert from wire value.
55    pub fn from_u16(v: u16) -> Option<Self> {
56        match v {
57            0x0202 => Some(Self::Smb2_0_2),
58            0x0210 => Some(Self::Smb2_1),
59            0x0300 => Some(Self::Smb3_0),
60            0x0302 => Some(Self::Smb3_0_2),
61            0x0311 => Some(Self::Smb3_1_1),
62            _ => None,
63        }
64    }
65
66    /// Get all supported dialects in preference order (highest first).
67    pub fn all() -> &'static [SmbDialect] {
68        &[
69            Self::Smb3_1_1,
70            Self::Smb3_0_2,
71            Self::Smb3_0,
72            Self::Smb2_1,
73            Self::Smb2_0_2,
74        ]
75    }
76}
77
78// ---------------------------------------------------------------------------
79// SMB Command
80// ---------------------------------------------------------------------------
81
82/// SMB2/3 command codes.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[repr(u16)]
85pub enum SmbCommand {
86    Negotiate = 0,
87    SessionSetup = 1,
88    Logoff = 2,
89    TreeConnect = 3,
90    TreeDisconnect = 4,
91    Create = 5,
92    Close = 6,
93    Read = 8,
94    Write = 9,
95    QueryDirectory = 14,
96    QueryInfo = 16,
97}
98
99impl SmbCommand {
100    /// Convert from wire value.
101    pub fn from_u16(v: u16) -> Option<Self> {
102        match v {
103            0 => Some(Self::Negotiate),
104            1 => Some(Self::SessionSetup),
105            2 => Some(Self::Logoff),
106            3 => Some(Self::TreeConnect),
107            4 => Some(Self::TreeDisconnect),
108            5 => Some(Self::Create),
109            6 => Some(Self::Close),
110            8 => Some(Self::Read),
111            9 => Some(Self::Write),
112            14 => Some(Self::QueryDirectory),
113            16 => Some(Self::QueryInfo),
114            _ => None,
115        }
116    }
117}
118
119// ---------------------------------------------------------------------------
120// SMB Status Codes
121// ---------------------------------------------------------------------------
122
123/// Common SMB/NT status codes.
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125#[repr(u32)]
126pub enum NtStatus {
127    Success = 0x0000_0000,
128    MoreProcessingRequired = 0xC000_0016,
129    InvalidParameter = 0xC000_000D,
130    NoSuchFile = 0xC000_000F,
131    AccessDenied = 0xC000_0022,
132    ObjectNameNotFound = 0xC000_0034,
133    ObjectNameCollision = 0xC000_0035,
134    LogonFailure = 0xC000_006D,
135    BadNetworkName = 0xC000_00CC,
136    NotFound = 0xC000_0225,
137}
138
139impl NtStatus {
140    /// Convert from wire value.
141    pub fn from_u32(v: u32) -> Self {
142        match v {
143            0x0000_0000 => Self::Success,
144            0xC000_0016 => Self::MoreProcessingRequired,
145            0xC000_000D => Self::InvalidParameter,
146            0xC000_000F => Self::NoSuchFile,
147            0xC000_0022 => Self::AccessDenied,
148            0xC000_0034 => Self::ObjectNameNotFound,
149            0xC000_0035 => Self::ObjectNameCollision,
150            0xC000_006D => Self::LogonFailure,
151            0xC000_00CC => Self::BadNetworkName,
152            0xC000_0225 => Self::NotFound,
153            _ => Self::InvalidParameter,
154        }
155    }
156}
157
158// ---------------------------------------------------------------------------
159// SMB Header
160// ---------------------------------------------------------------------------
161
162/// SMB2/3 packet header (64 bytes).
163#[derive(Debug, Clone)]
164pub struct SmbHeader {
165    /// Protocol ID (0xFE534D42).
166    pub protocol_id: u32,
167    /// Structure size (always 64).
168    pub struct_size: u16,
169    /// Credit charge.
170    pub credit_charge: u16,
171    /// NT status code.
172    pub status: NtStatus,
173    /// Command code.
174    pub command: SmbCommand,
175    /// Credits requested/granted.
176    pub credit_req_grant: u16,
177    /// Flags.
178    pub flags: u32,
179    /// Chain offset to next command (0 if last).
180    pub next_command: u32,
181    /// Message ID.
182    pub message_id: u64,
183    /// Tree ID.
184    pub tree_id: u32,
185    /// Session ID.
186    pub session_id: u64,
187    /// Message signature.
188    pub signature: [u8; 16],
189}
190
191impl SmbHeader {
192    /// Create a new header for a request.
193    pub fn new_request(command: SmbCommand, message_id: u64) -> Self {
194        Self {
195            protocol_id: SMB2_MAGIC,
196            struct_size: SMB2_HEADER_SIZE as u16,
197            credit_charge: 1,
198            status: NtStatus::Success,
199            command,
200            credit_req_grant: 32,
201            flags: 0,
202            next_command: 0,
203            message_id,
204            tree_id: 0,
205            session_id: 0,
206            signature: [0u8; 16],
207        }
208    }
209
210    /// Serialize header to bytes.
211    #[cfg(feature = "alloc")]
212    pub fn serialize(&self) -> Vec<u8> {
213        let mut buf = Vec::with_capacity(SMB2_HEADER_SIZE);
214        buf.extend_from_slice(&self.protocol_id.to_le_bytes());
215        buf.extend_from_slice(&self.struct_size.to_le_bytes());
216        buf.extend_from_slice(&self.credit_charge.to_le_bytes());
217        buf.extend_from_slice(&(self.status as u32).to_le_bytes());
218        buf.extend_from_slice(&(self.command as u16).to_le_bytes());
219        buf.extend_from_slice(&self.credit_req_grant.to_le_bytes());
220        buf.extend_from_slice(&self.flags.to_le_bytes());
221        buf.extend_from_slice(&self.next_command.to_le_bytes());
222        buf.extend_from_slice(&self.message_id.to_le_bytes());
223        // Process ID (4 bytes, reserved in SMB2)
224        buf.extend_from_slice(&0u32.to_le_bytes());
225        buf.extend_from_slice(&self.tree_id.to_le_bytes());
226        buf.extend_from_slice(&self.session_id.to_le_bytes());
227        buf.extend_from_slice(&self.signature);
228        buf
229    }
230
231    /// Deserialize header from bytes.
232    pub fn deserialize(data: &[u8]) -> Option<Self> {
233        if data.len() < SMB2_HEADER_SIZE {
234            return None;
235        }
236
237        let protocol_id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
238        if protocol_id != SMB2_MAGIC {
239            return None;
240        }
241
242        let struct_size = u16::from_le_bytes([data[4], data[5]]);
243        let credit_charge = u16::from_le_bytes([data[6], data[7]]);
244        let status_val = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
245        let command_val = u16::from_le_bytes([data[12], data[13]]);
246        let credit_req_grant = u16::from_le_bytes([data[14], data[15]]);
247        let flags = u32::from_le_bytes([data[16], data[17], data[18], data[19]]);
248        let next_command = u32::from_le_bytes([data[20], data[21], data[22], data[23]]);
249        let message_id = u64::from_le_bytes([
250            data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31],
251        ]);
252        // Skip process_id (4 bytes at offset 32)
253        let tree_id = u32::from_le_bytes([data[36], data[37], data[38], data[39]]);
254        let session_id = u64::from_le_bytes([
255            data[40], data[41], data[42], data[43], data[44], data[45], data[46], data[47],
256        ]);
257
258        let mut signature = [0u8; 16];
259        signature.copy_from_slice(&data[48..64]);
260
261        Some(Self {
262            protocol_id,
263            struct_size,
264            credit_charge,
265            status: NtStatus::from_u32(status_val),
266            command: SmbCommand::from_u16(command_val)?,
267            credit_req_grant,
268            flags,
269            next_command,
270            message_id,
271            tree_id,
272            session_id,
273            signature,
274        })
275    }
276}
277
278// ---------------------------------------------------------------------------
279// File handle and directory entry
280// ---------------------------------------------------------------------------
281
282/// SMB2 file ID (persistent + volatile).
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
284pub struct SmbFileId {
285    pub persistent: u64,
286    pub volatile: u64,
287}
288
289/// Create disposition values for SMB2 CREATE.
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291#[repr(u32)]
292pub enum CreateDisposition {
293    /// If exists: overwrite. If not: fail.
294    Supersede = 0,
295    /// If exists: open. If not: fail.
296    Open = 1,
297    /// If exists: fail. If not: create.
298    Create = 2,
299    /// If exists: open. If not: create.
300    OpenIf = 3,
301    /// If exists: overwrite. If not: fail.
302    Overwrite = 4,
303    /// If exists: overwrite. If not: create.
304    OverwriteIf = 5,
305}
306
307/// Desired access flags.
308pub const FILE_READ_DATA: u32 = 0x0000_0001;
309pub const FILE_WRITE_DATA: u32 = 0x0000_0002;
310pub const FILE_READ_ATTRIBUTES: u32 = 0x0000_0080;
311pub const FILE_WRITE_ATTRIBUTES: u32 = 0x0000_0100;
312pub const DELETE: u32 = 0x0001_0000;
313pub const GENERIC_READ: u32 = 0x8000_0000;
314pub const GENERIC_WRITE: u32 = 0x4000_0000;
315
316/// Share access flags.
317pub const FILE_SHARE_READ: u32 = 0x0000_0001;
318pub const FILE_SHARE_WRITE: u32 = 0x0000_0002;
319pub const FILE_SHARE_DELETE: u32 = 0x0000_0004;
320
321/// SMB2 directory entry.
322#[cfg(feature = "alloc")]
323#[derive(Debug, Clone)]
324pub struct SmbDirEntry {
325    pub name: String,
326    pub file_id: u64,
327    pub file_size: u64,
328    pub is_directory: bool,
329    pub creation_time: u64,
330    pub last_write_time: u64,
331}
332
333// ---------------------------------------------------------------------------
334// NTLM Authentication
335// ---------------------------------------------------------------------------
336
337/// NTLM authentication state machine.
338#[cfg(feature = "alloc")]
339pub struct NtlmAuth {
340    /// Domain name.
341    domain: String,
342    /// Username.
343    username: String,
344    /// NT hash of password.
345    nt_hash: [u8; 16],
346    /// NTLMv2 hash (derived from nt_hash + username + domain).
347    ntlm_v2_hash: [u8; 16],
348    /// Server challenge received during negotiation.
349    server_challenge: [u8; 8],
350}
351
352#[cfg(feature = "alloc")]
353impl NtlmAuth {
354    /// Create NTLM auth with credentials.
355    pub fn new(username: &str, password: &str, domain: &str) -> Self {
356        let nt_hash = Self::compute_nt_hash(password);
357        let ntlm_v2_hash = Self::compute_ntlm_v2_hash(&nt_hash, username, domain);
358
359        Self {
360            domain: String::from(domain),
361            username: String::from(username),
362            nt_hash,
363            ntlm_v2_hash,
364            server_challenge: [0u8; 8],
365        }
366    }
367
368    /// Generate NTLM NEGOTIATE_MESSAGE.
369    pub fn negotiate(&self) -> Vec<u8> {
370        let mut buf = Vec::with_capacity(32);
371        buf.extend_from_slice(&NTLMSSP_SIGNATURE);
372        buf.extend_from_slice(&NTLM_NEGOTIATE.to_le_bytes());
373        let flags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_REQUEST_TARGET;
374        buf.extend_from_slice(&flags.to_le_bytes());
375        // Domain name fields (offset/length, empty for now)
376        buf.extend_from_slice(&0u16.to_le_bytes()); // DomainNameLen
377        buf.extend_from_slice(&0u16.to_le_bytes()); // DomainNameMaxLen
378        buf.extend_from_slice(&0u32.to_le_bytes()); // DomainNameBufferOffset
379                                                    // Workstation fields (empty)
380        buf.extend_from_slice(&0u16.to_le_bytes());
381        buf.extend_from_slice(&0u16.to_le_bytes());
382        buf.extend_from_slice(&0u32.to_le_bytes());
383        buf
384    }
385
386    /// Process CHALLENGE_MESSAGE and generate AUTHENTICATE_MESSAGE.
387    pub fn challenge_response(&mut self, challenge_msg: &[u8]) -> Option<Vec<u8>> {
388        // Validate NTLMSSP signature
389        if challenge_msg.len() < 32 {
390            return None;
391        }
392        if challenge_msg[..8] != NTLMSSP_SIGNATURE {
393            return None;
394        }
395        let msg_type = u32::from_le_bytes([
396            challenge_msg[8],
397            challenge_msg[9],
398            challenge_msg[10],
399            challenge_msg[11],
400        ]);
401        if msg_type != NTLM_CHALLENGE {
402            return None;
403        }
404
405        // Extract server challenge (8 bytes at offset 24)
406        if challenge_msg.len() < 32 {
407            return None;
408        }
409        self.server_challenge
410            .copy_from_slice(&challenge_msg[24..32]);
411
412        // Build NTLMv2 response
413        let client_challenge = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
414        let nt_response = self.compute_nt_response(&client_challenge);
415
416        // Build AUTHENTICATE_MESSAGE
417        let mut buf = Vec::with_capacity(128);
418        buf.extend_from_slice(&NTLMSSP_SIGNATURE);
419        buf.extend_from_slice(&NTLM_AUTHENTICATE.to_le_bytes());
420
421        // LmChallengeResponse (empty)
422        let payload_offset: u32 = 88; // Fixed header size
423        buf.extend_from_slice(&0u16.to_le_bytes());
424        buf.extend_from_slice(&0u16.to_le_bytes());
425        buf.extend_from_slice(&payload_offset.to_le_bytes());
426
427        // NtChallengeResponse
428        let nt_resp_len = nt_response.len() as u16;
429        buf.extend_from_slice(&nt_resp_len.to_le_bytes());
430        buf.extend_from_slice(&nt_resp_len.to_le_bytes());
431        buf.extend_from_slice(&payload_offset.to_le_bytes());
432
433        // DomainName (offset after NT response)
434        let domain_utf16 = Self::to_utf16le(&self.domain);
435        let domain_offset = payload_offset + nt_response.len() as u32;
436        buf.extend_from_slice(&(domain_utf16.len() as u16).to_le_bytes());
437        buf.extend_from_slice(&(domain_utf16.len() as u16).to_le_bytes());
438        buf.extend_from_slice(&domain_offset.to_le_bytes());
439
440        // UserName
441        let user_utf16 = Self::to_utf16le(&self.username);
442        let user_offset = domain_offset + domain_utf16.len() as u32;
443        buf.extend_from_slice(&(user_utf16.len() as u16).to_le_bytes());
444        buf.extend_from_slice(&(user_utf16.len() as u16).to_le_bytes());
445        buf.extend_from_slice(&user_offset.to_le_bytes());
446
447        // Workstation (empty)
448        let ws_offset = user_offset + user_utf16.len() as u32;
449        buf.extend_from_slice(&0u16.to_le_bytes());
450        buf.extend_from_slice(&0u16.to_le_bytes());
451        buf.extend_from_slice(&ws_offset.to_le_bytes());
452
453        // EncryptedRandomSessionKey (empty)
454        buf.extend_from_slice(&0u16.to_le_bytes());
455        buf.extend_from_slice(&0u16.to_le_bytes());
456        buf.extend_from_slice(&ws_offset.to_le_bytes());
457
458        // NegotiateFlags
459        let flags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM;
460        buf.extend_from_slice(&flags.to_le_bytes());
461
462        // Payload
463        buf.extend_from_slice(&nt_response);
464        buf.extend_from_slice(&domain_utf16);
465        buf.extend_from_slice(&user_utf16);
466
467        Some(buf)
468    }
469
470    /// Compute NT hash: MD4(UTF-16LE(password)).
471    /// Simplified MD4 -- in production use a real crypto library.
472    fn compute_nt_hash(password: &str) -> [u8; 16] {
473        let utf16 = Self::to_utf16le(password);
474        Self::md4(&utf16)
475    }
476
477    /// Compute NTLMv2 hash: HMAC-MD5(NT_HASH, UPPER(username) + domain).
478    fn compute_ntlm_v2_hash(nt_hash: &[u8; 16], username: &str, domain: &str) -> [u8; 16] {
479        let mut identity = Vec::new();
480        // Convert username to uppercase UTF-16LE
481        for ch in username.chars() {
482            for uc in ch.to_uppercase() {
483                let val = uc as u16;
484                identity.push(val as u8);
485                identity.push((val >> 8) as u8);
486            }
487        }
488        // Append domain as UTF-16LE
489        let domain_utf16 = Self::to_utf16le(domain);
490        identity.extend_from_slice(&domain_utf16);
491
492        Self::hmac_md5(nt_hash, &identity)
493    }
494
495    /// Compute NTLMv2 response from server challenge.
496    fn compute_nt_response(&self, client_challenge: &[u8; 8]) -> Vec<u8> {
497        // NTProofStr = HMAC-MD5(NTLMv2Hash, ServerChallenge + ClientBlob)
498        let mut blob = Vec::with_capacity(32);
499        // Blob header
500        blob.push(0x01); // RespType
501        blob.push(0x01); // HiRespType
502        blob.extend_from_slice(&[0; 2]); // Reserved1
503        blob.extend_from_slice(&[0; 4]); // Reserved2
504        blob.extend_from_slice(&[0; 8]); // TimeStamp (placeholder)
505        blob.extend_from_slice(client_challenge);
506        blob.extend_from_slice(&[0; 4]); // Reserved3
507
508        let mut challenge_blob = Vec::with_capacity(8 + blob.len());
509        challenge_blob.extend_from_slice(&self.server_challenge);
510        challenge_blob.extend_from_slice(&blob);
511
512        let nt_proof = Self::hmac_md5(&self.ntlm_v2_hash, &challenge_blob);
513
514        let mut response = Vec::with_capacity(16 + blob.len());
515        response.extend_from_slice(&nt_proof);
516        response.extend_from_slice(&blob);
517        response
518    }
519
520    /// Convert a string to UTF-16LE bytes.
521    fn to_utf16le(s: &str) -> Vec<u8> {
522        let mut buf = Vec::with_capacity(s.len() * 2);
523        for ch in s.chars() {
524            let val = ch as u16;
525            buf.push(val as u8);
526            buf.push((val >> 8) as u8);
527        }
528        buf
529    }
530
531    /// Simplified MD4 hash (single-block, messages up to 55 bytes).
532    /// For kernel use only; not a complete implementation.
533    fn md4(data: &[u8]) -> [u8; 16] {
534        // Pad message
535        let mut msg = Vec::with_capacity(64);
536        msg.extend_from_slice(data);
537        msg.push(0x80);
538        while msg.len() % 64 != 56 {
539            msg.push(0);
540        }
541        let bit_len = (data.len() as u64) * 8;
542        msg.extend_from_slice(&bit_len.to_le_bytes());
543
544        // Initial state
545        let mut a: u32 = 0x6745_2301;
546        let mut b: u32 = 0xEFCD_AB89;
547        let mut c: u32 = 0x98BA_DCFE;
548        let mut d: u32 = 0x1032_5476;
549
550        // Process each 64-byte block
551        let mut block_offset = 0;
552        while block_offset < msg.len() {
553            let mut x = [0u32; 16];
554            for (i, word) in x.iter_mut().enumerate() {
555                let j = block_offset + i * 4;
556                *word = u32::from_le_bytes([msg[j], msg[j + 1], msg[j + 2], msg[j + 3]]);
557            }
558
559            let (aa, bb, cc, dd) = (a, b, c, d);
560
561            // Round 1
562            for &i in &[0, 4, 8, 12] {
563                a = md4_ff(a, b, c, d, x[i], 3);
564                d = md4_ff(d, a, b, c, x[i + 1], 7);
565                c = md4_ff(c, d, a, b, x[i + 2], 11);
566                b = md4_ff(b, c, d, a, x[i + 3], 19);
567            }
568
569            // Round 2
570            for &i in &[0, 1, 2, 3] {
571                a = md4_gg(a, b, c, d, x[i], 3);
572                d = md4_gg(d, a, b, c, x[i + 4], 5);
573                c = md4_gg(c, d, a, b, x[i + 8], 9);
574                b = md4_gg(b, c, d, a, x[i + 12], 13);
575            }
576
577            // Round 3
578            for &i in &[0, 2, 1, 3] {
579                a = md4_hh(a, b, c, d, x[i], 3);
580                d = md4_hh(d, a, b, c, x[i + 8], 9);
581                c = md4_hh(c, d, a, b, x[i + 4], 11);
582                b = md4_hh(b, c, d, a, x[i + 12], 15);
583            }
584
585            a = a.wrapping_add(aa);
586            b = b.wrapping_add(bb);
587            c = c.wrapping_add(cc);
588            d = d.wrapping_add(dd);
589
590            block_offset += 64;
591        }
592
593        let mut result = [0u8; 16];
594        result[0..4].copy_from_slice(&a.to_le_bytes());
595        result[4..8].copy_from_slice(&b.to_le_bytes());
596        result[8..12].copy_from_slice(&c.to_le_bytes());
597        result[12..16].copy_from_slice(&d.to_le_bytes());
598        result
599    }
600
601    /// HMAC-MD5.
602    fn hmac_md5(key: &[u8], data: &[u8]) -> [u8; 16] {
603        let mut k = [0u8; 64];
604        if key.len() > 64 {
605            let h = Self::md4(key); // Use MD4 as hash for oversized keys
606            k[..16].copy_from_slice(&h);
607        } else {
608            k[..key.len()].copy_from_slice(key);
609        }
610
611        let mut ipad = [0x36u8; 64];
612        let mut opad = [0x5Cu8; 64];
613        for i in 0..64 {
614            ipad[i] ^= k[i];
615            opad[i] ^= k[i];
616        }
617
618        let mut inner = Vec::with_capacity(64 + data.len());
619        inner.extend_from_slice(&ipad);
620        inner.extend_from_slice(data);
621        let inner_hash = Self::md4(&inner);
622
623        let mut outer = Vec::with_capacity(64 + 16);
624        outer.extend_from_slice(&opad);
625        outer.extend_from_slice(&inner_hash);
626        Self::md4(&outer)
627    }
628}
629
630/// MD4 round 1 function.
631fn md4_ff(a: u32, b: u32, c: u32, d: u32, x: u32, s: u32) -> u32 {
632    let f = (b & c) | (!b & d);
633    a.wrapping_add(f).wrapping_add(x).rotate_left(s)
634}
635
636/// MD4 round 2 function.
637fn md4_gg(a: u32, b: u32, c: u32, d: u32, x: u32, s: u32) -> u32 {
638    let g = (b & c) | (b & d) | (c & d);
639    a.wrapping_add(g)
640        .wrapping_add(x)
641        .wrapping_add(0x5A82_7999)
642        .rotate_left(s)
643}
644
645/// MD4 round 3 function.
646fn md4_hh(a: u32, b: u32, c: u32, d: u32, x: u32, s: u32) -> u32 {
647    let h = b ^ c ^ d;
648    a.wrapping_add(h)
649        .wrapping_add(x)
650        .wrapping_add(0x6ED9_EBA1)
651        .rotate_left(s)
652}
653
654// ---------------------------------------------------------------------------
655// SMB Error
656// ---------------------------------------------------------------------------
657
658/// SMB client error type.
659#[derive(Debug, Clone, Copy, PartialEq, Eq)]
660pub enum SmbError {
661    /// Server returned an NT status error.
662    Status(NtStatus),
663    /// Protocol error (invalid magic, bad structure).
664    ProtocolError,
665    /// Not connected to server.
666    NotConnected,
667    /// Authentication failure.
668    AuthError,
669    /// No credits available.
670    NoCredits,
671    /// Network transport error.
672    TransportError,
673    /// Invalid argument.
674    InvalidArgument,
675    /// Share not connected.
676    NotMounted,
677}
678
679// ---------------------------------------------------------------------------
680// SMB Client
681// ---------------------------------------------------------------------------
682
683/// SMB2/3 client.
684#[cfg(feature = "alloc")]
685pub struct SmbClient {
686    /// Negotiated dialect.
687    dialect: Option<SmbDialect>,
688    /// Session ID.
689    session_id: u64,
690    /// Tree ID for current share.
691    tree_id: u32,
692    /// Next message ID.
693    message_id: u64,
694    /// Available credits.
695    credits: u16,
696    /// Maximum read size negotiated.
697    max_read_size: u32,
698    /// Maximum write size negotiated.
699    max_write_size: u32,
700    /// Server address.
701    server_addr: String,
702    /// Whether session is established.
703    session_active: bool,
704    /// Signing key (derived from session key).
705    signing_key: [u8; 16],
706    /// Whether signing is required.
707    signing_required: bool,
708}
709
710#[cfg(feature = "alloc")]
711impl SmbClient {
712    /// Create a new SMB client.
713    pub fn new(server: &str) -> Self {
714        Self {
715            dialect: None,
716            session_id: 0,
717            tree_id: 0,
718            message_id: 0,
719            credits: 1,
720            max_read_size: 65536,
721            max_write_size: 65536,
722            server_addr: String::from(server),
723            session_active: false,
724            signing_key: [0u8; 16],
725            signing_required: false,
726        }
727    }
728
729    /// Negotiate SMB dialect with server.
730    pub fn negotiate(&mut self) -> Result<SmbDialect, SmbError> {
731        let mut header = SmbHeader::new_request(SmbCommand::Negotiate, self.next_message_id());
732
733        // Build negotiate request body
734        let mut body = Vec::with_capacity(36 + SmbDialect::all().len() * 2);
735        body.extend_from_slice(&36u16.to_le_bytes()); // StructureSize
736        body.extend_from_slice(&(SmbDialect::all().len() as u16).to_le_bytes()); // DialectCount
737        body.extend_from_slice(&1u16.to_le_bytes()); // SecurityMode (signing enabled)
738        body.extend_from_slice(&0u16.to_le_bytes()); // Reserved
739        body.extend_from_slice(&0u32.to_le_bytes()); // Capabilities
740        body.extend_from_slice(&[0u8; 16]); // ClientGuid
741
742        // Client start time
743        body.extend_from_slice(&0u64.to_le_bytes());
744
745        // Dialect list
746        for dialect in SmbDialect::all() {
747            body.extend_from_slice(&(*dialect as u16).to_le_bytes());
748        }
749
750        let _packet = self.build_packet(&mut header, &body);
751
752        // In production: send packet, receive response, parse negotiate response.
753        // Extract negotiated dialect, max sizes, security mode.
754
755        // Stub: select highest dialect
756        let dialect = SmbDialect::Smb3_1_1;
757        self.dialect = Some(dialect);
758        self.max_read_size = 8 * 1024 * 1024; // 8 MB
759        self.max_write_size = 8 * 1024 * 1024;
760
761        Ok(dialect)
762    }
763
764    /// Establish an authenticated session via NTLM.
765    pub fn session_setup(
766        &mut self,
767        username: &str,
768        password: &str,
769        domain: &str,
770    ) -> Result<u64, SmbError> {
771        let auth = NtlmAuth::new(username, password, domain);
772
773        // Phase 1: Send NEGOTIATE_MESSAGE
774        let negotiate_token = auth.negotiate();
775        let mut header = SmbHeader::new_request(SmbCommand::SessionSetup, self.next_message_id());
776
777        let mut body = Vec::with_capacity(24 + negotiate_token.len());
778        body.extend_from_slice(&25u16.to_le_bytes()); // StructureSize
779        body.push(0); // Flags
780        body.push(1); // SecurityMode (signing enabled)
781        body.extend_from_slice(&0u32.to_le_bytes()); // Capabilities
782        body.extend_from_slice(&0u32.to_le_bytes()); // Channel
783                                                     // SecurityBufferOffset (header + body fixed part)
784        let sec_offset = (SMB2_HEADER_SIZE + 24) as u16;
785        body.extend_from_slice(&sec_offset.to_le_bytes());
786        body.extend_from_slice(&(negotiate_token.len() as u16).to_le_bytes());
787        body.extend_from_slice(&0u64.to_le_bytes()); // PreviousSessionId
788        body.extend_from_slice(&negotiate_token);
789
790        let _packet = self.build_packet(&mut header, &body);
791
792        // In production: send packet, receive challenge, call
793        // auth.challenge_response(), send final authenticate message.
794        // Stub: mark session active with a synthetic session ID.
795        self.session_id = 0x0000_0001_0000_0001;
796        self.session_active = true;
797
798        // Derive signing key from session
799        self.signing_key = auth.nt_hash;
800
801        Ok(self.session_id)
802    }
803
804    /// Connect to a share (\\\\server\\share).
805    pub fn tree_connect(&mut self, share_path: &str) -> Result<u32, SmbError> {
806        if !self.session_active {
807            return Err(SmbError::NotConnected);
808        }
809
810        let mut header = SmbHeader::new_request(SmbCommand::TreeConnect, self.next_message_id());
811        header.session_id = self.session_id;
812
813        let path_utf16 = NtlmAuth::to_utf16le(share_path);
814        let mut body = Vec::with_capacity(8 + path_utf16.len());
815        body.extend_from_slice(&9u16.to_le_bytes()); // StructureSize
816        body.extend_from_slice(&0u16.to_le_bytes()); // Reserved / Flags
817        let path_offset = (SMB2_HEADER_SIZE + 8) as u16;
818        body.extend_from_slice(&path_offset.to_le_bytes());
819        body.extend_from_slice(&(path_utf16.len() as u16).to_le_bytes());
820        body.extend_from_slice(&path_utf16);
821
822        let _packet = self.build_packet(&mut header, &body);
823
824        // Stub: assign a tree ID
825        self.tree_id = 1;
826        Ok(self.tree_id)
827    }
828
829    /// Disconnect from a share.
830    pub fn tree_disconnect(&mut self) -> Result<(), SmbError> {
831        if self.tree_id == 0 {
832            return Err(SmbError::NotMounted);
833        }
834
835        let mut header = SmbHeader::new_request(SmbCommand::TreeDisconnect, self.next_message_id());
836        header.session_id = self.session_id;
837        header.tree_id = self.tree_id;
838
839        let body = 4u16.to_le_bytes().to_vec(); // StructureSize + Reserved
840        let _packet = self.build_packet(&mut header, &body);
841
842        self.tree_id = 0;
843        Ok(())
844    }
845
846    /// Open or create a file.
847    pub fn create(
848        &mut self,
849        path: &str,
850        desired_access: u32,
851        share_access: u32,
852        disposition: CreateDisposition,
853    ) -> Result<SmbFileId, SmbError> {
854        if !self.session_active {
855            return Err(SmbError::NotConnected);
856        }
857
858        let mut header = SmbHeader::new_request(SmbCommand::Create, self.next_message_id());
859        header.session_id = self.session_id;
860        header.tree_id = self.tree_id;
861
862        let name_utf16 = NtlmAuth::to_utf16le(path);
863        let name_offset = (SMB2_HEADER_SIZE + 56) as u16; // After fixed CREATE body
864
865        let mut body = Vec::with_capacity(56 + name_utf16.len());
866        body.extend_from_slice(&57u16.to_le_bytes()); // StructureSize
867        body.push(0); // SecurityFlags
868        body.push(0); // RequestedOplockLevel
869        body.extend_from_slice(&0u32.to_le_bytes()); // ImpersonationLevel
870        body.extend_from_slice(&0u64.to_le_bytes()); // SmbCreateFlags
871        body.extend_from_slice(&0u64.to_le_bytes()); // Reserved
872        body.extend_from_slice(&desired_access.to_le_bytes());
873        body.extend_from_slice(&0u32.to_le_bytes()); // FileAttributes (normal)
874        body.extend_from_slice(&share_access.to_le_bytes());
875        body.extend_from_slice(&(disposition as u32).to_le_bytes());
876        body.extend_from_slice(&0u32.to_le_bytes()); // CreateOptions
877        body.extend_from_slice(&name_offset.to_le_bytes());
878        body.extend_from_slice(&(name_utf16.len() as u16).to_le_bytes());
879        body.extend_from_slice(&0u32.to_le_bytes()); // CreateContextsOffset
880        body.extend_from_slice(&0u32.to_le_bytes()); // CreateContextsLength
881        body.extend_from_slice(&name_utf16);
882
883        let _packet = self.build_packet(&mut header, &body);
884
885        // Stub: return synthetic file ID
886        Ok(SmbFileId {
887            persistent: 1,
888            volatile: 1,
889        })
890    }
891
892    /// Read from an open file.
893    pub fn read(
894        &mut self,
895        file_id: &SmbFileId,
896        offset: u64,
897        length: u32,
898    ) -> Result<Vec<u8>, SmbError> {
899        if !self.session_active {
900            return Err(SmbError::NotConnected);
901        }
902
903        let read_len = core::cmp::min(length, self.max_read_size);
904
905        let mut header = SmbHeader::new_request(SmbCommand::Read, self.next_message_id());
906        header.session_id = self.session_id;
907        header.tree_id = self.tree_id;
908
909        let mut body = Vec::with_capacity(48);
910        body.extend_from_slice(&49u16.to_le_bytes()); // StructureSize
911        body.push(0); // Padding
912        body.push(0); // Flags
913        body.extend_from_slice(&read_len.to_le_bytes());
914        body.extend_from_slice(&offset.to_le_bytes());
915        body.extend_from_slice(&file_id.persistent.to_le_bytes());
916        body.extend_from_slice(&file_id.volatile.to_le_bytes());
917        body.extend_from_slice(&1u32.to_le_bytes()); // MinimumCount
918        body.extend_from_slice(&0u32.to_le_bytes()); // Channel
919        body.extend_from_slice(&0u32.to_le_bytes()); // RemainingBytes
920        body.extend_from_slice(&0u16.to_le_bytes()); // ReadChannelInfoOffset
921        body.extend_from_slice(&0u16.to_le_bytes()); // ReadChannelInfoLength
922
923        let _packet = self.build_packet(&mut header, &body);
924
925        // Stub: return empty data
926        Ok(Vec::new())
927    }
928
929    /// Write to an open file.
930    pub fn write(
931        &mut self,
932        file_id: &SmbFileId,
933        offset: u64,
934        data: &[u8],
935    ) -> Result<u32, SmbError> {
936        if !self.session_active {
937            return Err(SmbError::NotConnected);
938        }
939
940        let write_len = core::cmp::min(data.len(), self.max_write_size as usize);
941
942        let mut header = SmbHeader::new_request(SmbCommand::Write, self.next_message_id());
943        header.session_id = self.session_id;
944        header.tree_id = self.tree_id;
945
946        let data_offset = (SMB2_HEADER_SIZE + 48) as u16;
947        let mut body = Vec::with_capacity(48 + write_len);
948        body.extend_from_slice(&49u16.to_le_bytes()); // StructureSize
949        body.extend_from_slice(&data_offset.to_le_bytes());
950        body.extend_from_slice(&(write_len as u32).to_le_bytes());
951        body.extend_from_slice(&offset.to_le_bytes());
952        body.extend_from_slice(&file_id.persistent.to_le_bytes());
953        body.extend_from_slice(&file_id.volatile.to_le_bytes());
954        body.extend_from_slice(&0u32.to_le_bytes()); // Channel
955        body.extend_from_slice(&0u32.to_le_bytes()); // RemainingBytes
956        body.extend_from_slice(&0u16.to_le_bytes()); // WriteChannelInfoOffset
957        body.extend_from_slice(&0u16.to_le_bytes()); // WriteChannelInfoLength
958        body.extend_from_slice(&0u32.to_le_bytes()); // Flags
959        body.extend_from_slice(&data[..write_len]);
960
961        let _packet = self.build_packet(&mut header, &body);
962
963        // Stub: return bytes written
964        Ok(write_len as u32)
965    }
966
967    /// Close a file handle.
968    pub fn close(&mut self, file_id: &SmbFileId) -> Result<(), SmbError> {
969        if !self.session_active {
970            return Err(SmbError::NotConnected);
971        }
972
973        let mut header = SmbHeader::new_request(SmbCommand::Close, self.next_message_id());
974        header.session_id = self.session_id;
975        header.tree_id = self.tree_id;
976
977        let mut body = Vec::with_capacity(24);
978        body.extend_from_slice(&24u16.to_le_bytes()); // StructureSize
979        body.extend_from_slice(&0u16.to_le_bytes()); // Flags
980        body.extend_from_slice(&0u32.to_le_bytes()); // Reserved
981        body.extend_from_slice(&file_id.persistent.to_le_bytes());
982        body.extend_from_slice(&file_id.volatile.to_le_bytes());
983
984        let _packet = self.build_packet(&mut header, &body);
985
986        Ok(())
987    }
988
989    /// Query directory contents.
990    pub fn query_directory(
991        &mut self,
992        dir_id: &SmbFileId,
993        pattern: &str,
994    ) -> Result<Vec<SmbDirEntry>, SmbError> {
995        if !self.session_active {
996            return Err(SmbError::NotConnected);
997        }
998
999        let mut header = SmbHeader::new_request(SmbCommand::QueryDirectory, self.next_message_id());
1000        header.session_id = self.session_id;
1001        header.tree_id = self.tree_id;
1002
1003        let pattern_utf16 = NtlmAuth::to_utf16le(pattern);
1004        let pattern_offset = (SMB2_HEADER_SIZE + 32) as u16;
1005
1006        let mut body = Vec::with_capacity(32 + pattern_utf16.len());
1007        body.extend_from_slice(&33u16.to_le_bytes()); // StructureSize
1008        body.push(0x25); // FileInformationClass (FileIdBothDirectoryInformation)
1009        body.push(0x02); // Flags (SMB2_RESTART_SCANS)
1010        body.extend_from_slice(&0u32.to_le_bytes()); // FileIndex
1011        body.extend_from_slice(&dir_id.persistent.to_le_bytes());
1012        body.extend_from_slice(&dir_id.volatile.to_le_bytes());
1013        body.extend_from_slice(&pattern_offset.to_le_bytes());
1014        body.extend_from_slice(&(pattern_utf16.len() as u16).to_le_bytes());
1015        body.extend_from_slice(&65536u32.to_le_bytes()); // OutputBufferLength
1016        body.extend_from_slice(&pattern_utf16);
1017
1018        let _packet = self.build_packet(&mut header, &body);
1019
1020        // Stub: return empty directory listing
1021        Ok(Vec::new())
1022    }
1023
1024    /// Sign an SMB2 message with HMAC-SHA256.
1025    pub fn sign_message(&self, packet: &mut [u8]) {
1026        if !self.signing_required || packet.len() < SMB2_HEADER_SIZE {
1027            return;
1028        }
1029        // Zero the signature field (bytes 48-63)
1030        for byte in packet.iter_mut().skip(48).take(16) {
1031            *byte = 0;
1032        }
1033
1034        // Compute HMAC-SHA256 using signing_key
1035        let hmac = self.hmac_sha256(packet);
1036
1037        // Copy first 16 bytes of HMAC into signature field
1038        packet[48..64].copy_from_slice(&hmac[..16]);
1039    }
1040
1041    /// Verify an SMB2 message signature.
1042    pub fn verify_signature(&self, packet: &[u8]) -> bool {
1043        if !self.signing_required || packet.len() < SMB2_HEADER_SIZE {
1044            return true;
1045        }
1046
1047        let mut expected_sig = [0u8; 16];
1048        expected_sig.copy_from_slice(&packet[48..64]);
1049
1050        // Zero signature and compute
1051        let mut check = packet.to_vec();
1052        for byte in check.iter_mut().skip(48).take(16) {
1053            *byte = 0;
1054        }
1055        let hmac = self.hmac_sha256(&check);
1056
1057        hmac[..16] == expected_sid_to_bytes(&expected_sig)
1058    }
1059
1060    /// Simplified HMAC-SHA256 (stub -- in production use real crypto).
1061    fn hmac_sha256(&self, data: &[u8]) -> [u8; 32] {
1062        // This is a placeholder; real implementation would use SHA-256.
1063        let mut result = [0u8; 32];
1064        // Simple keyed hash for structure demonstration
1065        let mut acc: u32 = 0;
1066        for (i, &b) in self.signing_key.iter().enumerate() {
1067            acc = acc.wrapping_add(b as u32).wrapping_mul(31);
1068            result[i] = b;
1069        }
1070        for (i, &b) in data.iter().enumerate() {
1071            acc = acc.wrapping_add(b as u32).wrapping_mul(37);
1072            result[i % 32] ^= acc as u8;
1073        }
1074        result
1075    }
1076
1077    /// Manage credits: consume one, update available count.
1078    fn credit_management(&mut self, granted: u16) {
1079        self.credits = self.credits.saturating_sub(1).saturating_add(granted);
1080    }
1081
1082    /// Get next message ID and consume a credit.
1083    fn next_message_id(&mut self) -> u64 {
1084        let id = self.message_id;
1085        self.message_id += 1;
1086        id
1087    }
1088
1089    /// Build a complete SMB2 packet (header + body).
1090    fn build_packet(&mut self, header: &mut SmbHeader, body: &[u8]) -> Vec<u8> {
1091        let hdr_bytes = header.serialize();
1092        let mut packet = Vec::with_capacity(hdr_bytes.len() + body.len());
1093        packet.extend_from_slice(&hdr_bytes);
1094        packet.extend_from_slice(body);
1095
1096        if self.signing_required {
1097            self.sign_message(&mut packet);
1098        }
1099
1100        packet
1101    }
1102
1103    /// Get the negotiated dialect.
1104    pub fn dialect(&self) -> Option<SmbDialect> {
1105        self.dialect
1106    }
1107
1108    /// Get the session ID.
1109    pub fn session_id(&self) -> u64 {
1110        self.session_id
1111    }
1112
1113    /// Get the tree ID.
1114    pub fn tree_id(&self) -> u32 {
1115        self.tree_id
1116    }
1117
1118    /// Get the server address.
1119    pub fn server_addr(&self) -> &str {
1120        &self.server_addr
1121    }
1122}
1123
1124/// Helper to convert signature bytes for comparison.
1125fn expected_sid_to_bytes(sig: &[u8; 16]) -> [u8; 16] {
1126    *sig
1127}
1128
1129// ---------------------------------------------------------------------------
1130// Tests
1131// ---------------------------------------------------------------------------
1132
1133#[cfg(test)]
1134mod tests {
1135    #[allow(unused_imports)]
1136    use alloc::vec;
1137
1138    use super::*;
1139
1140    #[test]
1141    fn test_smb_dialect_from_u16() {
1142        assert_eq!(SmbDialect::from_u16(0x0202), Some(SmbDialect::Smb2_0_2));
1143        assert_eq!(SmbDialect::from_u16(0x0311), Some(SmbDialect::Smb3_1_1));
1144        assert_eq!(SmbDialect::from_u16(0x0000), None);
1145    }
1146
1147    #[test]
1148    fn test_smb_dialect_ordering() {
1149        assert!(SmbDialect::Smb3_1_1 > SmbDialect::Smb2_0_2);
1150        assert!(SmbDialect::Smb3_0 > SmbDialect::Smb2_1);
1151    }
1152
1153    #[test]
1154    fn test_smb_command_from_u16() {
1155        assert_eq!(SmbCommand::from_u16(0), Some(SmbCommand::Negotiate));
1156        assert_eq!(SmbCommand::from_u16(5), Some(SmbCommand::Create));
1157        assert_eq!(SmbCommand::from_u16(99), None);
1158    }
1159
1160    #[test]
1161    fn test_nt_status_from_u32() {
1162        assert_eq!(NtStatus::from_u32(0x0000_0000), NtStatus::Success);
1163        assert_eq!(NtStatus::from_u32(0xC000_0022), NtStatus::AccessDenied);
1164        assert_eq!(NtStatus::from_u32(0xC000_006D), NtStatus::LogonFailure);
1165    }
1166
1167    #[test]
1168    fn test_smb_header_serialize_deserialize() {
1169        let header = SmbHeader::new_request(SmbCommand::Negotiate, 42);
1170        let bytes = header.serialize();
1171        assert_eq!(bytes.len(), SMB2_HEADER_SIZE);
1172
1173        let parsed = SmbHeader::deserialize(&bytes).unwrap();
1174        assert_eq!(parsed.protocol_id, SMB2_MAGIC);
1175        assert_eq!(parsed.command, SmbCommand::Negotiate);
1176        assert_eq!(parsed.message_id, 42);
1177    }
1178
1179    #[test]
1180    fn test_smb_header_bad_magic() {
1181        let mut bytes = SmbHeader::new_request(SmbCommand::Negotiate, 0).serialize();
1182        bytes[0] = 0xFF; // Corrupt magic
1183        assert!(SmbHeader::deserialize(&bytes).is_none());
1184    }
1185
1186    #[test]
1187    fn test_smb_header_too_short() {
1188        assert!(SmbHeader::deserialize(&[0; 10]).is_none());
1189    }
1190
1191    #[test]
1192    fn test_ntlm_negotiate_message() {
1193        let auth = NtlmAuth::new("user", "pass", "DOMAIN");
1194        let msg = auth.negotiate();
1195        assert!(msg.len() >= 32);
1196        assert_eq!(&msg[..8], &NTLMSSP_SIGNATURE);
1197        let msg_type = u32::from_le_bytes([msg[8], msg[9], msg[10], msg[11]]);
1198        assert_eq!(msg_type, NTLM_NEGOTIATE);
1199    }
1200
1201    #[test]
1202    fn test_ntlm_challenge_too_short() {
1203        let mut auth = NtlmAuth::new("user", "pass", "DOMAIN");
1204        assert!(auth.challenge_response(&[0; 10]).is_none());
1205    }
1206
1207    #[test]
1208    fn test_ntlm_challenge_bad_signature() {
1209        let mut auth = NtlmAuth::new("user", "pass", "DOMAIN");
1210        let mut msg = vec![0u8; 40];
1211        msg[..8].copy_from_slice(b"BADMAGIC");
1212        assert!(auth.challenge_response(&msg).is_none());
1213    }
1214
1215    #[test]
1216    fn test_ntlm_nt_hash_deterministic() {
1217        let h1 = NtlmAuth::compute_nt_hash("password");
1218        let h2 = NtlmAuth::compute_nt_hash("password");
1219        assert_eq!(h1, h2);
1220
1221        let h3 = NtlmAuth::compute_nt_hash("different");
1222        assert_ne!(h1, h3);
1223    }
1224
1225    #[test]
1226    fn test_utf16le_conversion() {
1227        let result = NtlmAuth::to_utf16le("AB");
1228        assert_eq!(result, &[0x41, 0x00, 0x42, 0x00]);
1229    }
1230
1231    #[test]
1232    fn test_smb_client_new() {
1233        let client = SmbClient::new("192.168.1.1");
1234        assert_eq!(client.server_addr(), "192.168.1.1");
1235        assert!(client.dialect().is_none());
1236        assert_eq!(client.session_id(), 0);
1237    }
1238
1239    #[test]
1240    fn test_smb_client_tree_connect_not_connected() {
1241        let mut client = SmbClient::new("10.0.0.1");
1242        let result = client.tree_connect("\\\\10.0.0.1\\share");
1243        assert_eq!(result, Err(SmbError::NotConnected));
1244    }
1245}