Skip to content

Commit 6fbbdcf

Browse files
committed
feat: 🎸 add full NFS encoder
1 parent 37cd4cc commit 6fbbdcf

File tree

4 files changed

+479
-0
lines changed

4 files changed

+479
-0
lines changed

src/nfs/v3/FullNfsv3Encoder.ts

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

src/nfs/v3/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
## `FullNfsv3Encoder`
2+
3+
`FullNfsv3Encoder` encoder that combines all three protocol layers (RM, RPC, and NFS)
4+
into a single-pass encoding operation, eliminating intermediate data copying.
5+
6+
### Encoding NFS Requests (Call Messages)
7+
8+
```typescript
9+
import {FullNfsv3Encoder} from '@jsonjoy.com/json-pack/lib/nfs/v3';
10+
import {Reader} from '@jsonjoy.com/buffers/lib/Reader';
11+
import * as msg from '@jsonjoy.com/json-pack/lib/nfs/v3/messages';
12+
import * as structs from '@jsonjoy.com/json-pack/lib/nfs/v3/structs';
13+
14+
// Create the encoder
15+
const encoder = new FullNfsv3Encoder();
16+
17+
// Create NFS request
18+
const fhData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
19+
const request = new msg.Nfsv3GetattrRequest(
20+
new structs.Nfsv3Fh(new Reader(fhData))
21+
);
22+
23+
// Create RPC authentication
24+
const cred = {
25+
flavor: 0,
26+
body: new Reader(new Uint8Array()),
27+
};
28+
const verf = {
29+
flavor: 0,
30+
body: new Reader(new Uint8Array()),
31+
};
32+
33+
// Encode the complete NFS call (RM + RPC + NFS layers)
34+
const encoded = encoder.encodeCall(
35+
12345, // XID
36+
Nfsv3Proc.GETATTR, // Procedure
37+
cred, // Credentials
38+
verf, // Verifier
39+
request // NFS request
40+
);
41+
42+
// Send the encoded data over TCP
43+
socket.write(encoded);
44+
```
45+
46+
### Comparison with Separate Encoders
47+
48+
Traditional approach (3 copies):
49+
50+
```typescript
51+
// Step 1: Encode NFS layer
52+
const nfsEncoded = nfsEncoder.encodeMessage(request, proc, true);
53+
54+
// Step 2: Encode RPC layer (copies NFS data)
55+
const rpcEncoded = rpcEncoder.encodeCall(xid, prog, vers, proc, cred, verf, nfsEncoded);
56+
57+
// Step 3: Encode RM layer (copies RPC data)
58+
const rmEncoded = rmEncoder.encodeRecord(rpcEncoded);
59+
```
60+
61+
Optimized approach (zero copies):
62+
63+
```typescript
64+
// Single-pass encoding - writes all layers directly to output buffer
65+
const encoded = fullEncoder.encodeCall(xid, proc, cred, verf, request);
66+
```

0 commit comments

Comments
 (0)