UNPKG

@jsonjoy.com/json-pack

Version:

High-performance JSON serialization library

216 lines 8.4 kB
"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