use bytes::{Buf, Bytes};
use super::fastxdr::*;
use super::{NFS4_COMPOUND_PROC, NFS4_PROGRAM, NFS4_VERSION, NFS41_MINOR_VERSION};
use crate::error::{NfsError, Result};
use crate::nfs3::rpc_header;
use crate::rpc::auth::Auth;
const MAX_COMPOUND_OPS: usize = 256;
const MAX_BITMAP_WORDS: usize = 16;
const MAX_ENTRY4_PER_PAGE: usize = 65536;
const MAX_LAYOUT_SEGMENTS: usize = 1024;
#[repr(u32)]
#[derive(Debug, Clone, Copy)]
pub(crate) enum OpNum {
Access = 3,
Close = 4,
Commit = 5,
Create = 6,
GetAttr = 9,
GetFh = 10,
Link = 11,
Lookup = 15,
Lookupp = 16,
Open = 18,
PutFh = 22,
PutRootFh = 24,
Read = 25,
ReadDir = 26,
ReadLink = 27,
Remove = 28,
Rename = 29,
RestoreFh = 31,
SaveFh = 32,
SetAttr = 34,
Write = 38,
OpenAttr = 20,
Lock = 12,
Lockt = 13,
Locku = 14,
LayoutGet = 50,
LayoutCommit = 49,
LayoutReturn = 51,
GetDeviceInfo = 47,
ExchangeId = 42,
CreateSession = 43,
DestroySession = 44,
BindConnToSession = 41,
Sequence = 53,
ReclaimComplete = 58,
DestroyClientId = 57,
DelegReturn = 8,
TestStateId = 55,
FreeStateId = 56,
}
fn xdr_u32(buf: &mut Vec<u8>, v: u32) {
buf.extend_from_slice(&v.to_be_bytes());
}
fn xdr_u64(buf: &mut Vec<u8>, v: u64) {
buf.extend_from_slice(&v.to_be_bytes());
}
fn xdr_i64(buf: &mut Vec<u8>, v: i64) {
buf.extend_from_slice(&v.to_be_bytes());
}
fn xdr_bool(buf: &mut Vec<u8>, v: bool) {
xdr_u32(buf, if v { 1 } else { 0 });
}
fn xdr_fixed_bytes(buf: &mut Vec<u8>, data: &[u8]) {
buf.extend_from_slice(data);
let pad = (4 - data.len() % 4) % 4;
for _ in 0..pad {
buf.push(0);
}
}
fn xdr_var_bytes(buf: &mut Vec<u8>, data: &[u8]) {
xdr_u32(buf, data.len() as u32);
xdr_fixed_bytes(buf, data);
}
fn xdr_string(buf: &mut Vec<u8>, s: &str) {
xdr_var_bytes(buf, s.as_bytes());
}
fn xdr_bitmap(buf: &mut Vec<u8>, bitmap: &[u32]) {
xdr_u32(buf, bitmap.len() as u32);
for &word in bitmap {
xdr_u32(buf, word);
}
}
pub(crate) struct CompoundBuilder {
tag: String,
ops: Vec<EncodedOp>,
}
struct EncodedOp {
opcode: OpNum,
args: Vec<u8>,
}
#[allow(dead_code)] impl CompoundBuilder {
pub fn new(tag: &str) -> Self {
Self {
tag: tag.to_string(),
ops: Vec::new(),
}
}
pub fn op_count(&self) -> usize {
self.ops.len()
}
pub fn sequence(
mut self,
session_id: &[u8; 16],
sequence_id: u32,
slot_id: u32,
highest_slot_id: u32,
cachethis: bool,
) -> Self {
let mut args = Vec::new();
args.extend_from_slice(session_id); xdr_u32(&mut args, sequence_id);
xdr_u32(&mut args, slot_id);
xdr_u32(&mut args, highest_slot_id);
xdr_bool(&mut args, cachethis);
self.ops.push(EncodedOp {
opcode: OpNum::Sequence,
args,
});
self
}
pub fn exchange_id(
mut self,
co_verifier: &[u8; 8],
co_ownerid: &[u8],
flags: u32,
impl_domain: &str,
impl_name: &str,
) -> Self {
let mut args = Vec::new();
args.extend_from_slice(co_verifier);
xdr_var_bytes(&mut args, co_ownerid);
xdr_u32(&mut args, flags);
xdr_u32(&mut args, 0);
xdr_u32(&mut args, 1);
xdr_string(&mut args, impl_domain);
xdr_string(&mut args, impl_name);
xdr_i64(&mut args, 0);
xdr_u32(&mut args, 0);
self.ops.push(EncodedOp {
opcode: OpNum::ExchangeId,
args,
});
self
}
pub fn create_session(
mut self,
client_id: u64,
sequence_id: u32,
flags: u32,
fore_attrs: &ChannelAttrsArgs,
back_attrs: &ChannelAttrsArgs,
cb_program: u32,
) -> Self {
let mut args = Vec::new();
xdr_u64(&mut args, client_id);
xdr_u32(&mut args, sequence_id);
xdr_u32(&mut args, flags);
fore_attrs.encode(&mut args);
back_attrs.encode(&mut args);
xdr_u32(&mut args, cb_program);
xdr_u32(&mut args, 1); xdr_u32(&mut args, 0); self.ops.push(EncodedOp {
opcode: OpNum::CreateSession,
args,
});
self
}
pub fn destroy_session(mut self, session_id: &[u8; 16]) -> Self {
let mut args = Vec::new();
args.extend_from_slice(session_id);
self.ops.push(EncodedOp {
opcode: OpNum::DestroySession,
args,
});
self
}
pub fn bind_conn_to_session(
mut self,
session_id: &[u8; 16],
dir: u32,
use_conn_in_rdma_mode: bool,
) -> Self {
let mut args = Vec::new();
args.extend_from_slice(session_id);
xdr_u32(&mut args, dir); xdr_bool(&mut args, use_conn_in_rdma_mode);
self.ops.push(EncodedOp {
opcode: OpNum::BindConnToSession,
args,
});
self
}
pub fn reclaim_complete(mut self, one_fs: bool) -> Self {
let mut args = Vec::new();
xdr_bool(&mut args, one_fs);
self.ops.push(EncodedOp {
opcode: OpNum::ReclaimComplete,
args,
});
self
}
pub fn destroy_client_id(mut self, client_id: u64) -> Self {
let mut args = Vec::new();
xdr_u64(&mut args, client_id);
self.ops.push(EncodedOp {
opcode: OpNum::DestroyClientId,
args,
});
self
}
pub fn putrootfh(mut self) -> Self {
self.ops.push(EncodedOp {
opcode: OpNum::PutRootFh,
args: Vec::new(),
});
self
}
pub fn putfh(mut self, fh: &[u8]) -> Self {
let mut args = Vec::new();
xdr_var_bytes(&mut args, fh);
self.ops.push(EncodedOp {
opcode: OpNum::PutFh,
args,
});
self
}
pub fn getfh(mut self) -> Self {
self.ops.push(EncodedOp {
opcode: OpNum::GetFh,
args: Vec::new(),
});
self
}
pub fn savefh(mut self) -> Self {
self.ops.push(EncodedOp {
opcode: OpNum::SaveFh,
args: Vec::new(),
});
self
}
pub fn restorefh(mut self) -> Self {
self.ops.push(EncodedOp {
opcode: OpNum::RestoreFh,
args: Vec::new(),
});
self
}
pub fn openattr(mut self, create_dir: bool) -> Self {
let mut args = Vec::new();
xdr_bool(&mut args, create_dir);
self.ops.push(EncodedOp {
opcode: OpNum::OpenAttr,
args,
});
self
}
pub fn lookup(mut self, name: &str) -> Self {
let mut args = Vec::new();
xdr_string(&mut args, name);
self.ops.push(EncodedOp {
opcode: OpNum::Lookup,
args,
});
self
}
pub fn lookupp(mut self) -> Self {
self.ops.push(EncodedOp {
opcode: OpNum::Lookupp,
args: Vec::new(),
});
self
}
pub fn getattr(mut self, bitmap: &[u32]) -> Self {
let mut args = Vec::new();
xdr_bitmap(&mut args, bitmap);
self.ops.push(EncodedOp {
opcode: OpNum::GetAttr,
args,
});
self
}
pub fn setattr(mut self, stateid: &[u8; 16], attrmask: &[u32], attr_vals: &[u8]) -> Self {
let mut args = Vec::new();
args.extend_from_slice(stateid);
xdr_bitmap(&mut args, attrmask);
xdr_var_bytes(&mut args, attr_vals);
self.ops.push(EncodedOp {
opcode: OpNum::SetAttr,
args,
});
self
}
pub fn access(mut self, access_mask: u32) -> Self {
let mut args = Vec::new();
xdr_u32(&mut args, access_mask);
self.ops.push(EncodedOp {
opcode: OpNum::Access,
args,
});
self
}
pub fn read(mut self, stateid: &[u8; 16], offset: u64, count: u32) -> Self {
let mut args = Vec::new();
args.extend_from_slice(stateid);
xdr_u64(&mut args, offset);
xdr_u32(&mut args, count);
self.ops.push(EncodedOp {
opcode: OpNum::Read,
args,
});
self
}
pub fn write(mut self, stateid: &[u8; 16], offset: u64, stable: u32, data: &[u8]) -> Self {
let mut args = Vec::new();
args.extend_from_slice(stateid);
xdr_u64(&mut args, offset);
xdr_u32(&mut args, stable); xdr_var_bytes(&mut args, data);
self.ops.push(EncodedOp {
opcode: OpNum::Write,
args,
});
self
}
pub fn write_header(
mut self,
stateid: &[u8; 16],
offset: u64,
stable: u32,
data_len: u32,
) -> Self {
let mut args = Vec::new();
args.extend_from_slice(stateid);
xdr_u64(&mut args, offset);
xdr_u32(&mut args, stable); xdr_u32(&mut args, data_len);
self.ops.push(EncodedOp {
opcode: OpNum::Write,
args,
});
self
}
pub fn commit(mut self, offset: u64, count: u32) -> Self {
let mut args = Vec::new();
xdr_u64(&mut args, offset);
xdr_u32(&mut args, count);
self.ops.push(EncodedOp {
opcode: OpNum::Commit,
args,
});
self
}
pub fn readdir(
mut self,
cookie: u64,
cookieverf: &[u8; 8],
dircount: u32,
maxcount: u32,
attr_request: &[u32],
) -> Self {
let mut args = Vec::new();
xdr_u64(&mut args, cookie);
args.extend_from_slice(cookieverf);
xdr_u32(&mut args, dircount);
xdr_u32(&mut args, maxcount);
xdr_bitmap(&mut args, attr_request);
self.ops.push(EncodedOp {
opcode: OpNum::ReadDir,
args,
});
self
}
pub fn readlink(mut self) -> Self {
self.ops.push(EncodedOp {
opcode: OpNum::ReadLink,
args: Vec::new(),
});
self
}
pub fn open(mut self, open_args: &OpenArgs) -> Self {
let mut args = Vec::new();
open_args.encode(&mut args);
self.ops.push(EncodedOp {
opcode: OpNum::Open,
args,
});
self
}
pub fn close(mut self, seqid: u32, stateid: &[u8; 16]) -> Self {
let mut args = Vec::new();
xdr_u32(&mut args, seqid);
args.extend_from_slice(stateid);
self.ops.push(EncodedOp {
opcode: OpNum::Close,
args,
});
self
}
pub fn create(mut self, objtype: u32, name: &str, attrmask: &[u32], attr_vals: &[u8]) -> Self {
let mut args = Vec::new();
xdr_u32(&mut args, objtype); xdr_string(&mut args, name);
xdr_bitmap(&mut args, attrmask);
xdr_var_bytes(&mut args, attr_vals);
self.ops.push(EncodedOp {
opcode: OpNum::Create,
args,
});
self
}
pub fn create_symlink(
mut self,
name: &str,
link_target: &str,
attrmask: &[u32],
attr_vals: &[u8],
) -> Self {
let mut args = Vec::new();
xdr_u32(&mut args, 5); xdr_string(&mut args, link_target);
xdr_string(&mut args, name);
xdr_bitmap(&mut args, attrmask);
xdr_var_bytes(&mut args, attr_vals);
self.ops.push(EncodedOp {
opcode: OpNum::Create,
args,
});
self
}
pub fn remove(mut self, name: &str) -> Self {
let mut args = Vec::new();
xdr_string(&mut args, name);
self.ops.push(EncodedOp {
opcode: OpNum::Remove,
args,
});
self
}
pub fn rename(mut self, oldname: &str, newname: &str) -> Self {
let mut args = Vec::new();
xdr_string(&mut args, oldname);
xdr_string(&mut args, newname);
self.ops.push(EncodedOp {
opcode: OpNum::Rename,
args,
});
self
}
pub fn link(mut self, newname: &str) -> Self {
let mut args = Vec::new();
xdr_string(&mut args, newname);
self.ops.push(EncodedOp {
opcode: OpNum::Link,
args,
});
self
}
#[allow(clippy::too_many_arguments)]
pub fn layoutget(
mut self,
signal_layout_avail: bool,
layout_type: u32,
iomode: u32,
offset: u64,
length: u64,
min_length: u64,
stateid: &[u8; 16],
max_count: u32,
) -> Self {
let mut args = Vec::new();
xdr_bool(&mut args, signal_layout_avail);
xdr_u32(&mut args, layout_type);
xdr_u32(&mut args, iomode);
xdr_u64(&mut args, offset);
xdr_u64(&mut args, length);
xdr_u64(&mut args, min_length);
args.extend_from_slice(stateid);
xdr_u32(&mut args, max_count);
self.ops.push(EncodedOp {
opcode: OpNum::LayoutGet,
args,
});
self
}
pub fn layoutcommit(
mut self,
offset: u64,
length: u64,
reclaim: bool,
stateid: &[u8; 16],
last_write_offset: Option<u64>,
layout_type: u32,
) -> Self {
let mut args = Vec::new();
xdr_u64(&mut args, offset);
xdr_u64(&mut args, length);
xdr_bool(&mut args, reclaim);
args.extend_from_slice(stateid);
if let Some(off) = last_write_offset {
xdr_bool(&mut args, true);
xdr_u64(&mut args, off);
} else {
xdr_bool(&mut args, false);
}
xdr_u32(&mut args, 0);
xdr_u32(&mut args, layout_type);
xdr_u32(&mut args, 0);
self.ops.push(EncodedOp {
opcode: OpNum::LayoutCommit,
args,
});
self
}
#[allow(clippy::too_many_arguments)]
pub fn layoutreturn(
mut self,
reclaim: bool,
layout_type: u32,
iomode: u32,
return_type: u32,
offset: u64,
length: u64,
stateid: &[u8; 16],
) -> Self {
let mut args = Vec::new();
xdr_bool(&mut args, reclaim);
xdr_u32(&mut args, layout_type);
xdr_u32(&mut args, iomode);
xdr_u32(&mut args, return_type);
if return_type == 1 {
xdr_u64(&mut args, offset);
xdr_u64(&mut args, length);
args.extend_from_slice(stateid);
xdr_u32(&mut args, 0);
}
self.ops.push(EncodedOp {
opcode: OpNum::LayoutReturn,
args,
});
self
}
pub fn getdeviceinfo(mut self, device_id: &[u8; 16], layout_type: u32, max_count: u32) -> Self {
let mut args = Vec::new();
args.extend_from_slice(device_id); xdr_u32(&mut args, layout_type);
xdr_u32(&mut args, max_count);
xdr_u32(&mut args, 0);
self.ops.push(EncodedOp {
opcode: OpNum::GetDeviceInfo,
args,
});
self
}
#[allow(clippy::too_many_arguments)]
pub fn lock(
mut self,
lock_type: u32,
reclaim: bool,
offset: u64,
length: u64,
new_lock_owner: bool,
open_stateid: &[u8; 16],
lock_seqid: u32,
open_seqid: u32,
lock_owner: &[u8],
client_id: u64,
) -> Self {
let mut args = Vec::new();
xdr_u32(&mut args, lock_type);
xdr_bool(&mut args, reclaim);
xdr_u64(&mut args, offset);
xdr_u64(&mut args, length);
if new_lock_owner {
xdr_bool(&mut args, true); xdr_u32(&mut args, open_seqid);
args.extend_from_slice(open_stateid); xdr_u32(&mut args, lock_seqid);
xdr_u64(&mut args, client_id);
xdr_var_bytes(&mut args, lock_owner);
} else {
xdr_bool(&mut args, false); args.extend_from_slice(open_stateid); xdr_u32(&mut args, lock_seqid);
}
self.ops.push(EncodedOp {
opcode: OpNum::Lock,
args,
});
self
}
pub fn lockt(
mut self,
lock_type: u32,
offset: u64,
length: u64,
lock_owner: &[u8],
client_id: u64,
) -> Self {
let mut args = Vec::new();
xdr_u32(&mut args, lock_type);
xdr_u64(&mut args, offset);
xdr_u64(&mut args, length);
xdr_u64(&mut args, client_id);
xdr_var_bytes(&mut args, lock_owner);
self.ops.push(EncodedOp {
opcode: OpNum::Lockt,
args,
});
self
}
pub fn locku(
mut self,
lock_type: u32,
seqid: u32,
lock_stateid: &[u8; 16],
offset: u64,
length: u64,
) -> Self {
let mut args = Vec::new();
xdr_u32(&mut args, lock_type);
xdr_u32(&mut args, seqid);
args.extend_from_slice(lock_stateid);
xdr_u64(&mut args, offset);
xdr_u64(&mut args, length);
self.ops.push(EncodedOp {
opcode: OpNum::Locku,
args,
});
self
}
pub fn test_stateid(mut self, stateids: &[[u8; 16]]) -> Self {
let mut args = Vec::new();
xdr_u32(&mut args, stateids.len() as u32);
for sid in stateids {
args.extend_from_slice(sid);
}
self.ops.push(EncodedOp {
opcode: OpNum::TestStateId,
args,
});
self
}
pub fn delegreturn(mut self, stateid: &[u8; 16]) -> Self {
let mut args = Vec::new();
args.extend_from_slice(stateid); self.ops.push(EncodedOp {
opcode: OpNum::DelegReturn,
args,
});
self
}
pub fn free_stateid(mut self, stateid: &[u8; 16]) -> Self {
let mut args = Vec::new();
args.extend_from_slice(stateid);
self.ops.push(EncodedOp {
opcode: OpNum::FreeStateId,
args,
});
self
}
pub fn encode_with_header(self, auth: &Auth, buf: &mut Vec<u8>) {
rpc_header(NFS4_PROGRAM, NFS4_VERSION, NFS4_COMPOUND_PROC, auth).encode(buf);
self.encode_body(buf);
}
fn encode_body(self, buf: &mut Vec<u8>) {
xdr_string(buf, &self.tag);
xdr_u32(buf, NFS41_MINOR_VERSION);
xdr_u32(buf, self.ops.len() as u32);
for op in self.ops {
xdr_u32(buf, op.opcode as u32);
buf.extend_from_slice(&op.args);
}
}
}
pub(crate) struct ChannelAttrsArgs {
pub headerpadsize: u32,
pub maxrequestsize: u32,
pub maxresponsesize: u32,
pub maxresponsesize_cached: u32,
pub maxoperations: u32,
pub maxrequests: u32,
}
impl ChannelAttrsArgs {
fn encode(&self, buf: &mut Vec<u8>) {
xdr_u32(buf, self.headerpadsize);
xdr_u32(buf, self.maxrequestsize);
xdr_u32(buf, self.maxresponsesize);
xdr_u32(buf, self.maxresponsesize_cached);
xdr_u32(buf, self.maxoperations);
xdr_u32(buf, self.maxrequests);
xdr_u32(buf, 0);
}
}
const OPEN4_SHARE_ACCESS_WANT_NO_DELEG: u32 = 0x0100;
pub(crate) struct OpenArgs {
pub seqid: u32,
pub share_access: u32,
pub share_deny: u32,
pub client_id: u64,
pub owner: Bytes,
pub create: bool,
pub create_attrs_mask: Vec<u32>,
pub create_attrs_vals: Vec<u8>,
pub claim_file: String,
}
impl OpenArgs {
fn encode(&self, buf: &mut Vec<u8>) {
xdr_u32(buf, self.seqid);
xdr_u32(buf, self.share_access | OPEN4_SHARE_ACCESS_WANT_NO_DELEG);
xdr_u32(buf, self.share_deny);
xdr_u64(buf, self.client_id);
xdr_var_bytes(buf, &self.owner);
if self.create {
xdr_u32(buf, 1); xdr_u32(buf, 0); xdr_bitmap(buf, &self.create_attrs_mask);
xdr_var_bytes(buf, &self.create_attrs_vals);
} else {
xdr_u32(buf, 0); }
xdr_u32(buf, 0);
xdr_string(buf, &self.claim_file);
}
}
pub(crate) struct CompoundResponse {
#[cfg_attr(not(test), allow(dead_code))]
pub tag: String,
pub status: nfsstat4,
pub results: Vec<OpResponse>,
}
pub(crate) struct OpResponse {
pub opcode: u32,
pub status: nfsstat4,
pub data: Bytes,
}
impl CompoundResponse {
pub fn decode(mut buf: Bytes) -> Result<Self> {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("COMPOUND response too short".to_string()));
}
let status_val = buf.get_u32();
let status = decode_nfsstat4(status_val)?;
let tag = decode_string(&mut buf)?;
if buf.remaining() < 4 {
return Err(NfsError::Xdr(
"COMPOUND response missing resarray length".to_string(),
));
}
let num_results = buf.get_u32() as usize;
if num_results > MAX_COMPOUND_OPS {
return Err(NfsError::Xdr(format!(
"COMPOUND response has {} ops, max {}",
num_results, MAX_COMPOUND_OPS
)));
}
let mut results = Vec::with_capacity(num_results);
for _ in 0..num_results {
if buf.remaining() < 8 {
return Err(NfsError::Xdr(
"COMPOUND response truncated at op header".to_string(),
));
}
let opcode = buf.get_u32();
let op_status_val = buf.get_u32();
let op_status = decode_nfsstat4(op_status_val)?;
results.push(OpResponse {
opcode,
status: op_status,
data: buf.clone(), });
skip_op_result(opcode, op_status_val, &mut buf)?;
}
Ok(CompoundResponse {
tag,
status,
results,
})
}
pub fn check_status(&self) -> Result<()> {
if matches!(self.status, nfsstat4::NFS4_OK) {
Ok(())
} else {
Err(NfsError::Nfs4(self.status))
}
}
pub fn op_ok(&self, index: usize) -> Result<&OpResponse> {
let op = self.results.get(index).ok_or_else(|| {
NfsError::Xdr(format!("COMPOUND response missing op at index {}", index))
})?;
if !matches!(op.status, nfsstat4::NFS4_OK) {
return Err(NfsError::Nfs4(op.status));
}
Ok(op)
}
}
fn decode_nfsstat4(val: u32) -> Result<nfsstat4> {
let be = val.to_be_bytes();
nfsstat4::try_from(&mut Bytes::copy_from_slice(&be))
.map_err(|e| NfsError::Xdr(format!("invalid nfsstat4 {}: {}", val, e)))
}
fn decode_string(buf: &mut Bytes) -> Result<String> {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("string length truncated".to_string()));
}
let len = buf.get_u32() as usize;
let padded = (len + 3) & !3;
if buf.remaining() < padded {
return Err(NfsError::Xdr("string data truncated".to_string()));
}
let s = String::from_utf8(buf.slice(..len).to_vec())
.map_err(|e| NfsError::Xdr(format!("invalid UTF-8 in string: {}", e)))?;
buf.advance(padded);
Ok(s)
}
fn skip_var_bytes(buf: &mut Bytes) -> Result<()> {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("opaque length truncated".to_string()));
}
let len = buf.get_u32() as usize;
let padded = (len + 3) & !3;
if buf.remaining() < padded {
return Err(NfsError::Xdr("opaque data truncated".to_string()));
}
buf.advance(padded);
Ok(())
}
fn skip_bitmap(buf: &mut Bytes) -> Result<()> {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("bitmap length truncated".to_string()));
}
let n = buf.get_u32() as usize;
if n > MAX_BITMAP_WORDS {
return Err(NfsError::Xdr(format!(
"bitmap has {} words, max {}",
n, MAX_BITMAP_WORDS
)));
}
let bytes_needed = n * 4;
if buf.remaining() < bytes_needed {
return Err(NfsError::Xdr("bitmap data truncated".to_string()));
}
buf.advance(bytes_needed);
Ok(())
}
fn skip_fattr4(buf: &mut Bytes) -> Result<()> {
skip_bitmap(buf)?;
skip_var_bytes(buf)
}
fn skip_change_info(buf: &mut Bytes) -> Result<()> {
if buf.remaining() < 20 {
return Err(NfsError::Xdr("change_info truncated".to_string()));
}
buf.advance(20);
Ok(())
}
fn skip_stateid4(buf: &mut Bytes) -> Result<()> {
if buf.remaining() < 16 {
return Err(NfsError::Xdr("stateid4 truncated".to_string()));
}
buf.advance(16);
Ok(())
}
fn skip_op_result(opcode: u32, status: u32, buf: &mut Bytes) -> Result<()> {
if status != 0 {
if opcode == OpNum::SetAttr as u32 {
skip_bitmap(buf)?;
}
if opcode == OpNum::Lock as u32
&& status == 10012
&& buf.remaining() >= 28
{
buf.advance(28); skip_var_bytes(buf)?; }
return Ok(());
}
match opcode {
op if op == OpNum::PutRootFh as u32 => {}
op if op == OpNum::PutFh as u32 => {}
op if op == OpNum::SaveFh as u32 => {}
op if op == OpNum::RestoreFh as u32 => {}
op if op == OpNum::Lookup as u32 => {}
op if op == OpNum::Lookupp as u32 => {}
op if op == OpNum::ReclaimComplete as u32 => {}
op if op == OpNum::DestroySession as u32 => {}
op if op == OpNum::DestroyClientId as u32 => {}
op if op == OpNum::DelegReturn as u32 => {}
op if op == OpNum::Sequence as u32 => {
if buf.remaining() < 36 {
return Err(NfsError::Xdr("SEQUENCE result truncated".to_string()));
}
buf.advance(36);
}
op if op == OpNum::GetFh as u32 => {
skip_var_bytes(buf)?;
}
op if op == OpNum::GetAttr as u32 => {
skip_fattr4(buf)?;
}
op if op == OpNum::SetAttr as u32 => {
skip_bitmap(buf)?;
}
op if op == OpNum::Access as u32 => {
if buf.remaining() < 8 {
return Err(NfsError::Xdr("ACCESS result truncated".to_string()));
}
buf.advance(8);
}
op if op == OpNum::Read as u32 => {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("READ result truncated".to_string()));
}
buf.advance(4); skip_var_bytes(buf)?; }
op if op == OpNum::Write as u32 => {
if buf.remaining() < 16 {
return Err(NfsError::Xdr("WRITE result truncated".to_string()));
}
buf.advance(16);
}
op if op == OpNum::Commit as u32 => {
if buf.remaining() < 8 {
return Err(NfsError::Xdr("COMMIT result truncated".to_string()));
}
buf.advance(8);
}
op if op == OpNum::ReadDir as u32 => {
if buf.remaining() < 8 {
return Err(NfsError::Xdr("READDIR result truncated".to_string()));
}
buf.advance(8); skip_entry4_list(buf)?;
}
op if op == OpNum::ReadLink as u32 => {
skip_var_bytes(buf)?;
}
op if op == OpNum::OpenAttr as u32 => {}
op if op == OpNum::Open as u32 => {
skip_stateid4(buf)?;
skip_change_info(buf)?;
if buf.remaining() < 4 {
return Err(NfsError::Xdr("OPEN rflags truncated".to_string()));
}
buf.advance(4); skip_bitmap(buf)?; skip_open_delegation(buf)?;
}
op if op == OpNum::Close as u32 => {
skip_stateid4(buf)?;
}
op if op == OpNum::Lock as u32 => {
skip_stateid4(buf)?;
}
op if op == OpNum::Lockt as u32 => {}
op if op == OpNum::Locku as u32 => {
skip_stateid4(buf)?;
}
op if op == OpNum::TestStateId as u32 => {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("TEST_STATEID result truncated".to_string()));
}
let n = buf.get_u32() as usize;
let bytes_needed = n
.checked_mul(4)
.ok_or_else(|| NfsError::Xdr("TEST_STATEID count overflow".to_string()))?;
if buf.remaining() < bytes_needed {
return Err(NfsError::Xdr("TEST_STATEID statuses truncated".to_string()));
}
buf.advance(bytes_needed);
}
op if op == OpNum::FreeStateId as u32 => {}
op if op == OpNum::Create as u32 => {
skip_change_info(buf)?;
skip_bitmap(buf)?;
}
op if op == OpNum::Remove as u32 => {
skip_change_info(buf)?;
}
op if op == OpNum::Rename as u32 => {
skip_change_info(buf)?;
skip_change_info(buf)?;
}
op if op == OpNum::Link as u32 => {
skip_change_info(buf)?;
}
op if op == OpNum::ExchangeId as u32 => {
skip_exchange_id_result(buf)?;
}
op if op == OpNum::CreateSession as u32 => {
skip_create_session_result(buf)?;
}
op if op == OpNum::LayoutGet as u32 => {
if buf.remaining() < 20 {
return Err(NfsError::Xdr("LAYOUTGET result truncated".to_string()));
}
buf.advance(4); skip_stateid4(buf)?; if buf.remaining() < 4 {
return Err(NfsError::Xdr(
"LAYOUTGET segments len truncated".to_string(),
));
}
let n = buf.get_u32() as usize;
if n > MAX_LAYOUT_SEGMENTS {
return Err(NfsError::Xdr(format!(
"LAYOUTGET has {} segments, max {}",
n, MAX_LAYOUT_SEGMENTS
)));
}
for _ in 0..n {
if buf.remaining() < 24 {
return Err(NfsError::Xdr("layout4 segment truncated".to_string()));
}
buf.advance(24);
skip_var_bytes(buf)?; }
}
op if op == OpNum::LayoutCommit as u32 => {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("LAYOUTCOMMIT result truncated".to_string()));
}
let has_newsize = buf.get_u32();
if has_newsize != 0 && buf.remaining() >= 8 {
buf.advance(8);
}
}
op if op == OpNum::LayoutReturn as u32 => {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("LAYOUTRETURN result truncated".to_string()));
}
let has_stateid = buf.get_u32();
if has_stateid != 0 {
skip_stateid4(buf)?;
}
}
op if op == OpNum::GetDeviceInfo as u32 => {
if buf.remaining() < 4 {
return Err(NfsError::Xdr(
"GETDEVICEINFO layout_type truncated".to_string(),
));
}
buf.advance(4);
skip_var_bytes(buf)?; skip_bitmap(buf)?; }
op if op == OpNum::BindConnToSession as u32 => {
if buf.remaining() < 24 {
return Err(NfsError::Xdr(
"BIND_CONN_TO_SESSION result truncated".to_string(),
));
}
buf.advance(24);
}
_ => {
return Err(NfsError::Xdr(format!(
"unknown op {} in COMPOUND response, cannot skip",
opcode
)));
}
}
Ok(())
}
fn skip_entry4_list(buf: &mut Bytes) -> Result<()> {
let mut count = 0usize;
loop {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("entry4 list truncated".to_string()));
}
let has_entry = buf.get_u32();
if has_entry == 0 {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("dirlist4 eof truncated".to_string()));
}
buf.advance(4); return Ok(());
}
count += 1;
if count > MAX_ENTRY4_PER_PAGE {
return Err(NfsError::Xdr(format!(
"entry4 list exceeds max {}",
MAX_ENTRY4_PER_PAGE
)));
}
if buf.remaining() < 8 {
return Err(NfsError::Xdr("entry4 cookie truncated".to_string()));
}
buf.advance(8); skip_var_bytes(buf)?; skip_fattr4(buf)?; }
}
fn skip_open_delegation(buf: &mut Bytes) -> Result<()> {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("open_delegation type truncated".to_string()));
}
let deleg_type = buf.get_u32();
match deleg_type {
0 => {} 1 => {
skip_stateid4(buf)?;
if buf.remaining() < 4 {
return Err(NfsError::Xdr("delegation recall truncated".to_string()));
}
buf.advance(4); let _ace = super::acl::decode_nfsace4(buf)?;
}
2 => {
skip_stateid4(buf)?;
if buf.remaining() < 4 {
return Err(NfsError::Xdr("delegation recall truncated".to_string()));
}
buf.advance(4); skip_space_limit(buf)?;
let _ace = super::acl::decode_nfsace4(buf)?;
}
3 => {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("why_no_deleg truncated".to_string()));
}
let why = buf.get_u32();
match why {
1 | 2 => {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("why_no_deleg bool truncated".to_string()));
}
buf.advance(4);
}
_ => {} }
}
_ => {
return Err(NfsError::Xdr(format!(
"unknown delegation type {}",
deleg_type
)));
}
}
Ok(())
}
fn skip_space_limit(buf: &mut Bytes) -> Result<()> {
if buf.remaining() < 4 {
return Err(NfsError::Xdr("space_limit type truncated".to_string()));
}
let limit_by = buf.get_u32();
match limit_by {
1 => {
if buf.remaining() < 8 {
return Err(NfsError::Xdr("space_limit size truncated".to_string()));
}
buf.advance(8);
}
2 => {
if buf.remaining() < 8 {
return Err(NfsError::Xdr("space_limit blocks truncated".to_string()));
}
buf.advance(8);
}
_ => return Err(NfsError::Xdr(format!("unknown limit_by {}", limit_by))),
}
Ok(())
}
fn skip_exchange_id_result(buf: &mut Bytes) -> Result<()> {
if buf.remaining() < 16 {
return Err(NfsError::Xdr("EXCHANGE_ID result truncated".to_string()));
}
buf.advance(16);
if buf.remaining() < 4 {
return Err(NfsError::Xdr("state_protect4_r type truncated".to_string()));
}
let sp = buf.get_u32();
match sp {
0 => {} 1 => {
skip_bitmap(buf)?;
skip_bitmap(buf)?;
}
2 => {
skip_bitmap(buf)?; skip_bitmap(buf)?; if buf.remaining() < 16 {
return Err(NfsError::Xdr("SP4_SSV fields truncated".to_string()));
}
buf.advance(16); if buf.remaining() < 4 {
return Err(NfsError::Xdr(
"SP4_SSV gss_handles len truncated".to_string(),
));
}
let nh = buf.get_u32() as usize;
if nh > 64 {
return Err(NfsError::Xdr(format!(
"SP4_SSV has {} gss_handles, max 64",
nh
)));
}
for _ in 0..nh {
skip_var_bytes(buf)?;
}
}
_ => {
return Err(NfsError::Xdr(format!(
"unsupported state_protect type {}",
sp
)));
}
}
if buf.remaining() < 8 {
return Err(NfsError::Xdr("server_owner minor_id truncated".to_string()));
}
buf.advance(8);
skip_var_bytes(buf)?;
skip_var_bytes(buf)?;
if buf.remaining() < 4 {
return Err(NfsError::Xdr("impl_id array len truncated".to_string()));
}
let n = buf.get_u32() as usize;
if n > 1 {
return Err(NfsError::Xdr(format!(
"nfs_impl_id4 has {} elements, max 1",
n
)));
}
for _ in 0..n {
skip_var_bytes(buf)?; skip_var_bytes(buf)?; if buf.remaining() < 12 {
return Err(NfsError::Xdr("impl_id time truncated".to_string()));
}
buf.advance(12);
}
Ok(())
}
fn skip_create_session_result(buf: &mut Bytes) -> Result<()> {
if buf.remaining() < 24 {
return Err(NfsError::Xdr("CREATE_SESSION result truncated".to_string()));
}
buf.advance(24);
for _ in 0..2 {
if buf.remaining() < 24 {
return Err(NfsError::Xdr("channel_attrs truncated".to_string()));
}
buf.advance(24); if buf.remaining() < 4 {
return Err(NfsError::Xdr("ca_rdma_ird length truncated".to_string()));
}
let n = buf.get_u32() as usize;
let bytes_needed = n
.checked_mul(4)
.ok_or_else(|| NfsError::Xdr("ca_rdma_ird count overflow".to_string()))?;
if buf.remaining() < bytes_needed {
return Err(NfsError::Xdr("ca_rdma_ird data truncated".to_string()));
}
buf.advance(bytes_needed);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn xdr_u32_encode() {
let mut buf = Vec::new();
xdr_u32(&mut buf, 0x12345678);
assert_eq!(buf, vec![0x12, 0x34, 0x56, 0x78]);
}
#[test]
fn xdr_u64_encode() {
let mut buf = Vec::new();
xdr_u64(&mut buf, 0x0102030405060708);
assert_eq!(buf, vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
}
#[test]
fn xdr_bool_encode() {
let mut buf = Vec::new();
xdr_bool(&mut buf, true);
xdr_bool(&mut buf, false);
assert_eq!(buf, vec![0, 0, 0, 1, 0, 0, 0, 0]);
}
#[test]
fn xdr_var_bytes_with_padding() {
let mut buf = Vec::new();
xdr_var_bytes(&mut buf, &[0xAA, 0xBB, 0xCC]); assert_eq!(buf.len(), 4 + 4); assert_eq!(&buf[0..4], &3u32.to_be_bytes());
assert_eq!(&buf[4..7], &[0xAA, 0xBB, 0xCC]);
assert_eq!(buf[7], 0); }
#[test]
fn xdr_var_bytes_aligned() {
let mut buf = Vec::new();
xdr_var_bytes(&mut buf, &[1, 2, 3, 4]); assert_eq!(buf.len(), 8); }
#[test]
fn xdr_var_bytes_empty() {
let mut buf = Vec::new();
xdr_var_bytes(&mut buf, &[]);
assert_eq!(buf, vec![0, 0, 0, 0]); }
#[test]
fn xdr_string_encode() {
let mut buf = Vec::new();
xdr_string(&mut buf, "hi");
assert_eq!(buf.len(), 8); assert_eq!(&buf[0..4], &2u32.to_be_bytes());
assert_eq!(&buf[4..6], b"hi");
}
#[test]
fn xdr_bitmap_encode() {
let mut buf = Vec::new();
xdr_bitmap(&mut buf, &[0x1234, 0x5678]);
assert_eq!(buf.len(), 12); assert_eq!(&buf[0..4], &2u32.to_be_bytes());
assert_eq!(&buf[4..8], &0x1234u32.to_be_bytes());
assert_eq!(&buf[8..12], &0x5678u32.to_be_bytes());
}
#[test]
fn compound_builder_empty() {
let builder = CompoundBuilder::new("empty");
assert_eq!(builder.op_count(), 0);
let mut buf = Vec::new();
builder.encode_body(&mut buf);
assert_eq!(&buf[0..4], &5u32.to_be_bytes()); assert_eq!(&buf[4..9], b"empty");
let opcount_offset = 12 + 4; assert_eq!(
&buf[opcount_offset..opcount_offset + 4],
&0u32.to_be_bytes()
);
}
#[test]
fn compound_builder_putfh_encode() {
let fh = vec![0xDE, 0xAD, 0xBE, 0xEF];
let builder = CompoundBuilder::new("t").putfh(&fh);
assert_eq!(builder.op_count(), 1);
let mut buf = Vec::new();
builder.encode_body(&mut buf);
assert_eq!(&buf[16..20], &22u32.to_be_bytes()); assert_eq!(&buf[20..24], &4u32.to_be_bytes()); assert_eq!(&buf[24..28], &[0xDE, 0xAD, 0xBE, 0xEF]);
}
#[test]
fn compound_builder_lookup_encode() {
let builder = CompoundBuilder::new("t").lookup("test.txt");
let mut buf = Vec::new();
builder.encode_body(&mut buf);
assert_eq!(&buf[16..20], &15u32.to_be_bytes()); assert_eq!(&buf[20..24], &8u32.to_be_bytes()); assert_eq!(&buf[24..32], b"test.txt");
}
#[test]
fn compound_builder_read_encode() {
let stateid = [0u8; 16];
let builder = CompoundBuilder::new("t").read(&stateid, 1024, 4096);
let mut buf = Vec::new();
builder.encode_body(&mut buf);
assert_eq!(&buf[16..20], &25u32.to_be_bytes()); assert_eq!(&buf[36..44], &1024u64.to_be_bytes()); assert_eq!(&buf[44..48], &4096u32.to_be_bytes()); }
#[test]
fn compound_builder_chained_ops() {
let builder = CompoundBuilder::new("c")
.putrootfh()
.lookup("dir1")
.lookup("dir2")
.lookup("file")
.getfh()
.getattr(&[0, 0]);
assert_eq!(builder.op_count(), 6);
}
#[test]
fn compound_builder_write_encode() {
let stateid = [0xABu8; 16];
let data = [1u8, 2, 3, 4, 5];
let builder = CompoundBuilder::new("t").write(&stateid, 0, 2, &data);
let mut buf = Vec::new();
builder.encode_body(&mut buf);
assert_eq!(&buf[16..20], &38u32.to_be_bytes()); }
#[test]
fn compound_builder_remove_encode() {
let builder = CompoundBuilder::new("t").remove("oldfile");
let mut buf = Vec::new();
builder.encode_body(&mut buf);
assert_eq!(&buf[16..20], &28u32.to_be_bytes()); }
#[test]
fn compound_builder_rename_encode() {
let builder = CompoundBuilder::new("t").rename("old", "new");
assert_eq!(builder.op_count(), 1);
}
#[test]
fn compound_builder_create_symlink_encode() {
let builder = CompoundBuilder::new("t").create_symlink("link", "/target/path", &[], &[]);
assert_eq!(builder.op_count(), 1);
let mut buf = Vec::new();
builder.encode_body(&mut buf);
assert_eq!(&buf[16..20], &6u32.to_be_bytes()); assert_eq!(&buf[20..24], &5u32.to_be_bytes());
}
#[test]
fn compound_response_decode_simple_ok() {
let mut buf = Vec::new();
xdr_u32(&mut buf, 0); xdr_string(&mut buf, ""); xdr_u32(&mut buf, 1); xdr_u32(&mut buf, OpNum::PutRootFh as u32); xdr_u32(&mut buf, 0);
let resp = CompoundResponse::decode(Bytes::from(buf)).unwrap();
assert!(matches!(resp.status, nfsstat4::NFS4_OK));
assert_eq!(resp.results.len(), 1);
assert_eq!(resp.results[0].opcode, OpNum::PutRootFh as u32);
}
#[test]
fn compound_response_decode_with_sequence() {
let mut buf = Vec::new();
xdr_u32(&mut buf, 0); xdr_string(&mut buf, "test");
xdr_u32(&mut buf, 2);
xdr_u32(&mut buf, OpNum::Sequence as u32);
xdr_u32(&mut buf, 0); buf.extend_from_slice(&[0u8; 16]); xdr_u32(&mut buf, 1); xdr_u32(&mut buf, 0); xdr_u32(&mut buf, 3); xdr_u32(&mut buf, 3); xdr_u32(&mut buf, 0);
xdr_u32(&mut buf, OpNum::PutRootFh as u32);
xdr_u32(&mut buf, 0);
let resp = CompoundResponse::decode(Bytes::from(buf)).unwrap();
assert_eq!(resp.tag, "test");
assert_eq!(resp.results.len(), 2);
resp.op_ok(0).unwrap(); resp.op_ok(1).unwrap(); }
#[test]
fn compound_response_decode_error_status() {
let mut buf = Vec::new();
xdr_u32(&mut buf, 2); xdr_string(&mut buf, "");
xdr_u32(&mut buf, 0);
let resp = CompoundResponse::decode(Bytes::from(buf)).unwrap();
assert!(matches!(resp.status, nfsstat4::NFS4ERR_NOENT));
assert!(resp.check_status().is_err());
}
#[test]
fn compound_response_op_ok_out_of_bounds() {
let mut buf = Vec::new();
xdr_u32(&mut buf, 0);
xdr_string(&mut buf, "");
xdr_u32(&mut buf, 0);
let resp = CompoundResponse::decode(Bytes::from(buf)).unwrap();
assert!(resp.op_ok(0).is_err()); }
#[test]
fn compound_response_decode_getfh_ok() {
let mut buf = Vec::new();
xdr_u32(&mut buf, 0); xdr_string(&mut buf, "");
xdr_u32(&mut buf, 1); xdr_u32(&mut buf, OpNum::GetFh as u32);
xdr_u32(&mut buf, 0); xdr_var_bytes(&mut buf, &[0xDE, 0xAD]);
let resp = CompoundResponse::decode(Bytes::from(buf)).unwrap();
let op = resp.op_ok(0).unwrap();
let mut data = op.data.clone();
let len = data.get_u32() as usize;
assert_eq!(len, 2);
}
#[test]
fn compound_response_decode_truncated() {
let buf = vec![0u8; 2]; assert!(CompoundResponse::decode(Bytes::from(buf)).is_err());
}
#[test]
fn compound_builder_basic_encode() {
let builder = CompoundBuilder::new("test")
.putrootfh()
.lookup("mydir")
.getfh()
.getattr(&[0x0018_00bb, 0x0030_0b73]);
assert_eq!(builder.op_count(), 4);
let mut buf = Vec::new();
builder.encode_body(&mut buf);
assert_eq!(&buf[0..4], &4u32.to_be_bytes()); assert_eq!(&buf[4..8], b"test");
assert_eq!(&buf[8..12], &1u32.to_be_bytes());
assert_eq!(&buf[12..16], &4u32.to_be_bytes());
assert_eq!(&buf[16..20], &24u32.to_be_bytes());
}
#[test]
fn compound_builder_sequence_encode() {
let session_id = [1u8; 16];
let builder = CompoundBuilder::new("seq").sequence(&session_id, 42, 0, 0, false);
let mut buf = Vec::new();
builder.encode_body(&mut buf);
let op_start = 16; assert_eq!(
&buf[op_start..op_start + 4],
&(OpNum::Sequence as u32).to_be_bytes()
);
assert_eq!(&buf[op_start + 4..op_start + 20], &session_id);
assert_eq!(&buf[op_start + 20..op_start + 24], &42u32.to_be_bytes());
}
}