UNPKG

ilp-protocol-stream

Version:

Interledger Transport Protocol for sending multiple streams of money and data over ILP.

231 lines 9.67 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createServer = exports.Server = void 0; const events_1 = require("events"); const IlpPacket = __importStar(require("ilp-packet")); const ILDCP = __importStar(require("ilp-protocol-ildcp")); const ilp_logger_1 = __importDefault(require("ilp-logger")); const cryptoHelper = __importStar(require("./crypto")); const pool_1 = require("./pool"); const oer_utils_1 = require("oer-utils"); const DEFAULT_DISCONNECT_DELAY = 100; class Server extends events_1.EventEmitter { constructor(opts) { super(); this.pendingRequests = Promise.resolve(); this.serverSecret = opts.serverSecret || cryptoHelper.randomBytes(32); this.plugin = opts.plugin; this.log = (0, ilp_logger_1.default)('ilp-protocol-stream:Server'); this.connectionOpts = Object.assign({}, opts, { serverSecret: undefined, }); this.disconnectDelay = opts.disconnectDelay || DEFAULT_DISCONNECT_DELAY; } get connected() { return Boolean(this.pool); } async listen() { if (this.connected && this.plugin.isConnected()) { return; } this.plugin.registerDataHandler((data) => { this.emit('_incoming_prepare'); const request = this.handleData(data); this.pendingRequests = this.pendingRequests.then(() => request.finally()); return request; }); await this.plugin.connect(); const { clientAddress, assetCode, assetScale } = await ILDCP.fetch(this.plugin.sendData.bind(this.plugin)); this.pool = new pool_1.ServerConnectionPool(this.serverSecret, Object.assign(Object.assign({}, this.connectionOpts), { isServer: true, plugin: this.plugin, sourceAccount: clientAddress, assetCode, assetScale }), (connection) => { this.emit('connection', connection); }); } async close() { if (!this.pool) { return; } const serverAccount = this.pool.getServerAccount(); this.plugin.deregisterDataHandler(); this.plugin.registerDataHandler(async () => IlpPacket.serializeIlpReject({ code: IlpPacket.Errors.codes.T99_APPLICATION_ERROR, triggeredBy: serverAccount, message: 'Shutting down server', data: Buffer.alloc(0), })); await this.pendingRequests; await new Promise((r) => setTimeout(r, this.disconnectDelay)); await this.pool.close(); this.plugin.deregisterDataHandler(); await this.plugin.disconnect(); this.emit('_close'); this.pool = undefined; } async acceptConnection() { await this.listen(); return new Promise((resolve, reject) => { const done = (connection) => { this.removeListener('connection', done); this.removeListener('_close', done); if (connection) resolve(connection); else reject(new Error('server closed')); }; this.once('connection', done); this.once('_close', done); }); } generateAddressAndSecret(opts) { if (!this.pool) { throw new Error('Server must be connected to generate address and secret'); } let connectionTag = Buffer.alloc(0); let receiptNonce = Buffer.alloc(0); let receiptSecret = Buffer.alloc(0); let receiptsEnabled = false; if (opts) { if (typeof opts === 'object') { if (opts.connectionTag) { connectionTag = Buffer.from(opts.connectionTag, 'ascii'); } if (!opts.receiptNonce !== !opts.receiptSecret) { throw new Error('receiptNonce and receiptSecret must accompany each other'); } if (opts.receiptNonce) { if (opts.receiptNonce.length !== 16) { throw new Error('receiptNonce must be 16 bytes'); } receiptsEnabled = true; receiptNonce = opts.receiptNonce; } if (opts.receiptSecret) { if (opts.receiptSecret.length !== 32) { throw new Error('receiptSecret must be 32 bytes'); } receiptSecret = opts.receiptSecret; } } else { connectionTag = Buffer.from(opts, 'ascii'); } } const tokenNonce = cryptoHelper.generateTokenNonce(); const predictor = new oer_utils_1.Predictor(); predictor.writeOctetString(tokenNonce, cryptoHelper.TOKEN_NONCE_LENGTH); predictor.writeVarOctetString(connectionTag); predictor.writeVarOctetString(receiptNonce); predictor.writeVarOctetString(receiptSecret); const writer = new oer_utils_1.Writer(predictor.length); writer.writeOctetString(tokenNonce, cryptoHelper.TOKEN_NONCE_LENGTH); writer.writeVarOctetString(connectionTag); writer.writeVarOctetString(receiptNonce); writer.writeVarOctetString(receiptSecret); const token = cryptoHelper.encryptConnectionAddressToken(this.serverSecret, writer.getBuffer()); const sharedSecret = cryptoHelper.generateSharedSecretFromToken(this.serverSecret, token); return { destinationAccount: `${this.pool.getServerAccount()}.${base64url(token)}`, sharedSecret, receiptsEnabled, }; } get assetCode() { if (!this.pool) { throw new Error('Server must be connected to get asset code.'); } return this.pool.getAssetCode(); } get assetScale() { if (!this.pool) { throw new Error('Server must be connected to get asset scale.'); } return this.pool.getAssetScale(); } async handleData(data) { if (!this.pool) { throw new Error('Unexpected call to handleData - server is not connected'); } try { let prepare; try { prepare = IlpPacket.deserializeIlpPrepare(data); } catch (err) { this.log.error('got data that is not an ILP Prepare packet: %h', data); return IlpPacket.serializeIlpReject({ code: 'F00', message: `Expected an ILP Prepare packet (type 12), but got packet with type: ${data[0]}`, data: Buffer.alloc(0), triggeredBy: this.pool.getServerAccount(), }); } const localAddressParts = prepare.destination .replace(`${this.pool.getServerAccount()}.`, '') .split('.'); if (localAddressParts.length === 0 || !localAddressParts[0]) { this.log.error('destination in ILP Prepare packet does not have a Connection ID: %s', prepare.destination); throw new IlpPacket.Errors.UnreachableError(''); } const connectionId = localAddressParts[0]; const connection = await this.pool .getConnection(connectionId, prepare) .catch((_err) => { throw new IlpPacket.Errors.UnreachableError(''); }); const fulfill = await connection.handlePrepare(prepare); return IlpPacket.serializeIlpFulfill(fulfill); } catch (err) { const triggeredBy = this.pool.getServerAccount(); if (IlpPacket.isIlpError(err)) { return IlpPacket.errorToReject(triggeredBy, err); } else { this.log.error('error handling prepare:', err); return IlpPacket.serializeIlpReject({ code: 'F00', message: '', data: Buffer.alloc(0), triggeredBy, }); } } } } exports.Server = Server; async function createServer(opts) { const server = new Server(opts); await server.listen(); return server; } exports.createServer = createServer; function base64url(buffer) { return buffer.toString('base64').replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_'); } //# sourceMappingURL=server.js.map