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

veridian_kernel/fs/nfs/
client.rs

1//! NFS v4 Client (RFC 7530)
2//!
3//! Implements NFS v4 compound operations with XDR encoding/decoding,
4//! file handle management, and VFS mount point integration.
5
6#![allow(dead_code)]
7
8#[cfg(feature = "alloc")]
9extern crate alloc;
10
11#[cfg(feature = "alloc")]
12use alloc::{string::String, vec, vec::Vec};
13
14// ---------------------------------------------------------------------------
15// NFS File Handle
16// ---------------------------------------------------------------------------
17
18/// Maximum NFS file handle length (RFC 7530 Section 4).
19const NFS4_FHSIZE: usize = 128;
20
21/// Opaque NFS file handle.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct NfsFileHandle {
24    /// Raw handle bytes (up to 128).
25    data: [u8; NFS4_FHSIZE],
26    /// Actual length of the handle.
27    len: usize,
28}
29
30impl Default for NfsFileHandle {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl NfsFileHandle {
37    /// Create an empty file handle.
38    pub fn new() -> Self {
39        Self {
40            data: [0u8; NFS4_FHSIZE],
41            len: 0,
42        }
43    }
44
45    /// Create a file handle from a byte slice.
46    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
47        if bytes.len() > NFS4_FHSIZE {
48            return None;
49        }
50        let mut fh = Self::new();
51        fh.data[..bytes.len()].copy_from_slice(bytes);
52        fh.len = bytes.len();
53        Some(fh)
54    }
55
56    /// Get the handle bytes.
57    pub fn as_bytes(&self) -> &[u8] {
58        &self.data[..self.len]
59    }
60
61    /// Returns true if the handle is empty.
62    pub fn is_empty(&self) -> bool {
63        self.len == 0
64    }
65}
66
67// ---------------------------------------------------------------------------
68// NFS Types
69// ---------------------------------------------------------------------------
70
71/// NFS file type (nfs_ftype4 per RFC 7530 Section 5.8.1.2).
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[repr(u32)]
74pub enum NfsFtype {
75    Regular = 1,
76    Directory = 2,
77    BlockDevice = 3,
78    CharDevice = 4,
79    Symlink = 5,
80    Socket = 6,
81    Fifo = 7,
82}
83
84impl NfsFtype {
85    /// Convert from wire value.
86    pub fn from_u32(v: u32) -> Option<Self> {
87        match v {
88            1 => Some(Self::Regular),
89            2 => Some(Self::Directory),
90            3 => Some(Self::BlockDevice),
91            4 => Some(Self::CharDevice),
92            5 => Some(Self::Symlink),
93            6 => Some(Self::Socket),
94            7 => Some(Self::Fifo),
95            _ => None,
96        }
97    }
98}
99
100/// NFS file attributes (fattr4 subset).
101#[derive(Debug, Clone)]
102pub struct NfsAttr {
103    pub file_type: NfsFtype,
104    pub mode: u32,
105    pub nlink: u32,
106    pub uid: u32,
107    pub gid: u32,
108    pub size: u64,
109    pub used: u64,
110    pub rdev: u64,
111    pub fsid: u64,
112    pub fileid: u64,
113    pub atime: u64,
114    pub mtime: u64,
115    pub ctime: u64,
116}
117
118impl Default for NfsAttr {
119    fn default() -> Self {
120        Self {
121            file_type: NfsFtype::Regular,
122            mode: 0,
123            nlink: 0,
124            uid: 0,
125            gid: 0,
126            size: 0,
127            used: 0,
128            rdev: 0,
129            fsid: 0,
130            fileid: 0,
131            atime: 0,
132            mtime: 0,
133            ctime: 0,
134        }
135    }
136}
137
138// ---------------------------------------------------------------------------
139// NFS Operations
140// ---------------------------------------------------------------------------
141
142/// NFS v4 operations (opcode values per RFC 7530 Section 16).
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144#[repr(u32)]
145pub enum NfsOpcode {
146    Access = 3,
147    Close = 4,
148    Commit = 5,
149    Create = 6,
150    GetAttr = 9,
151    GetFH = 10,
152    Lookup = 15,
153    Open = 18,
154    PutFH = 22,
155    PutRootFH = 24,
156    Read = 25,
157    ReadDir = 26,
158    Remove = 28,
159    Rename = 29,
160    SetAttr = 34,
161    Write = 38,
162}
163
164/// NFS v4 operation with associated data.
165#[cfg(feature = "alloc")]
166#[derive(Debug, Clone)]
167pub enum NfsOperation {
168    Access {
169        access_mask: u32,
170    },
171    Close {
172        state_id: [u8; 16],
173    },
174    Commit {
175        offset: u64,
176        count: u32,
177    },
178    Create {
179        name: String,
180        file_type: NfsFtype,
181    },
182    GetAttr {
183        attr_request: u64,
184    },
185    GetFH,
186    Lookup {
187        name: String,
188    },
189    Open {
190        name: String,
191        access: u32,
192        deny: u32,
193    },
194    PutFH {
195        handle: NfsFileHandle,
196    },
197    PutRootFH,
198    Read {
199        state_id: [u8; 16],
200        offset: u64,
201        count: u32,
202    },
203    ReadDir {
204        cookie: u64,
205        count: u32,
206    },
207    Remove {
208        name: String,
209    },
210    Rename {
211        old_name: String,
212        new_name: String,
213    },
214    SetAttr {
215        state_id: [u8; 16],
216        attrs: NfsAttr,
217    },
218    Write {
219        state_id: [u8; 16],
220        offset: u64,
221        data: Vec<u8>,
222        stable: bool,
223    },
224}
225
226/// NFS v4 status codes.
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228#[repr(u32)]
229pub enum NfsStatus {
230    Ok = 0,
231    Perm = 1,
232    NoEnt = 2,
233    IO = 5,
234    Access = 13,
235    Exist = 17,
236    NotDir = 20,
237    IsDir = 21,
238    Inval = 22,
239    FBig = 27,
240    NoSpc = 28,
241    RoFs = 30,
242    Stale = 70,
243    BadHandle = 10001,
244    NotSupp = 10004,
245    ServerFault = 10006,
246}
247
248impl NfsStatus {
249    /// Convert from wire value.
250    pub fn from_u32(v: u32) -> Self {
251        match v {
252            0 => Self::Ok,
253            1 => Self::Perm,
254            2 => Self::NoEnt,
255            5 => Self::IO,
256            13 => Self::Access,
257            17 => Self::Exist,
258            20 => Self::NotDir,
259            21 => Self::IsDir,
260            22 => Self::Inval,
261            27 => Self::FBig,
262            28 => Self::NoSpc,
263            30 => Self::RoFs,
264            70 => Self::Stale,
265            10001 => Self::BadHandle,
266            10004 => Self::NotSupp,
267            10006 => Self::ServerFault,
268            _ => Self::ServerFault,
269        }
270    }
271}
272
273/// Result of an individual NFS operation.
274#[cfg(feature = "alloc")]
275#[derive(Debug, Clone)]
276pub enum NfsResult {
277    Access {
278        status: NfsStatus,
279        supported: u32,
280        access: u32,
281    },
282    Close {
283        status: NfsStatus,
284    },
285    Commit {
286        status: NfsStatus,
287    },
288    Create {
289        status: NfsStatus,
290    },
291    GetAttr {
292        status: NfsStatus,
293        attrs: Option<NfsAttr>,
294    },
295    GetFH {
296        status: NfsStatus,
297        handle: Option<NfsFileHandle>,
298    },
299    Lookup {
300        status: NfsStatus,
301    },
302    Open {
303        status: NfsStatus,
304        state_id: [u8; 16],
305    },
306    PutFH {
307        status: NfsStatus,
308    },
309    PutRootFH {
310        status: NfsStatus,
311    },
312    Read {
313        status: NfsStatus,
314        eof: bool,
315        data: Vec<u8>,
316    },
317    ReadDir {
318        status: NfsStatus,
319        entries: Vec<NfsDirEntry>,
320    },
321    Remove {
322        status: NfsStatus,
323    },
324    Rename {
325        status: NfsStatus,
326    },
327    SetAttr {
328        status: NfsStatus,
329    },
330    Write {
331        status: NfsStatus,
332        count: u32,
333        committed: bool,
334    },
335}
336
337/// NFS directory entry.
338#[cfg(feature = "alloc")]
339#[derive(Debug, Clone)]
340pub struct NfsDirEntry {
341    pub cookie: u64,
342    pub name: String,
343    pub fileid: u64,
344}
345
346// ---------------------------------------------------------------------------
347// Compound Request / Response
348// ---------------------------------------------------------------------------
349
350/// NFS v4 COMPOUND request (RFC 7530 Section 16.2).
351#[cfg(feature = "alloc")]
352#[derive(Debug, Clone)]
353pub struct CompoundRequest {
354    pub tag: String,
355    pub minor_version: u32,
356    pub operations: Vec<NfsOperation>,
357}
358
359/// NFS v4 COMPOUND response.
360#[cfg(feature = "alloc")]
361#[derive(Debug, Clone)]
362pub struct CompoundResponse {
363    pub status: NfsStatus,
364    pub tag: String,
365    pub results: Vec<NfsResult>,
366}
367
368// ---------------------------------------------------------------------------
369// XDR Encoder / Decoder
370// ---------------------------------------------------------------------------
371
372/// XDR (RFC 4506) encoder for NFS wire format.
373#[cfg(feature = "alloc")]
374pub struct XdrEncoder {
375    buf: Vec<u8>,
376}
377
378#[cfg(feature = "alloc")]
379impl Default for XdrEncoder {
380    fn default() -> Self {
381        Self::new()
382    }
383}
384
385#[cfg(feature = "alloc")]
386impl XdrEncoder {
387    /// Create a new encoder.
388    pub fn new() -> Self {
389        Self { buf: Vec::new() }
390    }
391
392    /// Create encoder with pre-allocated capacity.
393    pub fn with_capacity(cap: usize) -> Self {
394        Self {
395            buf: Vec::with_capacity(cap),
396        }
397    }
398
399    /// Encode a u32.
400    pub fn encode_u32(&mut self, v: u32) {
401        self.buf.extend_from_slice(&v.to_be_bytes());
402    }
403
404    /// Encode a u64.
405    pub fn encode_u64(&mut self, v: u64) {
406        self.buf.extend_from_slice(&v.to_be_bytes());
407    }
408
409    /// Encode a bool.
410    pub fn encode_bool(&mut self, v: bool) {
411        self.encode_u32(if v { 1 } else { 0 });
412    }
413
414    /// Encode an opaque byte array (length-prefixed, padded to 4-byte
415    /// boundary).
416    pub fn encode_opaque(&mut self, data: &[u8]) {
417        self.encode_u32(data.len() as u32);
418        self.buf.extend_from_slice(data);
419        // Pad to 4-byte boundary
420        let pad = (4 - (data.len() % 4)) % 4;
421        for _ in 0..pad {
422            self.buf.push(0);
423        }
424    }
425
426    /// Encode a string (same as opaque).
427    pub fn encode_string(&mut self, s: &str) {
428        self.encode_opaque(s.as_bytes());
429    }
430
431    /// Encode a fixed-size opaque array (no length prefix, padded).
432    pub fn encode_opaque_fixed(&mut self, data: &[u8]) {
433        self.buf.extend_from_slice(data);
434        let pad = (4 - (data.len() % 4)) % 4;
435        for _ in 0..pad {
436            self.buf.push(0);
437        }
438    }
439
440    /// Consume the encoder and return the buffer.
441    pub fn into_bytes(self) -> Vec<u8> {
442        self.buf
443    }
444
445    /// Current encoded length.
446    pub fn len(&self) -> usize {
447        self.buf.len()
448    }
449
450    /// Whether the buffer is empty.
451    pub fn is_empty(&self) -> bool {
452        self.buf.is_empty()
453    }
454}
455
456/// XDR decoder for NFS wire format.
457#[cfg(feature = "alloc")]
458pub struct XdrDecoder<'a> {
459    data: &'a [u8],
460    pos: usize,
461}
462
463#[cfg(feature = "alloc")]
464impl<'a> XdrDecoder<'a> {
465    /// Create a new decoder over a byte slice.
466    pub fn new(data: &'a [u8]) -> Self {
467        Self { data, pos: 0 }
468    }
469
470    /// Decode a u32.
471    pub fn decode_u32(&mut self) -> Option<u32> {
472        if self.pos + 4 > self.data.len() {
473            return None;
474        }
475        let v = u32::from_be_bytes([
476            self.data[self.pos],
477            self.data[self.pos + 1],
478            self.data[self.pos + 2],
479            self.data[self.pos + 3],
480        ]);
481        self.pos += 4;
482        Some(v)
483    }
484
485    /// Decode a u64.
486    pub fn decode_u64(&mut self) -> Option<u64> {
487        let hi = self.decode_u32()? as u64;
488        let lo = self.decode_u32()? as u64;
489        Some((hi << 32) | lo)
490    }
491
492    /// Decode a bool.
493    pub fn decode_bool(&mut self) -> Option<bool> {
494        self.decode_u32().map(|v| v != 0)
495    }
496
497    /// Decode a variable-length opaque byte array.
498    pub fn decode_opaque(&mut self) -> Option<Vec<u8>> {
499        let len = self.decode_u32()? as usize;
500        if self.pos + len > self.data.len() {
501            return None;
502        }
503        let v = self.data[self.pos..self.pos + len].to_vec();
504        self.pos += len;
505        // Skip padding
506        let pad = (4 - (len % 4)) % 4;
507        self.pos += pad;
508        Some(v)
509    }
510
511    /// Decode a string.
512    pub fn decode_string(&mut self) -> Option<String> {
513        let bytes = self.decode_opaque()?;
514        String::from_utf8(bytes).ok()
515    }
516
517    /// Decode a fixed-size opaque array.
518    pub fn decode_opaque_fixed(&mut self, len: usize) -> Option<Vec<u8>> {
519        if self.pos + len > self.data.len() {
520            return None;
521        }
522        let v = self.data[self.pos..self.pos + len].to_vec();
523        self.pos += len;
524        let pad = (4 - (len % 4)) % 4;
525        self.pos += pad;
526        Some(v)
527    }
528
529    /// Remaining bytes.
530    pub fn remaining(&self) -> usize {
531        self.data.len().saturating_sub(self.pos)
532    }
533}
534
535// ---------------------------------------------------------------------------
536// AUTH_SYS credentials
537// ---------------------------------------------------------------------------
538
539/// AUTH_SYS authentication credentials (RFC 5531).
540#[cfg(feature = "alloc")]
541#[derive(Debug, Clone)]
542pub struct AuthSys {
543    pub stamp: u32,
544    pub machine_name: String,
545    pub uid: u32,
546    pub gid: u32,
547    pub gids: Vec<u32>,
548}
549
550#[cfg(feature = "alloc")]
551impl Default for AuthSys {
552    fn default() -> Self {
553        Self {
554            stamp: 0,
555            machine_name: String::from("veridian"),
556            uid: 0,
557            gid: 0,
558            gids: Vec::new(),
559        }
560    }
561}
562
563#[cfg(feature = "alloc")]
564impl AuthSys {
565    /// Encode AUTH_SYS credentials to XDR.
566    pub fn encode(&self, enc: &mut XdrEncoder) {
567        enc.encode_u32(self.stamp);
568        enc.encode_string(&self.machine_name);
569        enc.encode_u32(self.uid);
570        enc.encode_u32(self.gid);
571        enc.encode_u32(self.gids.len() as u32);
572        for &g in &self.gids {
573            enc.encode_u32(g);
574        }
575    }
576}
577
578// ---------------------------------------------------------------------------
579// NFS Client
580// ---------------------------------------------------------------------------
581
582/// NFS error type.
583#[derive(Debug, Clone, Copy, PartialEq, Eq)]
584pub enum NfsError {
585    /// Server returned an NFS error status.
586    Status(NfsStatus),
587    /// XDR encoding/decoding error.
588    XdrError,
589    /// No file handle set (PUTFH/PUTROOTFH not issued).
590    NoFileHandle,
591    /// Network transport error.
592    TransportError,
593    /// Authentication failure.
594    AuthError,
595    /// Not connected to server.
596    NotConnected,
597    /// Invalid argument.
598    InvalidArgument,
599}
600
601/// NFS v4 client.
602#[cfg(feature = "alloc")]
603pub struct NfsClient {
604    /// Server address (IP:port string).
605    server_addr: String,
606    /// Root file handle obtained from PUTROOTFH.
607    root_fh: NfsFileHandle,
608    /// Current file handle (set by PUTFH/PUTROOTFH/LOOKUP).
609    current_fh: NfsFileHandle,
610    /// AUTH_SYS credentials.
611    auth: AuthSys,
612    /// Whether the client is connected.
613    connected: bool,
614    /// Next transaction ID.
615    xid: u32,
616}
617
618#[cfg(feature = "alloc")]
619impl NfsClient {
620    /// Create a new NFS client for the given server.
621    pub fn new(server_addr: String) -> Self {
622        Self {
623            server_addr,
624            root_fh: NfsFileHandle::new(),
625            current_fh: NfsFileHandle::new(),
626            auth: AuthSys::default(),
627            connected: false,
628            xid: 1,
629        }
630    }
631
632    /// Set authentication credentials.
633    pub fn set_auth(&mut self, uid: u32, gid: u32, machine_name: String) {
634        self.auth.uid = uid;
635        self.auth.gid = gid;
636        self.auth.machine_name = machine_name;
637    }
638
639    /// Mount the NFS export (PUTROOTFH + GETFH).
640    pub fn mount(&mut self) -> Result<NfsFileHandle, NfsError> {
641        let request = CompoundRequest {
642            tag: String::from("mount"),
643            minor_version: 0,
644            operations: vec![NfsOperation::PutRootFH, NfsOperation::GetFH],
645        };
646
647        let response = self.send_compound(&request)?;
648
649        // Extract root file handle from GETFH result
650        for result in &response.results {
651            if let NfsResult::GetFH {
652                status,
653                handle: Some(fh),
654            } = result
655            {
656                if *status != NfsStatus::Ok {
657                    return Err(NfsError::Status(*status));
658                }
659                self.root_fh = fh.clone();
660                self.current_fh = fh.clone();
661                return Ok(fh.clone());
662            }
663        }
664
665        Err(NfsError::NoFileHandle)
666    }
667
668    /// Lookup a name relative to a directory file handle.
669    pub fn lookup(
670        &mut self,
671        dir_fh: &NfsFileHandle,
672        name: &str,
673    ) -> Result<NfsFileHandle, NfsError> {
674        let request = CompoundRequest {
675            tag: String::from("lookup"),
676            minor_version: 0,
677            operations: vec![
678                NfsOperation::PutFH {
679                    handle: dir_fh.clone(),
680                },
681                NfsOperation::Lookup {
682                    name: String::from(name),
683                },
684                NfsOperation::GetFH,
685            ],
686        };
687
688        let response = self.send_compound(&request)?;
689
690        for result in &response.results {
691            if let NfsResult::GetFH {
692                status,
693                handle: Some(fh),
694            } = result
695            {
696                if *status != NfsStatus::Ok {
697                    return Err(NfsError::Status(*status));
698                }
699                self.current_fh = fh.clone();
700                return Ok(fh.clone());
701            }
702        }
703
704        Err(NfsError::NoFileHandle)
705    }
706
707    /// Read data from a file.
708    pub fn read(
709        &mut self,
710        fh: &NfsFileHandle,
711        offset: u64,
712        count: u32,
713    ) -> Result<(Vec<u8>, bool), NfsError> {
714        let request = CompoundRequest {
715            tag: String::from("read"),
716            minor_version: 0,
717            operations: vec![
718                NfsOperation::PutFH { handle: fh.clone() },
719                NfsOperation::Read {
720                    state_id: [0u8; 16],
721                    offset,
722                    count,
723                },
724            ],
725        };
726
727        let response = self.send_compound(&request)?;
728
729        for result in &response.results {
730            if let NfsResult::Read { status, eof, data } = result {
731                if *status != NfsStatus::Ok {
732                    return Err(NfsError::Status(*status));
733                }
734                return Ok((data.clone(), *eof));
735            }
736        }
737
738        Err(NfsError::XdrError)
739    }
740
741    /// Write data to a file.
742    pub fn write(
743        &mut self,
744        fh: &NfsFileHandle,
745        offset: u64,
746        data: &[u8],
747        stable: bool,
748    ) -> Result<(u32, bool), NfsError> {
749        let request = CompoundRequest {
750            tag: String::from("write"),
751            minor_version: 0,
752            operations: vec![
753                NfsOperation::PutFH { handle: fh.clone() },
754                NfsOperation::Write {
755                    state_id: [0u8; 16],
756                    offset,
757                    data: data.to_vec(),
758                    stable,
759                },
760            ],
761        };
762
763        let response = self.send_compound(&request)?;
764
765        for result in &response.results {
766            if let NfsResult::Write {
767                status,
768                count,
769                committed,
770            } = result
771            {
772                if *status != NfsStatus::Ok {
773                    return Err(NfsError::Status(*status));
774                }
775                return Ok((*count, *committed));
776            }
777        }
778
779        Err(NfsError::XdrError)
780    }
781
782    /// Read directory entries.
783    pub fn readdir(
784        &mut self,
785        dir_fh: &NfsFileHandle,
786        cookie: u64,
787        count: u32,
788    ) -> Result<Vec<NfsDirEntry>, NfsError> {
789        let request = CompoundRequest {
790            tag: String::from("readdir"),
791            minor_version: 0,
792            operations: vec![
793                NfsOperation::PutFH {
794                    handle: dir_fh.clone(),
795                },
796                NfsOperation::ReadDir { cookie, count },
797            ],
798        };
799
800        let response = self.send_compound(&request)?;
801
802        for result in &response.results {
803            if let NfsResult::ReadDir { status, entries } = result {
804                if *status != NfsStatus::Ok {
805                    return Err(NfsError::Status(*status));
806                }
807                return Ok(entries.clone());
808            }
809        }
810
811        Err(NfsError::XdrError)
812    }
813
814    /// Create a file or directory.
815    pub fn create(
816        &mut self,
817        dir_fh: &NfsFileHandle,
818        name: &str,
819        file_type: NfsFtype,
820    ) -> Result<NfsFileHandle, NfsError> {
821        let request = CompoundRequest {
822            tag: String::from("create"),
823            minor_version: 0,
824            operations: vec![
825                NfsOperation::PutFH {
826                    handle: dir_fh.clone(),
827                },
828                NfsOperation::Create {
829                    name: String::from(name),
830                    file_type,
831                },
832                NfsOperation::GetFH,
833            ],
834        };
835
836        let response = self.send_compound(&request)?;
837
838        for result in &response.results {
839            if let NfsResult::GetFH {
840                status,
841                handle: Some(fh),
842            } = result
843            {
844                if *status != NfsStatus::Ok {
845                    return Err(NfsError::Status(*status));
846                }
847                return Ok(fh.clone());
848            }
849        }
850
851        Err(NfsError::NoFileHandle)
852    }
853
854    /// Remove a file or directory.
855    pub fn remove(&mut self, dir_fh: &NfsFileHandle, name: &str) -> Result<(), NfsError> {
856        let request = CompoundRequest {
857            tag: String::from("remove"),
858            minor_version: 0,
859            operations: vec![
860                NfsOperation::PutFH {
861                    handle: dir_fh.clone(),
862                },
863                NfsOperation::Remove {
864                    name: String::from(name),
865                },
866            ],
867        };
868
869        let response = self.send_compound(&request)?;
870
871        for result in &response.results {
872            if let NfsResult::Remove { status } = result {
873                if *status != NfsStatus::Ok {
874                    return Err(NfsError::Status(*status));
875                }
876                return Ok(());
877            }
878        }
879
880        Err(NfsError::XdrError)
881    }
882
883    /// Get file attributes.
884    pub fn getattr(&mut self, fh: &NfsFileHandle) -> Result<NfsAttr, NfsError> {
885        // Request all standard attributes
886        let attr_request = 0xFFFF_FFFF_FFFF_FFFF;
887
888        let request = CompoundRequest {
889            tag: String::from("getattr"),
890            minor_version: 0,
891            operations: vec![
892                NfsOperation::PutFH { handle: fh.clone() },
893                NfsOperation::GetAttr { attr_request },
894            ],
895        };
896
897        let response = self.send_compound(&request)?;
898
899        for result in &response.results {
900            if let NfsResult::GetAttr {
901                status,
902                attrs: Some(a),
903            } = result
904            {
905                if *status != NfsStatus::Ok {
906                    return Err(NfsError::Status(*status));
907                }
908                return Ok(a.clone());
909            }
910        }
911
912        Err(NfsError::XdrError)
913    }
914
915    /// Set file attributes.
916    pub fn setattr(&mut self, fh: &NfsFileHandle, attrs: NfsAttr) -> Result<(), NfsError> {
917        let request = CompoundRequest {
918            tag: String::from("setattr"),
919            minor_version: 0,
920            operations: vec![
921                NfsOperation::PutFH { handle: fh.clone() },
922                NfsOperation::SetAttr {
923                    state_id: [0u8; 16],
924                    attrs,
925                },
926            ],
927        };
928
929        let response = self.send_compound(&request)?;
930
931        for result in &response.results {
932            if let NfsResult::SetAttr { status } = result {
933                if *status != NfsStatus::Ok {
934                    return Err(NfsError::Status(*status));
935                }
936                return Ok(());
937            }
938        }
939
940        Err(NfsError::XdrError)
941    }
942
943    /// Build an XDR-encoded compound request.
944    pub fn build_compound(&self, req: &CompoundRequest) -> Vec<u8> {
945        let mut enc = XdrEncoder::with_capacity(256);
946
947        // RPC header (simplified)
948        enc.encode_u32(self.xid);
949        enc.encode_u32(0); // call
950        enc.encode_u32(2); // RPC version 2
951        enc.encode_u32(100003); // NFS program
952        enc.encode_u32(4); // NFS v4
953        enc.encode_u32(1); // COMPOUND procedure
954
955        // AUTH_SYS
956        enc.encode_u32(1); // AUTH_SYS flavor
957        self.auth.encode(&mut enc);
958
959        // COMPOUND body
960        enc.encode_string(&req.tag);
961        enc.encode_u32(req.minor_version);
962        enc.encode_u32(req.operations.len() as u32);
963
964        for op in &req.operations {
965            self.encode_operation(&mut enc, op);
966        }
967
968        enc.into_bytes()
969    }
970
971    /// Parse an XDR-encoded compound response.
972    pub fn parse_compound(&self, data: &[u8]) -> Result<CompoundResponse, NfsError> {
973        let mut dec = XdrDecoder::new(data);
974
975        // Skip RPC reply header
976        let _xid = dec.decode_u32().ok_or(NfsError::XdrError)?;
977        let _msg_type = dec.decode_u32().ok_or(NfsError::XdrError)?;
978        let _reply_stat = dec.decode_u32().ok_or(NfsError::XdrError)?;
979
980        // Skip verifier
981        let _verf_flavor = dec.decode_u32().ok_or(NfsError::XdrError)?;
982        let _verf_body = dec.decode_opaque().ok_or(NfsError::XdrError)?;
983
984        // Accept stat
985        let _accept_stat = dec.decode_u32().ok_or(NfsError::XdrError)?;
986
987        // COMPOUND response
988        let status_val = dec.decode_u32().ok_or(NfsError::XdrError)?;
989        let status = NfsStatus::from_u32(status_val);
990        let tag = dec.decode_string().ok_or(NfsError::XdrError)?;
991        let num_results = dec.decode_u32().ok_or(NfsError::XdrError)? as usize;
992
993        let mut results = Vec::with_capacity(num_results);
994        for _ in 0..num_results {
995            let result = self.decode_result(&mut dec)?;
996            results.push(result);
997        }
998
999        Ok(CompoundResponse {
1000            status,
1001            tag,
1002            results,
1003        })
1004    }
1005
1006    /// Encode a single NFS operation to XDR.
1007    fn encode_operation(&self, enc: &mut XdrEncoder, op: &NfsOperation) {
1008        match op {
1009            NfsOperation::Access { access_mask } => {
1010                enc.encode_u32(NfsOpcode::Access as u32);
1011                enc.encode_u32(*access_mask);
1012            }
1013            NfsOperation::Close { state_id } => {
1014                enc.encode_u32(NfsOpcode::Close as u32);
1015                enc.encode_u32(0); // seqid
1016                enc.encode_opaque_fixed(state_id);
1017            }
1018            NfsOperation::Commit { offset, count } => {
1019                enc.encode_u32(NfsOpcode::Commit as u32);
1020                enc.encode_u64(*offset);
1021                enc.encode_u32(*count);
1022            }
1023            NfsOperation::Create { name, file_type } => {
1024                enc.encode_u32(NfsOpcode::Create as u32);
1025                enc.encode_u32(*file_type as u32);
1026                enc.encode_string(name);
1027            }
1028            NfsOperation::GetAttr { attr_request } => {
1029                enc.encode_u32(NfsOpcode::GetAttr as u32);
1030                enc.encode_u64(*attr_request);
1031            }
1032            NfsOperation::GetFH => {
1033                enc.encode_u32(NfsOpcode::GetFH as u32);
1034            }
1035            NfsOperation::Lookup { name } => {
1036                enc.encode_u32(NfsOpcode::Lookup as u32);
1037                enc.encode_string(name);
1038            }
1039            NfsOperation::Open { name, access, deny } => {
1040                enc.encode_u32(NfsOpcode::Open as u32);
1041                enc.encode_u32(0); // seqid
1042                enc.encode_u32(*access);
1043                enc.encode_u32(*deny);
1044                enc.encode_string(name);
1045            }
1046            NfsOperation::PutFH { handle } => {
1047                enc.encode_u32(NfsOpcode::PutFH as u32);
1048                enc.encode_opaque(handle.as_bytes());
1049            }
1050            NfsOperation::PutRootFH => {
1051                enc.encode_u32(NfsOpcode::PutRootFH as u32);
1052            }
1053            NfsOperation::Read {
1054                state_id,
1055                offset,
1056                count,
1057            } => {
1058                enc.encode_u32(NfsOpcode::Read as u32);
1059                enc.encode_opaque_fixed(state_id);
1060                enc.encode_u64(*offset);
1061                enc.encode_u32(*count);
1062            }
1063            NfsOperation::ReadDir { cookie, count } => {
1064                enc.encode_u32(NfsOpcode::ReadDir as u32);
1065                enc.encode_u64(*cookie);
1066                enc.encode_u32(*count);
1067            }
1068            NfsOperation::Remove { name } => {
1069                enc.encode_u32(NfsOpcode::Remove as u32);
1070                enc.encode_string(name);
1071            }
1072            NfsOperation::Rename { old_name, new_name } => {
1073                enc.encode_u32(NfsOpcode::Rename as u32);
1074                enc.encode_string(old_name);
1075                enc.encode_string(new_name);
1076            }
1077            NfsOperation::SetAttr { state_id, attrs } => {
1078                enc.encode_u32(NfsOpcode::SetAttr as u32);
1079                enc.encode_opaque_fixed(state_id);
1080                enc.encode_u32(attrs.mode);
1081                enc.encode_u64(attrs.size);
1082            }
1083            NfsOperation::Write {
1084                state_id,
1085                offset,
1086                data,
1087                stable,
1088            } => {
1089                enc.encode_u32(NfsOpcode::Write as u32);
1090                enc.encode_opaque_fixed(state_id);
1091                enc.encode_u64(*offset);
1092                enc.encode_u32(if *stable { 2 } else { 0 }); // FILE_SYNC / UNSTABLE4
1093                enc.encode_opaque(data);
1094            }
1095        }
1096    }
1097
1098    /// Decode a single NFS result from XDR.
1099    fn decode_result(&self, dec: &mut XdrDecoder) -> Result<NfsResult, NfsError> {
1100        let opcode = dec.decode_u32().ok_or(NfsError::XdrError)?;
1101        let status_val = dec.decode_u32().ok_or(NfsError::XdrError)?;
1102        let status = NfsStatus::from_u32(status_val);
1103
1104        match opcode {
1105            3 => {
1106                // ACCESS
1107                let supported = if status == NfsStatus::Ok {
1108                    dec.decode_u32().ok_or(NfsError::XdrError)?
1109                } else {
1110                    0
1111                };
1112                let access = if status == NfsStatus::Ok {
1113                    dec.decode_u32().ok_or(NfsError::XdrError)?
1114                } else {
1115                    0
1116                };
1117                Ok(NfsResult::Access {
1118                    status,
1119                    supported,
1120                    access,
1121                })
1122            }
1123            4 => Ok(NfsResult::Close { status }),
1124            5 => Ok(NfsResult::Commit { status }),
1125            6 => Ok(NfsResult::Create { status }),
1126            9 => {
1127                // GETATTR
1128                let attrs = if status == NfsStatus::Ok {
1129                    Some(self.decode_fattr(dec)?)
1130                } else {
1131                    None
1132                };
1133                Ok(NfsResult::GetAttr { status, attrs })
1134            }
1135            10 => {
1136                // GETFH
1137                let handle = if status == NfsStatus::Ok {
1138                    let fh_bytes = dec.decode_opaque().ok_or(NfsError::XdrError)?;
1139                    NfsFileHandle::from_bytes(&fh_bytes)
1140                } else {
1141                    None
1142                };
1143                Ok(NfsResult::GetFH { status, handle })
1144            }
1145            15 => Ok(NfsResult::Lookup { status }),
1146            18 => {
1147                // OPEN
1148                let mut state_id = [0u8; 16];
1149                if status == NfsStatus::Ok {
1150                    let sid = dec.decode_opaque_fixed(16).ok_or(NfsError::XdrError)?;
1151                    state_id.copy_from_slice(&sid);
1152                }
1153                Ok(NfsResult::Open { status, state_id })
1154            }
1155            22 => Ok(NfsResult::PutFH { status }),
1156            24 => Ok(NfsResult::PutRootFH { status }),
1157            25 => {
1158                // READ
1159                let (eof, data) = if status == NfsStatus::Ok {
1160                    let eof = dec.decode_bool().ok_or(NfsError::XdrError)?;
1161                    let d = dec.decode_opaque().ok_or(NfsError::XdrError)?;
1162                    (eof, d)
1163                } else {
1164                    (false, Vec::new())
1165                };
1166                Ok(NfsResult::Read { status, eof, data })
1167            }
1168            26 => {
1169                // READDIR
1170                let entries = if status == NfsStatus::Ok {
1171                    self.decode_dir_entries(dec)?
1172                } else {
1173                    Vec::new()
1174                };
1175                Ok(NfsResult::ReadDir { status, entries })
1176            }
1177            28 => Ok(NfsResult::Remove { status }),
1178            29 => Ok(NfsResult::Rename { status }),
1179            34 => Ok(NfsResult::SetAttr { status }),
1180            38 => {
1181                // WRITE
1182                let (count, committed) = if status == NfsStatus::Ok {
1183                    let c = dec.decode_u32().ok_or(NfsError::XdrError)?;
1184                    let com = dec.decode_u32().ok_or(NfsError::XdrError)?;
1185                    (c, com == 2)
1186                } else {
1187                    (0, false)
1188                };
1189                Ok(NfsResult::Write {
1190                    status,
1191                    count,
1192                    committed,
1193                })
1194            }
1195            _ => Err(NfsError::XdrError),
1196        }
1197    }
1198
1199    /// Decode file attributes from XDR.
1200    fn decode_fattr(&self, dec: &mut XdrDecoder) -> Result<NfsAttr, NfsError> {
1201        let file_type_val = dec.decode_u32().ok_or(NfsError::XdrError)?;
1202        let file_type = NfsFtype::from_u32(file_type_val).ok_or(NfsError::XdrError)?;
1203        let mode = dec.decode_u32().ok_or(NfsError::XdrError)?;
1204        let nlink = dec.decode_u32().ok_or(NfsError::XdrError)?;
1205        let uid = dec.decode_u32().ok_or(NfsError::XdrError)?;
1206        let gid = dec.decode_u32().ok_or(NfsError::XdrError)?;
1207        let size = dec.decode_u64().ok_or(NfsError::XdrError)?;
1208        let used = dec.decode_u64().ok_or(NfsError::XdrError)?;
1209        let rdev = dec.decode_u64().ok_or(NfsError::XdrError)?;
1210        let fsid = dec.decode_u64().ok_or(NfsError::XdrError)?;
1211        let fileid = dec.decode_u64().ok_or(NfsError::XdrError)?;
1212        let atime = dec.decode_u64().ok_or(NfsError::XdrError)?;
1213        let mtime = dec.decode_u64().ok_or(NfsError::XdrError)?;
1214        let ctime = dec.decode_u64().ok_or(NfsError::XdrError)?;
1215
1216        Ok(NfsAttr {
1217            file_type,
1218            mode,
1219            nlink,
1220            uid,
1221            gid,
1222            size,
1223            used,
1224            rdev,
1225            fsid,
1226            fileid,
1227            atime,
1228            mtime,
1229            ctime,
1230        })
1231    }
1232
1233    /// Decode directory entries from XDR.
1234    fn decode_dir_entries(&self, dec: &mut XdrDecoder) -> Result<Vec<NfsDirEntry>, NfsError> {
1235        let count = dec.decode_u32().ok_or(NfsError::XdrError)? as usize;
1236        let mut entries = Vec::with_capacity(count);
1237        for _ in 0..count {
1238            let cookie = dec.decode_u64().ok_or(NfsError::XdrError)?;
1239            let name = dec.decode_string().ok_or(NfsError::XdrError)?;
1240            let fileid = dec.decode_u64().ok_or(NfsError::XdrError)?;
1241            entries.push(NfsDirEntry {
1242                cookie,
1243                name,
1244                fileid,
1245            });
1246        }
1247        Ok(entries)
1248    }
1249
1250    /// Send a compound request and receive response.
1251    ///
1252    /// In a real implementation this would use the network stack.
1253    /// Currently returns a stub error for transport.
1254    fn send_compound(&mut self, req: &CompoundRequest) -> Result<CompoundResponse, NfsError> {
1255        let _encoded = self.build_compound(req);
1256        self.xid += 1;
1257
1258        // Transport layer placeholder -- would send via TCP to self.server_addr
1259        // and receive the response bytes, then call parse_compound().
1260        Err(NfsError::TransportError)
1261    }
1262
1263    /// Get the root file handle.
1264    pub fn root_fh(&self) -> &NfsFileHandle {
1265        &self.root_fh
1266    }
1267
1268    /// Get the current file handle.
1269    pub fn current_fh(&self) -> &NfsFileHandle {
1270        &self.current_fh
1271    }
1272
1273    /// Get the server address.
1274    pub fn server_addr(&self) -> &str {
1275        &self.server_addr
1276    }
1277}
1278
1279// ---------------------------------------------------------------------------
1280// VFS Mount Integration
1281// ---------------------------------------------------------------------------
1282
1283/// NFS mount point for VFS integration.
1284#[cfg(feature = "alloc")]
1285pub struct NfsMountPoint {
1286    /// Path where NFS share is mounted.
1287    pub mount_path: String,
1288    /// NFS export path on the server.
1289    pub export_path: String,
1290    /// NFS client for this mount.
1291    pub client: NfsClient,
1292}
1293
1294#[cfg(feature = "alloc")]
1295impl NfsMountPoint {
1296    /// Create a new NFS mount point.
1297    pub fn new(server: &str, export: &str, mount_path: &str) -> Self {
1298        Self {
1299            mount_path: String::from(mount_path),
1300            export_path: String::from(export),
1301            client: NfsClient::new(String::from(server)),
1302        }
1303    }
1304
1305    /// Mount the NFS export.
1306    pub fn mount(&mut self) -> Result<(), NfsError> {
1307        self.client.mount()?;
1308        Ok(())
1309    }
1310
1311    /// Get the mount path.
1312    pub fn path(&self) -> &str {
1313        &self.mount_path
1314    }
1315}
1316
1317// ---------------------------------------------------------------------------
1318// Tests
1319// ---------------------------------------------------------------------------
1320
1321#[cfg(test)]
1322mod tests {
1323    use super::*;
1324
1325    #[test]
1326    fn test_file_handle_new() {
1327        let fh = NfsFileHandle::new();
1328        assert!(fh.is_empty());
1329        assert_eq!(fh.as_bytes().len(), 0);
1330    }
1331
1332    #[test]
1333    fn test_file_handle_from_bytes() {
1334        let data = [1, 2, 3, 4, 5];
1335        let fh = NfsFileHandle::from_bytes(&data).unwrap();
1336        assert_eq!(fh.as_bytes(), &data);
1337        assert!(!fh.is_empty());
1338    }
1339
1340    #[test]
1341    fn test_file_handle_too_large() {
1342        let data = [0u8; NFS4_FHSIZE + 1];
1343        assert!(NfsFileHandle::from_bytes(&data).is_none());
1344    }
1345
1346    #[test]
1347    fn test_file_handle_max_size() {
1348        let data = [0xAB; NFS4_FHSIZE];
1349        let fh = NfsFileHandle::from_bytes(&data).unwrap();
1350        assert_eq!(fh.as_bytes().len(), NFS4_FHSIZE);
1351    }
1352
1353    #[test]
1354    fn test_nfs_ftype_roundtrip() {
1355        assert_eq!(NfsFtype::from_u32(1), Some(NfsFtype::Regular));
1356        assert_eq!(NfsFtype::from_u32(2), Some(NfsFtype::Directory));
1357        assert_eq!(NfsFtype::from_u32(7), Some(NfsFtype::Fifo));
1358        assert_eq!(NfsFtype::from_u32(0), None);
1359        assert_eq!(NfsFtype::from_u32(8), None);
1360    }
1361
1362    #[test]
1363    fn test_nfs_status_roundtrip() {
1364        assert_eq!(NfsStatus::from_u32(0), NfsStatus::Ok);
1365        assert_eq!(NfsStatus::from_u32(2), NfsStatus::NoEnt);
1366        assert_eq!(NfsStatus::from_u32(70), NfsStatus::Stale);
1367        assert_eq!(NfsStatus::from_u32(10001), NfsStatus::BadHandle);
1368    }
1369
1370    #[test]
1371    fn test_xdr_encode_u32() {
1372        let mut enc = XdrEncoder::new();
1373        enc.encode_u32(0x12345678);
1374        let bytes = enc.into_bytes();
1375        assert_eq!(bytes, &[0x12, 0x34, 0x56, 0x78]);
1376    }
1377
1378    #[test]
1379    fn test_xdr_encode_u64() {
1380        let mut enc = XdrEncoder::new();
1381        enc.encode_u64(0x0102030405060708);
1382        let bytes = enc.into_bytes();
1383        assert_eq!(bytes, &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
1384    }
1385
1386    #[test]
1387    fn test_xdr_encode_decode_string() {
1388        let mut enc = XdrEncoder::new();
1389        enc.encode_string("hello");
1390        let bytes = enc.into_bytes();
1391
1392        let mut dec = XdrDecoder::new(&bytes);
1393        let s = dec.decode_string().unwrap();
1394        assert_eq!(s, "hello");
1395    }
1396
1397    #[test]
1398    fn test_xdr_opaque_padding() {
1399        let mut enc = XdrEncoder::new();
1400        enc.encode_opaque(&[1, 2, 3]); // 3 bytes + 1 byte pad
1401        let bytes = enc.into_bytes();
1402        // Length(4) + data(3) + pad(1) = 8
1403        assert_eq!(bytes.len(), 8);
1404
1405        let mut dec = XdrDecoder::new(&bytes);
1406        let data = dec.decode_opaque().unwrap();
1407        assert_eq!(data, &[1, 2, 3]);
1408    }
1409
1410    #[test]
1411    fn test_xdr_bool() {
1412        let mut enc = XdrEncoder::new();
1413        enc.encode_bool(true);
1414        enc.encode_bool(false);
1415        let bytes = enc.into_bytes();
1416
1417        let mut dec = XdrDecoder::new(&bytes);
1418        assert!(dec.decode_bool().unwrap());
1419        assert!(!dec.decode_bool().unwrap());
1420    }
1421
1422    #[test]
1423    fn test_xdr_decode_empty() {
1424        let dec_data: &[u8] = &[];
1425        let mut dec = XdrDecoder::new(dec_data);
1426        assert!(dec.decode_u32().is_none());
1427        assert_eq!(dec.remaining(), 0);
1428    }
1429
1430    #[test]
1431    fn test_nfs_client_new() {
1432        let client = NfsClient::new(String::from("192.168.1.100:2049"));
1433        assert_eq!(client.server_addr(), "192.168.1.100:2049");
1434        assert!(client.root_fh().is_empty());
1435        assert!(client.current_fh().is_empty());
1436    }
1437
1438    #[test]
1439    fn test_nfs_client_build_compound() {
1440        let client = NfsClient::new(String::from("10.0.0.1:2049"));
1441        let req = CompoundRequest {
1442            tag: String::from("test"),
1443            minor_version: 0,
1444            operations: vec![NfsOperation::PutRootFH, NfsOperation::GetFH],
1445        };
1446        let encoded = client.build_compound(&req);
1447        assert!(!encoded.is_empty());
1448        // Should contain at least RPC header + compound header + 2 ops
1449        assert!(encoded.len() > 40);
1450    }
1451}