Nfsv4TcpClient.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Nfsv4TcpClient = void 0;
  4. const tslib_1 = require("tslib");
  5. const net = tslib_1.__importStar(require("node:net"));
  6. const Nfsv4Decoder_1 = require("../Nfsv4Decoder");
  7. const Nfsv4FullEncoder_1 = require("../Nfsv4FullEncoder");
  8. const rm_1 = require("../../../rm");
  9. const rpc_1 = require("../../../rpc");
  10. const constants_1 = require("../constants");
  11. const messages_1 = require("../messages");
  12. class Nfsv4TcpClient {
  13. static fromDuplex(duplex, opts = {}) {
  14. const client = new Nfsv4TcpClient(opts);
  15. client.setSocket(duplex);
  16. return client;
  17. }
  18. constructor(opts = {}) {
  19. this.socket = null;
  20. this.connected = false;
  21. this.connecting = false;
  22. this.xid = 0;
  23. this.seqid = 0;
  24. this.pendingRequests = new Map();
  25. this.host = opts.host || '127.0.0.1';
  26. this.port = opts.port || 2049;
  27. this.timeout = opts.timeout || 30000;
  28. this.debug = !!opts.debug;
  29. this.logger = opts.logger || console;
  30. this.rmDecoder = new rm_1.RmRecordDecoder();
  31. this.rpcDecoder = new rpc_1.RpcMessageDecoder();
  32. this.nfsDecoder = new Nfsv4Decoder_1.Nfsv4Decoder();
  33. this.nfsEncoder = new Nfsv4FullEncoder_1.Nfsv4FullEncoder();
  34. }
  35. nextXid() {
  36. this.xid = (this.xid + 1) >>> 0;
  37. if (this.xid === 0)
  38. this.xid = 1;
  39. return this.xid;
  40. }
  41. async connect() {
  42. if (this.connected)
  43. return;
  44. if (this.connecting)
  45. throw new Error('Connection already in progress');
  46. return new Promise((resolve, reject) => {
  47. this.connecting = true;
  48. const onError = (err) => {
  49. this.connecting = false;
  50. this.connected = false;
  51. if (this.debug)
  52. this.logger.error('Socket error:', err);
  53. reject(err);
  54. };
  55. const socket = net.connect({ host: this.host, port: this.port }, () => {
  56. if (this.debug)
  57. this.logger.log(`Connected to NFSv4 server at ${this.host}:${this.port}`);
  58. socket.removeListener('error', onError);
  59. resolve();
  60. this.setSocket(socket);
  61. });
  62. socket.once('error', onError);
  63. });
  64. }
  65. setSocket(socket) {
  66. socket.on('data', this.onData.bind(this));
  67. socket.on('close', this.onClose.bind(this));
  68. socket.on('error', (err) => {
  69. this.connecting = false;
  70. this.connected = false;
  71. if (this.debug)
  72. this.logger.error('Socket error:', err);
  73. });
  74. this.connected = true;
  75. this.connecting = false;
  76. this.socket = socket;
  77. }
  78. onData(data) {
  79. const { rmDecoder, rpcDecoder } = this;
  80. rmDecoder.push(data);
  81. let record = rmDecoder.readRecord();
  82. while (record) {
  83. if (record.size()) {
  84. const rpcMessage = rpcDecoder.decodeMessage(record);
  85. if (rpcMessage)
  86. this.onRpcMessage(rpcMessage);
  87. else if (this.debug)
  88. this.logger.error('Failed to decode RPC message');
  89. }
  90. record = rmDecoder.readRecord();
  91. }
  92. }
  93. onRpcMessage(msg) {
  94. if (msg instanceof rpc_1.RpcAcceptedReplyMessage) {
  95. const pending = this.pendingRequests.get(msg.xid);
  96. if (!pending) {
  97. if (this.debug)
  98. this.logger.error(`No pending request for XID ${msg.xid}`);
  99. return;
  100. }
  101. this.pendingRequests.delete(msg.xid);
  102. if (pending.timeout)
  103. clearTimeout(pending.timeout);
  104. if (msg.stat !== 0) {
  105. pending.reject(new Error(`RPC accepted reply error: stat=${msg.stat}`));
  106. return;
  107. }
  108. if (!msg.results) {
  109. if (pending.resolve.length === 0) {
  110. pending.resolve();
  111. return;
  112. }
  113. pending.reject(new Error('No results in accepted reply'));
  114. return;
  115. }
  116. const response = this.nfsDecoder.decodeCompoundResponse(msg.results);
  117. if (!response) {
  118. pending.reject(new Error('Failed to decode COMPOUND response'));
  119. return;
  120. }
  121. pending.resolve(response);
  122. }
  123. else if (msg instanceof rpc_1.RpcRejectedReplyMessage) {
  124. const pending = this.pendingRequests.get(msg.xid);
  125. if (!pending) {
  126. if (this.debug)
  127. this.logger.error(`No pending request for XID ${msg.xid}`);
  128. return;
  129. }
  130. this.pendingRequests.delete(msg.xid);
  131. if (pending.timeout)
  132. clearTimeout(pending.timeout);
  133. pending.reject(new Error(`RPC rejected reply: stat=${msg.stat}`));
  134. }
  135. else {
  136. if (this.debug)
  137. this.logger.error('Unexpected RPC message type:', msg);
  138. }
  139. }
  140. onClose() {
  141. this.connected = false;
  142. this.connecting = false;
  143. if (this.debug)
  144. this.logger.log('Connection closed');
  145. const error = new Error('Connection closed');
  146. this.pendingRequests.forEach((pending, xid) => {
  147. if (pending.timeout)
  148. clearTimeout(pending.timeout);
  149. pending.reject(error);
  150. });
  151. this.pendingRequests.clear();
  152. }
  153. async compound(requestOrOps, tag = '', minorversion = 0) {
  154. if (!this.connected)
  155. throw new Error('Not connected');
  156. const request = requestOrOps instanceof messages_1.Nfsv4CompoundRequest
  157. ? requestOrOps
  158. : new messages_1.Nfsv4CompoundRequest(tag, minorversion, requestOrOps);
  159. const xid = this.nextXid();
  160. const cred = new rpc_1.RpcOpaqueAuth(0, constants_1.EMPTY_READER);
  161. const verf = new rpc_1.RpcOpaqueAuth(0, constants_1.EMPTY_READER);
  162. const encoded = this.nfsEncoder.encodeCall(xid, 1, cred, verf, request);
  163. return new Promise((resolve, reject) => {
  164. const timeout = setTimeout(() => {
  165. this.pendingRequests.delete(xid);
  166. reject(new Error(`Request timeout (XID ${xid})`));
  167. }, this.timeout);
  168. this.pendingRequests.set(xid, { resolve, reject, timeout });
  169. this.socket.write(encoded);
  170. if (this.debug) {
  171. this.logger.log(`Sent COMPOUND request (XID ${xid}): ${request.argarray.length} operations`);
  172. }
  173. });
  174. }
  175. async null() {
  176. if (!this.connected)
  177. throw new Error('Not connected');
  178. const xid = this.nextXid();
  179. const cred = new rpc_1.RpcOpaqueAuth(0, constants_1.EMPTY_READER);
  180. const verf = new rpc_1.RpcOpaqueAuth(0, constants_1.EMPTY_READER);
  181. const writer = this.nfsEncoder.writer;
  182. const rmEncoder = this.nfsEncoder.rmEncoder;
  183. const rpcEncoder = this.nfsEncoder.rpcEncoder;
  184. const state = rmEncoder.startRecord();
  185. rpcEncoder.writeCall(xid, 100003, 4, 0, cred, verf);
  186. rmEncoder.endRecord(state);
  187. const encoded = writer.flush();
  188. return new Promise((resolve, reject) => {
  189. const timeout = setTimeout(() => {
  190. this.pendingRequests.delete(xid);
  191. reject(new Error(`NULL request timeout (XID ${xid})`));
  192. }, this.timeout);
  193. this.pendingRequests.set(xid, {
  194. resolve: () => resolve(),
  195. reject,
  196. timeout,
  197. });
  198. this.socket.write(encoded);
  199. if (this.debug)
  200. this.logger.log(`Sent NULL request (XID ${xid})`);
  201. });
  202. }
  203. close() {
  204. if (this.socket) {
  205. this.socket.end();
  206. this.socket = null;
  207. }
  208. this.connected = false;
  209. this.connecting = false;
  210. }
  211. isConnected() {
  212. return this.connected;
  213. }
  214. }
  215. exports.Nfsv4TcpClient = Nfsv4TcpClient;
  216. //# sourceMappingURL=Nfsv4TcpClient.js.map