@jsonjoy.com/json-pack
Version:
High-performance JSON serialization library
216 lines • 8.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Nfsv4TcpClient = void 0;
const tslib_1 = require("tslib");
const net = tslib_1.__importStar(require("node:net"));
const Nfsv4Decoder_1 = require("../Nfsv4Decoder");
const Nfsv4FullEncoder_1 = require("../Nfsv4FullEncoder");
const rm_1 = require("../../../rm");
const rpc_1 = require("../../../rpc");
const constants_1 = require("../constants");
const messages_1 = require("../messages");
class Nfsv4TcpClient {
static fromDuplex(duplex, opts = {}) {
const client = new Nfsv4TcpClient(opts);
client.setSocket(duplex);
return client;
}
constructor(opts = {}) {
this.socket = null;
this.connected = false;
this.connecting = false;
this.xid = 0;
this.pendingRequests = new Map();
this.host = opts.host || '127.0.0.1';
this.port = opts.port || 2049;
this.timeout = opts.timeout || 30000;
this.debug = !!opts.debug;
this.logger = opts.logger || console;
this.rmDecoder = new rm_1.RmRecordDecoder();
this.rpcDecoder = new rpc_1.RpcMessageDecoder();
this.nfsDecoder = new Nfsv4Decoder_1.Nfsv4Decoder();
this.nfsEncoder = new Nfsv4FullEncoder_1.Nfsv4FullEncoder();
}
nextXid() {
this.xid = (this.xid + 1) >>> 0;
if (this.xid === 0)
this.xid = 1;
return this.xid;
}
async connect() {
if (this.connected)
return;
if (this.connecting)
throw new Error('Connection already in progress');
return new Promise((resolve, reject) => {
this.connecting = true;
const onError = (err) => {
this.connecting = false;
this.connected = false;
if (this.debug)
this.logger.error('Socket error:', err);
reject(err);
};
const socket = net.connect({ host: this.host, port: this.port }, () => {
if (this.debug)
this.logger.log(`Connected to NFSv4 server at ${this.host}:${this.port}`);
socket.removeListener('error', onError);
resolve();
this.setSocket(socket);
});
socket.once('error', onError);
});
}
setSocket(socket) {
socket.on('data', this.onData.bind(this));
socket.on('close', this.onClose.bind(this));
socket.on('error', (err) => {
this.connecting = false;
this.connected = false;
if (this.debug)
this.logger.error('Socket error:', err);
});
this.connected = true;
this.connecting = false;
this.socket = socket;
}
onData(data) {
const { rmDecoder, rpcDecoder } = this;
rmDecoder.push(data);
let record = rmDecoder.readRecord();
while (record) {
if (record.size()) {
const rpcMessage = rpcDecoder.decodeMessage(record);
if (rpcMessage)
this.onRpcMessage(rpcMessage);
else if (this.debug)
this.logger.error('Failed to decode RPC message');
}
record = rmDecoder.readRecord();
}
}
onRpcMessage(msg) {
if (msg instanceof rpc_1.RpcAcceptedReplyMessage) {
const pending = this.pendingRequests.get(msg.xid);
if (!pending) {
if (this.debug)
this.logger.error(`No pending request for XID ${msg.xid}`);
return;
}
this.pendingRequests.delete(msg.xid);
if (pending.timeout)
clearTimeout(pending.timeout);
if (msg.stat !== 0) {
pending.reject(new Error(`RPC accepted reply error: stat=${msg.stat}`));
return;
}
if (!msg.results) {
// NULL procedure has no results, check if resolve expects no arguments
if (pending.resolve.length === 0) {
pending.resolve();
return;
}
pending.reject(new Error('No results in accepted reply'));
return;
}
const response = this.nfsDecoder.decodeCompoundResponse(msg.results);
if (!response) {
pending.reject(new Error('Failed to decode COMPOUND response'));
return;
}
pending.resolve(response);
}
else if (msg instanceof rpc_1.RpcRejectedReplyMessage) {
const pending = this.pendingRequests.get(msg.xid);
if (!pending) {
if (this.debug)
this.logger.error(`No pending request for XID ${msg.xid}`);
return;
}
this.pendingRequests.delete(msg.xid);
if (pending.timeout)
clearTimeout(pending.timeout);
pending.reject(new Error(`RPC rejected reply: stat=${msg.stat}`));
}
else {
if (this.debug)
this.logger.error('Unexpected RPC message type:', msg);
}
}
onClose() {
this.connected = false;
this.connecting = false;
if (this.debug)
this.logger.log('Connection closed');
const error = new Error('Connection closed');
this.pendingRequests.forEach((pending, xid) => {
if (pending.timeout)
clearTimeout(pending.timeout);
pending.reject(error);
});
this.pendingRequests.clear();
}
async compound(requestOrOps, tag = '', minorversion = 0) {
if (!this.connected)
throw new Error('Not connected');
const request = requestOrOps instanceof messages_1.Nfsv4CompoundRequest
? requestOrOps
: new messages_1.Nfsv4CompoundRequest(tag, minorversion, requestOrOps);
const xid = this.nextXid();
const cred = new rpc_1.RpcOpaqueAuth(0, constants_1.EMPTY_READER);
const verf = new rpc_1.RpcOpaqueAuth(0, constants_1.EMPTY_READER);
const encoded = this.nfsEncoder.encodeCall(xid, 1 /* Nfsv4Proc.COMPOUND */, cred, verf, request);
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.pendingRequests.delete(xid);
reject(new Error(`Request timeout (XID ${xid})`));
}, this.timeout);
this.pendingRequests.set(xid, { resolve, reject, timeout });
this.socket.write(encoded);
if (this.debug) {
this.logger.log(`Sent COMPOUND request (XID ${xid}): ${request.argarray.length} operations`);
}
});
}
async null() {
if (!this.connected)
throw new Error('Not connected');
const xid = this.nextXid();
const cred = new rpc_1.RpcOpaqueAuth(0, constants_1.EMPTY_READER);
const verf = new rpc_1.RpcOpaqueAuth(0, constants_1.EMPTY_READER);
const writer = this.nfsEncoder.writer;
const rmEncoder = this.nfsEncoder.rmEncoder;
const rpcEncoder = this.nfsEncoder.rpcEncoder;
const state = rmEncoder.startRecord();
rpcEncoder.writeCall(xid, 100003 /* Nfsv4Const.PROGRAM */, 4 /* Nfsv4Const.VERSION */, 0 /* Nfsv4Proc.NULL */, cred, verf);
rmEncoder.endRecord(state);
const encoded = writer.flush();
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.pendingRequests.delete(xid);
reject(new Error(`NULL request timeout (XID ${xid})`));
}, this.timeout);
this.pendingRequests.set(xid, {
resolve: () => resolve(),
reject,
timeout,
});
this.socket.write(encoded);
if (this.debug)
this.logger.log(`Sent NULL request (XID ${xid})`);
});
}
close() {
if (this.socket) {
this.socket.end();
this.socket = null;
}
this.connected = false;
this.connecting = false;
}
isConnected() {
return this.connected;
}
}
exports.Nfsv4TcpClient = Nfsv4TcpClient;
//# sourceMappingURL=Nfsv4TcpClient.js.map