Skip to content

Commit 811316b

Browse files
committed
feat: 🎸 add full NFSv4 encoder
1 parent 8d56a39 commit 811316b

File tree

5 files changed

+575
-1
lines changed

5 files changed

+575
-1
lines changed

src/nfs/v3/__demos__/tcp-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const HOST = '127.0.0.1';
1212

1313
const createTestRequest = (): Nfsv3GetattrRequest => {
1414
const fhData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
15-
return new Nfsv3GetattrRequest(new Nfsv3Fh(new Reader(fhData)));
15+
return new Nfsv3GetattrRequest(new Nfsv3Fh(fhData));
1616
};
1717

1818
const createTestCred = () => {

src/nfs/v4/FullNfsv4Encoder.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer';
2+
import {Nfsv4Encoder} from './Nfsv4Encoder';
3+
import {RpcMessageEncoder} from '../../rpc/RpcMessageEncoder';
4+
import {RmRecordEncoder} from '../../rm/RmRecordEncoder';
5+
import {Nfsv4Proc, Nfsv4Const} from './constants';
6+
import {RpcOpaqueAuth} from '../../rpc/messages';
7+
import {RpcAcceptStat} from '../../rpc/constants';
8+
import type * as msg from './messages';
9+
import type {IWriter, IWriterGrowable} from '@jsonjoy.com/util/lib/buffers';
10+
11+
const MAX_SINGLE_FRAME_SIZE = 0x7fffffff;
12+
const RM_HEADER_SIZE = 4;
13+
14+
export class FullNfsv4Encoder<W extends IWriter & IWriterGrowable = IWriter & IWriterGrowable> {
15+
protected readonly nfsEncoder: Nfsv4Encoder<W>;
16+
protected readonly rpcEncoder: RpcMessageEncoder<W>;
17+
protected readonly rmEncoder: RmRecordEncoder<W>;
18+
19+
constructor(
20+
public program: number = Nfsv4Const.PROGRAM,
21+
public readonly writer: W = new Writer() as any,
22+
) {
23+
this.nfsEncoder = new Nfsv4Encoder(writer);
24+
this.rpcEncoder = new RpcMessageEncoder(writer);
25+
this.rmEncoder = new RmRecordEncoder(writer);
26+
}
27+
28+
public encodeCall(
29+
xid: number,
30+
proc: Nfsv4Proc,
31+
cred: RpcOpaqueAuth,
32+
verf: RpcOpaqueAuth,
33+
request: msg.Nfsv4CompoundRequest,
34+
): Uint8Array {
35+
this.writeCall(xid, proc, cred, verf, request);
36+
return this.writer.flush();
37+
}
38+
39+
public writeCall(
40+
xid: number,
41+
proc: Nfsv4Proc,
42+
cred: RpcOpaqueAuth,
43+
verf: RpcOpaqueAuth,
44+
request: msg.Nfsv4CompoundRequest,
45+
): void {
46+
const writer = this.writer;
47+
const rmHeaderPosition = writer.x;
48+
writer.x += RM_HEADER_SIZE;
49+
this.rpcEncoder.writeCall(xid, Nfsv4Const.PROGRAM, Nfsv4Const.VERSION, proc, cred, verf);
50+
this.nfsEncoder.writeCompound(request, true);
51+
this.writeRmHeader(rmHeaderPosition, writer.x);
52+
}
53+
54+
public encodeAcceptedReply(
55+
xid: number,
56+
proc: Nfsv4Proc,
57+
verf: RpcOpaqueAuth,
58+
response: msg.Nfsv4CompoundResponse,
59+
): Uint8Array {
60+
this.writeAcceptedReply(xid, proc, verf, response);
61+
return this.writer.flush();
62+
}
63+
64+
public writeAcceptedReply(
65+
xid: number,
66+
proc: Nfsv4Proc,
67+
verf: RpcOpaqueAuth,
68+
response: msg.Nfsv4CompoundResponse,
69+
): void {
70+
const writer = this.writer;
71+
const rmHeaderPosition = writer.x;
72+
writer.x += RM_HEADER_SIZE;
73+
this.rpcEncoder.writeAcceptedReply(xid, verf, RpcAcceptStat.SUCCESS);
74+
this.nfsEncoder.writeCompound(response, false);
75+
this.writeRmHeader(rmHeaderPosition, writer.x);
76+
}
77+
78+
public encodeRejectedReply(
79+
xid: number,
80+
rejectStat: number,
81+
mismatchInfo?: {low: number; high: number},
82+
authStat?: number,
83+
): Uint8Array {
84+
this.writeRejectedReply(xid, rejectStat, mismatchInfo, authStat);
85+
return this.writer.flush();
86+
}
87+
88+
public writeRejectedReply(
89+
xid: number,
90+
rejectStat: number,
91+
mismatchInfo?: {low: number; high: number},
92+
authStat?: number,
93+
): void {
94+
const writer = this.writer;
95+
const rmHeaderPosition = writer.x;
96+
writer.x += RM_HEADER_SIZE;
97+
this.rpcEncoder.writeRejectedReply(xid, rejectStat, mismatchInfo, authStat);
98+
this.writeRmHeader(rmHeaderPosition, writer.x);
99+
}
100+
101+
private writeRmHeader(rmHeaderPosition: number, endPosition: number): void {
102+
const writer = this.writer;
103+
const rmEncoder = this.rmEncoder;
104+
const totalSize = endPosition - rmHeaderPosition - RM_HEADER_SIZE;
105+
if (totalSize <= MAX_SINGLE_FRAME_SIZE) {
106+
const currentX = writer.x;
107+
writer.x = rmHeaderPosition;
108+
rmEncoder.writeHdr(1, totalSize);
109+
writer.x = currentX;
110+
} else {
111+
const currentX = writer.x;
112+
writer.x = rmHeaderPosition;
113+
const data = writer.uint8.subarray(rmHeaderPosition + RM_HEADER_SIZE, currentX);
114+
writer.reset();
115+
rmEncoder.writeRecord(data);
116+
}
117+
}
118+
}

src/nfs/v4/README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,112 @@ NFSv4 is a distributed file system protocol that integrates:
1313
- Client caching and delegations
1414
- Internationalization support
1515

16+
## `FullNfsv4Encoder`
17+
18+
`FullNfsv4Encoder` is an optimized encoder that combines all three protocol layers (RM, RPC, and NFS)
19+
into a single-pass encoding operation, eliminating intermediate data copying.
20+
21+
### Encoding NFS Requests (Call Messages)
22+
23+
```typescript
24+
import {FullNfsv4Encoder} from '@jsonjoy.com/json-pack/lib/nfs/v4';
25+
import {Reader} from '@jsonjoy.com/buffers/lib/Reader';
26+
import * as msg from '@jsonjoy.com/json-pack/lib/nfs/v4/messages';
27+
import * as structs from '@jsonjoy.com/json-pack/lib/nfs/v4/structs';
28+
29+
// Create the encoder
30+
const encoder = new FullNfsv4Encoder();
31+
32+
// Create NFSv4 COMPOUND request
33+
const fhData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
34+
const putfh = new msg.Nfsv4PutfhRequest(new structs.Nfsv4Fh(fhData));
35+
const getattr = new msg.Nfsv4GetattrRequest(new structs.Nfsv4Bitmap([0, 1]));
36+
const request = new msg.Nfsv4CompoundRequest('getattr', 0, [putfh, getattr]);
37+
38+
// Create RPC authentication
39+
const cred = {
40+
flavor: 0,
41+
body: new Reader(new Uint8Array()),
42+
};
43+
const verf = {
44+
flavor: 0,
45+
body: new Reader(new Uint8Array()),
46+
};
47+
48+
// Encode the complete NFS call (RM + RPC + NFS layers)
49+
const encoded = encoder.encodeCall(
50+
12345, // XID
51+
Nfsv4Proc.COMPOUND, // Procedure
52+
cred, // Credentials
53+
verf, // Verifier
54+
request, // NFSv4 COMPOUND request
55+
);
56+
57+
// Send the encoded data over TCP
58+
socket.write(encoded);
59+
```
60+
61+
### NFSv4 COMPOUND Operations
62+
63+
NFSv4 uses a COMPOUND-based architecture where multiple operations are bundled into a single RPC call:
64+
65+
```typescript
66+
// Multi-operation COMPOUND request
67+
const putfh = new msg.Nfsv4PutfhRequest(new structs.Nfsv4Fh(fhData));
68+
const lookup = new msg.Nfsv4LookupRequest('file.txt');
69+
const getfh = new msg.Nfsv4GetfhRequest();
70+
const read = new msg.Nfsv4ReadRequest(stateid, BigInt(0), 4096);
71+
72+
const request = new msg.Nfsv4CompoundRequest('read-file', 0, [
73+
putfh,
74+
lookup,
75+
getfh,
76+
read,
77+
]);
78+
79+
const encoded = encoder.encodeCall(xid, Nfsv4Proc.COMPOUND, cred, verf, request);
80+
```
81+
82+
### Comparison with Separate Encoders
83+
84+
Traditional approach (3 copies):
85+
86+
```typescript
87+
// Step 1: Encode NFS layer
88+
const nfsEncoded = nfsEncoder.encodeCompound(request, true);
89+
90+
// Step 2: Encode RPC layer (copies NFS data)
91+
const rpcEncoded = rpcEncoder.encodeCall(xid, prog, vers, proc, cred, verf, nfsEncoded);
92+
93+
// Step 3: Encode RM layer (copies RPC data)
94+
const rmEncoded = rmEncoder.encodeRecord(rpcEncoded);
95+
```
96+
97+
Optimized approach (zero copies):
98+
99+
```typescript
100+
// Single-pass encoding - writes all layers directly to output buffer
101+
const encoded = fullEncoder.encodeCall(xid, proc, cred, verf, request);
102+
```
103+
104+
### Encoding Response Messages
105+
106+
```typescript
107+
// Create NFSv4 COMPOUND response
108+
const putfhRes = new msg.Nfsv4PutfhResponse(Nfsv4Stat.NFS4_OK);
109+
const getattrRes = new msg.Nfsv4GetattrResponse(
110+
Nfsv4Stat.NFS4_OK,
111+
new msg.Nfsv4GetattrResOk(fattr),
112+
);
113+
const response = new msg.Nfsv4CompoundResponse(Nfsv4Stat.NFS4_OK, 'getattr', [
114+
putfhRes,
115+
getattrRes,
116+
]);
117+
118+
// Encode the complete NFS reply (RM + RPC + NFS layers)
119+
const encoded = encoder.encodeAcceptedReply(xid, proc, verf, response);
120+
```
121+
16122
## References
17123

18124
- [RFC 7530](https://tools.ietf.org/html/rfc7530): NFSv4 Protocol

0 commit comments

Comments
 (0)