UNPKG

madeline-ton

Version:

Pure JS client-side implementation of the Telegram TON blockchain protocol

313 lines (255 loc) 10.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _stream = _interopRequireDefault(require("../TL/stream")); var _tools = require("../tools"); var _random = require("../crypto-sync/random"); var ADNL = /*#__PURE__*/ function () { function ADNL() { (0, _classCallCheck2["default"])(this, ADNL); (0, _defineProperty2["default"])(this, "allRead", 0); (0, _defineProperty2["default"])(this, "toRead", 4); (0, _defineProperty2["default"])(this, "buffers", []); (0, _defineProperty2["default"])(this, "offset", 0); (0, _defineProperty2["default"])(this, "bufferOffset", 0); (0, _defineProperty2["default"])(this, "cBufferIndex", 0); } (0, _createClass2["default"])(ADNL, [{ key: "connect", /** * * @param {Object} ctx Custom connection context */ value: function () { var _connect = (0, _asyncToGenerator2["default"])( /*#__PURE__*/ _regenerator["default"].mark(function _callee(ctx) { var _this$crypto, _this$crypto2, _this = this; return _regenerator["default"].wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: this.crypto = ctx.crypto; _context.next = 3; return (_this$crypto = this.crypto).getCtr.apply(_this$crypto, (0, _toConsumableArray2["default"])(ctx.encrypt)); case 3: this.encrypt = _context.sent; _context.next = 6; return (_this$crypto2 = this.crypto).getCtr.apply(_this$crypto2, (0, _toConsumableArray2["default"])(ctx.decrypt)); case 6: this.decrypt = _context.sent; this.previous = Promise.resolve(); _context.next = 10; return new Promise(function (resolve, reject) { _this.socket = new WebSocket(ctx.uri); _this.socket.binaryType = "arraybuffer"; _this.socket.onmessage = function (message) { message = message.data; var idx = _this.cBufferIndex++; _this.cBufferIndex %= 0xFFFFFFFF; var previous = _this.previous; _this.previous = _this.decrypt.process(new Uint8Array(message)).then(function (result) { _this.buffers[idx] = new Uint8Array(result); _this.allRead += result.byteLength; return previous; }).then(function () { //console.log("Got payload with length " + message.byteLength) if (_this.allRead >= _this.toRead && !_this.isRunning) { _this.process(); } }); // Here we have the nasty detail that a protocol-unaware websocket proxy might send out unproperly framed data // All websocket proxies in this case are protocol-unaware, since MITM is NOT possible due to ECC }; _this.socket.onopen = resolve; _this.socket.onerror = reject; _this.socket.onclose = _this.close.bind(_this); }); case 10: this.socket.onerror = function (e) { console.log("Websocket error: ", e); _this.close(); }; return _context.abrupt("return", this.socket.send(ctx.init)); case 12: case "end": return _context.stop(); } } }, _callee, this); })); function connect(_x) { return _connect.apply(this, arguments); } return connect; }() /** * Read N bytes from the decrypted buffer(s) */ }, { key: "read", value: function read(length) { var left = this.buffers[this.bufferOffset].byteLength - this.offset; if (left > length) { var _buffer = this.buffers[this.bufferOffset].slice(this.offset, this.offset + length); this.offset += length; return _buffer.buffer; } if (left === length) { var _buffer2 = this.buffers[this.bufferOffset]; if (this.offset) { _buffer2 = _buffer2.slice(this.offset); } delete this.buffers[this.bufferOffset++]; this.offset = 0; return _buffer2.buffer; } var buffer = this.buffers[this.bufferOffset]; if (this.offset) { buffer = buffer.slice(this.offset); } delete this.buffers[this.bufferOffset++]; this.offset = 0; return (0, _tools.bufferConcat)(buffer, new Uint8Array(this.read(length - buffer.byteLength))).buffer; } }, { key: "process", value: function () { var _process = (0, _asyncToGenerator2["default"])( /*#__PURE__*/ _regenerator["default"].mark(function _callee2() { var toRead, message, sha; return _regenerator["default"].wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: this.isRunning = true; if (!(this.toRead === 4)) { _context2.next = 7; break; } // Read length and possibly more this.allRead -= this.toRead; this.toRead = new Uint32Array(this.read(this.toRead))[0]; // Forget about exotic endiannesses for now if (!(this.allRead < this.toRead)) { _context2.next = 7; break; } this.isRunning = false; //console.log(`Not enough decrypted data to read actual packet, suspending (need ${this.toRead}, have ${this.allRead})`) return _context2.abrupt("return"); case 7: this.allRead -= this.toRead; toRead = this.toRead - 32; this.toRead = 4; message = new Uint32Array(this.read(toRead)); _context2.t0 = Uint32Array; _context2.next = 14; return this.crypto.sha256(message); case 14: _context2.t1 = _context2.sent; sha = new _context2.t0(_context2.t1); if ((0, _tools.bufferViewEqual)(sha, new Uint32Array(this.read(32)))) { _context2.next = 18; break; } throw new Error('SHA256 mismatch!'); case 18: if (message.length === 8) { console.log("OK, got empty message!"); } else { this.onMessage(new _stream["default"](message.slice(8).buffer)); } if (this.allRead >= this.toRead) { this.process(); } this.isRunning = false; case 21: case "end": return _context2.stop(); } } }, _callee2, this); })); function process() { return _process.apply(this, arguments); } return process; }() }, { key: "write", value: function () { var _write = (0, _asyncToGenerator2["default"])( /*#__PURE__*/ _regenerator["default"].mark(function _callee3(payload) { var _this2 = this; return _regenerator["default"].wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: payload.pos = 0; payload.writeUnsignedInt(payload.bBuf.length - 4); payload.writeUnsignedInts((0, _random.fastRandom)(new Uint32Array(8))); payload.pos = payload.uBuf.length - 8; _context3.t0 = payload; _context3.t1 = Uint32Array; _context3.next = 8; return this.crypto.sha256(payload.uBuf.slice(1, payload.pos)); case 8: _context3.t2 = _context3.sent; _context3.t3 = new _context3.t1(_context3.t2); _context3.t0.writeUnsignedInts.call(_context3.t0, _context3.t3); payload = payload.bBuf; return _context3.abrupt("return", this.encrypt.process(payload).then(function (payload) { return _this2.socket.send(payload); })); case 13: case "end": return _context3.stop(); } } }, _callee3, this); })); function write(_x2) { return _write.apply(this, arguments); } return write; }() }, { key: "getBuffer", value: function getBuffer(length) { var s = new _stream["default"](new Uint32Array(17 + (length || 0))); s.pos += 9; return s; } }, { key: "close", value: function close() { console.log("Closing socket!"); if (this.socket) { this.socket.close(); this.socket = undefined; this.onClose(); } } }, { key: "isHttp", value: function isHttp() { return false; } }]); return ADNL; }(); var _default = ADNL; exports["default"] = _default;