Skip to content

Commit 0570a39

Browse files
committed
test: 💍 add TCP demo server
1 parent 06d2a98 commit 0570a39

File tree

3 files changed

+307
-0
lines changed

3 files changed

+307
-0
lines changed

src/nfs/v3/__demos__/README.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# NFSv3 TCP Server Demo
2+
3+
This demo shows how to create a simple NFSv3 server that listens on a TCP socket and decodes incoming NFSv3 packets.
4+
5+
## What it does
6+
7+
1. Starts a TCP server on `127.0.0.1:2049` (default NFS port)
8+
2. Accepts incoming connections
9+
3. Receives TCP data and prints it in hexadecimal format
10+
4. Decodes RPC record marking (RM) frames
11+
5. Decodes RPC call messages
12+
6. Decodes NFSv3 procedure calls
13+
7. Pretty-prints all decoded information to the console
14+
15+
## Running the demo
16+
17+
```bash
18+
# Build the project first
19+
npm run build
20+
21+
# Run the demo
22+
node lib/nfs/v3/__demos__/tcp-server.js
23+
```
24+
25+
Or run directly with ts-node:
26+
27+
```bash
28+
npx ts-node src/nfs/v3/__demos__/tcp-server.ts
29+
```
30+
31+
## Testing the server
32+
33+
You can test the server using various methods:
34+
35+
### Using the included test client
36+
37+
First, start the server in one terminal:
38+
39+
```bash
40+
npx ts-node src/nfs/v3/__demos__/tcp-server.ts
41+
```
42+
43+
Then, in another terminal, run the test client:
44+
45+
```bash
46+
npx ts-node src/nfs/v3/__demos__/tcp-client.ts
47+
```
48+
49+
The test client will send a GETATTR request to the server, and you'll see the decoded output in the server terminal.
50+
51+
### Using a real NFS client
52+
53+
```bash
54+
# Mount the NFS server (will likely fail since we only decode, not respond)
55+
mount -t nfs -o vers=3,tcp 127.0.0.1:/ /mnt/test
56+
```
57+
58+
### Using netcat to send raw data
59+
60+
```bash
61+
# Send raw bytes to test the decoder
62+
echo -n "80000028000000010000000200000003000000010000000000000000000000000000000000000008010203040506" | xxd -r -p | nc 127.0.0.1 2049
63+
```
64+
65+
## Output Example
66+
67+
When a client connects and sends data, you'll see output like:
68+
69+
```
70+
13:53 $ npx ts-node src/nfs/v3/__demos__/tcp-server.ts
71+
NFSv3 TCP Server listening on 127.0.0.1:2049
72+
Waiting for connections...
73+
74+
[2025-10-08T11:53:14.082Z] Client connected from 127.0.0.1:59751
75+
76+
================================================================================
77+
[2025-10-08T11:53:14.084Z] Received 56 bytes
78+
HEX: 80000034000030390000000000000002000186a3000000030000000100000000000000000000000000000000000000080102030405060708
79+
--------------------------------------------------------------------------------
80+
81+
RPC Record (52 bytes):
82+
HEX: 000030390000000000000002000186a3000000030000000100000000000000000000000000000000000000080102030405060708
83+
84+
RPC Message:
85+
RpcCallMessage {
86+
xid: 12345,
87+
rpcvers: 2,
88+
prog: 100003,
89+
vers: 3,
90+
proc: 1,
91+
cred: RpcOpaqueAuth {
92+
flavor: 0,
93+
body: Reader { uint8: Uint8Array(0) [], view: [DataView], x: 0, end: 0 }
94+
},
95+
verf: RpcOpaqueAuth {
96+
flavor: 0,
97+
body: Reader { uint8: Uint8Array(0) [], view: [DataView], x: 0, end: 0 }
98+
},
99+
params: Reader {
100+
uint8: Uint8Array(16384) [
101+
128, 0, 0, 52, 0, 0, 48, 57, 0, 0, 0, 0,
102+
0, 0, 0, 2, 0, 1, 134, 163, 0, 0, 0, 3,
103+
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
104+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8,
105+
1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0,
106+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
107+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
108+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
109+
0, 0, 0, 0,
110+
... 16284 more items
111+
],
112+
view: DataView {
113+
byteLength: 16384,
114+
byteOffset: 0,
115+
buffer: [ArrayBuffer]
116+
},
117+
x: 44,
118+
end: 56
119+
}
120+
}
121+
122+
NFS Procedure: GETATTR
123+
124+
NFS Message:
125+
Nfsv3GetattrRequest {
126+
object: Nfsv3Fh {
127+
data: Reader { uint8: [Uint8Array], view: [DataView], x: 0, end: 8 }
128+
}
129+
}
130+
================================================================================
131+
132+
[2025-10-08T11:53:14.183Z] Client disconnected
133+
```
134+
135+
## Stopping the server
136+
137+
Press `Ctrl+C` to gracefully shut down the server.

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as net from 'net';
2+
import {Reader} from '@jsonjoy.com/buffers/lib/Reader';
3+
import {FullNfsv3Encoder} from '../FullNfsv3Encoder';
4+
import {Nfsv3GetattrRequest} from '../messages';
5+
import {Nfsv3Fh} from '../structs';
6+
import {Nfsv3Proc} from '../constants';
7+
8+
const PORT = 2049;
9+
const HOST = '127.0.0.1';
10+
11+
const createTestRequest = (): Nfsv3GetattrRequest => {
12+
const fhData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
13+
return new Nfsv3GetattrRequest(new Nfsv3Fh(new Reader(fhData)));
14+
};
15+
16+
const createTestCred = () => {
17+
return {
18+
flavor: 0,
19+
body: new Reader(new Uint8Array()),
20+
};
21+
};
22+
23+
const createTestVerf = () => {
24+
return {
25+
flavor: 0,
26+
body: new Reader(new Uint8Array()),
27+
};
28+
};
29+
30+
console.log('Connecting to NFSv3 server...');
31+
32+
const client = net.connect({port: PORT, host: HOST}, () => {
33+
console.log(`Connected to ${HOST}:${PORT}`);
34+
console.log('Sending GETATTR request...\n');
35+
const encoder = new FullNfsv3Encoder();
36+
const request = createTestRequest();
37+
const xid = 12345;
38+
const proc = Nfsv3Proc.GETATTR;
39+
const cred = createTestCred();
40+
const verf = createTestVerf();
41+
const encoded = encoder.encodeCall(xid, proc, cred, verf, request);
42+
console.log(`Sending ${encoded.length} bytes`);
43+
console.log('HEX:', Array.from(encoded).map(b => b.toString(16).padStart(2, '0')).join(' '));
44+
client.write(encoded);
45+
setTimeout(() => {
46+
console.log('\nClosing connection...');
47+
client.end();
48+
}, 100);
49+
});
50+
51+
client.on('data', (data) => {
52+
console.log('Received response:', data.length, 'bytes');
53+
});
54+
55+
client.on('end', () => {
56+
console.log('Connection closed');
57+
process.exit(0);
58+
});
59+
60+
client.on('error', (err) => {
61+
console.error('Connection error:', err.message);
62+
process.exit(1);
63+
});

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

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import * as net from 'net';
2+
import {RmRecordDecoder} from '../../../rm';
3+
import {RpcMessageDecoder, RpcCallMessage} from '../../../rpc';
4+
import {Nfsv3Decoder} from '../Nfsv3Decoder';
5+
6+
const PORT = 2049;
7+
const HOST = '127.0.0.1';
8+
9+
const toHex = (buffer: Uint8Array | Buffer): string => {
10+
return Array.from(buffer)
11+
.map(byte => byte.toString(16).padStart(2, '0'))
12+
.join('');
13+
};
14+
15+
const getProcName = (proc: number): string => {
16+
const names: Record<number, string> = {
17+
0: 'NULL',
18+
1: 'GETATTR',
19+
2: 'SETATTR',
20+
3: 'LOOKUP',
21+
4: 'ACCESS',
22+
5: 'READLINK',
23+
6: 'READ',
24+
7: 'WRITE',
25+
8: 'CREATE',
26+
9: 'MKDIR',
27+
10: 'SYMLINK',
28+
11: 'MKNOD',
29+
12: 'REMOVE',
30+
13: 'RMDIR',
31+
14: 'RENAME',
32+
15: 'LINK',
33+
16: 'READDIR',
34+
17: 'READDIRPLUS',
35+
18: 'FSSTAT',
36+
19: 'FSINFO',
37+
20: 'PATHCONF',
38+
21: 'COMMIT',
39+
};
40+
return names[proc] || `UNKNOWN(${proc})`;
41+
};
42+
43+
const server = net.createServer((socket) => {
44+
console.log(`[${new Date().toISOString()}] Client connected from ${socket.remoteAddress}:${socket.remotePort}`);
45+
const rmDecoder = new RmRecordDecoder();
46+
const rpcDecoder = new RpcMessageDecoder();
47+
const nfsDecoder = new Nfsv3Decoder();
48+
socket.on('data', (data) => {
49+
console.log('\n' + '='.repeat(80));
50+
console.log(`[${new Date().toISOString()}] Received ${data.length} bytes`);
51+
console.log('HEX:', toHex(data));
52+
console.log('-'.repeat(80));
53+
const uint8Data = new Uint8Array(data);
54+
rmDecoder.push(uint8Data);
55+
let record = rmDecoder.readRecord();
56+
while (record) {
57+
console.log(`\nRPC Record (${record.size()} bytes):`);
58+
console.log('HEX:', toHex(record.subarray()));
59+
const rpcMessage = rpcDecoder.decodeMessage(record);
60+
if (rpcMessage) {
61+
console.log('\nRPC Message:');
62+
console.log(rpcMessage);
63+
if (rpcMessage instanceof RpcCallMessage) {
64+
const proc = rpcMessage.proc;
65+
console.log(`\nNFS Procedure: ${getProcName(proc)}`);
66+
if (rpcMessage.params) {
67+
const nfsMessage = nfsDecoder.decodeMessage(rpcMessage.params, proc, true);
68+
if (nfsMessage) {
69+
console.log('\nNFS Message:');
70+
console.log(nfsMessage);
71+
} else {
72+
console.log('Could not decode NFS message');
73+
}
74+
}
75+
}
76+
} else {
77+
console.log('Could not decode RPC message');
78+
}
79+
record = rmDecoder.readRecord();
80+
}
81+
console.log('='.repeat(80) + '\n');
82+
});
83+
socket.on('end', () => {
84+
console.log(`[${new Date().toISOString()}] Client disconnected`);
85+
});
86+
socket.on('error', (err) => {
87+
console.error(`[${new Date().toISOString()}] Socket error:`, err.message);
88+
});
89+
});
90+
91+
server.on('error', (err) => {
92+
console.error('Server error:', err.message);
93+
process.exit(1);
94+
});
95+
96+
server.listen(PORT, HOST, () => {
97+
console.log(`NFSv3 TCP Server listening on ${HOST}:${PORT}`);
98+
console.log('Waiting for connections...\n');
99+
});
100+
101+
process.on('SIGINT', () => {
102+
console.log('\nShutting down server...');
103+
server.close(() => {
104+
console.log('Server closed');
105+
process.exit(0);
106+
});
107+
});

0 commit comments

Comments
 (0)