@fibercom/routeros-api
Version:
Robust MikroTik RouterOS API client for Node.js and TypeScript
383 lines • 30.7 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouterOSAPI = void 0;
const Connector_1 = require("./connector/Connector");
const Channel_1 = require("./Channel");
const RosException_1 = require("./RosException");
const RStream_1 = require("./RStream");
const crypto = require("crypto");
const debug = require("debug");
const timers_1 = require("timers");
const events_1 = require("events");
let info;
let error;
/**
* Creates a connection object with the credentials provided
*/
class RouterOSAPI extends events_1.EventEmitter {
constructor(options) {
super();
/** Connected flag */
this.connected = false;
/** Connecting flag */
this.connecting = false;
/** Closing flag */
this.closing = false;
this.channelsOpen = 0;
this.holdingConnectionWithKeepalive = false;
this.connectionHoldInterval = null;
this.registeredStreams = [];
this.setOptions(options);
this.initializeDebuggers();
}
setOptions(options) {
this.host = options.host;
this.user = options.user;
this.password = options.password;
this.port = options.port || 8728;
this.timeout = options.timeout || 10;
this.tls = options.tls;
this.keepalive = options.keepalive || false;
this.debug = options.debug || false;
}
initializeDebuggers() {
if (this.debug) {
// Enable debug output by setting the DEBUG environment variable
process.env.DEBUG = process.env.DEBUG
? `${process.env.DEBUG},routeros-api:*`
: "routeros-api:*";
info = debug("routeros-api:api:info");
error = debug("routeros-api:api:error");
}
else {
// Create no-op debuggers when debug is disabled
info = (...args) => { };
error = (...args) => { };
}
}
/**
* Attempts to connect to the RouterOS device with retry logic.
*
* This method wraps the `.connect()` method and retries the connection
* multiple times if it fails, waiting a specified delay between each attempt.
*
* @param attempts - Maximum number of connection attempts (default: 3)
* @param delay - Delay (in ms) between retries (default: 1000 ms)
* @returns A Promise resolving to the Connector instance upon successful connection
* @throws The last encountered error if all retry attempts fail
*/
connectWithRetry() {
return __awaiter(this, arguments, void 0, function* (attempts = 3, delay = 1000) {
let tries = 1;
while (tries <= attempts) {
try {
// Try connecting
if (this.debug) {
console.debug(`[DEBUG] Connection attempt: ${tries}`);
}
return yield this.connect();
}
catch (err) {
if (this.debug) {
console.warn(`[WARN] Connection attempt ${tries} failed: ${err.message}`);
}
tries++;
// If we've exhausted all retries, rethrow the last error
if (tries > attempts) {
throw err;
}
// Wait for the specified delay before trying again
yield new Promise((res) => setTimeout(res, delay));
}
}
// This fallback should never hit, but satisfies TypeScript return rules
throw new RosException_1.RosException("RETRYFAIL", {
message: "Failed to connect after multiple attempts",
});
});
}
connect() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
if (this.connecting)
throw new RosException_1.RosException("ALRDYCONNECTING");
if (this.connected)
return this;
this.connecting = true;
this.connected = false;
this.connector = new Connector_1.Connector({
host: this.host,
port: this.port,
timeout: this.timeout,
tls: this.tls,
debug: this.debug,
});
try {
yield this.connector.connect(); // ✅ await critical!
yield this.login();
this.connected = true;
this.connecting = false;
this.connector.once("error", (e) => {
this.connected = false;
this.connecting = false;
this.emit("error", e);
});
this.connector.once("timeout", (e) => {
this.connected = false;
this.connecting = false;
this.emit("timeout", e);
});
if (this.keepalive)
this.keepaliveBy("#");
return this;
}
catch (err) {
this.connecting = false;
this.connected = false;
(_a = this.connector) === null || _a === void 0 ? void 0 : _a.destroy();
throw err;
}
});
}
write(params, ...moreParams) {
params = this.concatParams(params, moreParams);
let chann = this.openChannel();
this.holdConnection();
chann.once("close", () => {
chann = null;
this.decreaseChannelsOpen();
this.releaseConnectionHold();
});
return chann.write(params);
}
writeStream(params, ...moreParams) {
params = this.concatParams(params, moreParams);
const stream = new RStream_1.RStream(this.openChannel(), params);
stream.on("started", () => this.holdConnection());
stream.on("stopped", () => {
this.unregisterStream(stream);
this.decreaseChannelsOpen();
this.releaseConnectionHold();
});
stream.start();
this.registerStream(stream);
return stream;
}
stream(params = [], ...moreParams) {
let callback = moreParams.pop();
if (typeof callback !== "function") {
if (callback)
moreParams.push(callback);
callback = null;
}
params = this.concatParams(params, moreParams);
const stream = new RStream_1.RStream(this.openChannel(), params, callback);
stream.on("started", () => this.holdConnection());
stream.on("stopped", () => {
this.unregisterStream(stream);
this.decreaseChannelsOpen();
this.releaseConnectionHold();
stream.removeAllListeners();
});
stream.start();
stream.prepareDebounceEmptyData();
this.registerStream(stream);
return stream;
}
keepaliveBy(params = [], ...moreParams) {
this.holdingConnectionWithKeepalive = true;
if (this.keptaliveby)
(0, timers_1.clearTimeout)(this.keptaliveby);
let callback = moreParams.pop();
if (typeof callback !== "function") {
if (callback)
moreParams.push(callback);
callback = null;
}
params = this.concatParams(params, moreParams);
const exec = () => {
if (!this.closing) {
if (this.keptaliveby)
(0, timers_1.clearTimeout)(this.keptaliveby);
this.keptaliveby = setTimeout(() => {
this.write(params.slice())
.then((data) => {
if (typeof callback === "function")
callback(null, data);
exec();
})
.catch((err) => {
if (typeof callback === "function")
callback(err, null);
exec();
});
}, (this.timeout * 1000) / 2);
}
};
exec();
}
close() {
if (this.closing) {
if (this.debug) {
console.debug("[DEBUG] Close already in progress.");
}
return Promise.reject(new RosException_1.RosException("ALRDYCLOSNG"));
}
if (!this.connected) {
if (this.debug) {
console.debug("[DEBUG] Already disconnected. Skipping close.");
}
return Promise.resolve(this);
}
if (this.debug) {
console.debug("[DEBUG] Initiating RouterOSAPI close sequence...");
}
if (this.connectionHoldInterval) {
(0, timers_1.clearTimeout)(this.connectionHoldInterval);
if (this.debug) {
console.debug("[DEBUG] Cleared connection hold interval.");
}
}
(0, timers_1.clearTimeout)(this.keptaliveby);
if (this.debug) {
console.debug("[DEBUG] Cleared keepalive timer.");
}
this.stopAllStreams();
if (this.debug) {
console.debug("[DEBUG] Stopped all active streams.");
}
return new Promise((resolve) => {
this.closing = true;
if (this.debug) {
console.debug("[DEBUG] Waiting for connector to emit 'close'...");
}
this.connector.once("close", () => {
if (this.debug) {
console.debug("[DEBUG] Connector closed. Cleaning up.");
}
this.connector.destroy();
this.connector = null;
this.closing = false;
this.connected = false;
resolve(this);
});
this.connector.close();
});
}
openChannel() {
this.increaseChannelsOpen();
return new Channel_1.Channel(this.connector);
}
increaseChannelsOpen() {
this.channelsOpen++;
}
decreaseChannelsOpen() {
this.channelsOpen--;
}
registerStream(stream) {
this.registeredStreams.push(stream);
}
unregisterStream(stream) {
this.registeredStreams = this.registeredStreams.filter((s) => s !== stream);
}
stopAllStreams() {
for (const stream of this.registeredStreams) {
stream.stop();
}
}
holdConnection() {
if (this.channelsOpen !== 1)
return;
if (this.connected && !this.holdingConnectionWithKeepalive) {
if (this.connectionHoldInterval)
(0, timers_1.clearTimeout)(this.connectionHoldInterval);
const holdConnInterval = () => {
this.connectionHoldInterval = setTimeout(() => {
let chann = new Channel_1.Channel(this.connector);
chann.on("close", () => {
chann = null;
});
chann.write(["#"]).then(holdConnInterval).catch(holdConnInterval);
}, (this.timeout * 1000) / 2);
};
holdConnInterval();
}
}
releaseConnectionHold() {
if (this.channelsOpen > 0)
return;
if (this.connectionHoldInterval)
(0, timers_1.clearTimeout)(this.connectionHoldInterval);
}
login() {
return __awaiter(this, void 0, void 0, function* () {
this.connecting = true;
info("Sending /login to %s", this.host);
const loginTimeoutMs = this.timeout * 1000;
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
var _a;
const timeout = setTimeout(() => {
reject(new RosException_1.RosException("LOGINBLOCKED", {
message: "No response to /login. Your IP might not be allowed in /ip service.",
}));
}, loginTimeoutMs);
try {
const data = yield this.write("/login", [
`=name=${this.user}`,
`=password=${this.password}`,
]);
(0, timers_1.clearTimeout)(timeout);
if (data.length === 0) {
info("Login accepted on %s", this.host);
return resolve(this);
}
if (data.length === 1 && ((_a = data[0]) === null || _a === void 0 ? void 0 : _a.ret)) {
const ret = data[0].ret;
const challenge = Buffer.alloc(this.password.length + 17);
challenge.write(String.fromCharCode(0) + this.password);
challenge.write(ret, this.password.length + 1, ret.length / 2, "hex");
const resp = "00" + crypto.createHash("MD5").update(challenge).digest("hex");
const data2 = yield this.write("/login", [
`=name=${this.user}`,
`=response=${resp}`,
]);
(0, timers_1.clearTimeout)(timeout);
return resolve(this);
}
(0, timers_1.clearTimeout)(timeout);
return reject(new RosException_1.RosException("CANTLOGIN"));
}
catch (err) {
(0, timers_1.clearTimeout)(timeout);
if (typeof err.message === "string" &&
err.message.includes("invalid user name or password")) {
return reject(new RosException_1.RosException("CANTLOGIN"));
}
return reject(err);
}
}));
});
}
concatParams(firstParameter, parameters) {
if (typeof firstParameter === "string")
firstParameter = [firstParameter];
for (let parameter of parameters) {
if (typeof parameter === "string")
parameter = [parameter];
if (parameter.length > 0)
firstParameter = firstParameter.concat(parameter);
}
return firstParameter;
}
}
exports.RouterOSAPI = RouterOSAPI;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUm91dGVyT1NBUEkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvUm91dGVyT1NBUEkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQ0EscURBQWtEO0FBQ2xELHVDQUFvQztBQUNwQyxpREFBOEM7QUFFOUMsdUNBQW9DO0FBQ3BDLGlDQUFpQztBQUNqQywrQkFBK0I7QUFDL0IsbUNBQXNDO0FBQ3RDLG1DQUFzQztBQUd0QyxJQUFJLElBQWtELENBQUM7QUFDdkQsSUFBSSxLQUFtRCxDQUFDO0FBRXhEOztHQUVHO0FBQ0gsTUFBYSxXQUFZLFNBQVEscUJBQVk7SUErQjNDLFlBQVksT0FBb0I7UUFDOUIsS0FBSyxFQUFFLENBQUM7UUFqQlYscUJBQXFCO1FBQ2QsY0FBUyxHQUFZLEtBQUssQ0FBQztRQUNsQyxzQkFBc0I7UUFDZixlQUFVLEdBQVksS0FBSyxDQUFDO1FBQ25DLG1CQUFtQjtRQUNaLFlBQU8sR0FBWSxLQUFLLENBQUM7UUFNeEIsaUJBQVksR0FBVyxDQUFDLENBQUM7UUFDekIsbUNBQThCLEdBQVksS0FBSyxDQUFDO1FBQ2hELDJCQUFzQixHQUEwQixJQUFJLENBQUM7UUFDckQsc0JBQWlCLEdBQWMsRUFBRSxDQUFDO1FBSXhDLElBQUksQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDekIsSUFBSSxDQUFDLG1CQUFtQixFQUFFLENBQUM7SUFDN0IsQ0FBQztJQUVNLFVBQVUsQ0FBQyxPQUFvQjtRQUNwQyxJQUFJLENBQUMsSUFBSSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUM7UUFDekIsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUNqQyxJQUFJLENBQUMsSUFBSSxHQUFHLE9BQU8sQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFDO1FBQ2pDLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUM7UUFDckMsSUFBSSxDQUFDLEdBQUcsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxTQUFTLEdBQUcsT0FBTyxDQUFDLFNBQVMsSUFBSSxLQUFLLENBQUM7UUFDNUMsSUFBSSxDQUFDLEtBQUssR0FBRyxPQUFPLENBQUMsS0FBSyxJQUFJLEtBQUssQ0FBQztJQUN0QyxDQUFDO0lBRU8sbUJBQW1CO1FBQ3pCLElBQUksSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ2YsZ0VBQWdFO1lBQ2hFLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSztnQkFDbkMsQ0FBQyxDQUFDLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxLQUFLLGlCQUFpQjtnQkFDdkMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDO1lBQ3JCLElBQUksR0FBRyxLQUFLLENBQUMsdUJBQXVCLENBQUMsQ0FBQztZQUN0QyxLQUFLLEdBQUcsS0FBSyxDQUFDLHdCQUF3QixDQUFDLENBQUM7UUFDMUMsQ0FBQzthQUFNLENBQUM7WUFDTixnREFBZ0Q7WUFDaEQsSUFBSSxHQUFHLENBQUMsR0FBRyxJQUFXLEVBQUUsRUFBRSxHQUFFLENBQUMsQ0FBQztZQUM5QixLQUFLLEdBQUcsQ0FBQyxHQUFHLElBQVcsRUFBRSxFQUFFLEdBQUUsQ0FBQyxDQUFDO1FBQ2pDLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNVLGdCQUFnQjs2REFDM0IsUUFBUSxHQUFHLENBQUMsRUFDWixLQUFLLEdBQUcsSUFBSTtZQUVaLElBQUksS0FBSyxHQUFHLENBQUMsQ0FBQztZQUVkLE9BQU8sS0FBSyxJQUFJLFFBQVEsRUFBRSxDQUFDO2dCQUN6QixJQUFJLENBQUM7b0JBQ0gsaUJBQWlCO29CQUNqQixJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQzt3QkFDZixPQUFPLENBQUMsS0FBSyxDQUFDLCtCQUErQixLQUFLLEVBQUUsQ0FBQyxDQUFDO29CQUN4RCxDQUFDO29CQUNELE9BQU8sTUFBTSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7Z0JBQzlCLENBQUM7Z0JBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztvQkFDYixJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQzt3QkFDZixPQUFPLENBQUMsSUFBSSxDQUNWLDZCQUE2QixLQUFLLFlBQVksR0FBRyxDQUFDLE9BQU8sRUFBRSxDQUM1RCxDQUFDO29CQUNKLENBQUM7b0JBQ0QsS0FBSyxFQUFFLENBQUM7b0JBRVIseURBQXlEO29CQUN6RCxJQUFJLEtBQUssR0FBRyxRQUFRLEVBQUUsQ0FBQzt3QkFDckIsTUFBTSxHQUFHLENBQUM7b0JBQ1osQ0FBQztvQkFFRCxtREFBbUQ7b0JBQ25ELE1BQU0sSUFBSSxPQUFPLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQztnQkFDckQsQ0FBQztZQUNILENBQUM7WUFFRCx3RUFBd0U7WUFDeEUsTUFBTSxJQUFJLDJCQUFZLENBQUMsV0FBVyxFQUFFO2dCQUNsQyxPQUFPLEVBQUUsMkNBQTJDO2FBQ3JELENBQUMsQ0FBQztRQUNMLENBQUM7S0FBQTtJQUVZLE9BQU87OztZQUNsQixJQUFJLElBQUksQ0FBQyxVQUFVO2dCQUFFLE1BQU0sSUFBSSwyQkFBWSxDQUFDLGlCQUFpQixDQUFDLENBQUM7WUFDL0QsSUFBSSxJQUFJLENBQUMsU0FBUztnQkFBRSxPQUFPLElBQUksQ0FBQztZQUVoQyxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQztZQUN2QixJQUFJLENBQUMsU0FBUyxHQUFHLEtBQUssQ0FBQztZQUV2QixJQUFJLENBQUMsU0FBUyxHQUFHLElBQUkscUJBQVMsQ0FBQztnQkFDN0IsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJO2dCQUNmLElBQUksRUFBRSxJQUFJLENBQUMsSUFBSTtnQkFDZixPQUFPLEVBQUUsSUFBSSxDQUFDLE9BQU87Z0JBQ3JCLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztnQkFDYixLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUs7YUFDbEIsQ0FBQyxDQUFDO1lBRUgsSUFBSSxDQUFDO2dCQUNILE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLG9CQUFvQjtnQkFFcEQsTUFBTSxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7Z0JBRW5CLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDO2dCQUN0QixJQUFJLENBQUMsVUFBVSxHQUFHLEtBQUssQ0FBQztnQkFFeEIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxFQUFFLEVBQUU7b0JBQ2pDLElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO29CQUN2QixJQUFJLENBQUMsVUFBVSxHQUFHLEtBQUssQ0FBQztvQkFDeEIsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQ3hCLENBQUMsQ0FBQyxDQUFDO2dCQUVILElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUMsRUFBRSxFQUFFO29CQUNuQyxJQUFJLENBQUMsU0FBUyxHQUFHLEtBQUssQ0FBQztvQkFDdkIsSUFBSSxDQUFDLFVBQVUsR0FBRyxLQUFLLENBQUM7b0JBQ3hCLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDO2dCQUMxQixDQUFDLENBQUMsQ0FBQztnQkFFSCxJQUFJLElBQUksQ0FBQyxTQUFTO29CQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBRTFDLE9BQU8sSUFBSSxDQUFDO1lBQ2QsQ0FBQztZQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7Z0JBQ2IsSUFBSSxDQUFDLFVBQVUsR0FBRyxLQUFLLENBQUM7Z0JBQ3hCLElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO2dCQUN2QixNQUFBLElBQUksQ0FBQyxTQUFTLDBDQUFFLE9BQU8sRUFBRSxDQUFDO2dCQUMxQixNQUFNLEdBQUcsQ0FBQztZQUNaLENBQUM7UUFDSCxDQUFDO0tBQUE7SUFFTSxLQUFLLENBQ1YsTUFBeUIsRUFDekIsR0FBRyxVQUFvQztRQUV2QyxNQUFNLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFDL0MsSUFBSSxLQUFLLEdBQUcsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQy9CLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztRQUV0QixLQUFLLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUU7WUFDdkIsS0FBSyxHQUFHLElBQUksQ0FBQztZQUNiLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxDQUFDO1lBQzVCLElBQUksQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1FBQy9CLENBQUMsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxLQUFLLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzdCLENBQUM7SUFFTSxXQUFXLENBQ2hCLE1BQXlCLEVBQ3pCLEdBQUcsVUFBb0M7UUFFdkMsTUFBTSxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQy9DLE1BQU0sTUFBTSxHQUFHLElBQUksaUJBQU8sQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFdkQsTUFBTSxDQUFDLEVBQUUsQ0FBQyxTQUFTLEVBQUUsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDLENBQUM7UUFDbEQsTUFBTSxDQUFDLEVBQUUsQ0FBQyxTQUFTLEVBQUUsR0FBRyxFQUFFO1lBQ3hCLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUM5QixJQUFJLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztZQUM1QixJQUFJLENBQUMscUJBQXFCLEVBQUUsQ0FBQztRQUMvQixDQUFDLENBQUMsQ0FBQztRQUVILE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNmLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDNUIsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVNLE1BQU0sQ0FBQyxTQUE0QixFQUFFLEVBQUUsR0FBRyxVQUFpQjtRQUNoRSxJQUFJLFFBQVEsR0FBRyxVQUFVLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDaEMsSUFBSSxPQUFPLFFBQVEsS0FBSyxVQUFVLEVBQUUsQ0FBQztZQUNuQyxJQUFJLFFBQVE7Z0JBQUUsVUFBVSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUN4QyxRQUFRLEdBQUcsSUFBSSxDQUFDO1FBQ2xCLENBQUM7UUFDRCxNQUFNLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFDL0MsTUFBTSxNQUFNLEdBQUcsSUFBSSxpQkFBTyxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsRUFBRSxNQUFNLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFFakUsTUFBTSxDQUFDLEVBQUUsQ0FBQyxTQUFTLEVBQUUsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDLENBQUM7UUFDbEQsTUFBTSxDQUFDLEVBQUUsQ0FBQyxTQUFTLEVBQUUsR0FBRyxFQUFFO1lBQ3hCLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUM5QixJQUFJLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztZQUM1QixJQUFJLENBQUMscUJBQXFCLEVBQUUsQ0FBQztZQUM3QixNQUFNLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUM5QixDQUFDLENBQUMsQ0FBQztRQUVILE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNmLE1BQU0sQ0FBQyx3QkFBd0IsRUFBRSxDQUFDO1FBQ2xDLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDNUIsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVNLFdBQVcsQ0FDaEIsU0FBNEIsRUFBRSxFQUM5QixHQUFHLFVBQWlCO1FBRXBCLElBQUksQ0FBQyw4QkFBOEIsR0FBRyxJQUFJLENBQUM7UUFDM0MsSUFBSSxJQUFJLENBQUMsV0FBVztZQUFFLElBQUEscUJBQVksRUFBQyxJQUFJLENBQUMsV0FBNkIsQ0FBQyxDQUFDO1FBRXZFLElBQUksUUFBUSxHQUFHLFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUNoQyxJQUFJLE9BQU8sUUFBUSxLQUFLLFVBQVUsRUFBRSxDQUFDO1lBQ25DLElBQUksUUFBUTtnQkFBRSxVQUFVLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBQ3hDLFFBQVEsR0FBRyxJQUFJLENBQUM7UUFDbEIsQ0FBQztRQUNELE1BQU0sR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUMsQ0FBQztRQUUvQyxNQUFNLElBQUksR0FBRyxHQUFHLEVBQUU7WUFDaEIsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDbEIsSUFBSSxJQUFJLENBQUMsV0FBVztvQkFBRSxJQUFBLHFCQUFZLEVBQUMsSUFBSSxDQUFDLFdBQTZCLENBQUMsQ0FBQztnQkFDdkUsSUFBSSxDQUFDLFdBQVcsR0FBRyxVQUFVLENBQzNCLEdBQUcsRUFBRTtvQkFDSCxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQzt5QkFDdkIsSUFBSSxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUU7d0JBQ2IsSUFBSSxPQUFPLFFBQVEsS0FBSyxVQUFVOzRCQUFFLFFBQVEsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7d0JBQ3pELElBQUksRUFBRSxDQUFDO29CQUNULENBQUMsQ0FBQzt5QkFDRCxLQUFLLENBQUMsQ0FBQyxHQUFVLEVBQUUsRUFBRTt3QkFDcEIsSUFBSSxPQUFPLFFBQVEsS0FBSyxVQUFVOzRCQUFFLFFBQVEsQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLENBQUM7d0JBQ3hELElBQUksRUFBRSxDQUFDO29CQUNULENBQUMsQ0FBQyxDQUFDO2dCQUNQLENBQUMsRUFDRCxDQUFDLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUMxQixDQUFDO1lBQ0osQ0FBQztRQUNILENBQUMsQ0FBQztRQUNGLElBQUksRUFBRSxDQUFDO0lBQ1QsQ0FBQztJQUVNLEtBQUs7UUFDVixJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNqQixJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFDZixPQUFPLENBQUMsS0FBSyxDQUFDLG9DQUFvQyxDQUFDLENBQUM7WUFDdEQsQ0FBQztZQUNELE9BQU8sT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLDJCQUFZLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQztRQUN6RCxDQUFDO1FBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUNwQixJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFDZixPQUFPLENBQUMsS0FBSyxDQUFDLCtDQUErQyxDQUFDLENBQUM7WUFDakUsQ0FBQztZQUNELE9BQU8sT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMvQixDQUFDO1FBRUQsSUFBSSxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDZixPQUFPLENBQUMsS0FBSyxDQUFDLGtEQUFrRCxDQUFDLENBQUM7UUFDcEUsQ0FBQztRQUVELElBQUksSUFBSSxDQUFDLHNCQUFzQixFQUFFLENBQUM7WUFDaEMsSUFBQSxxQkFBWSxFQUFDLElBQUksQ0FBQyxzQkFBd0MsQ0FBQyxDQUFDO1lBQzVELElBQUksSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO2dCQUNmLE9BQU8sQ0FBQyxLQUFLLENBQUMsMkNBQTJDLENBQUMsQ0FBQztZQUM3RCxDQUFDO1FBQ0gsQ0FBQztRQUVELElBQUEscUJBQVksRUFBQyxJQUFJLENBQUMsV0FBNkIsQ0FBQyxDQUFDO1FBQ2pELElBQUksSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ2YsT0FBTyxDQUFDLEtBQUssQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFDO1FBQ3BELENBQUM7UUFFRCxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7UUFDdEIsSUFBSSxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDZixPQUFPLENBQUMsS0FBSyxDQUFDLHFDQUFxQyxDQUFDLENBQUM7UUFDdkQsQ0FBQztRQUVELE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsRUFBRTtZQUM3QixJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQztZQUNwQixJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFDZixPQUFPLENBQUMsS0FBSyxDQUFDLGtEQUFrRCxDQUFDLENBQUM7WUFDcEUsQ0FBQztZQUVELElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUU7Z0JBQ2hDLElBQUksSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO29CQUNmLE9BQU8sQ0FBQyxLQUFLLENBQUMsd0NBQXdDLENBQUMsQ0FBQztnQkFDMUQsQ0FBQztnQkFDRCxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUN6QixJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQztnQkFDdEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUM7Z0JBQ3JCLElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO2dCQUN2QixPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDaEIsQ0FBQyxDQUFDLENBQUM7WUFFSCxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ3pCLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVPLFdBQVc7UUFDakIsSUFBSSxDQUFDLG9CQUFvQixFQUFFLENBQUM7UUFDNUIsT0FBTyxJQUFJLGlCQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ3JDLENBQUM7SUFFTyxvQkFBb0I7UUFDMUIsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQ3RCLENBQUM7SUFFTyxvQkFBb0I7UUFDMUIsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQ3RCLENBQUM7SUFFTyxjQUFjLENBQUMsTUFBZTtRQUNwQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ3RDLENBQUM7SUFFTyxnQkFBZ0IsQ0FBQyxNQUFlO1FBQ3RDLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEtBQUssTUFBTSxDQUFDLENBQUM7SUFDOUUsQ0FBQztJQUVPLGNBQWM7UUFDcEIsS0FBSyxNQUFNLE1BQU0sSUFBSSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztZQUM1QyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDaEIsQ0FBQztJQUNILENBQUM7SUFFTyxjQUFjO1FBQ3BCLElBQUksSUFBSSxDQUFDLFlBQVksS0FBSyxDQUFDO1lBQUUsT0FBTztRQUNwQyxJQUFJLElBQUksQ0FBQyxTQUFTLElBQUksQ0FBQyxJQUFJLENBQUMsOEJBQThCLEVBQUUsQ0FBQztZQUMzRCxJQUFJLElBQUksQ0FBQyxzQkFBc0I7Z0JBQzdCLElBQUEscUJBQVksRUFBQyxJQUFJLENBQUMsc0JBQXdDLENBQUMsQ0FBQztZQUM5RCxNQUFNLGdCQUFnQixHQUFHLEdBQUcsRUFBRTtnQkFDNUIsSUFBSSxDQUFDLHNCQUFzQixHQUFHLFVBQVUsQ0FDdEMsR0FBRyxFQUFFO29CQUNILElBQUksS0FBSyxHQUFHLElBQUksaUJBQU8sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7b0JBQ3hDLEtBQUssQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRTt3QkFDckIsS0FBSyxHQUFHLElBQUksQ0FBQztvQkFDZixDQUFDLENBQUMsQ0FBQztvQkFDSCxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxLQUFLLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztnQkFDcEUsQ0FBQyxFQUNELENBQUMsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQzFCLENBQUM7WUFDSixDQUFDLENBQUM7WUFDRixnQkFBZ0IsRUFBRSxDQUFDO1FBQ3JCLENBQUM7SUFDSCxDQUFDO0lBRU8scUJBQXFCO1FBQzNCLElBQUksSUFBSSxDQUFDLFlBQVksR0FBRyxDQUFDO1lBQUUsT0FBTztRQUNsQyxJQUFJLElBQUksQ0FBQyxzQkFBc0I7WUFDN0IsSUFBQSxxQkFBWSxFQUFDLElBQUksQ0FBQyxzQkFBd0MsQ0FBQyxDQUFDO0lBQ2hFLENBQUM7SUFFYSxLQUFLOztZQUNqQixJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQztZQUN2QixJQUFJLENBQUMsc0JBQXNCLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBRXhDLE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDO1lBRTNDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBTyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7O2dCQUMzQyxNQUFNLE9BQU8sR0FBRyxVQUFVLENBQUMsR0FBRyxFQUFFO29CQUM5QixNQUFNLENBQ0osSUFBSSwyQkFBWSxDQUFDLGNBQWMsRUFBRTt3QkFDL0IsT0FBTyxFQUNMLHFFQUFxRTtxQkFDeEUsQ0FBQyxDQUNILENBQUM7Z0JBQ0osQ0FBQyxFQUFFLGNBQWMsQ0FBQyxDQUFDO2dCQUVuQixJQUFJLENBQUM7b0JBQ0gsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsRUFBRTt3QkFDdEMsU0FBUyxJQUFJLENBQUMsSUFBSSxFQUFFO3dCQUNwQixhQUFhLElBQUksQ0FBQyxRQUFRLEVBQUU7cUJBQzdCLENBQUMsQ0FBQztvQkFFSCxJQUFBLHFCQUFZLEVBQUMsT0FBTyxDQUFDLENBQUM7b0JBRXRCLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQzt3QkFDdEIsSUFBSSxDQUFDLHNCQUFzQixFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQzt3QkFDeEMsT0FBTyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7b0JBQ3ZCLENBQUM7b0JBRUQsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsS0FBSSxNQUFBLElBQUksQ0FBQyxDQUFDLENBQUMsMENBQUUsR0FBRyxDQUFBLEVBQUUsQ0FBQzt3QkFDdEMsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQzt3QkFDeEIsTUFBTSxTQUFTLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sR0FBRyxFQUFFLENBQUMsQ0FBQzt3QkFDMUQsU0FBUyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQzt3QkFDeEQsU0FBUyxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDO3dCQUN0RSxNQUFNLElBQUksR0FDUixJQUFJLEdBQUcsTUFBTSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO3dCQUVsRSxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFOzRCQUN2QyxTQUFTLElBQUksQ0FBQyxJQUFJLEVBQUU7NEJBQ3BCLGFBQWEsSUFBSSxFQUFFO3lCQUNwQixDQUFDLENBQUM7d0JBRUgsSUFBQSxxQkFBWSxFQUFDLE9BQU8sQ0FBQyxDQUFDO3dCQUN0QixPQUFPLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztvQkFDdkIsQ0FBQztvQkFFRCxJQUFBLHFCQUFZLEVBQUMsT0FBTyxDQUFDLENBQUM7b0JBQ3RCLE9BQU8sTUFBTSxDQUFDLElBQUksMkJBQVksQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDO2dCQUMvQyxDQUFDO2dCQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7b0JBQ2IsSUFBQSxxQkFBWSxFQUFDLE9BQU8sQ0FBQyxDQUFDO29CQUN0QixJQUNFLE9BQU8sR0FBRyxDQUFDLE9BQU8sS0FBSyxRQUFRO3dCQUMvQixHQUFHLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQywrQkFBK0IsQ0FBQyxFQUNyRCxDQUFDO3dCQUNELE9BQU8sTUFBTSxDQUFDLElBQUksMkJBQVksQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDO29CQUMvQyxDQUFDO29CQUVELE9BQU8sTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUNyQixDQUFDO1lBQ0gsQ0FBQyxDQUFBLENBQUMsQ0FBQztRQUNMLENBQUM7S0FBQTtJQUVPLFlBQVksQ0FBQyxjQUFpQyxFQUFFLFVBQWlCO1FBQ3ZFLElBQUksT0FBTyxjQUFjLEtBQUssUUFBUTtZQUFFLGNBQWMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQzFFLEtBQUssSUFBSSxTQUFTLElBQUksVUFBVSxFQUFFLENBQUM7WUFDakMsSUFBSSxPQUFPLFNBQVMsS0FBSyxRQUFRO2dCQUFFLFNBQVMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQzNELElBQUksU0FBUyxDQUFDLE1BQU0sR0FBRyxDQUFDO2dCQUN0QixjQUFjLEdBQUcsY0FBYyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUN0RCxDQUFDO1FBQ0QsT0FBTyxjQUFjLENBQUM7SUFDeEIsQ0FBQztDQUNGO0FBamJELGtDQWliQyJ9