socksProxy.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.SocksProxyHandler = exports.SocksProxy = void 0;
  6. exports.parsePattern = parsePattern;
  7. var _events = _interopRequireDefault(require("events"));
  8. var _net = _interopRequireDefault(require("net"));
  9. var _debugLogger = require("./debugLogger");
  10. var _happyEyeballs = require("../utils/happy-eyeballs");
  11. var _utils = require("../utils");
  12. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  13. /**
  14. * Copyright (c) Microsoft Corporation.
  15. *
  16. * Licensed under the Apache License, Version 2.0 (the "License");
  17. * you may not use this file except in compliance with the License.
  18. * You may obtain a copy of the License at
  19. *
  20. * http://www.apache.org/licenses/LICENSE-2.0
  21. *
  22. * Unless required by applicable law or agreed to in writing, software
  23. * distributed under the License is distributed on an "AS IS" BASIS,
  24. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  25. * See the License for the specific language governing permissions and
  26. * limitations under the License.
  27. */
  28. // https://tools.ietf.org/html/rfc1928
  29. var SocksAuth;
  30. (function (SocksAuth) {
  31. SocksAuth[SocksAuth["NO_AUTHENTICATION_REQUIRED"] = 0] = "NO_AUTHENTICATION_REQUIRED";
  32. SocksAuth[SocksAuth["GSSAPI"] = 1] = "GSSAPI";
  33. SocksAuth[SocksAuth["USERNAME_PASSWORD"] = 2] = "USERNAME_PASSWORD";
  34. SocksAuth[SocksAuth["NO_ACCEPTABLE_METHODS"] = 255] = "NO_ACCEPTABLE_METHODS";
  35. })(SocksAuth || (SocksAuth = {}));
  36. var SocksAddressType;
  37. (function (SocksAddressType) {
  38. SocksAddressType[SocksAddressType["IPv4"] = 1] = "IPv4";
  39. SocksAddressType[SocksAddressType["FqName"] = 3] = "FqName";
  40. SocksAddressType[SocksAddressType["IPv6"] = 4] = "IPv6";
  41. })(SocksAddressType || (SocksAddressType = {}));
  42. var SocksCommand;
  43. (function (SocksCommand) {
  44. SocksCommand[SocksCommand["CONNECT"] = 1] = "CONNECT";
  45. SocksCommand[SocksCommand["BIND"] = 2] = "BIND";
  46. SocksCommand[SocksCommand["UDP_ASSOCIATE"] = 3] = "UDP_ASSOCIATE";
  47. })(SocksCommand || (SocksCommand = {}));
  48. var SocksReply;
  49. (function (SocksReply) {
  50. SocksReply[SocksReply["Succeeded"] = 0] = "Succeeded";
  51. SocksReply[SocksReply["GeneralServerFailure"] = 1] = "GeneralServerFailure";
  52. SocksReply[SocksReply["NotAllowedByRuleSet"] = 2] = "NotAllowedByRuleSet";
  53. SocksReply[SocksReply["NetworkUnreachable"] = 3] = "NetworkUnreachable";
  54. SocksReply[SocksReply["HostUnreachable"] = 4] = "HostUnreachable";
  55. SocksReply[SocksReply["ConnectionRefused"] = 5] = "ConnectionRefused";
  56. SocksReply[SocksReply["TtlExpired"] = 6] = "TtlExpired";
  57. SocksReply[SocksReply["CommandNotSupported"] = 7] = "CommandNotSupported";
  58. SocksReply[SocksReply["AddressTypeNotSupported"] = 8] = "AddressTypeNotSupported";
  59. })(SocksReply || (SocksReply = {}));
  60. class SocksConnection {
  61. constructor(uid, socket, client) {
  62. this._buffer = Buffer.from([]);
  63. this._offset = 0;
  64. this._fence = 0;
  65. this._fenceCallback = void 0;
  66. this._socket = void 0;
  67. this._boundOnData = void 0;
  68. this._uid = void 0;
  69. this._client = void 0;
  70. this._uid = uid;
  71. this._socket = socket;
  72. this._client = client;
  73. this._boundOnData = this._onData.bind(this);
  74. socket.on('data', this._boundOnData);
  75. socket.on('close', () => this._onClose());
  76. socket.on('end', () => this._onClose());
  77. socket.on('error', () => this._onClose());
  78. this._run().catch(() => this._socket.end());
  79. }
  80. async _run() {
  81. (0, _utils.assert)(await this._authenticate());
  82. const {
  83. command,
  84. host,
  85. port
  86. } = await this._parseRequest();
  87. if (command !== SocksCommand.CONNECT) {
  88. this._writeBytes(Buffer.from([0x05, SocksReply.CommandNotSupported, 0x00,
  89. // RSV
  90. 0x01,
  91. // IPv4
  92. 0x00, 0x00, 0x00, 0x00,
  93. // Address
  94. 0x00, 0x00 // Port
  95. ]));
  96. return;
  97. }
  98. this._socket.off('data', this._boundOnData);
  99. this._client.onSocketRequested({
  100. uid: this._uid,
  101. host,
  102. port
  103. });
  104. }
  105. async _authenticate() {
  106. // Request:
  107. // +----+----------+----------+
  108. // |VER | NMETHODS | METHODS |
  109. // +----+----------+----------+
  110. // | 1 | 1 | 1 to 255 |
  111. // +----+----------+----------+
  112. // Response:
  113. // +----+--------+
  114. // |VER | METHOD |
  115. // +----+--------+
  116. // | 1 | 1 |
  117. // +----+--------+
  118. const version = await this._readByte();
  119. (0, _utils.assert)(version === 0x05, 'The VER field must be set to x05 for this version of the protocol, was ' + version);
  120. const nMethods = await this._readByte();
  121. (0, _utils.assert)(nMethods, 'No authentication methods specified');
  122. const methods = await this._readBytes(nMethods);
  123. for (const method of methods) {
  124. if (method === 0) {
  125. this._writeBytes(Buffer.from([version, method]));
  126. return true;
  127. }
  128. }
  129. this._writeBytes(Buffer.from([version, SocksAuth.NO_ACCEPTABLE_METHODS]));
  130. return false;
  131. }
  132. async _parseRequest() {
  133. // Request.
  134. // +----+-----+-------+------+----------+----------+
  135. // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
  136. // +----+-----+-------+------+----------+----------+
  137. // | 1 | 1 | X'00' | 1 | Variable | 2 |
  138. // +----+-----+-------+------+----------+----------+
  139. // Response.
  140. // +----+-----+-------+------+----------+----------+
  141. // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
  142. // +----+-----+-------+------+----------+----------+
  143. // | 1 | 1 | X'00' | 1 | Variable | 2 |
  144. // +----+-----+-------+------+----------+----------+
  145. const version = await this._readByte();
  146. (0, _utils.assert)(version === 0x05, 'The VER field must be set to x05 for this version of the protocol, was ' + version);
  147. const command = await this._readByte();
  148. await this._readByte(); // skip reserved.
  149. const addressType = await this._readByte();
  150. let host = '';
  151. switch (addressType) {
  152. case SocksAddressType.IPv4:
  153. host = (await this._readBytes(4)).join('.');
  154. break;
  155. case SocksAddressType.FqName:
  156. const length = await this._readByte();
  157. host = (await this._readBytes(length)).toString();
  158. break;
  159. case SocksAddressType.IPv6:
  160. const bytes = await this._readBytes(16);
  161. const tokens = [];
  162. for (let i = 0; i < 8; ++i) tokens.push(bytes.readUInt16BE(i * 2).toString(16));
  163. host = tokens.join(':');
  164. break;
  165. }
  166. const port = (await this._readBytes(2)).readUInt16BE(0);
  167. this._buffer = Buffer.from([]);
  168. this._offset = 0;
  169. this._fence = 0;
  170. return {
  171. command,
  172. host,
  173. port
  174. };
  175. }
  176. async _readByte() {
  177. const buffer = await this._readBytes(1);
  178. return buffer[0];
  179. }
  180. async _readBytes(length) {
  181. this._fence = this._offset + length;
  182. if (!this._buffer || this._buffer.length < this._fence) await new Promise(f => this._fenceCallback = f);
  183. this._offset += length;
  184. return this._buffer.slice(this._offset - length, this._offset);
  185. }
  186. _writeBytes(buffer) {
  187. if (this._socket.writable) this._socket.write(buffer);
  188. }
  189. _onClose() {
  190. this._client.onSocketClosed({
  191. uid: this._uid
  192. });
  193. }
  194. _onData(buffer) {
  195. this._buffer = Buffer.concat([this._buffer, buffer]);
  196. if (this._fenceCallback && this._buffer.length >= this._fence) {
  197. const callback = this._fenceCallback;
  198. this._fenceCallback = undefined;
  199. callback();
  200. }
  201. }
  202. socketConnected(host, port) {
  203. this._writeBytes(Buffer.from([0x05, SocksReply.Succeeded, 0x00,
  204. // RSV
  205. ...ipToSocksAddress(host),
  206. // ATYP, Address
  207. port >> 8, port & 0xFF // Port
  208. ]));
  209. this._socket.on('data', data => this._client.onSocketData({
  210. uid: this._uid,
  211. data
  212. }));
  213. }
  214. socketFailed(errorCode) {
  215. const buffer = Buffer.from([0x05, 0, 0x00,
  216. // RSV
  217. ...ipToSocksAddress('0.0.0.0'),
  218. // ATYP, Address
  219. 0, 0 // Port
  220. ]);
  221. switch (errorCode) {
  222. case 'ENOENT':
  223. case 'ENOTFOUND':
  224. case 'ETIMEDOUT':
  225. case 'EHOSTUNREACH':
  226. buffer[1] = SocksReply.HostUnreachable;
  227. break;
  228. case 'ENETUNREACH':
  229. buffer[1] = SocksReply.NetworkUnreachable;
  230. break;
  231. case 'ECONNREFUSED':
  232. buffer[1] = SocksReply.ConnectionRefused;
  233. break;
  234. case 'ERULESET':
  235. buffer[1] = SocksReply.NotAllowedByRuleSet;
  236. break;
  237. }
  238. this._writeBytes(buffer);
  239. this._socket.end();
  240. }
  241. sendData(data) {
  242. this._socket.write(data);
  243. }
  244. end() {
  245. this._socket.end();
  246. }
  247. error(error) {
  248. this._socket.destroy(new Error(error));
  249. }
  250. }
  251. function hexToNumber(hex) {
  252. // Note: parseInt has a few issues including ignoring trailing characters and allowing leading 0x.
  253. return [...hex].reduce((value, digit) => {
  254. const code = digit.charCodeAt(0);
  255. if (code >= 48 && code <= 57)
  256. // 0..9
  257. return value + code;
  258. if (code >= 97 && code <= 102)
  259. // a..f
  260. return value + (code - 97) + 10;
  261. if (code >= 65 && code <= 70)
  262. // A..F
  263. return value + (code - 65) + 10;
  264. throw new Error('Invalid IPv6 token ' + hex);
  265. }, 0);
  266. }
  267. function ipToSocksAddress(address) {
  268. if (_net.default.isIPv4(address)) {
  269. return [0x01,
  270. // IPv4
  271. ...address.split('.', 4).map(t => +t & 0xFF) // Address
  272. ];
  273. }
  274. if (_net.default.isIPv6(address)) {
  275. const result = [0x04]; // IPv6
  276. const tokens = address.split(':', 8);
  277. while (tokens.length < 8) tokens.unshift('');
  278. for (const token of tokens) {
  279. const value = hexToNumber(token);
  280. result.push(value >> 8 & 0xFF, value & 0xFF); // Big-endian
  281. }
  282. return result;
  283. }
  284. throw new Error('Only IPv4 and IPv6 addresses are supported');
  285. }
  286. function starMatchToRegex(pattern) {
  287. const source = pattern.split('*').map(s => {
  288. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
  289. return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  290. }).join('.*');
  291. return new RegExp('^' + source + '$');
  292. }
  293. // This follows "Proxy bypass rules" syntax without implicit and negative rules.
  294. // https://source.chromium.org/chromium/chromium/src/+/main:net/docs/proxy.md;l=331
  295. function parsePattern(pattern) {
  296. if (!pattern) return () => false;
  297. const matchers = pattern.split(',').map(token => {
  298. const match = token.match(/^(.*?)(?::(\d+))?$/);
  299. if (!match) throw new Error(`Unsupported token "${token}" in pattern "${pattern}"`);
  300. const tokenPort = match[2] ? +match[2] : undefined;
  301. const portMatches = port => tokenPort === undefined || tokenPort === port;
  302. let tokenHost = match[1];
  303. if (tokenHost === '<loopback>') {
  304. return (host, port) => {
  305. if (!portMatches(port)) return false;
  306. return host === 'localhost' || host.endsWith('.localhost') || host === '127.0.0.1' || host === '[::1]';
  307. };
  308. }
  309. if (tokenHost === '*') return (host, port) => portMatches(port);
  310. if (_net.default.isIPv4(tokenHost) || _net.default.isIPv6(tokenHost)) return (host, port) => host === tokenHost && portMatches(port);
  311. if (tokenHost[0] === '.') tokenHost = '*' + tokenHost;
  312. const tokenRegex = starMatchToRegex(tokenHost);
  313. return (host, port) => {
  314. if (!portMatches(port)) return false;
  315. if (_net.default.isIPv4(host) || _net.default.isIPv6(host)) return false;
  316. return !!host.match(tokenRegex);
  317. };
  318. });
  319. return (host, port) => matchers.some(matcher => matcher(host, port));
  320. }
  321. class SocksProxy extends _events.default {
  322. constructor() {
  323. super();
  324. this._server = void 0;
  325. this._connections = new Map();
  326. this._sockets = new Set();
  327. this._closed = false;
  328. this._port = void 0;
  329. this._patternMatcher = () => false;
  330. this._directSockets = new Map();
  331. this._server = new _net.default.Server(socket => {
  332. const uid = (0, _utils.createGuid)();
  333. const connection = new SocksConnection(uid, socket, this);
  334. this._connections.set(uid, connection);
  335. });
  336. this._server.on('connection', socket => {
  337. if (this._closed) {
  338. socket.destroy();
  339. return;
  340. }
  341. this._sockets.add(socket);
  342. socket.once('close', () => this._sockets.delete(socket));
  343. });
  344. }
  345. setPattern(pattern) {
  346. try {
  347. this._patternMatcher = parsePattern(pattern);
  348. } catch (e) {
  349. this._patternMatcher = () => false;
  350. }
  351. }
  352. async _handleDirect(request) {
  353. try {
  354. var _this$_connections$ge4;
  355. const socket = await (0, _happyEyeballs.createSocket)(request.host, request.port);
  356. socket.on('data', data => {
  357. var _this$_connections$ge;
  358. return (_this$_connections$ge = this._connections.get(request.uid)) === null || _this$_connections$ge === void 0 ? void 0 : _this$_connections$ge.sendData(data);
  359. });
  360. socket.on('error', error => {
  361. var _this$_connections$ge2;
  362. (_this$_connections$ge2 = this._connections.get(request.uid)) === null || _this$_connections$ge2 === void 0 ? void 0 : _this$_connections$ge2.error(error.message);
  363. this._directSockets.delete(request.uid);
  364. });
  365. socket.on('end', () => {
  366. var _this$_connections$ge3;
  367. (_this$_connections$ge3 = this._connections.get(request.uid)) === null || _this$_connections$ge3 === void 0 ? void 0 : _this$_connections$ge3.end();
  368. this._directSockets.delete(request.uid);
  369. });
  370. const localAddress = socket.localAddress;
  371. const localPort = socket.localPort;
  372. this._directSockets.set(request.uid, socket);
  373. (_this$_connections$ge4 = this._connections.get(request.uid)) === null || _this$_connections$ge4 === void 0 ? void 0 : _this$_connections$ge4.socketConnected(localAddress, localPort);
  374. } catch (error) {
  375. var _this$_connections$ge5;
  376. (_this$_connections$ge5 = this._connections.get(request.uid)) === null || _this$_connections$ge5 === void 0 ? void 0 : _this$_connections$ge5.socketFailed(error.code);
  377. }
  378. }
  379. port() {
  380. return this._port;
  381. }
  382. async listen(port) {
  383. return new Promise(f => {
  384. this._server.listen(port, () => {
  385. const port = this._server.address().port;
  386. this._port = port;
  387. f(port);
  388. });
  389. });
  390. }
  391. async close() {
  392. if (this._closed) return;
  393. this._closed = true;
  394. for (const socket of this._sockets) socket.destroy();
  395. this._sockets.clear();
  396. await new Promise(f => this._server.close(f));
  397. }
  398. onSocketRequested(payload) {
  399. if (!this._patternMatcher(payload.host, payload.port)) {
  400. this._handleDirect(payload);
  401. return;
  402. }
  403. this.emit(SocksProxy.Events.SocksRequested, payload);
  404. }
  405. onSocketData(payload) {
  406. const direct = this._directSockets.get(payload.uid);
  407. if (direct) {
  408. direct.write(payload.data);
  409. return;
  410. }
  411. this.emit(SocksProxy.Events.SocksData, payload);
  412. }
  413. onSocketClosed(payload) {
  414. const direct = this._directSockets.get(payload.uid);
  415. if (direct) {
  416. direct.destroy();
  417. this._directSockets.delete(payload.uid);
  418. return;
  419. }
  420. this.emit(SocksProxy.Events.SocksClosed, payload);
  421. }
  422. socketConnected({
  423. uid,
  424. host,
  425. port
  426. }) {
  427. var _this$_connections$ge6;
  428. (_this$_connections$ge6 = this._connections.get(uid)) === null || _this$_connections$ge6 === void 0 ? void 0 : _this$_connections$ge6.socketConnected(host, port);
  429. }
  430. socketFailed({
  431. uid,
  432. errorCode
  433. }) {
  434. var _this$_connections$ge7;
  435. (_this$_connections$ge7 = this._connections.get(uid)) === null || _this$_connections$ge7 === void 0 ? void 0 : _this$_connections$ge7.socketFailed(errorCode);
  436. }
  437. sendSocketData({
  438. uid,
  439. data
  440. }) {
  441. var _this$_connections$ge8;
  442. (_this$_connections$ge8 = this._connections.get(uid)) === null || _this$_connections$ge8 === void 0 ? void 0 : _this$_connections$ge8.sendData(data);
  443. }
  444. sendSocketEnd({
  445. uid
  446. }) {
  447. var _this$_connections$ge9;
  448. (_this$_connections$ge9 = this._connections.get(uid)) === null || _this$_connections$ge9 === void 0 ? void 0 : _this$_connections$ge9.end();
  449. }
  450. sendSocketError({
  451. uid,
  452. error
  453. }) {
  454. var _this$_connections$ge10;
  455. (_this$_connections$ge10 = this._connections.get(uid)) === null || _this$_connections$ge10 === void 0 ? void 0 : _this$_connections$ge10.error(error);
  456. }
  457. }
  458. exports.SocksProxy = SocksProxy;
  459. SocksProxy.Events = {
  460. SocksRequested: 'socksRequested',
  461. SocksData: 'socksData',
  462. SocksClosed: 'socksClosed'
  463. };
  464. class SocksProxyHandler extends _events.default {
  465. constructor(pattern, redirectPortForTest) {
  466. super();
  467. this._sockets = new Map();
  468. this._patternMatcher = () => false;
  469. this._redirectPortForTest = void 0;
  470. this._patternMatcher = parsePattern(pattern);
  471. this._redirectPortForTest = redirectPortForTest;
  472. }
  473. cleanup() {
  474. for (const uid of this._sockets.keys()) this.socketClosed({
  475. uid
  476. });
  477. }
  478. async socketRequested({
  479. uid,
  480. host,
  481. port
  482. }) {
  483. _debugLogger.debugLogger.log('socks', `[${uid}] => request ${host}:${port}`);
  484. if (!this._patternMatcher(host, port)) {
  485. const payload = {
  486. uid,
  487. errorCode: 'ERULESET'
  488. };
  489. _debugLogger.debugLogger.log('socks', `[${uid}] <= pattern error ${payload.errorCode}`);
  490. this.emit(SocksProxyHandler.Events.SocksFailed, payload);
  491. return;
  492. }
  493. if (host === 'local.playwright') host = 'localhost';
  494. try {
  495. if (this._redirectPortForTest) port = this._redirectPortForTest;
  496. const socket = await (0, _happyEyeballs.createSocket)(host, port);
  497. socket.on('data', data => {
  498. const payload = {
  499. uid,
  500. data
  501. };
  502. this.emit(SocksProxyHandler.Events.SocksData, payload);
  503. });
  504. socket.on('error', error => {
  505. const payload = {
  506. uid,
  507. error: error.message
  508. };
  509. _debugLogger.debugLogger.log('socks', `[${uid}] <= network socket error ${payload.error}`);
  510. this.emit(SocksProxyHandler.Events.SocksError, payload);
  511. this._sockets.delete(uid);
  512. });
  513. socket.on('end', () => {
  514. const payload = {
  515. uid
  516. };
  517. _debugLogger.debugLogger.log('socks', `[${uid}] <= network socket closed`);
  518. this.emit(SocksProxyHandler.Events.SocksEnd, payload);
  519. this._sockets.delete(uid);
  520. });
  521. const localAddress = socket.localAddress;
  522. const localPort = socket.localPort;
  523. this._sockets.set(uid, socket);
  524. const payload = {
  525. uid,
  526. host: localAddress,
  527. port: localPort
  528. };
  529. _debugLogger.debugLogger.log('socks', `[${uid}] <= connected to network ${payload.host}:${payload.port}`);
  530. this.emit(SocksProxyHandler.Events.SocksConnected, payload);
  531. } catch (error) {
  532. const payload = {
  533. uid,
  534. errorCode: error.code
  535. };
  536. _debugLogger.debugLogger.log('socks', `[${uid}] <= connect error ${payload.errorCode}`);
  537. this.emit(SocksProxyHandler.Events.SocksFailed, payload);
  538. }
  539. }
  540. sendSocketData({
  541. uid,
  542. data
  543. }) {
  544. var _this$_sockets$get;
  545. (_this$_sockets$get = this._sockets.get(uid)) === null || _this$_sockets$get === void 0 ? void 0 : _this$_sockets$get.write(data);
  546. }
  547. socketClosed({
  548. uid
  549. }) {
  550. var _this$_sockets$get2;
  551. _debugLogger.debugLogger.log('socks', `[${uid}] <= browser socket closed`);
  552. (_this$_sockets$get2 = this._sockets.get(uid)) === null || _this$_sockets$get2 === void 0 ? void 0 : _this$_sockets$get2.destroy();
  553. this._sockets.delete(uid);
  554. }
  555. }
  556. exports.SocksProxyHandler = SocksProxyHandler;
  557. SocksProxyHandler.Events = {
  558. SocksConnected: 'socksConnected',
  559. SocksData: 'socksData',
  560. SocksError: 'socksError',
  561. SocksFailed: 'socksFailed',
  562. SocksEnd: 'socksEnd'
  563. };