Skip to content

Commit 554069f

Browse files
committed
test: 💍 add decoder tests
1 parent db42dad commit 554069f

File tree

4 files changed

+436
-16
lines changed

4 files changed

+436
-16
lines changed

src/nfs/v4/Nfsv4Decoder.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ export class Nfsv4Decoder {
1212
this.xdr = new XdrDecoder(reader);
1313
}
1414

15-
public decodeCompound(reader: Reader, isRequest: boolean): msg.Nfsv4CompoundRequest | msg.Nfsv4CompoundResponse | undefined {
15+
public decodeCompound(
16+
reader: Reader,
17+
isRequest: boolean,
18+
): msg.Nfsv4CompoundRequest | msg.Nfsv4CompoundResponse | undefined {
1619
this.xdr.reader = reader;
1720
const startPos = reader.x;
1821
try {
@@ -31,25 +34,27 @@ export class Nfsv4Decoder {
3134
}
3235

3336
private decodeCompoundRequest(): msg.Nfsv4CompoundRequest {
34-
const tag = this.xdr.readString();
35-
const minorversion = this.xdr.readUnsignedInt();
37+
const xdr = this.xdr;
38+
const tag = xdr.readString();
39+
const minorversion = xdr.readUnsignedInt();
3640
const argarray: msg.Nfsv4Request[] = [];
37-
const count = this.xdr.readUnsignedInt();
41+
const count = xdr.readUnsignedInt();
3842
for (let i = 0; i < count; i++) {
39-
const op = this.xdr.readUnsignedInt() as Nfsv4Op;
43+
const op = xdr.readUnsignedInt() as Nfsv4Op;
4044
const request = this.decodeRequest(op);
4145
if (request) argarray.push(request);
4246
}
4347
return new msg.Nfsv4CompoundRequest(tag, minorversion, argarray);
4448
}
4549

4650
private decodeCompoundResponse(): msg.Nfsv4CompoundResponse {
47-
const status = this.xdr.readUnsignedInt();
48-
const tag = this.xdr.readString();
51+
const xdr = this.xdr;
52+
const status = xdr.readUnsignedInt();
53+
const tag = xdr.readString();
4954
const resarray: msg.Nfsv4Response[] = [];
50-
const count = this.xdr.readUnsignedInt();
55+
const count = xdr.readUnsignedInt();
5156
for (let i = 0; i < count; i++) {
52-
const op = this.xdr.readUnsignedInt() as Nfsv4Op;
57+
const op = xdr.readUnsignedInt() as Nfsv4Op;
5358
const response = this.decodeResponse(op);
5459
if (response) resarray.push(response);
5560
}
@@ -353,7 +358,10 @@ export class Nfsv4Decoder {
353358
for (let i = 0; i < aceCount; i++) {
354359
permissions.push(this.readAce());
355360
}
356-
return new structs.Nfsv4OpenDelegation(delegationType, new structs.Nfsv4OpenReadDelegation(stateid, recall, permissions));
361+
return new structs.Nfsv4OpenDelegation(
362+
delegationType,
363+
new structs.Nfsv4OpenReadDelegation(stateid, recall, permissions),
364+
);
357365
} else if (delegationType === Nfsv4DelegType.OPEN_DELEGATE_WRITE) {
358366
const stateid = this.readStateid();
359367
const recall = this.xdr.readBoolean();
@@ -363,7 +371,10 @@ export class Nfsv4Decoder {
363371
for (let i = 0; i < aceCount; i++) {
364372
permissions.push(this.readAce());
365373
}
366-
return new structs.Nfsv4OpenDelegation(delegationType, new structs.Nfsv4OpenWriteDelegation(stateid, recall, spaceLimit, permissions));
374+
return new structs.Nfsv4OpenDelegation(
375+
delegationType,
376+
new structs.Nfsv4OpenWriteDelegation(stateid, recall, spaceLimit, permissions),
377+
);
367378
}
368379
throw new Nfsv4DecodingError(`Unknown delegation type: ${delegationType}`);
369380
}
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
import {RmRecordDecoder} from '../../../rm';
2+
import {RpcCallMessage, RpcMessageDecoder, RpcAcceptedReplyMessage} from '../../../rpc';
3+
import {Nfsv4Decoder} from '../Nfsv4Decoder';
4+
import {Nfsv4Const, Nfsv4Op, Nfsv4Stat, Nfsv4Proc} from '../constants';
5+
import * as msg from '../messages';
6+
import {nfsv4} from './fixtures';
7+
8+
const rmDecoder = new RmRecordDecoder();
9+
const rpcDecoder = new RpcMessageDecoder();
10+
const nfsDecoder = new Nfsv4Decoder();
11+
12+
const decodeMessage = (hex: string) => {
13+
const buffer = Buffer.from(hex, 'hex');
14+
rmDecoder.push(new Uint8Array(buffer));
15+
const record = rmDecoder.readRecord();
16+
if (!record) return undefined;
17+
const rpcMessage = rpcDecoder.decodeMessage(record);
18+
return rpcMessage;
19+
};
20+
21+
const decodeCall = (hex: string): msg.Nfsv4CompoundRequest | undefined => {
22+
const rpcMessage = decodeMessage(hex);
23+
if (!(rpcMessage instanceof RpcCallMessage)) return undefined;
24+
return nfsDecoder.decodeCompound(rpcMessage.params!, true) as msg.Nfsv4CompoundRequest;
25+
};
26+
27+
const decodeReply = (hex: string): msg.Nfsv4CompoundResponse | undefined => {
28+
const rpcMessage = decodeMessage(hex);
29+
if (!(rpcMessage instanceof RpcAcceptedReplyMessage)) return undefined;
30+
return nfsDecoder.decodeCompound(rpcMessage.results!, false) as msg.Nfsv4CompoundResponse;
31+
};
32+
33+
describe('NFSv4 Decoder', () => {
34+
describe('NULL procedure', () => {
35+
test('decodes NULL call', () => {
36+
const rpcMessage = decodeMessage(nfsv4.NULL.Call[0]);
37+
expect(rpcMessage).toBeInstanceOf(RpcCallMessage);
38+
const rpcMessageStrict = rpcMessage as RpcCallMessage;
39+
expect(rpcMessageStrict).toBeDefined();
40+
expect(rpcMessageStrict.rpcvers).toBe(2);
41+
expect(rpcMessageStrict.prog).toBe(Nfsv4Const.PROGRAM);
42+
expect(rpcMessageStrict.vers).toBe(Nfsv4Const.VERSION);
43+
expect(rpcMessageStrict.proc).toBe(Nfsv4Proc.NULL);
44+
});
45+
46+
test('decodes NULL reply', () => {
47+
const rpcMessage = decodeMessage(nfsv4.NULL.Reply[0]);
48+
expect(rpcMessage).toBeInstanceOf(RpcAcceptedReplyMessage);
49+
const rpcMessageStrict = rpcMessage as RpcAcceptedReplyMessage;
50+
expect(rpcMessageStrict.stat).toBe(0);
51+
});
52+
});
53+
54+
describe('COMPOUND structure', () => {
55+
test('decodes COMPOUND call with tag', () => {
56+
const request = decodeCall(nfsv4.COMPOUND.GETATTR.Call[0]);
57+
if (!request) return;
58+
expect(request).toBeInstanceOf(msg.Nfsv4CompoundRequest);
59+
expect(request.tag).toBeDefined();
60+
expect(request.minorversion).toBe(0);
61+
expect(request.argarray.length).toBeGreaterThan(0);
62+
});
63+
64+
test('decodes COMPOUND reply with status', () => {
65+
const response = decodeReply(nfsv4.COMPOUND.GETATTR.Reply[0]);
66+
if (!response) return;
67+
expect(response).toBeInstanceOf(msg.Nfsv4CompoundResponse);
68+
expect(response.status).toBe(Nfsv4Stat.NFS4_OK);
69+
expect(response.tag).toBeDefined();
70+
expect(response.resarray.length).toBeGreaterThan(0);
71+
});
72+
});
73+
74+
describe('GETATTR operation', () => {
75+
test('decodes COMPOUND with GETATTR request', () => {
76+
const request = decodeCall(nfsv4.COMPOUND.GETATTR.Call[0]);
77+
if (!request) return;
78+
const getattrOp = request.argarray.find((op: msg.Nfsv4Request) => op instanceof msg.Nfsv4GetattrRequest);
79+
expect(getattrOp).toBeDefined();
80+
expect(getattrOp).toBeInstanceOf(msg.Nfsv4GetattrRequest);
81+
const getattrOpStrict = getattrOp as msg.Nfsv4GetattrRequest;
82+
expect(getattrOpStrict.attrRequest).toBeDefined();
83+
expect(Array.isArray(getattrOpStrict.attrRequest.mask)).toBe(true);
84+
expect(getattrOpStrict.attrRequest.mask.length).toBeGreaterThan(0);
85+
console.log(request.argarray[0]);
86+
});
87+
88+
test('decodes COMPOUND with GETATTR response', () => {
89+
const response = decodeReply(nfsv4.COMPOUND.GETATTR.Reply[0]);
90+
if (!response) return;
91+
const getattrRes = response.resarray.find((op: msg.Nfsv4Response) => op instanceof msg.Nfsv4GetattrResponse);
92+
expect(getattrRes).toBeDefined();
93+
expect(getattrRes).toBeInstanceOf(msg.Nfsv4GetattrResponse);
94+
const getattrResStrict = getattrRes as msg.Nfsv4GetattrResponse;
95+
expect(getattrResStrict.status).toBe(Nfsv4Stat.NFS4_OK);
96+
expect(getattrResStrict.resok).toBeDefined();
97+
});
98+
});
99+
100+
describe('LOOKUP operation', () => {
101+
test('decodes COMPOUND with LOOKUP request', () => {
102+
const request = decodeCall(nfsv4.COMPOUND.LOOKUP.Call[0]);
103+
if (!request) return;
104+
const lookupOp = request.argarray.find((op: msg.Nfsv4Request) => op instanceof msg.Nfsv4LookupRequest);
105+
expect(lookupOp).toBeDefined();
106+
expect(lookupOp).toBeInstanceOf(msg.Nfsv4LookupRequest);
107+
expect((lookupOp as msg.Nfsv4LookupRequest).objname).toBeDefined();
108+
expect((lookupOp as msg.Nfsv4LookupRequest).objname).toBe('nst');
109+
});
110+
111+
test('decodes COMPOUND with LOOKUP success response', () => {
112+
const response = decodeReply(nfsv4.COMPOUND.LOOKUP.Reply[0]);
113+
if (!response) return;
114+
const lookupRes = response.resarray.find((op: msg.Nfsv4Response) => op instanceof msg.Nfsv4LookupResponse);
115+
expect(lookupRes).toBeDefined();
116+
expect(lookupRes).toBeInstanceOf(msg.Nfsv4LookupResponse);
117+
const lookupResStrict = lookupRes as msg.Nfsv4LookupResponse;
118+
expect(lookupResStrict.status).toBe(Nfsv4Stat.NFS4_OK);
119+
});
120+
121+
test('decodes COMPOUND with LOOKUP error response', () => {
122+
const response = decodeReply(nfsv4.COMPOUND.LOOKUP_ERROR.Reply[0]);
123+
if (!response) return;
124+
const lookupRes = response.resarray.find((op: msg.Nfsv4Response) => op instanceof msg.Nfsv4LookupResponse);
125+
expect(lookupRes).toBeDefined();
126+
expect(lookupRes).toBeInstanceOf(msg.Nfsv4LookupResponse);
127+
const lookupResStrict = lookupRes as msg.Nfsv4LookupResponse;
128+
expect(lookupResStrict.status).toBe(Nfsv4Stat.NFS4ERR_NOENT);
129+
});
130+
});
131+
132+
describe('OPEN operation', () => {
133+
test('decodes COMPOUND with OPEN error', () => {
134+
const response = decodeReply(nfsv4.COMPOUND.OPEN_ERROR.Reply[0]);
135+
if (!response) return;
136+
const openRes = response.resarray.find((op: msg.Nfsv4Response) => op instanceof msg.Nfsv4OpenResponse);
137+
expect(openRes).toBeDefined();
138+
expect(openRes).toBeInstanceOf(msg.Nfsv4OpenResponse);
139+
const openResStrict = openRes as msg.Nfsv4OpenResponse;
140+
expect(openResStrict.status).toBe(Nfsv4Stat.NFS4ERR_NOENT);
141+
});
142+
});
143+
144+
describe('READDIR operation', () => {
145+
test('decodes COMPOUND with READDIR request', () => {
146+
const request = decodeCall(nfsv4.COMPOUND.READDIR.Call[0]);
147+
if (!request) return;
148+
const readdirOp = request.argarray.find((op: msg.Nfsv4Request) => op instanceof msg.Nfsv4ReaddirRequest);
149+
expect(readdirOp).toBeDefined();
150+
expect(readdirOp).toBeInstanceOf(msg.Nfsv4ReaddirRequest);
151+
const readdirOpStrict = readdirOp as msg.Nfsv4ReaddirRequest;
152+
expect(readdirOpStrict.cookie).toBeDefined();
153+
expect(readdirOpStrict.dircount).toBeDefined();
154+
expect(readdirOpStrict.maxcount).toBeDefined();
155+
});
156+
157+
test('decodes COMPOUND with READDIR response', () => {
158+
const response = decodeReply(nfsv4.COMPOUND.READDIR.Reply[0]);
159+
if (!response) return;
160+
const readdirRes = response.resarray.find((op: msg.Nfsv4Response) => op instanceof msg.Nfsv4ReaddirResponse);
161+
expect(readdirRes).toBeDefined();
162+
expect(readdirRes).toBeInstanceOf(msg.Nfsv4ReaddirResponse);
163+
if (readdirRes instanceof msg.Nfsv4ReaddirResponse && readdirRes.status === Nfsv4Stat.NFS4_OK) {
164+
expect(readdirRes.resok).toBeDefined();
165+
// Real-world data contains "testdir" entry
166+
}
167+
});
168+
});
169+
170+
describe('PUTFH operation', () => {
171+
test('decodes COMPOUND with PUTFH request', () => {
172+
const request = decodeCall(nfsv4.COMPOUND.GETATTR.Call[0]);
173+
if (!request) return;
174+
const putfhOp = request.argarray.find((op: msg.Nfsv4Request) => op instanceof msg.Nfsv4PutfhRequest);
175+
expect(putfhOp).toBeDefined();
176+
expect(putfhOp).toBeInstanceOf(msg.Nfsv4PutfhRequest);
177+
const putfhOpStrict = putfhOp as msg.Nfsv4PutfhRequest;
178+
expect(putfhOpStrict.object).toBeDefined();
179+
});
180+
181+
test('decodes COMPOUND with PUTFH response', () => {
182+
const response = decodeReply(nfsv4.COMPOUND.GETATTR.Reply[0]);
183+
if (!response) return;
184+
const putfhRes = response.resarray.find((op: msg.Nfsv4Response) => op instanceof msg.Nfsv4PutfhResponse);
185+
expect(putfhRes).toBeDefined();
186+
expect(putfhRes).toBeInstanceOf(msg.Nfsv4PutfhResponse);
187+
const putfhResStrict = putfhRes as msg.Nfsv4PutfhResponse;
188+
expect(putfhResStrict.status).toBe(Nfsv4Stat.NFS4_OK);
189+
});
190+
});
191+
192+
describe('ACCESS operation', () => {
193+
test('decodes COMPOUND with ACCESS request', () => {
194+
const request = decodeCall(nfsv4.COMPOUND.ACCESS.Call[0]);
195+
if (!request) return;
196+
const accessOp = request.argarray.find((op: msg.Nfsv4Request) => op instanceof msg.Nfsv4AccessRequest);
197+
expect(accessOp).toBeDefined();
198+
expect(accessOp).toBeInstanceOf(msg.Nfsv4AccessRequest);
199+
const accessOpStrict = accessOp as msg.Nfsv4AccessRequest;
200+
expect(accessOpStrict.access).toBeDefined();
201+
expect(accessOpStrict.access).toBe(0x1f);
202+
});
203+
204+
test('decodes COMPOUND with ACCESS response', () => {
205+
const response = decodeReply(nfsv4.COMPOUND.ACCESS.Reply[0]);
206+
if (!response) return;
207+
const accessRes = response.resarray.find((op: msg.Nfsv4Response) => op instanceof msg.Nfsv4AccessResponse);
208+
expect(accessRes).toBeDefined();
209+
expect(accessRes).toBeInstanceOf(msg.Nfsv4AccessResponse);
210+
if (accessRes instanceof msg.Nfsv4AccessResponse && accessRes.status === Nfsv4Stat.NFS4_OK) {
211+
expect(accessRes.resok).toBeDefined();
212+
}
213+
});
214+
});
215+
216+
describe('PUTROOTFH operation', () => {
217+
test('decodes COMPOUND with PUTROOTFH + GETATTR', () => {
218+
const request = decodeCall(nfsv4.COMPOUND.PUTROOTFH_GETATTR.Call[0]);
219+
if (!request) return;
220+
const putrootfhOp = request.argarray.find((op: msg.Nfsv4Request) => op instanceof msg.Nfsv4PutrootfhRequest);
221+
expect(putrootfhOp).toBeDefined();
222+
expect(putrootfhOp).toBeInstanceOf(msg.Nfsv4PutrootfhRequest);
223+
});
224+
225+
test('decodes PUTROOTFH response', () => {
226+
const response = decodeReply(nfsv4.COMPOUND.PUTROOTFH_GETATTR.Reply[0]);
227+
if (!response) return;
228+
const putrootfhRes = response.resarray.find((op: msg.Nfsv4Response) => op instanceof msg.Nfsv4PutrootfhResponse);
229+
expect(putrootfhRes).toBeDefined();
230+
expect(putrootfhRes).toBeInstanceOf(msg.Nfsv4PutrootfhResponse);
231+
const putrootfhResStrict = putrootfhRes as msg.Nfsv4PutrootfhResponse;
232+
expect(putrootfhResStrict.status).toBe(Nfsv4Stat.NFS4_OK);
233+
});
234+
});
235+
236+
describe('SETCLIENTID operation', () => {
237+
test('decodes SETCLIENTID request', () => {
238+
const request = decodeCall(nfsv4.SETCLIENTID.Call[0]);
239+
if (!request) return;
240+
const setclientidOp = request.argarray.find((op: msg.Nfsv4Request) => op instanceof msg.Nfsv4SetclientidRequest);
241+
expect(setclientidOp).toBeDefined();
242+
expect(setclientidOp).toBeInstanceOf(msg.Nfsv4SetclientidRequest);
243+
const setclientidOpStrict = setclientidOp as msg.Nfsv4SetclientidRequest;
244+
expect(setclientidOpStrict.client).toBeDefined();
245+
expect(setclientidOpStrict.callback).toBeDefined();
246+
});
247+
248+
test('decodes SETCLIENTID response', () => {
249+
const response = decodeReply(nfsv4.SETCLIENTID.Reply[0]);
250+
if (!response) return;
251+
const setclientidRes = response.resarray.find(
252+
(op: msg.Nfsv4Response) => op instanceof msg.Nfsv4SetclientidResponse,
253+
);
254+
expect(setclientidRes).toBeDefined();
255+
expect(setclientidRes).toBeInstanceOf(msg.Nfsv4SetclientidResponse);
256+
if (setclientidRes instanceof msg.Nfsv4SetclientidResponse && setclientidRes.status === Nfsv4Stat.NFS4_OK) {
257+
expect(setclientidRes.resok).toBeDefined();
258+
expect(setclientidRes.resok?.clientid).toBeDefined();
259+
}
260+
});
261+
});
262+
263+
describe('SETCLIENTID_CONFIRM operation', () => {
264+
test('decodes SETCLIENTID_CONFIRM request', () => {
265+
const request = decodeCall(nfsv4.SETCLIENTID_CONFIRM.Call[0]);
266+
if (!request) return;
267+
const confirmOp = request.argarray.find(
268+
(op: msg.Nfsv4Request) => op instanceof msg.Nfsv4SetclientidConfirmRequest,
269+
);
270+
expect(confirmOp).toBeDefined();
271+
expect(confirmOp).toBeInstanceOf(msg.Nfsv4SetclientidConfirmRequest);
272+
const confirmOpStrict = confirmOp as msg.Nfsv4SetclientidConfirmRequest;
273+
expect(confirmOpStrict.clientid).toBeDefined();
274+
expect(confirmOpStrict.setclientidConfirm).toBeDefined();
275+
});
276+
277+
test('decodes SETCLIENTID_CONFIRM response', () => {
278+
const response = decodeReply(nfsv4.SETCLIENTID_CONFIRM.Reply[0]);
279+
if (!response) return;
280+
const confirmRes = response.resarray.find(
281+
(op: msg.Nfsv4Response) => op instanceof msg.Nfsv4SetclientidConfirmResponse,
282+
);
283+
expect(confirmRes).toBeDefined();
284+
expect(confirmRes).toBeInstanceOf(msg.Nfsv4SetclientidConfirmResponse);
285+
const confirmResStrict = confirmRes as msg.Nfsv4SetclientidConfirmResponse;
286+
expect(confirmResStrict.status).toBe(Nfsv4Stat.NFS4_OK);
287+
});
288+
});
289+
});

0 commit comments

Comments
 (0)