]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
nfs4: initial implementation
authorVictor Julien <victor@inliniac.net>
Fri, 16 Mar 2018 11:24:51 +0000 (12:24 +0100)
committerVictor Julien <victor@inliniac.net>
Thu, 5 Apr 2018 13:21:48 +0000 (15:21 +0200)
Implements record parsing and file extraction for READs and WRITEs.

Defines all types from RFC 7530.

rust/src/nfs/log.rs
rust/src/nfs/mod.rs
rust/src/nfs/nfs.rs
rust/src/nfs/nfs4.rs [new file with mode: 0644]
rust/src/nfs/nfs4_records.rs [new file with mode: 0644]
rust/src/nfs/types.rs

index c1071419d0dcf11e5b421b9d0ddaa578a82b13fe..1f5bde8b71a66996e05ef784db4fc05800b7a5cc 100644 (file)
@@ -94,7 +94,12 @@ fn nfs_common_header(state: &NFSState, tx: &NFSTransaction) -> Json
 {
     let js = Json::object();
     js.set_integer("version", state.nfs_version as u64);
-    js.set_string("procedure", &nfs3_procedure_string(tx.procedure));
+    let proc_string = if state.nfs_version < 4 {
+        nfs3_procedure_string(tx.procedure)
+    } else {
+        nfs4_procedure_string(tx.procedure)
+    };
+    js.set_string("procedure", &proc_string);
     let file_name = String::from_utf8_lossy(&tx.file_name);
     js.set_string("filename", &file_name);
 
index b9b797083635278ca101b20cafb65d6e08015c63..5638b0c8cb7a8cdf5aa2991844490a6c31010199 100644 (file)
@@ -20,6 +20,8 @@ pub mod rpc_records;
 pub mod nfs_records;
 pub mod nfs2_records;
 pub mod nfs3_records;
+pub mod nfs4_records;
+pub mod nfs4;
 pub mod nfs;
 pub mod log;
 
index c9e8e7548efb3b1a9de71ebace87a9bcc2ac9c12..643af76279cbfb9e12bacae65252133e611489d6 100644 (file)
@@ -155,8 +155,8 @@ pub struct NFSTransaction {
 
     /// for state tracking. false means this side is in progress, true
     /// that it's complete.
-    request_done: bool,
-    response_done: bool,
+    pub request_done: bool,
+    pub response_done: bool,
 
     pub nfs_version: u16,
 
@@ -231,13 +231,13 @@ impl Drop for NFSTransaction {
 
 #[derive(Debug)]
 pub struct NFSRequestXidMap {
-    progver: u32,
-    procedure: u32,
-    chunk_offset: u64,
-    file_name:Vec<u8>,
+    pub progver: u32,
+    pub procedure: u32,
+    pub chunk_offset: u64,
+    pub file_name:Vec<u8>,
 
     /// READ replies can use this to get to the handle the request used
-    file_handle:Vec<u8>,
+    pub file_handle:Vec<u8>,
 }
 
 impl NFSRequestXidMap {
@@ -285,7 +285,7 @@ impl NFSFiles {
 }
 
 /// little wrapper around the FileTransferTracker::new_chunk method
-fn filetracker_newchunk(ft: &mut FileTransferTracker, files: &mut FileContainer,
+pub fn filetracker_newchunk(ft: &mut FileTransferTracker, files: &mut FileContainer,
         flags: u16, name: &Vec<u8>, data: &[u8],
         chunk_offset: u64, chunk_size: u32, fill_bytes: u8, is_last: bool, xid: &u32)
 {
@@ -315,11 +315,11 @@ pub struct NFSState {
     pub files: NFSFiles,
 
     /// partial record tracking
-    ts_chunk_xid: u32,
-    tc_chunk_xid: u32,
+    pub ts_chunk_xid: u32,
+    pub tc_chunk_xid: u32,
     /// size of the current chunk that we still need to receive
-    ts_chunk_left: u32,
-    tc_chunk_left: u32,
+    pub ts_chunk_left: u32,
+    pub tc_chunk_left: u32,
 
     ts_ssn_gap: bool,
     tc_ssn_gap: bool,
@@ -483,7 +483,7 @@ impl NFSState {
         };
     }
 
-    fn xidmap_handle2name(&mut self, xidmap: &mut NFSRequestXidMap) {
+    pub fn xidmap_handle2name(&mut self, xidmap: &mut NFSRequestXidMap) {
         match self.namemap.get(&xidmap.file_handle) {
             Some(n) => {
                 SCLogDebug!("xidmap_handle2name: name {:?}", n);
@@ -501,6 +501,10 @@ impl NFSState {
         SCLogDebug!("REQUEST {} procedure {} ({}) blob size {}",
                 r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len());
 
+        if r.progver == 4 {
+            return self.process_request_record_v4(r);
+        }
+
         let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0);
         let mut aux_file_name = Vec::new();
 
@@ -779,7 +783,7 @@ impl NFSState {
         0
     }
 
-    fn new_file_tx(&mut self, file_handle: &Vec<u8>, file_name: &Vec<u8>, direction: u8)
+    pub fn new_file_tx(&mut self, file_handle: &Vec<u8>, file_name: &Vec<u8>, direction: u8)
         -> (&mut NFSTransaction, &mut FileContainer, u16)
     {
         let mut tx = self.new_tx();
@@ -803,7 +807,7 @@ impl NFSState {
         return (tx_ref.unwrap(), files, flags)
     }
 
-    fn get_file_tx_by_handle(&mut self, file_handle: &Vec<u8>, direction: u8)
+    pub fn get_file_tx_by_handle(&mut self, file_handle: &Vec<u8>, direction: u8)
         -> Option<(&mut NFSTransaction, &mut FileContainer, u16)>
     {
         let fh = file_handle.to_vec();
@@ -1080,9 +1084,8 @@ impl NFSState {
                 return self.process_reply_record_v3(r, &mut xidmap);
             },
             4 => {
-                SCLogDebug!("NFSv4 unsupported");
-                self.set_event(NFSEvent::UnsupportedVersion);
-                return 0;
+                SCLogDebug!("NFSv4 reply record");
+                return self.process_reply_record_v4(r, &mut xidmap);
             },
             _ => {
                 SCLogDebug!("Invalid NFS version");
@@ -1199,7 +1202,7 @@ impl NFSState {
 
     /// xidmapr is an Option as it's already removed from the map if we
     /// have a complete record. Otherwise we do a lookup ourselves.
-    fn process_read_record<'b>(&mut self, r: &RpcReplyPacket<'b>,
+    pub fn process_read_record<'b>(&mut self, r: &RpcReplyPacket<'b>,
             reply: &NfsReplyRead<'b>, xidmapr: Option<&NFSRequestXidMap>) -> u32
     {
         let file_name;
@@ -1226,6 +1229,7 @@ impl NFSState {
                 }
             },
         }
+        SCLogDebug!("chunk_offset {}", chunk_offset);
 
         let mut is_last = reply.eof;
         let mut fill_bytes = 0;
@@ -1233,7 +1237,8 @@ impl NFSState {
         if pad != 0 {
             fill_bytes = 4 - pad;
         }
-        SCLogDebug!("XID {} fill_bytes {} reply.count {} reply.data_len {} reply.data.len() {}", r.hdr.xid, fill_bytes, reply.count, reply.data_len, reply.data.len());
+        SCLogDebug!("XID {} is_last {} fill_bytes {} reply.count {} reply.data_len {} reply.data.len() {}",
+                r.hdr.xid, is_last, fill_bytes, reply.count, reply.data_len, reply.data.len());
 
         if nfs_version == 2 {
             let size = match parse_nfs2_attribs(reply.attr_blob) {
@@ -1256,6 +1261,7 @@ impl NFSState {
 
         let found = match self.get_file_tx_by_handle(&file_handle, STREAM_TOCLIENT) {
             Some((tx, files, flags)) => {
+                SCLogDebug!("updated TX {:?}", tx);
                 let ref mut tdf = match tx.type_data {
                     Some(NFSTransactionTypeData::FILE(ref mut x)) => x,
                     _ => { panic!("BUG") },
@@ -1291,7 +1297,7 @@ impl NFSState {
             filetracker_newchunk(&mut tdf.file_tracker, files, flags,
                     &file_name, reply.data, chunk_offset,
                     reply.count, fill_bytes as u8, is_last, &r.hdr.xid);
-            tx.procedure = NFSPROC3_READ;
+            tx.procedure = if nfs_version < 4 { NFSPROC3_READ } else { NFSPROC4_READ };
             tx.xid = r.hdr.xid;
             tx.is_first = true;
             if is_last {
@@ -2106,7 +2112,8 @@ pub fn nfs3_probe(i: &[u8], direction: u8) -> i8 {
         match parse_rpc(i) {
             IResult::Done(_, ref rpc) => {
                 if rpc.hdr.frag_len >= 40 && rpc.hdr.msgtype == 0 &&
-                   rpc.rpcver == 2 && rpc.progver == 3 && rpc.program == 100003 &&
+                   rpc.rpcver == 2 && (rpc.progver == 3 || rpc.progver == 4) &&
+                   rpc.program == 100003 &&
                    rpc.procedure <= NFSPROC3_COMMIT
                 {
                     return 1;
diff --git a/rust/src/nfs/nfs4.rs b/rust/src/nfs/nfs4.rs
new file mode 100644 (file)
index 0000000..480fd36
--- /dev/null
@@ -0,0 +1,250 @@
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// written by Victor Julien
+
+extern crate libc;
+
+use nom::IResult;
+
+use core::*;
+use log::*;
+
+use nfs::nfs::*;
+use nfs::types::*;
+use nfs::rpc_records::*;
+use nfs::nfs_records::*;
+use nfs::nfs4_records::*;
+
+impl NFSState {
+    /* normal write: PUTFH (file handle), WRITE (write opts/data). File handle
+     * is not part of the write record itself so we pass it in here. */
+    fn write_v4<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs4RequestWrite<'b>, fh: &'b[u8])
+    {
+        // for now assume that stable FILE_SYNC flags means a single chunk
+        let is_last = if w.stable == 2 { true } else { false };
+        SCLogDebug!("is_last {}", is_last);
+
+        let mut fill_bytes = 0;
+        let pad = w.write_len % 4;
+        if pad != 0 {
+            fill_bytes = 4 - pad;
+        }
+
+        let file_handle = fh.to_vec();
+        let file_name = match self.namemap.get(fh) {
+            Some(n) => {
+                SCLogDebug!("WRITE name {:?}", n);
+                n.to_vec()
+            },
+            None => {
+                SCLogDebug!("WRITE object {:?} not found", w.stateid.data);
+                Vec::new()
+            },
+        };
+
+        let found = match self.get_file_tx_by_handle(&file_handle, STREAM_TOSERVER) {
+            Some((tx, files, flags)) => {
+                if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                    filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                            &file_name, w.data, w.offset,
+                            w.write_len, fill_bytes as u8, is_last, &r.hdr.xid);
+                    tdf.chunk_count += 1;
+                    if is_last {
+                        tdf.file_last_xid = r.hdr.xid;
+                        tx.is_last = true;
+                        tx.response_done = true;
+                    }
+                }
+                true
+            },
+            None => { false },
+        };
+        if !found {
+            let (tx, files, flags) = self.new_file_tx(&file_handle, &file_name, STREAM_TOSERVER);
+            if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                        &file_name, w.data, w.offset,
+                        w.write_len, fill_bytes as u8, is_last, &r.hdr.xid);
+                tx.procedure = NFSPROC4_WRITE;
+                tx.xid = r.hdr.xid;
+                tx.is_first = true;
+                tx.nfs_version = r.progver as u16;
+                if is_last {
+                    tdf.file_last_xid = r.hdr.xid;
+                    tx.is_last = true;
+                    tx.request_done = true;
+                }
+            }
+        }
+        self.ts_chunk_xid = r.hdr.xid;
+        let file_data_len = w.data.len() as u32 - fill_bytes as u32;
+        self.ts_chunk_left = w.write_len as u32 - file_data_len as u32;
+    }
+
+    /* A normal READ request looks like: PUTFH (file handle) READ (read opts).
+     * We need the file handle for the READ.
+     */
+    fn compound_request<'b>(&mut self, r: &RpcPacket<'b>,
+            cr: &Nfs4RequestCompoundRecord<'b>,
+            xidmap: &mut NFSRequestXidMap)
+    {
+        let mut last_putfh : Option<&'b[u8]> = None;
+
+        for c in &cr.commands {
+            SCLogDebug!("c {:?}", c);
+            match c {
+                &Nfs4RequestContent::PutFH(ref rd) => {
+                    last_putfh = Some(rd.value);
+                }
+                &Nfs4RequestContent::Read(ref rd) => {
+                    SCLogDebug!("READv4: {:?}", rd);
+                    if let Some(fh) = last_putfh {
+                        xidmap.chunk_offset = rd.offset;
+                        xidmap.file_handle = fh.to_vec();
+                        self.xidmap_handle2name(xidmap);
+                    }
+                }
+                &Nfs4RequestContent::Open(ref rd) => {
+                    SCLogDebug!("OPENv4: {}", String::from_utf8_lossy(&rd.filename));
+                    xidmap.file_name = rd.filename.to_vec();
+                }
+                &Nfs4RequestContent::Lookup(ref rd) => {
+                    SCLogDebug!("LOOKUPv4: {}", String::from_utf8_lossy(&rd.filename));
+                    xidmap.file_name = rd.filename.to_vec();
+                }
+                &Nfs4RequestContent::Write(ref rd) => {
+                    SCLogDebug!("WRITEv4: {:?}", rd);
+                    if let Some(fh) = last_putfh {
+                        self.write_v4(r, rd, fh);
+                    }
+                }
+                &Nfs4RequestContent::Close(ref rd) => {
+                    SCLogDebug!("CLOSEv4: {:?}", rd);
+                }
+                &Nfs4RequestContent::SetClientId(ref rd) => {
+                    SCLogDebug!("SETCLIENTIDv4: client id {} r_netid {} r_addr {}",
+                            String::from_utf8_lossy(&rd.client_id),
+                            String::from_utf8_lossy(&rd.r_netid),
+                            String::from_utf8_lossy(&rd.r_addr));
+                }
+                &_ => { },
+            }
+        }
+    }
+
+    /// complete request record
+    pub fn process_request_record_v4<'b>(&mut self, r: &RpcPacket<'b>) -> u32 {
+        SCLogDebug!("NFSv4 REQUEST {} procedure {} ({}) blob size {}",
+                r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len());
+
+        let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0);
+
+        if r.procedure == NFSPROC4_COMPOUND {
+            match parse_nfs4_request_compound(r.prog_data) {
+                IResult::Done(_, rd) => {
+                    SCLogDebug!("NFSPROC4_COMPOUND: {:?}", rd);
+                    self.compound_request(&r, &rd, &mut xidmap);
+                },
+                IResult::Incomplete(_n) => {
+                    SCLogNotice!("NFSPROC4_COMPOUND: INCOMPLETE {:?}", _n);
+                    self.set_event(NFSEvent::MalformedData);
+                },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+            };
+        }
+
+        self.requestmap.insert(r.hdr.xid, xidmap);
+        0
+    }
+
+    fn compound_response<'b>(&mut self, r: &RpcReplyPacket<'b>,
+            cr: &Nfs4ResponseCompoundRecord<'b>,
+            xidmap: &mut NFSRequestXidMap)
+    {
+        let mut insert_filename_with_getfh = false;
+
+        for c in &cr.commands {
+            SCLogDebug!("c {:?}", c);
+            match c {
+                &Nfs4ResponseContent::ReadDir(s, ref rd) => {
+                    if let &Some(ref rd) = rd {
+                        SCLogDebug!("READDIRv4: status {} eof {}", s, rd.eof);
+
+                        for d in &rd.listing {
+                            if let &Some(ref d) = d {
+                                SCLogDebug!("READDIRv4: dir {}", String::from_utf8_lossy(&d.name));
+                            }
+                        }
+
+                    }
+                }
+                &Nfs4ResponseContent::Remove(s) => {
+                    SCLogDebug!("REMOVE4: status {}", s);
+                },
+                &Nfs4ResponseContent::Read(s, ref rd) => {
+                    if let &Some(ref rd) = rd {
+                        SCLogDebug!("READ4: xidmap {:?} status {} data {}", xidmap, s, rd.data.len());
+                        // convert record to generic read reply
+                        let reply = NfsReplyRead {
+                            status: s,
+                            attr_follows: 0,
+                            attr_blob: &[],
+                            count: rd.count,
+                            eof: rd.eof,
+                            data_len: rd.data.len() as u32,
+                            data: rd.data,
+                        };
+                        self.process_read_record(r, &reply, Some(&xidmap));
+                    }
+                },
+                &Nfs4ResponseContent::Open(s, ref rd) => {
+                    if let &Some(ref rd) = rd {
+                        SCLogDebug!("OPENv4: status {} opendata {:?}", s, rd);
+                        insert_filename_with_getfh = true;
+                    }
+                },
+                &Nfs4ResponseContent::GetFH(_s, ref rd) => {
+                    if let &Some(ref rd) = rd {
+                        if insert_filename_with_getfh {
+                            self.namemap.insert(rd.value.to_vec(),
+                                    xidmap.file_name.to_vec());
+                        }
+                    }
+                },
+                &_ => { },
+            }
+        }
+    }
+
+    pub fn process_reply_record_v4<'b>(&mut self, r: &RpcReplyPacket<'b>,
+            xidmap: &mut NFSRequestXidMap) -> u32 {
+        if xidmap.procedure == NFSPROC4_COMPOUND {
+            match parse_nfs4_response_compound(r.prog_data) {
+                IResult::Done(_, rd) => {
+                    SCLogDebug!("COMPOUNDv4: {:?}", rd);
+                    self.compound_response(&r, &rd, xidmap);
+                },
+                IResult::Incomplete(_) => {
+                    self.set_event(NFSEvent::MalformedData);
+                },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+            };
+        }
+        0
+    }
+}
diff --git a/rust/src/nfs/nfs4_records.rs b/rust/src/nfs/nfs4_records.rs
new file mode 100644 (file)
index 0000000..7053865
--- /dev/null
@@ -0,0 +1,843 @@
+/* Copyright (C) 2018 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+//! Nom parsers for NFSv4 records
+use nom::{be_u32, be_u64};
+
+use nfs::types::*;
+
+#[derive(Debug,PartialEq)]
+pub enum Nfs4RequestContent<'a> {
+    PutFH(Nfs4Handle<'a>),
+    GetFH,
+    SaveFH,
+    PutRootFH,
+    ReadDir,
+    Open(Nfs4RequestOpen<'a>),
+    Lookup(Nfs4RequestLookup<'a>),
+    Read(Nfs4RequestRead<'a>),
+    Write(Nfs4RequestWrite<'a>),
+    Close(Nfs4StateId<'a>),
+    Rename(Nfs4RequestRename<'a>),
+    Create(Nfs4RequestCreate<'a>),
+    OpenConfirm(Nfs4RequestOpenConfirm<'a>),
+    Access(u32),
+    GetAttr(Nfs4Attr),
+    SetAttr(Nfs4RequestSetAttr<'a>),
+    Renew(u64),
+    Remove(&'a[u8]),
+    DelegReturn(Nfs4StateId<'a>),
+    SetClientId(Nfs4RequestSetClientId<'a>),
+    SetClientIdConfirm,
+    ExchangeId(Nfs4RequestExchangeId<'a>),
+}
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4Attr {
+    attr_mask: u64,
+}
+
+named!(nfs4_parse_attr_fields<u32>,
+    do_parse!(
+        len: be_u32
+    >>  take!(len)
+    >> (len)
+));
+
+named!(nfs4_parse_attrs<Nfs4Attr>,
+    do_parse!(
+        attr_cnt: be_u32
+    >>  attr_mask1: be_u32
+    >>  attr_mask2: cond!(attr_cnt == 2, be_u32)
+    >>  nfs4_parse_attr_fields
+    >> ( Nfs4Attr {
+            attr_mask: ((attr_mask1 as u64) << 32) | attr_mask2.unwrap_or(0) as u64,
+        } )
+));
+
+named!(nfs4_parse_attrbits<Nfs4Attr>,
+    do_parse!(
+        attr_cnt: be_u32
+    >>  attr_mask1: be_u32
+    >>  attr_mask2: cond!(attr_cnt == 2, be_u32)
+    >> ( Nfs4Attr {
+            attr_mask: ((attr_mask1 as u64) << 32) | attr_mask2.unwrap_or(0) as u64,
+        } )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4StateId<'a> {
+    pub seqid: u32,
+    pub data: &'a[u8],
+}
+
+named!(nfs4_parse_stateid<Nfs4StateId>,
+    do_parse!(
+            seqid: be_u32
+        >>  data: take!(12)
+        >> ( Nfs4StateId {
+                seqid: seqid,
+                data: data,
+            })
+        )
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4Handle<'a> {
+    pub len: u32,
+    pub value: &'a[u8],
+}
+
+named!(nfs4_parse_handle<Nfs4Handle>,
+    do_parse!(
+            obj_len: be_u32
+        >>  obj: take!(obj_len)
+        >> ( Nfs4Handle {
+                len: obj_len,
+                value: obj,
+            })
+));
+
+named!(nfs4_parse_nfsstring<&[u8]>,
+    do_parse!(
+            len: be_u32
+        >>  data: take!(len)
+        >>  _fill_bytes: cond!(len % 4 != 0, take!(4 - len % 4))
+        >> ( data )
+));
+
+named!(nfs4_req_putfh<Nfs4RequestContent>,
+    do_parse!(
+            h: nfs4_parse_handle
+        >> ( Nfs4RequestContent::PutFH(h) )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestSetClientId<'a> {
+    pub client_id: &'a[u8],
+    pub r_netid: &'a[u8],
+    pub r_addr: &'a[u8],
+}
+
+named!(nfs4_req_setclientid<Nfs4RequestContent>,
+    do_parse!(
+            client_verifier: take!(8)
+        >>  client_id: nfs4_parse_nfsstring
+        >>  cb_program: be_u32
+        >>  r_netid: nfs4_parse_nfsstring
+        >>  r_addr: nfs4_parse_nfsstring
+        >>  cb_id: be_u32
+        >> (Nfs4RequestContent::SetClientId(Nfs4RequestSetClientId {
+                client_id: client_id,
+                r_netid: r_netid,
+                r_addr: r_addr,
+            }))
+));
+
+named!(nfs4_req_setclientid_confirm<Nfs4RequestContent>,
+    do_parse!(
+            client_id: take!(8)
+        >>  verifier: take!(8)
+        >> (Nfs4RequestContent::SetClientIdConfirm)
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestCreate<'a> {
+    pub ftype4: u32,
+    pub filename: &'a[u8],
+}
+
+named!(nfs4_req_create<Nfs4RequestContent>,
+    do_parse!(
+            ftype4: be_u32
+        >>  filename: nfs4_parse_nfsstring
+        >>  attrs: nfs4_parse_attrs
+        >> ( Nfs4RequestContent::Create(Nfs4RequestCreate {
+                ftype4: ftype4,
+                filename: filename,
+            })
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub enum Nfs4OpenRequestContent<'a> {
+    Exclusive4(&'a[u8]),
+    Unchecked4(Nfs4Attr),
+    Guarded4(Nfs4Attr),
+}
+
+named!(nfs4_req_open_unchecked4<Nfs4OpenRequestContent>,
+    do_parse!(
+            attrs: nfs4_parse_attrs
+        >> ( Nfs4OpenRequestContent::Unchecked4(attrs) )
+));
+
+named!(nfs4_req_open_guarded4<Nfs4OpenRequestContent>,
+    do_parse!(
+            attrs: nfs4_parse_attrs
+        >> ( Nfs4OpenRequestContent::Guarded4(attrs) )
+));
+
+named!(nfs4_req_open_exclusive4<Nfs4OpenRequestContent>,
+    do_parse!(
+            ver: take!(8)
+        >> ( Nfs4OpenRequestContent::Exclusive4(ver) )
+));
+
+
+named!(nfs4_req_open_type<Nfs4OpenRequestContent>,
+    do_parse!(
+            mode: be_u32
+        >>  data: switch!(value!(mode),
+                0 => call!(nfs4_req_open_unchecked4)  |
+                1 => call!(nfs4_req_open_guarded4)    |
+                2 => call!(nfs4_req_open_exclusive4))
+        >> ( data )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestOpen<'a> {
+    pub open_type: u32,
+    pub filename: &'a[u8],
+    pub open_data: Option<Nfs4OpenRequestContent<'a>>,
+}
+
+named!(nfs4_req_open<Nfs4RequestContent>,
+    do_parse!(
+            seqid: be_u32
+        >>  share_access: be_u32
+        >>  share_deny: be_u32
+        >>  client_id: be_u64
+        >>  owner_len: be_u32
+        >>  cond!(owner_len > 0, take!(owner_len))
+        >>  open_type: be_u32
+        >>  open_data: cond!(open_type == 1, nfs4_req_open_type)
+        >>  claim_type: be_u32
+        >>  filename: nfs4_parse_nfsstring
+        >> ( Nfs4RequestContent::Open(Nfs4RequestOpen {
+                open_type: open_type,
+                filename: filename,
+                open_data: open_data,
+            })
+        ))
+);
+
+named!(nfs4_req_readdir<Nfs4RequestContent>,
+    do_parse!(
+            cookie: be_u64
+        >>  cookie_verf: be_u64
+        >>  dir_cnt: be_u32
+        >>  max_cnt: be_u32
+        >>  attr: nfs4_parse_attrbits
+        >> ( Nfs4RequestContent::ReadDir )
+    )
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestRename<'a> {
+    pub oldname: &'a[u8],
+    pub newname: &'a[u8],
+}
+
+named!(nfs4_req_rename<Nfs4RequestContent>,
+    do_parse!(
+            oldname: nfs4_parse_nfsstring
+        >>  newname: nfs4_parse_nfsstring
+        >> ( Nfs4RequestContent::Rename(Nfs4RequestRename {
+                oldname: oldname,
+                newname: newname,
+            })
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestLookup<'a> {
+    pub filename: &'a[u8],
+}
+
+named!(nfs4_req_lookup<Nfs4RequestContent>,
+    do_parse!(
+            filename: nfs4_parse_nfsstring
+        >> ( Nfs4RequestContent::Lookup(Nfs4RequestLookup {
+                filename: filename,
+            })
+        ))
+);
+
+named!(nfs4_req_remove<Nfs4RequestContent>,
+    do_parse!(
+            filename: nfs4_parse_nfsstring
+        >> ( Nfs4RequestContent::Remove(filename) )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestSetAttr<'a> {
+    pub stateid: Nfs4StateId<'a>,
+}
+
+named!(nfs4_req_setattr<Nfs4RequestContent>,
+    do_parse!(
+            stateid: nfs4_parse_stateid
+        >>  attrs: nfs4_parse_attrs
+        >> (Nfs4RequestContent::SetAttr(Nfs4RequestSetAttr {
+                stateid: stateid,
+            }))
+));
+
+named!(nfs4_req_getattr<Nfs4RequestContent>,
+    do_parse!(
+            attrs: nfs4_parse_attrbits
+        >> ( Nfs4RequestContent::GetAttr(attrs) )
+    )
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestWrite<'a> {
+    pub stateid: Nfs4StateId<'a>,
+    pub offset: u64,
+    pub stable: u32,
+    pub write_len: u32,
+    pub data: &'a[u8],
+}
+
+named!(nfs4_req_write<Nfs4RequestContent>,
+    do_parse!(
+            stateid: nfs4_parse_stateid
+        >>  offset: be_u64
+        >>  stable: be_u32
+        >>  write_len: be_u32
+        >>  data: take!(write_len)
+        >>  _padding: cond!(write_len % 4 != 0, take!(4 - write_len % 4))
+        >> (Nfs4RequestContent::Write(Nfs4RequestWrite {
+                stateid: stateid,
+                offset: offset,
+                stable: stable,
+                write_len: write_len,
+                data: data,
+            }))
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestRead<'a> {
+    pub stateid: Nfs4StateId<'a>,
+    pub offset: u64,
+    pub count: u32,
+}
+
+named!(nfs4_req_read<Nfs4RequestContent>,
+    do_parse!(
+            stateid: nfs4_parse_stateid
+        >>  offset: be_u64
+        >>  count: be_u32
+        >> ( Nfs4RequestContent::Read(Nfs4RequestRead {
+                stateid: stateid,
+                offset: offset,
+                count: count,
+            })
+        ))
+);
+
+named!(nfs4_req_close<Nfs4RequestContent>,
+    do_parse!(
+            seqid: be_u32
+        >>  stateid: nfs4_parse_stateid
+        >> ( Nfs4RequestContent::Close(stateid) )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestOpenConfirm<'a> {
+    pub stateid: Nfs4StateId<'a>,
+}
+
+named!(nfs4_req_open_confirm<Nfs4RequestContent>,
+    do_parse!(
+            stateid: nfs4_parse_stateid
+        >>  seqid: be_u32
+        >> ( Nfs4RequestContent::OpenConfirm(Nfs4RequestOpenConfirm {
+                stateid: stateid,
+            })
+        ))
+);
+
+named!(nfs4_req_delegreturn<Nfs4RequestContent>,
+    do_parse!(
+            a: nfs4_parse_stateid
+        >> ( Nfs4RequestContent::DelegReturn(a) )
+    )
+);
+
+named!(nfs4_req_renew<Nfs4RequestContent>,
+    do_parse!(
+            a: be_u64
+        >> ( Nfs4RequestContent::Renew(a) )
+    )
+);
+
+named!(nfs4_req_getfh<Nfs4RequestContent>,
+    do_parse!( ( Nfs4RequestContent::GetFH ) ));
+
+named!(nfs4_req_savefh<Nfs4RequestContent>,
+    do_parse!( ( Nfs4RequestContent::SaveFH ) ));
+
+named!(nfs4_req_putrootfh<Nfs4RequestContent>,
+    do_parse!( ( Nfs4RequestContent::PutRootFH ) ));
+
+named!(nfs4_req_access<Nfs4RequestContent>,
+    do_parse!(
+            a: be_u32
+        >> ( Nfs4RequestContent::Access(a) )
+    )
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestExchangeId<'a> {
+    pub client_string: &'a[u8],
+    pub nii_domain: &'a[u8],
+    pub nii_name: &'a[u8],
+}
+
+named!(nfs4_req_exchangeid<Nfs4RequestContent>,
+    do_parse!(
+        verifier: take!(8)
+    >>  eia_clientstring: nfs4_parse_nfsstring
+    >>  eia_clientflags: be_u32
+    >>  eia_state_protect: be_u32
+    >>  eia_client_impl_id: be_u32
+    >>  nii_domain: nfs4_parse_nfsstring
+    >>  nii_name: nfs4_parse_nfsstring
+    >>  nii_data_sec: be_u64
+    >>  nii_data_nsec: be_u32
+    >> (Nfs4RequestContent::ExchangeId(
+            Nfs4RequestExchangeId {
+                client_string: eia_clientstring,
+                nii_domain: nii_domain,
+                nii_name: nii_name,
+            }
+        ))
+));
+
+named!(parse_request_compound_command<Nfs4RequestContent>,
+    do_parse!(
+        cmd: be_u32
+    >>  cmd_data: switch!(value!(cmd),
+            NFSPROC4_PUTFH                  => call!(nfs4_req_putfh)                |
+            NFSPROC4_READ                   => call!(nfs4_req_read)                 |
+            NFSPROC4_WRITE                  => call!(nfs4_req_write)                |
+            NFSPROC4_GETFH                  => call!(nfs4_req_getfh)                |
+            NFSPROC4_SAVEFH                 => call!(nfs4_req_savefh)               |
+            NFSPROC4_OPEN                   => call!(nfs4_req_open)                 |
+            NFSPROC4_CLOSE                  => call!(nfs4_req_close)                |
+            NFSPROC4_LOOKUP                 => call!(nfs4_req_lookup)               |
+            NFSPROC4_ACCESS                 => call!(nfs4_req_access)               |
+            NFSPROC4_GETATTR                => call!(nfs4_req_getattr)              |
+            NFSPROC4_READDIR                => call!(nfs4_req_readdir)              |
+            NFSPROC4_RENEW                  => call!(nfs4_req_renew)                |
+            NFSPROC4_OPEN_CONFIRM           => call!(nfs4_req_open_confirm)         |
+            NFSPROC4_REMOVE                 => call!(nfs4_req_remove)               |
+            NFSPROC4_RENAME                 => call!(nfs4_req_rename)               |
+            NFSPROC4_CREATE                 => call!(nfs4_req_create)               |
+            NFSPROC4_DELEGRETURN            => call!(nfs4_req_delegreturn)          |
+            NFSPROC4_SETATTR                => call!(nfs4_req_setattr)              |
+            NFSPROC4_PUTROOTFH              => call!(nfs4_req_putrootfh)            |
+            NFSPROC4_SETCLIENTID            => call!(nfs4_req_setclientid)          |
+            NFSPROC4_SETCLIENTID_CONFIRM    => call!(nfs4_req_setclientid_confirm)  |
+            NFSPROC4_EXCHANGE_ID            => call!(nfs4_req_exchangeid)
+            )
+        >> ( cmd_data )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestCompoundRecord<'a> {
+    pub commands: Vec<Nfs4RequestContent<'a>>,
+}
+
+named!(pub parse_nfs4_request_compound<Nfs4RequestCompoundRecord>,
+    do_parse!(
+            tag_len: be_u32
+        >>  tag: cond!(tag_len > 0, take!(tag_len))
+        >>  min_ver: be_u32
+        >>  ops_cnt: be_u32
+        >>  commands: count!(parse_request_compound_command, ops_cnt as usize)
+        >> (Nfs4RequestCompoundRecord {
+                commands: commands,
+            })
+));
+
+#[derive(Debug,PartialEq)]
+pub enum Nfs4ResponseContent<'a> {
+    PutFH(u32),
+    PutRootFH(u32),
+    GetFH(u32, Option<Nfs4Handle<'a>>),
+    Lookup(u32),
+    SaveFH(u32),
+    Rename(u32),
+    Write(u32, Option<Nfs4ResponseWrite>),
+    Read(u32, Option<Nfs4ResponseRead<'a>>),
+    Renew(u32),
+    Open(u32, Option<Nfs4ResponseOpen<'a>>),
+    OpenConfirm(u32, Option<Nfs4StateId<'a>>),
+    Close(u32, Option<Nfs4StateId<'a>>),
+    GetAttr(u32, Option<Nfs4Attr>),
+    SetAttr(u32),
+    Access(u32, Option<Nfs4ResponseAccess>),
+    ReadDir(u32, Option<Nfs4ResponseReaddir<'a>>),
+    Remove(u32),
+    DelegReturn(u32),
+    SetClientId(u32),
+    SetClientIdConfirm(u32),
+    Create(u32),
+}
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4ResponseWrite {
+    pub count: u32,
+    pub committed: u32,
+}
+
+named!(nfs4_res_write_ok<Nfs4ResponseWrite>,
+    do_parse!(
+            count: be_u32
+        >>  committed: be_u32
+        >>  verifier: be_u64
+        >> (Nfs4ResponseWrite {
+                count: count,
+                committed: committed,
+           })
+));
+
+named!(nfs4_res_write<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  wd: cond!(status == 0, nfs4_res_write_ok)
+        >> (Nfs4ResponseContent::Write(status, wd) )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4ResponseRead<'a> {
+    pub eof: bool,
+    pub count: u32,
+    pub data: &'a[u8],
+}
+
+named!(nfs4_res_read_ok<Nfs4ResponseRead>,
+    do_parse!(
+            eof: be_u32
+        >>  read_len: be_u32
+        >>  read_data: take!(read_len)
+        >> (Nfs4ResponseRead {
+                eof: eof==1,
+                count: read_len,
+                data: read_data,
+            })
+));
+
+named!(nfs4_res_read<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  rd: cond!(status == 0, nfs4_res_read_ok)
+        >> (Nfs4ResponseContent::Read(status, rd) )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4ResponseOpen<'a> {
+    pub stateid: Nfs4StateId<'a>,
+    pub result_flags: u32,
+    pub delegation_type: u32,
+    pub delegate_read: Option<Nfs4ResponseOpenDelegateRead<'a>>,
+}
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4ResponseOpenDelegateRead<'a> {
+    pub stateid: Nfs4StateId<'a>,
+}
+
+named!(nfs4_res_open_ok_delegate_read<Nfs4ResponseOpenDelegateRead>,
+    do_parse!(
+            stateid: nfs4_parse_stateid
+        >>  recall: be_u32
+        >>  ace_type: be_u32
+        >>  ace_flags: be_u32
+        >>  ace_mask: be_u32
+        >>  who_len: be_u32
+        >>  who: take!(who_len)
+        >> (Nfs4ResponseOpenDelegateRead {
+                stateid: stateid,
+            })
+));
+
+named!(nfs4_res_open_ok<Nfs4ResponseOpen>,
+    do_parse!(
+            stateid: nfs4_parse_stateid
+        >>  change_info: take!(20)
+        >>  result_flags: be_u32
+        >>  attrs: nfs4_parse_attrbits
+        >>  delegation_type: be_u32
+        >>  delegate_read: cond!(delegation_type == 1, nfs4_res_open_ok_delegate_read)
+        >> ( Nfs4ResponseOpen {
+                 stateid: stateid, result_flags: result_flags,
+                 delegation_type: delegation_type,
+                 delegate_read: delegate_read,
+             } )
+));
+
+named!(nfs4_res_open<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  open_data: cond!(status == 0, nfs4_res_open_ok)
+        >> ( Nfs4ResponseContent::Open(status, open_data) )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4ResponseReaddirEntry<'a> {
+    pub name: &'a[u8],
+}
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4ResponseReaddir<'a> {
+    pub eof: bool,
+    pub listing: Vec<Option<Nfs4ResponseReaddirEntry<'a>>>,
+}
+
+named!(nfs4_res_readdir_entry_do<Nfs4ResponseReaddirEntry>,
+    do_parse!(
+            cookie: be_u64
+        >>  name: nfs4_parse_nfsstring
+        >>  attrs: nfs4_parse_attrs
+        >> ( Nfs4ResponseReaddirEntry {
+                name: name,
+            })
+));
+
+named!(nfs4_res_readdir_entry<Option<Nfs4ResponseReaddirEntry>>,
+    do_parse!(
+            value_follows: be_u32
+        >>  entry: cond!(value_follows == 1, nfs4_res_readdir_entry_do)
+        >> (entry)
+));
+
+named!(nfs4_res_readdir_ok<Nfs4ResponseReaddir>,
+    do_parse!(
+            verifier: be_u64
+        // run parser until we find a 'value follows == 0'
+        >>  listing: many_till!(call!(nfs4_res_readdir_entry), peek!(tag!(b"\x00\x00\x00\x00")))
+        // value follows == 0 checked by line above
+        >>  _value_follows: be_u32
+        >>  eof: be_u32
+        >> ( Nfs4ResponseReaddir { eof: eof==1, listing: listing.0 })
+));
+
+named!(nfs4_res_readdir<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  rd: cond!(status == 0, nfs4_res_readdir_ok)
+        >> ( Nfs4ResponseContent::ReadDir(status, rd) )
+));
+
+named!(nfs4_res_create_ok<Nfs4Attr>,
+    do_parse!(
+            change_info: take!(20)
+        >>  attrs: nfs4_parse_attrbits
+        >> ( attrs )
+));
+
+named!(nfs4_res_create<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  attrs: cond!(status == 0, nfs4_res_create_ok)
+        >> ( Nfs4ResponseContent::Create(status) )
+));
+
+named!(nfs4_res_setattr_ok<Nfs4Attr>,
+    do_parse!(
+            attrs: nfs4_parse_attrbits
+        >> ( attrs )
+));
+
+named!(nfs4_res_setattr<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  attrs: cond!(status == 0, nfs4_res_setattr_ok)
+        >> ( Nfs4ResponseContent::SetAttr(status) )
+));
+
+named!(nfs4_res_getattr_ok<Nfs4Attr>,
+    do_parse!(
+            attrs: nfs4_parse_attrs
+        >> ( attrs )
+));
+
+named!(nfs4_res_getattr<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  attrs: cond!(status == 0, nfs4_res_getattr_ok)
+        >> ( Nfs4ResponseContent::GetAttr(status, attrs) )
+));
+
+named!(nfs4_res_openconfirm<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  stateid: cond!(status == 0, nfs4_parse_stateid)
+        >> ( Nfs4ResponseContent::OpenConfirm(status, stateid) )
+));
+
+named!(nfs4_res_close<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  stateid: cond!(status == 0, nfs4_parse_stateid)
+        >> ( Nfs4ResponseContent::Close(status, stateid) )
+));
+
+named!(nfs4_res_remove<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  cond!(status == 0, take!(20))   // change_info
+        >> ( Nfs4ResponseContent::Remove(status) )
+));
+
+named!(nfs4_res_rename<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >> ( Nfs4ResponseContent::Rename(status) )
+));
+
+named!(nfs4_res_savefh<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >> ( Nfs4ResponseContent::SaveFH(status) )
+));
+
+named!(nfs4_res_lookup<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >> ( Nfs4ResponseContent::Lookup(status) )
+));
+
+named!(nfs4_res_renew<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >> ( Nfs4ResponseContent::Renew(status) )
+));
+
+named!(nfs4_res_getfh<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  fh: cond!(status == 0, nfs4_parse_handle)
+        >> ( Nfs4ResponseContent::GetFH(status, fh) )
+));
+
+named!(nfs4_res_putfh<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >> ( Nfs4ResponseContent::PutFH(status) )
+));
+
+named!(nfs4_res_putrootfh<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >> ( Nfs4ResponseContent::PutRootFH(status) )
+));
+
+named!(nfs4_res_delegreturn<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >> ( Nfs4ResponseContent::DelegReturn(status) )
+));
+
+named!(nfs4_res_setclientid<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  client_id: be_u64
+        >>  verifier: be_u32
+        >> ( Nfs4ResponseContent::SetClientId(status) )
+));
+
+named!(nfs4_res_setclientid_confirm<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >> ( Nfs4ResponseContent::SetClientIdConfirm(status) )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4ResponseAccess {
+    pub supported_types: u32,
+    pub access_rights: u32,
+}
+
+named!(nfs4_res_access_ok<Nfs4ResponseAccess>,
+    do_parse!(
+            s: be_u32
+        >>  a: be_u32
+        >> (Nfs4ResponseAccess {
+                supported_types: s,
+                access_rights: a,
+            })
+));
+
+named!(nfs4_res_access<Nfs4ResponseContent>,
+    do_parse!(
+            status: be_u32
+        >>  ad: cond!(status == 0, nfs4_res_access_ok)
+        >> ( Nfs4ResponseContent::Access(
+                status, ad, ))
+));
+
+named!(nfs4_res_compound_command<Nfs4ResponseContent>,
+    do_parse!(
+        cmd: be_u32
+    >>  cmd_data: switch!(value!(cmd),
+            NFSPROC4_READ                   => call!(nfs4_res_read)                |
+            NFSPROC4_WRITE                  => call!(nfs4_res_write)               |
+            NFSPROC4_ACCESS                 => call!(nfs4_res_access)              |
+            NFSPROC4_GETFH                  => call!(nfs4_res_getfh)               |
+            NFSPROC4_PUTFH                  => call!(nfs4_res_putfh)               |
+            NFSPROC4_SAVEFH                 => call!(nfs4_res_savefh)              |
+            NFSPROC4_RENAME                 => call!(nfs4_res_rename)              |
+            NFSPROC4_READDIR                => call!(nfs4_res_readdir)             |
+            NFSPROC4_GETATTR                => call!(nfs4_res_getattr)             |
+            NFSPROC4_SETATTR                => call!(nfs4_res_setattr)             |
+            NFSPROC4_LOOKUP                 => call!(nfs4_res_lookup)              |
+            NFSPROC4_OPEN                   => call!(nfs4_res_open)                |
+            NFSPROC4_OPEN_CONFIRM           => call!(nfs4_res_openconfirm)         |
+            NFSPROC4_CLOSE                  => call!(nfs4_res_close)               |
+            NFSPROC4_REMOVE                 => call!(nfs4_res_remove)              |
+            NFSPROC4_CREATE                 => call!(nfs4_res_create)              |
+            NFSPROC4_DELEGRETURN            => call!(nfs4_res_delegreturn)         |
+            NFSPROC4_SETCLIENTID            => call!(nfs4_res_setclientid)         |
+            NFSPROC4_SETCLIENTID_CONFIRM    => call!(nfs4_res_setclientid_confirm) |
+            NFSPROC4_PUTROOTFH              => call!(nfs4_res_putrootfh)           |
+            NFSPROC4_RENEW                  => call!(nfs4_res_renew))
+    >> (cmd_data)
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4ResponseCompoundRecord<'a> {
+    pub status: u32,
+    pub commands: Vec<Nfs4ResponseContent<'a>>,
+}
+
+named!(pub parse_nfs4_response_compound<Nfs4ResponseCompoundRecord>,
+    do_parse!(
+            status: be_u32
+        >>  tag_len: be_u32
+        >>  tag: cond!(tag_len > 0, take!(tag_len))
+        >>  ops_cnt: be_u32
+        >>  commands: count!(nfs4_res_compound_command, ops_cnt as usize)
+        >> (Nfs4ResponseCompoundRecord {
+                status: status,
+                commands: commands,
+            })
+));
index b0a0f88db96aeb96bacc1a9860faad048cf64105..bc1a68b92f80d5cbb80120a63812ea3ccad6508a 100644 (file)
@@ -224,3 +224,99 @@ pub fn rpc_auth_status_string(auth_status: u32) -> String {
         },
     }.to_string()
 }
+
+pub const NFSPROC4_NULL:                u32 = 0;
+pub const NFSPROC4_COMPOUND:            u32 = 1;
+/* ops */
+pub const NFSPROC4_ACCESS:              u32 = 3;
+pub const NFSPROC4_CLOSE:               u32 = 4;
+pub const NFSPROC4_COMMIT:              u32 = 5;
+pub const NFSPROC4_CREATE:              u32 = 6;
+pub const NFSPROC4_DELEGPURGE:          u32 = 7;
+pub const NFSPROC4_DELEGRETURN:         u32 = 8;
+pub const NFSPROC4_GETATTR:             u32 = 9;
+pub const NFSPROC4_GETFH:               u32 = 10;
+pub const NFSPROC4_LINK:                u32 = 11;
+pub const NFSPROC4_LOCK:                u32 = 12;
+pub const NFSPROC4_LOCKT:               u32 = 13;
+pub const NFSPROC4_LOCKU:               u32 = 14;
+pub const NFSPROC4_LOOKUP:              u32 = 15;
+pub const NFSPROC4_LOOKUPP:             u32 = 16;
+pub const NFSPROC4_NVERIFY:             u32 = 17;
+pub const NFSPROC4_OPEN:                u32 = 18;
+pub const NFSPROC4_OPENATTR:            u32 = 19;
+pub const NFSPROC4_OPEN_CONFIRM:        u32 = 20;
+pub const NFSPROC4_OPEN_DOWNGRADE:      u32 = 21;
+pub const NFSPROC4_PUTFH:               u32 = 22;
+pub const NFSPROC4_PUTPUBFH:            u32 = 23;
+pub const NFSPROC4_PUTROOTFH:           u32 = 24;
+pub const NFSPROC4_READ:                u32 = 25;
+pub const NFSPROC4_READDIR:             u32 = 26;
+pub const NFSPROC4_READLINK:            u32 = 27;
+pub const NFSPROC4_REMOVE:              u32 = 28;
+pub const NFSPROC4_RENAME:              u32 = 29;
+pub const NFSPROC4_RENEW:               u32 = 30;
+pub const NFSPROC4_RESTOREFH:           u32 = 31;
+pub const NFSPROC4_SAVEFH:              u32 = 32;
+pub const NFSPROC4_SECINFO:             u32 = 33;
+pub const NFSPROC4_SETATTR:             u32 = 34;
+pub const NFSPROC4_SETCLIENTID:         u32 = 35;
+pub const NFSPROC4_SETCLIENTID_CONFIRM: u32 = 36;
+pub const NFSPROC4_VERIFY:              u32 = 37;
+pub const NFSPROC4_WRITE:               u32 = 38;
+pub const NFSPROC4_RELEASE_LOCKOWNER:   u32 = 39;
+
+
+pub const NFSPROC4_EXCHANGE_ID:         u32 = 42;
+
+pub const NFSPROC4_ILLEGAL:             u32 = 10044;
+
+
+pub fn nfs4_procedure_string(procedure: u32) -> String {
+    match procedure {
+        NFSPROC4_COMPOUND               => "COMPOUND",
+        NFSPROC4_NULL                   => "NULL",
+        // ops
+        NFSPROC4_ACCESS                 => "ACCESS",
+        NFSPROC4_CLOSE                  => "CLOSE",
+        NFSPROC4_COMMIT                 => "COMMIT",
+        NFSPROC4_CREATE                 => "CREATE",
+        NFSPROC4_DELEGPURGE             => "DELEGPURGE",
+        NFSPROC4_DELEGRETURN            => "DELEGRETURN",
+        NFSPROC4_GETATTR                => "GETATTR",
+        NFSPROC4_GETFH                  => "GETFH",
+        NFSPROC4_LINK                   => "LINK",
+        NFSPROC4_LOCK                   => "LOCK",
+        NFSPROC4_LOCKT                  => "LOCKT",
+        NFSPROC4_LOCKU                  => "LOCKU",
+        NFSPROC4_LOOKUP                 => "LOOKUP",
+        NFSPROC4_LOOKUPP                => "LOOKUPP",
+        NFSPROC4_NVERIFY                => "NVERIFY",
+        NFSPROC4_OPEN                   => "OPEN",
+        NFSPROC4_OPENATTR               => "OPENATTR",
+        NFSPROC4_OPEN_CONFIRM           => "OPEN_CONFIRM",
+        NFSPROC4_OPEN_DOWNGRADE         => "OPEN_DOWNGRADE",
+        NFSPROC4_PUTFH                  => "PUTFH",
+        NFSPROC4_PUTPUBFH               => "PUTPUBFH",
+        NFSPROC4_PUTROOTFH              => "PUTROOTFH",
+        NFSPROC4_READ                   => "READ",
+        NFSPROC4_READDIR                => "READDIR",
+        NFSPROC4_READLINK               => "READLINK",
+        NFSPROC4_REMOVE                 => "REMOVE",
+        NFSPROC4_RENAME                 => "RENAME",
+        NFSPROC4_RENEW                  => "RENEW",
+        NFSPROC4_RESTOREFH              => "RESTOREFH",
+        NFSPROC4_SAVEFH                 => "SAVEFH",
+        NFSPROC4_SECINFO                => "SECINFO",
+        NFSPROC4_SETATTR                => "SETATTR",
+        NFSPROC4_SETCLIENTID            => "SETCLIENTID",
+        NFSPROC4_SETCLIENTID_CONFIRM    => "SETCLIENTID_CONFIRM",
+        NFSPROC4_VERIFY                 => "VERIFY",
+        NFSPROC4_WRITE                  => "WRITE",
+        NFSPROC4_RELEASE_LOCKOWNER      => "RELEASE_LOCKOWNER",
+        NFSPROC4_ILLEGAL                => "ILLEGAL",
+        _ => {
+            return (procedure).to_string();
+        }
+    }.to_string()
+}