rocketmq-client-nodejs
Version:
RocketMQ Node.js Client
458 lines • 40.3 kB
JavaScript
"use strict";
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseClient = void 0;
const node_util_1 = require("node:util");
const node_crypto_1 = require("node:crypto");
const grpc_js_1 = require("@grpc/grpc-js");
const definition_pb_1 = require("../../proto/apache/rocketmq/v2/definition_pb");
const service_pb_1 = require("../../proto/apache/rocketmq/v2/service_pb");
const util_1 = require("../util");
const route_1 = require("../route");
const exception_1 = require("../exception");
const UserAgent_1 = require("./UserAgent");
const Logger_1 = require("./Logger");
const RpcClientManager_1 = require("./RpcClientManager");
const TelemetrySession_1 = require("./TelemetrySession");
const ClientId_1 = require("./ClientId");
const debug = (0, node_util_1.debuglog)('rocketmq-client-nodejs:client:BaseClient');
/**
* RocketMQ Base Client, Consumer and Producer should extends this class
*
* it handle:
* - RpcClient lifecycle, e.g: cleanup the idle clients
* - startup flow
* - periodic Task
*/
class BaseClient {
clientId = ClientId_1.ClientId.create();
sslEnabled;
#sessionCredentials;
namespace;
endpoints;
isolated = new Map();
requestTimeout;
topics = new Set();
topicRouteCache = new Map();
// In-flight route query futures to avoid duplicate queries for the same topic
inflightRouteFutures = new Map();
logger;
rpcClientManager;
#telemetrySessions = new Map();
#startupResolve;
#startupReject;
#timers = [];
#running = false;
/**
* Get the client type.
* Subclasses should override this method to return their specific client type.
*
* @return The client type identifier
*/
getClientType() {
return definition_pb_1.ClientType.CLIENT_TYPE_UNSPECIFIED;
}
constructor(options) {
// Validate required parameters
if (!options.endpoints) {
throw new TypeError('endpoints is required');
}
if (typeof options.endpoints !== 'string' || options.endpoints.trim().length === 0) {
throw new TypeError('endpoints must be a non-empty string');
}
// Validate optional parameters
if (options.requestTimeout !== undefined) {
if (typeof options.requestTimeout !== 'number' || options.requestTimeout < 1000) {
throw new RangeError('requestTimeout must be a number >= 1000ms');
}
}
this.logger = options.logger ?? (0, Logger_1.getDefaultLogger)();
this.sslEnabled = options.sslEnabled === true;
this.endpoints = new route_1.Endpoints(options.endpoints);
this.namespace = options.namespace ?? '';
this.#sessionCredentials = options.sessionCredentials;
// https://rocketmq.apache.org/docs/introduction/03limits/
// Default request timeout is 3000ms
this.requestTimeout = options.requestTimeout ?? 3000;
this.rpcClientManager = new RpcClientManager_1.RpcClientManager(this, this.logger);
if (options.topics) {
for (const topic of options.topics) {
if (topic && topic.trim().length > 0) {
this.topics.add(topic);
}
}
}
}
/**
* Startup flow
* https://github.com/apache/rocketmq-clients/blob/master/docs/workflow.md#startup
*/
async startup() {
this.logger.info('Begin to startup the rocketmq client, clientId=%s', this.clientId);
try {
await this.#startup();
}
catch (e) {
const err = new Error(`Startup the rocketmq client failed, clientId=${this.clientId}, error=${e}`);
this.logger.error(err);
err.cause = e;
throw err;
}
this.logger.info('Startup the rocketmq client successfully, clientId=%s', this.clientId);
}
async #startup() {
this.logger.info('Begin to execute startup flow, clientId=%s, topics=%d', this.clientId, this.topics.size);
// Fetch topic route with retry mechanism (align with Java client)
const maxAttempts = 3;
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await this.updateRoutes();
this.logger.info('Fetch topic route successfully during startup, clientId=%s, attempt=%d', this.clientId, attempt);
break;
}
catch (e) {
lastError = e;
if (attempt < maxAttempts) {
const backoffMs = 1000 * attempt; // Simple linear backoff: 1s, 2s, 3s
this.logger.warn('Fetch topic route failed during startup, will retry, clientId=%s, attempt=%d/%d, error=%s, backoff=%dms', this.clientId, attempt, maxAttempts, e instanceof Error ? e.message : String(e), backoffMs);
await this.sleep(backoffMs);
}
else {
this.logger.error('Fetch topic route failed after %d attempts, clientId=%s, error=%s', maxAttempts, this.clientId, e instanceof Error ? e.message : String(e));
}
}
}
if (lastError && !this.topicRouteCache.size) {
throw new Error(`Failed to fetch topic routes after ${maxAttempts} attempts`, { cause: lastError });
}
this.logger.info('Topic routes updated, clientId=%s', this.clientId);
// update topic route every 30s
this.#timers.push(setInterval(async () => {
try {
await this.updateRoutes();
}
catch (e) {
this.logger.error('Failed to update routes periodically, clientId=%s, error=%s', this.clientId, e instanceof Error ? e.message : String(e));
}
}, 30000));
// sync settings every 5m
this.#timers.push(setInterval(async () => {
try {
this.#syncSettings();
}
catch (e) {
this.logger.error('Failed to sync settings, clientId=%s, error=%s', this.clientId, e instanceof Error ? e.message : String(e));
}
}, 5 * 60000));
// heartbeat every 10s
this.#timers.push(setInterval(async () => {
this.#doHeartbeat();
}, 10000));
// doStats every 60s
// doStats()
if (this.topics.size > 0) {
this.logger.info('Waiting for first onSettingsCommand, clientId=%s', this.clientId);
// wait for this first onSettingsCommand call
// eslint-disable-next-line @typescript-eslint/no-unused-vars
await new Promise((resolve, reject) => {
this.#startupReject = reject;
this.#startupResolve = resolve;
});
this.logger.info('Received first onSettingsCommand, clientId=%s', this.clientId);
this.#startupReject = undefined;
this.#startupResolve = undefined;
}
this.#running = true;
}
isRunning() {
return this.#running;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async shutdown() {
this.logger.info('Begin to shutdown the rocketmq client, clientId=%s', this.clientId);
this.#running = false;
// 1. Clear all timers
while (this.#timers.length > 0) {
const timer = this.#timers.pop();
if (timer) {
clearInterval(timer);
}
}
// 2. Notify server termination
await this.#notifyClientTermination();
// 3. Release all telemetry sessions
this.logger.info('Begin to release all telemetry sessions, clientId=%s', this.clientId);
this.#releaseTelemetrySessions();
this.logger.info('Release all telemetry sessions successfully, clientId=%s', this.clientId);
// 4. Close RPC connections
this.rpcClientManager.close();
// 5. Clear caches
this.topicRouteCache.clear();
this.inflightRouteFutures.clear();
this.isolated.clear();
this.logger.info('Shutdown the rocketmq client successfully, clientId=%s', this.clientId);
this.logger.close && this.logger.close();
}
async #doHeartbeat() {
try {
const request = this.wrapHeartbeatRequest();
const endpointsList = this.getTotalRouteEndpoints();
if (endpointsList.length === 0) {
debug('No endpoints available for heartbeat, clientId=%s', this.clientId);
return;
}
for (const endpoints of endpointsList) {
try {
await this.rpcClientManager.heartbeat(endpoints, request, this.requestTimeout);
}
catch (e) {
// Log but don't throw - heartbeat is best-effort
this.logger.warn('Heartbeat failed for endpoints=%s, clientId=%s, error=%s', endpoints.facade, this.clientId, e instanceof Error ? e.message : String(e));
}
}
}
catch (e) {
this.logger.error('Unexpected error in heartbeat, clientId=%s, error=%s', this.clientId, e instanceof Error ? e.message : String(e));
}
}
#getTotalRouteEndpointsMap() {
const endpointsMap = new Map();
for (const topicRoute of this.topicRouteCache.values()) {
for (const endpoints of topicRoute.getTotalEndpoints()) {
endpointsMap.set(endpoints.facade, endpoints);
}
}
return endpointsMap;
}
getTotalRouteEndpoints() {
const endpointsMap = this.#getTotalRouteEndpointsMap();
return Array.from(endpointsMap.values());
}
findNewRouteEndpoints(endpointsList) {
const endpointsMap = this.#getTotalRouteEndpointsMap();
const newEndpoints = [];
for (const endpoints of endpointsList) {
if (!endpointsMap.has(endpoints.facade)) {
newEndpoints.push(endpoints);
}
}
return newEndpoints;
}
async updateRoutes() {
for (const topic of this.topics) {
await this.#fetchTopicRoute(topic);
}
}
async getRouteData(topic) {
// Check cache first
const cached = this.topicRouteCache.get(topic);
if (cached) {
return cached;
}
// Check if there's an in-flight request for this topic
const inFlight = this.inflightRouteFutures.get(topic);
if (inFlight) {
debug('Reusing in-flight route query for topic=%s', topic);
return inFlight;
}
// Add topic to topics set
this.topics.add(topic);
// Create new route query and cache the promise
const future = this.#fetchTopicRoute(topic);
this.inflightRouteFutures.set(topic, future);
try {
const result = await future;
return result;
}
finally {
// Clean up in-flight cache after completion (success or failure)
this.inflightRouteFutures.delete(topic);
}
}
async #fetchTopicRoute(topic) {
const req = new service_pb_1.QueryRouteRequest();
req.setTopic((0, util_1.createResource)(topic));
req.setEndpoints(this.endpoints.toProtobuf());
try {
const response = await this.rpcClientManager.queryRoute(this.endpoints, req, this.requestTimeout);
exception_1.StatusChecker.check(response.getStatus()?.toObject());
const topicRouteData = new route_1.TopicRouteData(response.getMessageQueuesList());
const newEndpoints = this.findNewRouteEndpoints(topicRouteData.getTotalEndpoints());
for (const endpoints of newEndpoints) {
// sync current settings to new endpoints
this.getTelemetrySession(endpoints).syncSettings();
}
this.topicRouteCache.set(topic, topicRouteData);
this.onTopicRouteDataUpdate(topic, topicRouteData);
debug('fetchTopicRoute topic=%o topicRouteData=%j', topic, topicRouteData);
return topicRouteData;
}
catch (e) {
const error = e instanceof Error ? e : new Error(String(e));
this.logger.error('Failed to fetch topic route, clientId=%s, topic=%s, endpoints=%s, error=%s', this.clientId, topic, this.endpoints.facade, error.message);
throw error;
}
}
#syncSettings() {
const command = this.settingsCommand();
for (const endpoints of this.getTotalRouteEndpoints()) {
this.telemetry(endpoints, command);
}
}
settingsCommand() {
const command = new service_pb_1.TelemetryCommand();
command.setSettings(this.getSettings().toProtobuf());
return command;
}
getTelemetrySession(endpoints) {
let session = this.#telemetrySessions.get(endpoints.facade);
if (!session) {
session = new TelemetrySession_1.TelemetrySession(this, endpoints, this.logger);
this.#telemetrySessions.set(endpoints.facade, session);
}
return session;
}
createTelemetryStream(endpoints) {
const metadata = this.getRequestMetadata();
return this.rpcClientManager.telemetry(endpoints, metadata);
}
telemetry(endpoints, command) {
this.getTelemetrySession(endpoints).write(command);
}
getRequestMetadata() {
// https://github.com/apache/rocketmq-clients/blob/master/docs/transport.md
// Transport Header
const metadata = new grpc_js_1.Metadata();
// version of protocol
metadata.set('x-mq-protocol', 'v2');
// client unique identifier: mbp@78774@2@3549a8wsr
metadata.set('x-mq-client-id', this.clientId);
// current timestamp: 20210309T195445Z, DATE_TIME_FORMAT = "yyyyMMdd'T'HHmmss'Z'"
const dateTime = (0, util_1.getRequestDateTime)();
metadata.set('x-mq-date-time', dateTime);
// request id for each gRPC header: f122a1e0-dbcf-4ca4-9db7-221903354be7
metadata.set('x-mq-request-id', (0, node_crypto_1.randomUUID)());
// language of client
// FIXME: java.lang.IllegalArgumentException: No enum constant org.apache.rocketmq.remoting.protocol.LanguageCode.nodejs
// https://github.com/apache/rocketmq/blob/master/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/LanguageCode.java
metadata.set('x-mq-language', 'HTTP');
// version of client
metadata.set('x-mq-client-version', UserAgent_1.UserAgent.INSTANCE.version);
if (this.namespace) {
metadata.set('x-mq-namespace', this.namespace);
}
if (this.#sessionCredentials) {
if (this.#sessionCredentials.securityToken) {
metadata.set('x-mq-session-token', this.#sessionCredentials.securityToken);
}
const signature = (0, util_1.sign)(this.#sessionCredentials.accessSecret, dateTime);
const authorization = `MQv2-HMAC-SHA1 Credential=${this.#sessionCredentials.accessKey}, SignedHeaders=x-mq-date-time, Signature=${signature}`;
metadata.set('authorization', authorization);
}
return metadata;
}
#releaseTelemetrySessions() {
for (const session of this.#telemetrySessions.values()) {
session.release();
}
this.#telemetrySessions.clear();
}
/**
* Notify remote that current client is prepared to be terminated.
*/
async #notifyClientTermination() {
this.logger.info('Notify remote that client is terminated, clientId=%s', this.clientId);
const request = this.wrapNotifyClientTerminationRequest();
for (const endpoints of this.getTotalRouteEndpoints()) {
await this.rpcClientManager.notifyClientTermination(endpoints, request, this.requestTimeout);
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onTopicRouteDataUpdate(_topic, _topicRouteData) {
// sub class can monitor topic route data change here
}
onUnknownCommand(endpoints, status) {
try {
exception_1.StatusChecker.check(status);
}
catch (err) {
this.logger.error('Get error status from telemetry session, status=%j, endpoints=%j, clientId=%s', status, endpoints, this.clientId);
this.#startupReject && this.#startupReject(err);
}
}
onSettingsCommand(_endpoints, settings) {
this.logger.info('Received settings command, clientId=%s, settings=%j', this.clientId, settings.toObject());
// final Metric metric = new Metric(settings.getMetric());
// clientMeterManager.reset(metric);
this.getSettings().sync(settings);
this.logger.info('Sync settings=%j, clientId=%s', this.getSettings(), this.clientId);
this.#startupResolve && this.#startupResolve();
}
onRecoverOrphanedTransactionCommand(_endpoints, command) {
this.logger.warn('Ignore orphaned transaction recovery command from remote, which is not expected, clientId=%s, command=%j', this.clientId, command.toObject());
// const telemetryCommand = new TelemetryCommand();
// telemetryCommand.setStatus(new Status().setCode(Code.NOT_IMPLEMENTED));
// telemetryCommand.setRecoverOrphanedTransactionCommand(new RecoverOrphanedTransactionCommand());
// this.telemetry(endpoints, telemetryCommand);
}
onVerifyMessageCommand(endpoints, command) {
const obj = command.toObject();
this.logger.warn('Ignore verify message command from remote, which is not expected, clientId=%s, command=%j', this.clientId, obj);
const telemetryCommand = new service_pb_1.TelemetryCommand();
telemetryCommand.setStatus(new definition_pb_1.Status().setCode(definition_pb_1.Code.NOT_IMPLEMENTED));
telemetryCommand.setVerifyMessageCommand(new service_pb_1.VerifyMessageCommand().setNonce(obj.nonce));
this.telemetry(endpoints, telemetryCommand);
}
onPrintThreadStackTraceCommand(endpoints, command) {
const obj = command.toObject();
this.logger.warn('Ignore orphaned transaction recovery command from remote, which is not expected, clientId=%s, command=%j', this.clientId, obj);
const nonce = obj.nonce;
const telemetryCommand = new service_pb_1.TelemetryCommand();
telemetryCommand.setThreadStackTrace(new service_pb_1.ThreadStackTrace().setThreadStackTrace('mock stack').setNonce(nonce));
telemetryCommand.setStatus(new definition_pb_1.Status().setCode(definition_pb_1.Code.OK));
this.telemetry(endpoints, telemetryCommand);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onReconnectEndpointsCommand(endpoints, _command) {
this.logger.info('Received reconnect endpoints command from remote, will refresh telemetry session, endpoints=%s, clientId=%s', endpoints, this.clientId);
// Refresh the telemetry session to use the latest endpoints
const session = this.getTelemetrySession(endpoints);
session.refresh();
}
/**
* Get the endpoints of this client.
*
* @return The endpoints
*/
getEndpoints() {
return this.endpoints;
}
/**
* Get the RPC client manager.
*
* @return The RPC client manager
*/
getRpcClientManager() {
return this.rpcClientManager;
}
}
exports.BaseClient = BaseClient;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQmFzZUNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jbGllbnQvQmFzZUNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHOzs7QUFFSCx5Q0FBcUM7QUFDckMsNkNBQXlDO0FBQ3pDLDJDQUF5QztBQUN6QyxnRkFLc0Q7QUFDdEQsMEVBVW1EO0FBQ25ELGtDQUFtRTtBQUNuRSxvQ0FBcUQ7QUFDckQsNENBQThEO0FBRTlELDJDQUF3QztBQUN4QyxxQ0FBcUQ7QUFFckQseURBQXNEO0FBQ3RELHlEQUFzRDtBQUN0RCx5Q0FBc0M7QUFFdEMsTUFBTSxLQUFLLEdBQUcsSUFBQSxvQkFBUSxFQUFDLDBDQUEwQyxDQUFDLENBQUM7QUFtQm5FOzs7Ozs7O0dBT0c7QUFDSCxNQUFzQixVQUFVO0lBQ3JCLFFBQVEsR0FBRyxtQkFBUSxDQUFDLE1BQU0sRUFBRSxDQUFDO0lBQzdCLFVBQVUsQ0FBVTtJQUNwQixtQkFBbUIsQ0FBc0I7SUFDekMsU0FBUyxDQUFTO0lBQ1IsU0FBUyxDQUFZO0lBQ3JCLFFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBcUIsQ0FBQztJQUN4QyxjQUFjLENBQVM7SUFDdkIsTUFBTSxHQUFHLElBQUksR0FBRyxFQUFVLENBQUM7SUFDM0IsZUFBZSxHQUFHLElBQUksR0FBRyxFQUEwQixDQUFDO0lBQ3ZFLDhFQUE4RTtJQUMzRCxvQkFBb0IsR0FBRyxJQUFJLEdBQUcsRUFBbUMsQ0FBQztJQUNsRSxNQUFNLENBQVU7SUFDaEIsZ0JBQWdCLENBQW1CO0lBQzdDLGtCQUFrQixHQUFHLElBQUksR0FBRyxFQUE0QixDQUFDO0lBQ2xFLGVBQWUsQ0FBYztJQUM3QixjQUFjLENBQXdCO0lBQ3RDLE9BQU8sR0FBcUIsRUFBRSxDQUFDO0lBQy9CLFFBQVEsR0FBRyxLQUFLLENBQUM7SUFFakI7Ozs7O09BS0c7SUFDTyxhQUFhO1FBQ3JCLE9BQU8sMEJBQVUsQ0FBQyx1QkFBdUIsQ0FBQztJQUM1QyxDQUFDO0lBRUQsWUFBWSxPQUEwQjtRQUNwQywrQkFBK0I7UUFDL0IsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUN2QixNQUFNLElBQUksU0FBUyxDQUFDLHVCQUF1QixDQUFDLENBQUM7UUFDL0MsQ0FBQztRQUNELElBQUksT0FBTyxPQUFPLENBQUMsU0FBUyxLQUFLLFFBQVEsSUFBSSxPQUFPLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNuRixNQUFNLElBQUksU0FBUyxDQUFDLHNDQUFzQyxDQUFDLENBQUM7UUFDOUQsQ0FBQztRQUVELCtCQUErQjtRQUMvQixJQUFJLE9BQU8sQ0FBQyxjQUFjLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDekMsSUFBSSxPQUFPLE9BQU8sQ0FBQyxjQUFjLEtBQUssUUFBUSxJQUFJLE9BQU8sQ0FBQyxjQUFjLEdBQUcsSUFBSSxFQUFFLENBQUM7Z0JBQ2hGLE1BQU0sSUFBSSxVQUFVLENBQUMsMkNBQTJDLENBQUMsQ0FBQztZQUNwRSxDQUFDO1FBQ0gsQ0FBQztRQUVELElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sSUFBSSxJQUFBLHlCQUFnQixHQUFFLENBQUM7UUFDbkQsSUFBSSxDQUFDLFVBQVUsR0FBRyxPQUFPLENBQUMsVUFBVSxLQUFLLElBQUksQ0FBQztRQUM5QyxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksaUJBQVMsQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDbEQsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxJQUFJLEVBQUUsQ0FBQztRQUN6QyxJQUFJLENBQUMsbUJBQW1CLEdBQUcsT0FBTyxDQUFDLGtCQUFrQixDQUFDO1FBQ3RELDBEQUEwRDtRQUMxRCxvQ0FBb0M7UUFDcEMsSUFBSSxDQUFDLGNBQWMsR0FBRyxPQUFPLENBQUMsY0FBYyxJQUFJLElBQUksQ0FBQztRQUNyRCxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsSUFBSSxtQ0FBZ0IsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2hFLElBQUksT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ25CLEtBQUssTUFBTSxLQUFLLElBQUksT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO2dCQUNuQyxJQUFJLEtBQUssSUFBSSxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO29CQUNyQyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztnQkFDekIsQ0FBQztZQUNILENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7T0FHRztJQUNILEtBQUssQ0FBQyxPQUFPO1FBQ1gsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsbURBQW1ELEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3JGLElBQUksQ0FBQztZQUNILE1BQU0sSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ3hCLENBQUM7UUFBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1lBQ1gsTUFBTSxHQUFHLEdBQUcsSUFBSSxLQUFLLENBQUMsZ0RBQWdELElBQUksQ0FBQyxRQUFRLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNuRyxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUN2QixHQUFHLENBQUMsS0FBSyxHQUFHLENBQUMsQ0FBQztZQUNkLE1BQU0sR0FBRyxDQUFDO1FBQ1osQ0FBQztRQUNELElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLHVEQUF1RCxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUMzRixDQUFDO0lBRUQsS0FBSyxDQUFDLFFBQVE7UUFDWixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyx1REFBdUQsRUFDdEUsSUFBSSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBRW5DLGtFQUFrRTtRQUNsRSxNQUFNLFdBQVcsR0FBRyxDQUFDLENBQUM7UUFDdEIsSUFBSSxTQUE0QixDQUFDO1FBQ2pDLEtBQUssSUFBSSxPQUFPLEdBQUcsQ0FBQyxFQUFFLE9BQU8sSUFBSSxXQUFXLEVBQUUsT0FBTyxFQUFFLEVBQUUsQ0FBQztZQUN4RCxJQUFJLENBQUM7Z0JBQ0gsTUFBTSxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7Z0JBQzFCLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLHdFQUF3RSxFQUN2RixJQUFJLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUMxQixNQUFNO1lBQ1IsQ0FBQztZQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7Z0JBQ1gsU0FBUyxHQUFHLENBQVUsQ0FBQztnQkFDdkIsSUFBSSxPQUFPLEdBQUcsV0FBVyxFQUFFLENBQUM7b0JBQzFCLE1BQU0sU0FBUyxHQUFHLElBQUksR0FBRyxPQUFPLENBQUMsQ0FBQyxvQ0FBb0M7b0JBQ3RFLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLHlHQUF5RyxFQUN4SCxJQUFJLENBQUMsUUFBUSxFQUFFLE9BQU8sRUFBRSxXQUFXLEVBQUUsQ0FBQyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxDQUFDO29CQUM5RixNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUM7Z0JBQzlCLENBQUM7cUJBQU0sQ0FBQztvQkFDTixJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxtRUFBbUUsRUFDbkYsV0FBVyxFQUFFLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQzVFLENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQztRQUVELElBQUksU0FBUyxJQUFJLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUM1QyxNQUFNLElBQUksS0FBSyxDQUFDLHNDQUFzQyxXQUFXLFdBQVcsRUFBRSxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO1FBQ3RHLENBQUM7UUFFRCxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxtQ0FBbUMsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDckUsK0JBQStCO1FBQy9CLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLElBQUksRUFBRTtZQUN2QyxJQUFJLENBQUM7Z0JBQ0gsTUFBTSxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7WUFDNUIsQ0FBQztZQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7Z0JBQ1gsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsNkRBQTZELEVBQzdFLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDL0QsQ0FBQztRQUNILENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBRVgseUJBQXlCO1FBQ3pCLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLElBQUksRUFBRTtZQUN2QyxJQUFJLENBQUM7Z0JBQ0gsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1lBQ3ZCLENBQUM7WUFBQyxPQUFPLENBQUMsRUFBRSxDQUFDO2dCQUNYLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLGdEQUFnRCxFQUNoRSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQy9ELENBQUM7UUFDSCxDQUFDLEVBQUUsQ0FBQyxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUM7UUFFZixzQkFBc0I7UUFDdEIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLEtBQUssSUFBSSxFQUFFO1lBQ3ZDLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQztRQUN0QixDQUFDLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUVYLG9CQUFvQjtRQUNwQixZQUFZO1FBRVosSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUN6QixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxrREFBa0QsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDcEYsNkNBQTZDO1lBQzdDLDZEQUE2RDtZQUM3RCxNQUFNLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO2dCQUMxQyxJQUFJLENBQUMsY0FBYyxHQUFHLE1BQU0sQ0FBQztnQkFDN0IsSUFBSSxDQUFDLGVBQWUsR0FBRyxPQUFPLENBQUM7WUFDakMsQ0FBQyxDQUFDLENBQUM7WUFDSCxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQywrQ0FBK0MsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDakYsSUFBSSxDQUFDLGNBQWMsR0FBRyxTQUFTLENBQUM7WUFDaEMsSUFBSSxDQUFDLGVBQWUsR0FBRyxTQUFTLENBQUM7UUFDbkMsQ0FBQztRQUNELElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFDO0lBQ3ZCLENBQUM7SUFFRCxTQUFTO1FBQ1AsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDO0lBQ3ZCLENBQUM7SUFFUyxLQUFLLENBQUMsRUFBVTtRQUN4QixPQUFPLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ3pELENBQUM7SUFFRCxLQUFLLENBQUMsUUFBUTtRQUNaLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLG9EQUFvRCxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUN0RixJQUFJLENBQUMsUUFBUSxHQUFHLEtBQUssQ0FBQztRQUV0QixzQkFBc0I7UUFDdEIsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUMvQixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxDQUFDO1lBQ2pDLElBQUksS0FBSyxFQUFFLENBQUM7Z0JBQ1YsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQ3ZCLENBQUM7UUFDSCxDQUFDO1FBRUQsK0JBQStCO1FBQy9CLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixFQUFFLENBQUM7UUFFdEMsb0NBQW9DO1FBQ3BDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLHNEQUFzRCxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUN4RixJQUFJLENBQUMseUJBQXlCLEVBQUUsQ0FBQztRQUNqQyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQywwREFBMEQsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFFNUYsMkJBQTJCO1FBQzNCLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUU5QixrQkFBa0I7UUFDbEIsSUFBSSxDQUFDLGVBQWUsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUM3QixJQUFJLENBQUMsb0JBQW9CLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDbEMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUV0QixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyx3REFBd0QsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDMUYsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUMzQyxDQUFDO0lBRUQsS0FBSyxDQUFDLFlBQVk7UUFDaEIsSUFBSSxDQUFDO1lBQ0gsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLG9CQUFvQixFQUFFLENBQUM7WUFDNUMsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLHNCQUFzQixFQUFFLENBQUM7WUFDcEQsSUFBSSxhQUFhLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO2dCQUMvQixLQUFLLENBQUMsbURBQW1ELEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO2dCQUMxRSxPQUFPO1lBQ1QsQ0FBQztZQUNELEtBQUssTUFBTSxTQUFTLElBQUksYUFBYSxFQUFFLENBQUM7Z0JBQ3RDLElBQUksQ0FBQztvQkFDSCxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsU0FBUyxFQUFFLE9BQU8sRUFBRSxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUM7Z0JBQ2pGLENBQUM7Z0JBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztvQkFDWCxpREFBaUQ7b0JBQ2pELElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLDBEQUEwRCxFQUN6RSxTQUFTLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQ2pGLENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQztRQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDWCxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxzREFBc0QsRUFDdEUsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUMvRCxDQUFDO0lBQ0gsQ0FBQztJQUVELDBCQUEwQjtRQUN4QixNQUFNLFlBQVksR0FBRyxJQUFJLEdBQUcsRUFBcUIsQ0FBQztRQUNsRCxLQUFLLE1BQU0sVUFBVSxJQUFJLElBQUksQ0FBQyxlQUFlLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQztZQUN2RCxLQUFLLE1BQU0sU0FBUyxJQUFJLFVBQVUsQ0FBQyxpQkFBaUIsRUFBRSxFQUFFLENBQUM7Z0JBQ3ZELFlBQVksQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxTQUFTLENBQUMsQ0FBQztZQUNoRCxDQUFDO1FBQ0gsQ0FBQztRQUNELE9BQU8sWUFBWSxDQUFDO0lBQ3RCLENBQUM7SUFFUyxzQkFBc0I7UUFDOUIsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLDBCQUEwQixFQUFFLENBQUM7UUFDdkQsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBQzNDLENBQUM7SUFFUyxxQkFBcUIsQ0FBQyxhQUEwQjtRQUN4RCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsMEJBQTBCLEVBQUUsQ0FBQztRQUN2RCxNQUFNLFlBQVksR0FBZ0IsRUFBRSxDQUFDO1FBQ3JDLEtBQUssTUFBTSxTQUFTLElBQUksYUFBYSxFQUFFLENBQUM7WUFDdEMsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7Z0JBQ3hDLFlBQVksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDL0IsQ0FBQztRQUNILENBQUM7UUFDRCxPQUFPLFlBQVksQ0FBQztJQUN0QixDQUFDO0lBRVMsS0FBSyxDQUFDLFlBQVk7UUFDMUIsS0FBSyxNQUFNLEtBQUssSUFBSSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDaEMsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDckMsQ0FBQztJQUNILENBQUM7SUFFUyxLQUFLLENBQUMsWUFBWSxDQUFDLEtBQWE7UUFDeEMsb0JBQW9CO1FBQ3BCLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxlQUFlLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQy9DLElBQUksTUFBTSxFQUFFLENBQUM7WUFDWCxPQUFPLE1BQU0sQ0FBQztRQUNoQixDQUFDO1FBRUQsdURBQXVEO1FBQ3ZELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDdEQsSUFBSSxRQUFRLEVBQUUsQ0FBQztZQUNiLEtBQUssQ0FBQyw0Q0FBNEMsRUFBRSxLQUFLLENBQUMsQ0FBQztZQUMzRCxPQUFPLFFBQVEsQ0FBQztRQUNsQixDQUFDO1FBRUQsMEJBQTBCO1FBQzFCLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBRXZCLCtDQUErQztRQUMvQyxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDNUMsSUFBSSxDQUFDLG9CQUFvQixDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFN0MsSUFBSSxDQUFDO1lBQ0gsTUFBTSxNQUFNLEdBQUcsTUFBTSxNQUFNLENBQUM7WUFDNUIsT0FBTyxNQUFNLENBQUM7UUFDaEIsQ0FBQztnQkFBUyxDQUFDO1lBQ1QsaUVBQWlFO1lBQ2pFLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDMUMsQ0FBQztJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsZ0JBQWdCLENBQUMsS0FBYTtRQUNsQyxNQUFNLEdBQUcsR0FBRyxJQUFJLDhCQUFpQixFQUFFLENBQUM7UUFDcEMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxJQUFBLHFCQUFjLEVBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUNwQyxHQUFHLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQztRQUU5QyxJQUFJLENBQUM7WUFDSCxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxHQUFHLEVBQUUsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFDO1lBQ2xHLHlCQUFhLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxTQUFTLEVBQUUsRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDO1lBQ3RELE1BQU0sY0FBYyxHQUFHLElBQUksc0JBQWMsQ0FBQyxRQUFRLENBQUMsb0JBQW9CLEVBQUUsQ0FBQyxDQUFDO1lBQzNFLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxjQUFjLENBQUMsaUJBQWlCLEVBQUUsQ0FBQyxDQUFDO1lBQ3BGLEtBQUssTUFBTSxTQUFTLElBQUksWUFBWSxFQUFFLENBQUM7Z0JBQ3JDLHlDQUF5QztnQkFDekMsSUFBSSxDQUFDLG1CQUFtQixDQUFDLFNBQVMsQ0FBQyxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3JELENBQUM7WUFDRCxJQUFJLENBQUMsZUFBZSxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsY0FBYyxDQUFDLENBQUM7WUFDaEQsSUFBSSxDQUFDLHNCQUFzQixDQUFDLEtBQUssRUFBRSxjQUFjLENBQUMsQ0FBQztZQUNuRCxLQUFLLENBQUMsNENBQTRDLEVBQUUsS0FBSyxFQUFFLGNBQWMsQ0FBQyxDQUFDO1lBQzNFLE9BQU8sY0FBYyxDQUFDO1FBQ3hCLENBQUM7UUFBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1lBQ1gsTUFBTSxLQUFLLEdBQUcsQ0FBQyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUM1RCxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyw0RUFBNEUsRUFDNUYsSUFBSSxDQUFDLFFBQVEsRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQzlELE1BQU0sS0FBSyxDQUFDO1FBQ2QsQ0FBQztJQUNILENBQUM7SUFFRCxhQUFhO1FBQ1gsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1FBQ3ZDLEtBQUssTUFBTSxTQUFTLElBQUksSUFBSSxDQUFDLHNCQUFzQixFQUFFLEVBQUUsQ0FBQztZQUN0RCxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUNyQyxDQUFDO0lBQ0gsQ0FBQztJQUVELGVBQWU7UUFDYixNQUFNLE9BQU8sR0FBRyxJQUFJLDZCQUFnQixFQUFFLENBQUM7UUFDdkMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQztRQUNyRCxPQUFPLE9BQU8sQ0FBQztJQUNqQixDQUFDO0lBRUQsbUJBQW1CLENBQUMsU0FBb0I7UUFDdEMsSUFBSSxPQUFPLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDNUQsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ2IsT0FBTyxHQUFHLElBQUksbUNBQWdCLENBQUMsSUFBSSxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDN0QsSUFBSSxDQUFDLGtCQUFrQixDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ3pELENBQUM7UUFDRCxPQUFPLE9BQU8sQ0FBQztJQUNqQixDQUFDO0lBRUQscUJBQXFCLENBQUMsU0FBb0I7UUFDeEMsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7UUFDM0MsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxRQUFRLENBQUMsQ0FBQztJQUM5RCxDQUFDO0lBRUQsU0FBUyxDQUFDLFNBQW9CLEVBQUUsT0FBeUI7UUFDdkQsSUFBSSxDQUFDLG1CQUFtQixDQUFDLFNBQVMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNyRCxDQUFDO0lBRUQsa0JBQWtCO1FBQ2hCLDJFQUEyRTtRQUMzRSxtQkFBbUI7UUFDbkIsTUFBTSxRQUFRLEdBQUcsSUFBSSxrQkFBUSxFQUFFLENBQUM7UUFDaEMsc0JBQXNCO1FBQ3RCLFFBQVEsQ0FBQyxHQUFHLENBQUMsZUFBZSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ3BDLGtEQUFrRDtRQUNsRCxRQUFRLENBQUMsR0FBRyxDQUFDLGdCQUFnQixFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUM5QyxpRkFBaUY7UUFDakYsTUFBTSxRQUFRLEdBQUcsSUFBQSx5QkFBa0IsR0FBRSxDQUFDO1FBQ3RDLFFBQVEsQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFDekMsd0VBQXdFO1FBQ3hFLFFBQVEsQ0FBQyxHQUFHLENBQUMsaUJBQWlCLEVBQUUsSUFBQSx3QkFBVSxHQUFFLENBQUMsQ0FBQztRQUM5QyxxQkFBcUI7UUFDckIsd0hBQXdIO1FBQ3hILGdJQUFnSTtRQUNoSSxRQUFRLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUN0QyxvQkFBb0I7UUFDcEIsUUFBUSxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsRUFBRSxxQkFBUyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNoRSxJQUFJLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUNuQixRQUFRLENBQUMsR0FBRyxDQUFDLGdCQUFnQixFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUNqRCxDQUFDO1FBQ0QsSUFBSSxJQUFJLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztZQUM3QixJQUFJLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxhQUFhLEVBQUUsQ0FBQztnQkFDM0MsUUFBUSxDQUFDLEdBQUcsQ0FBQyxvQkFBb0IsRUFBRSxJQUFJLENBQUMsbUJBQW1CLENBQUMsYUFBYSxDQUFDLENBQUM7WUFDN0UsQ0FBQztZQUNELE1BQU0sU0FBUyxHQUFHLElBQUEsV0FBSSxFQUFDLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxZQUFZLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDeEUsTUFBTSxhQUFhLEdBQUcsNkJBQTZCLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxTQUFTLDZDQUE2QyxTQUFTLEVBQUUsQ0FBQztZQUM5SSxRQUFRLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxhQUFhLENBQUMsQ0FBQztRQUMvQyxDQUFDO1FBQ0QsT0FBTyxRQUFRLENBQUM7SUFDbEIsQ0FBQztJQWNELHlCQUF5QjtRQUN2QixLQUFLLE1BQU0sT0FBTyxJQUFJLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDO1lBQ3ZELE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNwQixDQUFDO1FBQ0QsSUFBSSxDQUFDLGtCQUFrQixDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ2xDLENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyx3QkFBd0I7UUFDNUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsc0RBQXNELEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3hGLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxrQ0FBa0MsRUFBRSxDQUFDO1FBQzFELEtBQUssTUFBTSxTQUFTLElBQUksSUFBSSxDQUFDLHNCQUFzQixFQUFFLEVBQUUsQ0FBQztZQUN0RCxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyx1QkFBdUIsQ0FBQyxTQUFTLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztRQUMvRixDQUFDO0lBQ0gsQ0FBQztJQUVELDZEQUE2RDtJQUNuRCxzQkFBc0IsQ0FBQyxNQUFjLEVBQUUsZUFBK0I7UUFDOUUscURBQXFEO0lBQ3ZELENBQUM7SUFFRCxnQkFBZ0IsQ0FBQyxTQUFvQixFQUFFLE1BQXVCO1FBQzVELElBQUksQ0FBQztZQUNILHlCQUFhLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzlCLENBQUM7UUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1lBQ2IsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsK0VBQStFLEVBQy9GLE1BQU0sRUFBRSxTQUFTLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBQ3BDLElBQUksQ0FBQyxjQUFjLElBQUksSUFBSSxDQUFDLGNBQWMsQ0FBQyxHQUFzQixDQUFDLENBQUM7UUFDckUsQ0FBQztJQUNILENBQUM7SUFFRCxpQkFBaUIsQ0FBQyxVQUFxQixFQUFFLFFBQW9CO1FBQzNELElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLHFEQUFxRCxFQUNwRSxJQUFJLENBQUMsUUFBUSxFQUFFLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQ3RDLDBEQUEwRDtRQUMxRCxvQ0FBb0M7UUFDcEMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNsQyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQywrQkFBK0IsRUFBRSxJQUFJLENBQUMsV0FBVyxFQUFFLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3JGLElBQUksQ0FBQyxlQUFlLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO0lBQ2pELENBQUM7SUFFRCxtQ0FBbUMsQ0FBQyxVQUFxQixFQUFFLE9BQTBDO1FBQ25HLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLDBHQUEwRyxFQUN6SCxJQUFJLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQ3JDLG1EQUFtRDtRQUNuRCwwRUFBMEU7UUFDMUUsa0dBQWtHO1FBQ2xHLCtDQUErQztJQUNqRCxDQUFDO0lBRUQsc0JBQXNCLENBQUMsU0FBb0IsRUFBRSxPQUE2QjtRQUN4RSxNQUFNLEdBQUcsR0FBRyxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDL0IsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsMkZBQTJGLEVBQzFHLElBQUksQ0FBQyxRQUFRLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDdEIsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLDZCQUFnQixFQUFFLENBQUM7UUFDaEQsZ0JBQWdCLENBQUMsU0FBUyxDQUFDLElBQUksc0JBQU0sRUFBRSxDQUFDLE9BQU8sQ0FBQyxvQkFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUM7UUFDdkUsZ0JBQWdCLENBQUMsdUJBQXVCLENBQUMsSUFBSSxpQ0FBb0IsRUFBRSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUN6RixJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO0lBQzlDLENBQUM7SUFFRCw4QkFBOEIsQ0FBQyxTQUFvQixFQUFFLE9BQXFDO1FBQ3hGLE1BQU0sR0FBRyxHQUFHLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUMvQixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQywwR0FBMEcsRUFDekgsSUFBSSxDQUFDLFFBQVEsRUFBRSxHQUFHLENBQUMsQ0FBQztRQUN0QixNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDO1FBQ3hCLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSw2QkFBZ0IsRUFBRSxDQUFDO1FBQ2hELGdCQUFnQixDQUFDLG1CQUFtQixDQUFDLElBQUksNkJBQWdCLEVBQUUsQ0FBQyxtQkFBbUIsQ0FBQyxZQUFZLENBQUMsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUMvRyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsSUFBSSxzQkFBTSxFQUFFLENBQUMsT0FBTyxDQUFDLG9CQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUMxRCxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO0lBQzlDLENBQUM7SUFFRCw2REFBNkQ7SUFDN0QsMkJBQTJCLENBQUMsU0FBb0IsRUFBRSxRQUFtQztRQUNuRixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyw2R0FBNkcsRUFDNUgsU0FBUyxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUM1Qiw0REFBNEQ7UUFDNUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLG1CQUFtQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ3BELE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUNwQixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILFlBQVk7UUFDVixPQUFPLElBQUksQ0FBQyxTQUFTLENBQUM7SUFDeEIsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxtQkFBbUI7UUFDakIsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUM7SUFDL0IsQ0FBQztDQUNGO0FBbGVELGdDQWtlQyJ9