@apiclient.xyz/docker
Version:
Provides easy communication with Docker remote API from Node.js, with TypeScript support.
797 lines • 62.4 kB
JavaScript
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import * as interfaces from './interfaces/index.js';
import { DockerContainer } from './classes.container.js';
import { DockerNetwork } from './classes.network.js';
import { DockerService } from './classes.service.js';
import { DockerSecret } from './classes.secret.js';
import { logger } from './logger.js';
import { DockerImageStore } from './classes.imagestore.js';
import { DockerImage } from './classes.image.js';
export class DockerHost {
/**
* the constructor to instantiate a new docker sock instance
* @param pathArg
*/
constructor(optionsArg) {
this.registryToken = '';
this.options = {
...{
imageStoreDir: plugins.path.join(paths.nogitDir, 'temp-docker-image-store'),
},
...optionsArg,
};
let pathToUse;
if (optionsArg.socketPath) {
pathToUse = optionsArg.socketPath;
}
else if (process.env.DOCKER_HOST) {
pathToUse = process.env.DOCKER_HOST;
}
else if (process.env.CI) {
pathToUse = 'http://docker:2375/';
}
else {
pathToUse = 'http://unix:/var/run/docker.sock:';
}
if (pathToUse.startsWith('unix:///')) {
pathToUse = pathToUse.replace('unix://', 'http://unix:');
}
if (pathToUse.endsWith('.sock')) {
pathToUse = pathToUse.replace('.sock', '.sock:');
}
console.log(`using docker sock at ${pathToUse}`);
this.socketPath = pathToUse;
this.imageStore = new DockerImageStore({
bucketDir: null,
localDirPath: this.options.imageStoreDir,
});
}
async start() {
await this.imageStore.start();
}
async stop() {
await this.imageStore.stop();
if (this.smartBucket) {
this.smartBucket.storageClient.destroy();
}
}
/**
* Ping the Docker daemon to check if it's running and accessible
* @returns Promise that resolves if Docker is available, rejects otherwise
* @throws Error if Docker ping fails
*/
async ping() {
const response = await this.request('GET', '/_ping');
if (response.statusCode !== 200) {
throw new Error(`Docker ping failed with status ${response.statusCode}`);
}
}
/**
* Get Docker daemon version information
* @returns Version info including Docker version, API version, OS, architecture, etc.
*/
async getVersion() {
const response = await this.request('GET', '/version');
return response.body;
}
/**
* authenticate against a registry
* @param userArg
* @param passArg
*/
async auth(authData) {
const response = await this.request('POST', '/auth', authData);
if (response.body.Status !== 'Login Succeeded') {
console.log(`Login failed with ${response.body.Status}`);
throw new Error(response.body.Status);
}
console.log(response.body.Status);
this.registryToken = plugins.smartstring.base64.encode(plugins.smartjson.stringify(authData));
}
/**
* gets the token from the .docker/config.json file for GitLab registry
*/
async getAuthTokenFromDockerConfig(registryUrlArg) {
const dockerConfigPath = plugins.smartpath.get.home('~/.docker/config.json');
const configObject = JSON.parse(plugins.fs.readFileSync(dockerConfigPath, 'utf8'));
const gitlabAuthBase64 = configObject.auths[registryUrlArg].auth;
const gitlabAuth = plugins.smartstring.base64.decode(gitlabAuthBase64);
const gitlabAuthArray = gitlabAuth.split(':');
await this.auth({
username: gitlabAuthArray[0],
password: gitlabAuthArray[1],
serveraddress: registryUrlArg,
});
}
// ==============
// NETWORKS - Public Factory API
// ==============
/**
* Lists all networks
*/
async listNetworks() {
return await DockerNetwork._list(this);
}
/**
* Gets a network by name
*/
async getNetworkByName(networkNameArg) {
return await DockerNetwork._fromName(this, networkNameArg);
}
/**
* Creates a network
*/
async createNetwork(descriptor) {
return await DockerNetwork._create(this, descriptor);
}
// ==============
// CONTAINERS - Public Factory API
// ==============
/**
* Lists all containers
*/
async listContainers() {
return await DockerContainer._list(this);
}
/**
* Gets a container by ID
* Returns undefined if container does not exist
*/
async getContainerById(containerId) {
return await DockerContainer._fromId(this, containerId);
}
/**
* Creates a container
*/
async createContainer(descriptor) {
return await DockerContainer._create(this, descriptor);
}
// ==============
// SERVICES - Public Factory API
// ==============
/**
* Lists all services
*/
async listServices() {
return await DockerService._list(this);
}
/**
* Gets a service by name
*/
async getServiceByName(serviceName) {
return await DockerService._fromName(this, serviceName);
}
/**
* Creates a service
*/
async createService(descriptor) {
return await DockerService._create(this, descriptor);
}
// ==============
// IMAGES - Public Factory API
// ==============
/**
* Lists all images
*/
async listImages() {
return await DockerImage._list(this);
}
/**
* Gets an image by name
*/
async getImageByName(imageNameArg) {
return await DockerImage._fromName(this, imageNameArg);
}
/**
* Creates an image from a registry
*/
async createImageFromRegistry(descriptor) {
return await DockerImage._createFromRegistry(this, {
creationObject: descriptor,
});
}
/**
* Creates an image from a tar stream
*/
async createImageFromTarStream(tarStream, descriptor) {
return await DockerImage._createFromTarStream(this, {
creationObject: descriptor,
tarStream: tarStream,
});
}
/**
* Prune unused images
* @param options Optional filters (dangling, until, label)
* @returns Object with deleted images and space reclaimed
*/
async pruneImages(options) {
const filters = options?.filters || {};
// Add dangling filter if specified
if (options?.dangling !== undefined) {
filters.dangling = [options.dangling.toString()];
}
let route = '/images/prune';
if (filters && Object.keys(filters).length > 0) {
route += `?filters=${encodeURIComponent(JSON.stringify(filters))}`;
}
const response = await this.request('POST', route);
return response.body;
}
/**
* Builds an image from a Dockerfile
*/
async buildImage(imageTag) {
return await DockerImage._build(this, imageTag);
}
// ==============
// SECRETS - Public Factory API
// ==============
/**
* Lists all secrets
*/
async listSecrets() {
return await DockerSecret._list(this);
}
/**
* Gets a secret by name
*/
async getSecretByName(secretName) {
return await DockerSecret._fromName(this, secretName);
}
/**
* Gets a secret by ID
*/
async getSecretById(secretId) {
return await DockerSecret._fromId(this, secretId);
}
/**
* Creates a secret
*/
async createSecret(descriptor) {
return await DockerSecret._create(this, descriptor);
}
// ==============
// IMAGE STORE - Public API
// ==============
/**
* Stores an image in the local image store
*/
async storeImage(imageName, tarStream) {
return await this.imageStore.storeImage(imageName, tarStream);
}
/**
* Retrieves an image from the local image store
*/
async retrieveImage(imageName) {
return await this.imageStore.getImage(imageName);
}
/**
*
*/
async getEventObservable() {
const response = await this.requestStreaming('GET', '/events');
// requestStreaming now returns Node.js stream, not web stream
const nodeStream = response;
return plugins.rxjs.Observable.create((observer) => {
nodeStream.on('data', (data) => {
const eventString = data.toString();
try {
const eventObject = JSON.parse(eventString);
observer.next(eventObject);
}
catch (e) {
console.log(e);
}
});
nodeStream.on('error', (err) => {
// Connection resets are expected when the stream is destroyed
if (err.code !== 'ECONNRESET') {
observer.error(err);
}
});
return () => {
nodeStream.destroy();
};
});
}
/**
* activates docker swarm
*/
async activateSwarm(addvertisementIpArg) {
// determine advertisement address
let addvertisementIp = '';
if (addvertisementIpArg) {
addvertisementIp = addvertisementIpArg;
}
else {
try {
const smartnetworkInstance = new plugins.smartnetwork.SmartNetwork();
const defaultGateway = await smartnetworkInstance.getDefaultGateway();
if (defaultGateway) {
addvertisementIp = defaultGateway.ipv4.address;
}
}
catch (err) {
// Failed to determine default gateway (e.g. in Deno without --allow-run)
// Docker will auto-detect the advertise address
}
}
const response = await this.request('POST', '/swarm/init', {
ListenAddr: '0.0.0.0:2377',
AdvertiseAddr: addvertisementIp,
DataPathPort: 4789,
DefaultAddrPool: ['10.10.0.0/8', '20.20.0.0/8'],
SubnetSize: 24,
ForceNewCluster: false,
});
if (response.statusCode === 200) {
logger.log('info', 'created Swam succesfully');
}
else {
logger.log('error', 'could not initiate swarm');
}
}
/**
* fire a request
*/
async request(methodArg, routeArg, dataArg = {}) {
const requestUrl = `${this.socketPath}${routeArg}`;
// Build the request using the fluent API
const smartRequest = plugins.smartrequest.SmartRequest.create()
.url(requestUrl)
.header('Content-Type', 'application/json')
.header('X-Registry-Auth', this.registryToken)
.header('Host', 'docker.sock')
.options({ keepAlive: false });
// Add body for methods that support it
if (dataArg && Object.keys(dataArg).length > 0) {
smartRequest.json(dataArg);
}
// Execute the request based on method
let response;
switch (methodArg.toUpperCase()) {
case 'GET':
response = await smartRequest.get();
break;
case 'POST':
response = await smartRequest.post();
break;
case 'PUT':
response = await smartRequest.put();
break;
case 'DELETE':
response = await smartRequest.delete();
break;
default:
throw new Error(`Unsupported HTTP method: ${methodArg}`);
}
// Parse the response body based on content type
let body;
const contentType = response.headers['content-type'] || '';
// Docker's streaming endpoints (like /images/create) return newline-delimited JSON
// which can't be parsed as a single JSON object
const isStreamingEndpoint = routeArg.includes('/images/create') ||
routeArg.includes('/images/load') ||
routeArg.includes('/build');
if (contentType.includes('application/json') && !isStreamingEndpoint) {
body = await response.json();
}
else {
body = await response.text();
// Try to parse as JSON if it looks like JSON and is not a streaming response
if (!isStreamingEndpoint &&
body &&
(body.startsWith('{') || body.startsWith('['))) {
try {
body = JSON.parse(body);
}
catch {
// Keep as text if parsing fails
}
}
}
// Create a response object compatible with existing code
const legacyResponse = {
statusCode: response.status,
body: body,
headers: response.headers,
};
if (response.status !== 200) {
console.log(body);
}
return legacyResponse;
}
async requestStreaming(methodArg, routeArg, readStream, jsonData) {
const requestUrl = `${this.socketPath}${routeArg}`;
// Build the request using the fluent API
const smartRequest = plugins.smartrequest.SmartRequest.create()
.url(requestUrl)
.header('Content-Type', 'application/json')
.header('X-Registry-Auth', this.registryToken)
.header('Host', 'docker.sock')
.timeout(30000)
.options({ keepAlive: false, autoDrain: true }); // Disable auto-drain for streaming
// If we have JSON data, add it to the request
if (jsonData && Object.keys(jsonData).length > 0) {
smartRequest.json(jsonData);
}
// If we have a readStream, use the new stream method with logging
if (readStream) {
let counter = 0;
const smartduplex = new plugins.smartstream.SmartDuplex({
writeFunction: async (chunkArg) => {
if (counter % 1000 === 0) {
console.log(`posting chunk ${counter}`);
}
counter++;
return chunkArg;
},
});
// Pipe through the logging duplex stream
const loggedStream = readStream.pipe(smartduplex);
// Use the new stream method to stream the data
smartRequest.stream(loggedStream, 'application/octet-stream');
}
// Execute the request based on method
let response;
switch (methodArg.toUpperCase()) {
case 'GET':
response = await smartRequest.get();
break;
case 'POST':
response = await smartRequest.post();
break;
case 'PUT':
response = await smartRequest.put();
break;
case 'DELETE':
response = await smartRequest.delete();
break;
default:
throw new Error(`Unsupported HTTP method: ${methodArg}`);
}
console.log(response.status);
// For streaming responses, get the web stream
const webStream = response.stream();
if (!webStream) {
// If no stream is available, consume the body as text
const body = await response.text();
console.log(body);
// Return a compatible response object
return {
statusCode: response.status,
body: body,
headers: response.headers,
};
}
// Convert web ReadableStream to Node.js stream for backward compatibility
const nodeStream = plugins.smartstream.nodewebhelpers.convertWebReadableToNodeReadable(webStream);
// Add a default error handler to prevent unhandled 'error' events from crashing the process.
// Callers that attach their own 'error' listener will still receive the event.
nodeStream.on('error', () => { });
// Add properties for compatibility
nodeStream.statusCode = response.status;
nodeStream.body = ''; // For compatibility
return nodeStream;
}
async requestHijackedStreaming(methodArg, routeArg, jsonData = {}) {
const body = JSON.stringify(jsonData);
const headers = {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
'Connection': 'Upgrade',
'Upgrade': 'tcp',
'X-Registry-Auth': this.registryToken,
'Host': 'docker.sock',
};
if (this.socketPath.startsWith('http://unix:')) {
return await this.requestHijackedStreamingOverRawSocket(methodArg, routeArg, body, headers);
}
const { requestModule, options } = this.getNodeRequestOptions(methodArg, routeArg, headers);
return await new Promise((resolve, reject) => {
const request = requestModule.request(options, (response) => {
if ((response.statusCode || 0) >= 400) {
this.collectErrorResponse(response).then((bodyText) => {
reject(new Error(`Docker hijack request failed with HTTP ${response.statusCode}: ${bodyText}`));
}).catch(reject);
return;
}
if (!response.socket) {
reject(new Error('Docker hijack response did not include a socket'));
return;
}
resolve({
stream: this.createDuplexForHijackedResponse(response, response.socket),
close: async () => {
response.destroy();
response.socket?.destroy();
},
statusCode: response.statusCode || 0,
headers: response.headers,
});
});
request.on('upgrade', (response, socket, head) => {
if (head.length > 0) {
socket.unshift(head);
}
resolve({
stream: socket,
close: async () => {
socket.destroy();
},
statusCode: response.statusCode || 0,
headers: response.headers,
});
});
request.on('error', reject);
request.end(body);
});
}
async requestHijackedStreamingOverRawSocket(methodArg, routeArg, bodyArg, headersArg) {
const socketPath = this.socketPath.slice('http://unix:'.length, -1);
const denoGlobal = globalThis.Deno;
if (denoGlobal?.connect) {
return await this.requestHijackedStreamingOverDenoUnixSocket(socketPath, methodArg, routeArg, bodyArg, headersArg);
}
const socket = plugins.net.connect(socketPath);
const requestHead = [
`${methodArg.toUpperCase()} ${routeArg} HTTP/1.1`,
...Object.entries(headersArg).map(([key, value]) => `${key}: ${value}`),
'',
bodyArg,
].join('\r\n');
return await new Promise((resolve, reject) => {
let settled = false;
let responseBuffer = Buffer.alloc(0);
const cleanupBeforeResolve = () => {
socket.off('data', handleData);
socket.off('error', handleConnectError);
};
const handleConnectError = (errorArg) => {
if (!settled) {
settled = true;
reject(errorArg);
}
};
const rejectWithDockerError = (statusCodeArg, bodyHeadArg) => {
const chunks = [];
if (bodyHeadArg.length > 0) {
chunks.push(bodyHeadArg);
}
socket.on('data', (chunkArg) => chunks.push(Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg)));
socket.on('end', () => {
reject(new Error(`Docker hijack request failed with HTTP ${statusCodeArg}: ${Buffer.concat(chunks).toString('utf8')}`));
});
socket.end();
};
const handleData = (chunkArg) => {
responseBuffer = Buffer.concat([responseBuffer, chunkArg]);
const headerEndIndex = responseBuffer.indexOf('\r\n\r\n');
if (headerEndIndex === -1) {
return;
}
cleanupBeforeResolve();
const headerBuffer = responseBuffer.subarray(0, headerEndIndex);
const bodyHead = responseBuffer.subarray(headerEndIndex + 4);
const { statusCode, headers } = this.parseRawHttpResponseHeaders(headerBuffer.toString('utf8'));
if (statusCode >= 400) {
if (!settled) {
settled = true;
rejectWithDockerError(statusCode, bodyHead);
}
return;
}
if (!settled) {
settled = true;
resolve({
stream: this.createDuplexForRawSocket(socket, bodyHead),
close: async () => {
socket.destroy();
},
statusCode,
headers,
});
}
};
socket.once('connect', () => {
socket.write(requestHead);
});
socket.on('data', handleData);
socket.on('error', handleConnectError);
});
}
async requestHijackedStreamingOverDenoUnixSocket(socketPathArg, methodArg, routeArg, bodyArg, headersArg) {
const denoGlobal = globalThis.Deno;
const conn = await denoGlobal.connect({ transport: 'unix', path: socketPathArg });
const requestHead = [
`${methodArg.toUpperCase()} ${routeArg} HTTP/1.1`,
...Object.entries(headersArg).map(([key, value]) => `${key}: ${value}`),
'',
bodyArg,
].join('\r\n');
await conn.write(new TextEncoder().encode(requestHead));
let responseBuffer = Buffer.alloc(0);
const readBuffer = new Uint8Array(65536);
while (true) {
const bytesRead = await conn.read(readBuffer);
if (bytesRead === null) {
conn.close();
throw new Error('Docker hijack connection closed before response headers were received');
}
responseBuffer = Buffer.concat([
responseBuffer,
Buffer.from(readBuffer.subarray(0, bytesRead)),
]);
const headerEndIndex = responseBuffer.indexOf('\r\n\r\n');
if (headerEndIndex === -1) {
continue;
}
const headerBuffer = responseBuffer.subarray(0, headerEndIndex);
const bodyHead = responseBuffer.subarray(headerEndIndex + 4);
const { statusCode, headers } = this.parseRawHttpResponseHeaders(headerBuffer.toString('utf8'));
if (statusCode >= 400) {
conn.close();
throw new Error(`Docker hijack request failed with HTTP ${statusCode}: ${bodyHead.toString('utf8')}`);
}
const stream = this.createDuplexForDenoConn(conn, bodyHead);
return {
stream,
close: async () => {
stream.destroy();
},
statusCode,
headers,
};
}
}
getNodeRequestOptions(methodArg, routeArg, headersArg) {
const options = {
method: methodArg.toUpperCase(),
headers: headersArg,
};
if (this.socketPath.startsWith('http://unix:')) {
options.socketPath = this.socketPath.slice('http://unix:'.length, -1);
options.path = routeArg;
return { requestModule: plugins.http, options };
}
const requestUrl = new URL(routeArg, this.socketPath);
options.protocol = requestUrl.protocol;
options.hostname = requestUrl.hostname;
options.port = requestUrl.port;
options.path = `${requestUrl.pathname}${requestUrl.search}`;
return {
requestModule: requestUrl.protocol === 'https:' ? plugins.https : plugins.http,
options,
};
}
createDuplexForHijackedResponse(responseArg, writableArg) {
const duplex = new plugins.stream.Duplex({
write(chunkArg, encodingArg, callbackArg) {
writableArg.write(chunkArg, encodingArg, callbackArg);
},
read() { },
});
responseArg.on('data', (chunkArg) => duplex.push(chunkArg));
responseArg.on('end', () => duplex.push(null));
responseArg.on('error', (errorArg) => duplex.destroy(errorArg));
writableArg.on('error', (errorArg) => duplex.destroy(errorArg));
duplex.on('finish', () => writableArg.end());
return duplex;
}
createDuplexForRawSocket(socketArg, bodyHeadArg) {
const duplex = new plugins.stream.Duplex({
write(chunkArg, encodingArg, callbackArg) {
socketArg.write(chunkArg, encodingArg, callbackArg);
},
read() { },
});
if (bodyHeadArg.length > 0) {
duplex.push(bodyHeadArg);
}
socketArg.on('data', (chunkArg) => duplex.push(chunkArg));
socketArg.on('end', () => duplex.push(null));
socketArg.on('error', (errorArg) => duplex.destroy(errorArg));
duplex.on('finish', () => socketArg.end());
return duplex;
}
createDuplexForDenoConn(connArg, bodyHeadArg) {
let closed = false;
const closeConn = () => {
if (closed) {
return;
}
closed = true;
try {
connArg.close();
}
catch { }
};
const duplex = new plugins.stream.Duplex({
async write(chunkArg, encodingArg, callbackArg) {
try {
const chunkBuffer = Buffer.isBuffer(chunkArg)
? chunkArg
: Buffer.from(chunkArg, encodingArg);
await connArg.write(new Uint8Array(chunkBuffer));
callbackArg();
}
catch (error) {
callbackArg(error);
}
},
read() { },
destroy(errorArg, callbackArg) {
closeConn();
callbackArg(errorArg || null);
},
});
if (bodyHeadArg.length > 0) {
duplex.push(bodyHeadArg);
}
const readLoop = async () => {
const readBuffer = new Uint8Array(65536);
try {
while (!duplex.destroyed) {
const bytesRead = await connArg.read(readBuffer);
if (bytesRead === null) {
break;
}
if (bytesRead > 0) {
duplex.push(Buffer.from(readBuffer.subarray(0, bytesRead)));
}
}
duplex.push(null);
}
catch (error) {
if (!closed && !duplex.destroyed) {
duplex.destroy(error);
}
}
};
void readLoop();
duplex.on('finish', () => {
closeConn();
});
return duplex;
}
parseRawHttpResponseHeaders(headerTextArg) {
const [statusLine, ...headerLines] = headerTextArg.split('\r\n');
const statusCode = Number(statusLine.split(' ')[1]);
const headers = {};
for (const headerLine of headerLines) {
const separatorIndex = headerLine.indexOf(':');
if (separatorIndex === -1) {
continue;
}
const key = headerLine.slice(0, separatorIndex).trim().toLowerCase();
const value = headerLine.slice(separatorIndex + 1).trim();
headers[key] = value;
}
return { statusCode, headers };
}
async collectErrorResponse(responseArg) {
const chunks = [];
for await (const chunk of responseArg) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
return Buffer.concat(chunks).toString('utf8');
}
/**
* add s3 storage
* @param optionsArg
*/
async addS3Storage(optionsArg) {
this.smartBucket = new plugins.smartbucket.SmartBucket(optionsArg);
if (!optionsArg.bucketName) {
throw new Error('bucketName is required');
}
const bucket = await this.smartBucket.getBucketByName(optionsArg.bucketName);
let wantedDirectory = await bucket.getBaseDirectory();
if (optionsArg.directoryPath) {
wantedDirectory = await wantedDirectory.getSubDirectoryByName(optionsArg.directoryPath);
}
this.imageStore.options.bucketDir = wantedDirectory;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ob3N0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5ob3N0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sS0FBSyxLQUFLLE1BQU0sWUFBWSxDQUFDO0FBQ3BDLE9BQU8sS0FBSyxVQUFVLE1BQU0sdUJBQXVCLENBQUM7QUFDcEQsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3pELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNyRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDckQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ25ELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDckMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDM0QsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBb0JqRCxNQUFNLE9BQU8sVUFBVTtJQVdyQjs7O09BR0c7SUFDSCxZQUFZLFVBQXlDO1FBUjdDLGtCQUFhLEdBQVcsRUFBRSxDQUFDO1FBU2pDLElBQUksQ0FBQyxPQUFPLEdBQUc7WUFDYixHQUFHO2dCQUNELGFBQWEsRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FDOUIsS0FBSyxDQUFDLFFBQVEsRUFDZCx5QkFBeUIsQ0FDMUI7YUFDRjtZQUNELEdBQUcsVUFBVTtTQUNkLENBQUM7UUFDRixJQUFJLFNBQWlCLENBQUM7UUFDdEIsSUFBSSxVQUFVLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDMUIsU0FBUyxHQUFHLFVBQVUsQ0FBQyxVQUFVLENBQUM7UUFDcEMsQ0FBQzthQUFNLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNuQyxTQUFTLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLENBQUM7UUFDdEMsQ0FBQzthQUFNLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUMxQixTQUFTLEdBQUcscUJBQXFCLENBQUM7UUFDcEMsQ0FBQzthQUFNLENBQUM7WUFDTixTQUFTLEdBQUcsbUNBQW1DLENBQUM7UUFDbEQsQ0FBQztRQUNELElBQUksU0FBUyxDQUFDLFVBQVUsQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1lBQ3JDLFNBQVMsR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxjQUFjLENBQUMsQ0FBQztRQUMzRCxDQUFDO1FBQ0QsSUFBSSxTQUFTLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDaEMsU0FBUyxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ25ELENBQUM7UUFDRCxPQUFPLENBQUMsR0FBRyxDQUFDLHdCQUF3QixTQUFTLEVBQUUsQ0FBQyxDQUFDO1FBQ2pELElBQUksQ0FBQyxVQUFVLEdBQUcsU0FBUyxDQUFDO1FBQzVCLElBQUksQ0FBQyxVQUFVLEdBQUcsSUFBSSxnQkFBZ0IsQ0FBQztZQUNyQyxTQUFTLEVBQUUsSUFBSztZQUNoQixZQUFZLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxhQUFjO1NBQzFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFTSxLQUFLLENBQUMsS0FBSztRQUNoQixNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDaEMsQ0FBQztJQUNNLEtBQUssQ0FBQyxJQUFJO1FBQ2YsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQzdCLElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQ3JCLElBQUksQ0FBQyxXQUFXLENBQUMsYUFBYSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQzNDLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLEtBQUssQ0FBQyxJQUFJO1FBQ2YsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxRQUFRLENBQUMsQ0FBQztRQUNyRCxJQUFJLFFBQVEsQ0FBQyxVQUFVLEtBQUssR0FBRyxFQUFFLENBQUM7WUFDaEMsTUFBTSxJQUFJLEtBQUssQ0FBQyxrQ0FBa0MsUUFBUSxDQUFDLFVBQVUsRUFBRSxDQUFDLENBQUM7UUFDM0UsQ0FBQztJQUNILENBQUM7SUFFRDs7O09BR0c7SUFDSSxLQUFLLENBQUMsVUFBVTtRQVdyQixNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ3ZELE9BQU8sUUFBUSxDQUFDLElBQUksQ0FBQztJQUN2QixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLEtBQUssQ0FBQyxJQUFJLENBQUMsUUFBbUI7UUFDbkMsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxPQUFPLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFDL0QsSUFBSSxRQUFRLENBQUMsSUFBSSxDQUFDLE1BQU0sS0FBSyxpQkFBaUIsRUFBRSxDQUFDO1lBQy9DLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCLFFBQVEsQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztZQUN6RCxNQUFNLElBQUksS0FBSyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDeEMsQ0FBQztRQUNELE9BQU8sQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNsQyxJQUFJLENBQUMsYUFBYSxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FDcEQsT0FBTyxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLENBQ3RDLENBQUM7SUFDSixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsNEJBQTRCLENBQUMsY0FBc0I7UUFDOUQsTUFBTSxnQkFBZ0IsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQ2pELHVCQUF1QixDQUN4QixDQUFDO1FBQ0YsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxnQkFBZ0IsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO1FBQ25GLE1BQU0sZ0JBQWdCLEdBQUcsWUFBWSxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsQ0FBQyxJQUFJLENBQUM7UUFDakUsTUFBTSxVQUFVLEdBQ2QsT0FBTyxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFDdEQsTUFBTSxlQUFlLEdBQUcsVUFBVSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUM5QyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUM7WUFDZCxRQUFRLEVBQUUsZUFBZSxDQUFDLENBQUMsQ0FBQztZQUM1QixRQUFRLEVBQUUsZUFBZSxDQUFDLENBQUMsQ0FBQztZQUM1QixhQUFhLEVBQUUsY0FBYztTQUM5QixDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQsaUJBQWlCO0lBQ2pCLGdDQUFnQztJQUNoQyxpQkFBaUI7SUFFakI7O09BRUc7SUFDSSxLQUFLLENBQUMsWUFBWTtRQUN2QixPQUFPLE1BQU0sYUFBYSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN6QyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsZ0JBQWdCLENBQUMsY0FBc0I7UUFDbEQsT0FBTyxNQUFNLGFBQWEsQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLGNBQWMsQ0FBQyxDQUFDO0lBQzdELENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxhQUFhLENBQ3hCLFVBQWlEO1FBRWpELE9BQU8sTUFBTSxhQUFhLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxVQUFVLENBQUMsQ0FBQztJQUN2RCxDQUFDO0lBRUQsaUJBQWlCO0lBQ2pCLGtDQUFrQztJQUNsQyxpQkFBaUI7SUFFakI7O09BRUc7SUFDSSxLQUFLLENBQUMsY0FBYztRQUN6QixPQUFPLE1BQU0sZUFBZSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUMzQyxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksS0FBSyxDQUFDLGdCQUFnQixDQUFDLFdBQW1CO1FBQy9DLE9BQU8sTUFBTSxlQUFlLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxXQUFXLENBQUMsQ0FBQztJQUMxRCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsZUFBZSxDQUMxQixVQUFtRDtRQUVuRCxPQUFPLE1BQU0sZUFBZSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVELGlCQUFpQjtJQUNqQixnQ0FBZ0M7SUFDaEMsaUJBQWlCO0lBRWpCOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFlBQVk7UUFDdkIsT0FBTyxNQUFNLGFBQWEsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDekMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLGdCQUFnQixDQUFDLFdBQW1CO1FBQy9DLE9BQU8sTUFBTSxhQUFhLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxXQUFXLENBQUMsQ0FBQztJQUMxRCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsYUFBYSxDQUN4QixVQUFpRDtRQUVqRCxPQUFPLE1BQU0sYUFBYSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDdkQsQ0FBQztJQUVELGlCQUFpQjtJQUNqQiw4QkFBOEI7SUFDOUIsaUJBQWlCO0lBRWpCOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFVBQVU7UUFDckIsT0FBTyxNQUFNLFdBQVcsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDdkMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLGNBQWMsQ0FBQyxZQUFvQjtRQUM5QyxPQUFPLE1BQU0sV0FBVyxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsWUFBWSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLHVCQUF1QixDQUNsQyxVQUErQztRQUUvQyxPQUFPLE1BQU0sV0FBVyxDQUFDLG1CQUFtQixDQUFDLElBQUksRUFBRTtZQUNqRCxjQUFjLEVBQUUsVUFBVTtTQUMzQixDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsd0JBQXdCLENBQ25DLFNBQThDLEVBQzlDLFVBQStDO1FBRS9DLE9BQU8sTUFBTSxXQUFXLENBQUMsb0JBQW9CLENBQUMsSUFBSSxFQUFFO1lBQ2xELGNBQWMsRUFBRSxVQUFVO1lBQzFCLFNBQVMsRUFBRSxTQUFTO1NBQ3JCLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksS0FBSyxDQUFDLFdBQVcsQ0FBQyxPQUd4QjtRQUlDLE1BQU0sT0FBTyxHQUE2QixPQUFPLEVBQUUsT0FBTyxJQUFJLEVBQUUsQ0FBQztRQUVqRSxtQ0FBbUM7UUFDbkMsSUFBSSxPQUFPLEVBQUUsUUFBUSxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQ3BDLE9BQU8sQ0FBQyxRQUFRLEdBQUcsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDbkQsQ0FBQztRQUVELElBQUksS0FBSyxHQUFHLGVBQWUsQ0FBQztRQUM1QixJQUFJLE9BQU8sSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUMvQyxLQUFLLElBQUksWUFBWSxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUNyRSxDQUFDO1FBRUQsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsQ0FBQztRQUVuRCxPQUFPLFFBQVEsQ0FBQyxJQUFJLENBQUM7SUFDdkIsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFVBQVUsQ0FBQyxRQUFnQjtRQUN0QyxPQUFPLE1BQU0sV0FBVyxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDbEQsQ0FBQztJQUVELGlCQUFpQjtJQUNqQiwrQkFBK0I7SUFDL0IsaUJBQWlCO0lBRWpCOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFdBQVc7UUFDdEIsT0FBTyxNQUFNLFlBQVksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDeEMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLGVBQWUsQ0FBQyxVQUFrQjtRQUM3QyxPQUFPLE1BQU0sWUFBWSxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDeEQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLGFBQWEsQ0FBQyxRQUFnQjtRQUN6QyxPQUFPLE1BQU0sWUFBWSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDcEQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFlBQVksQ0FDdkIsVUFBZ0Q7UUFFaEQsT0FBTyxNQUFNLFlBQVksQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQ3RELENBQUM7SUFFRCxpQkFBaUI7SUFDakIsMkJBQTJCO0lBQzNCLGlCQUFpQjtJQUVqQjs7T0FFRztJQUNJLEtBQUssQ0FBQyxVQUFVLENBQ3JCLFNBQWlCLEVBQ2pCLFNBQThDO1FBRTlDLE9BQU8sTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDLENBQUM7SUFDaEUsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLGFBQWEsQ0FDeEIsU0FBaUI7UUFFakIsT0FBTyxNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ25ELENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxrQkFBa0I7UUFDN0IsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxFQUFFLFNBQVMsQ0FBQyxDQUFDO1FBRS9ELDhEQUE4RDtRQUM5RCxNQUFNLFVBQVUsR0FBRyxRQUErQyxDQUFDO1FBRW5FLE9BQU8sT0FBTyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7WUFDakQsVUFBVSxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsRUFBRTtnQkFDN0IsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO2dCQUNwQyxJQUFJLENBQUM7b0JBQ0gsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsQ0FBQztvQkFDNUMsUUFBUSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztnQkFDN0IsQ0FBQztnQkFBQyxPQUFPLENBQUMsRUFBRSxDQUFDO29CQUNYLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQ2pCLENBQUM7WUFDSCxDQUFDLENBQUMsQ0FBQztZQUNILFVBQVUsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsR0FBRyxFQUFFLEVBQUU7Z0JBQzdCLDhEQUE4RDtnQkFDOUQsSUFBSyxHQUFXLENBQUMsSUFBSSxLQUFLLFlBQVksRUFBRSxDQUFDO29CQUN2QyxRQUFRLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUN0QixDQUFDO1lBQ0gsQ0FBQyxDQUFDLENBQUM7WUFDSCxPQUFPLEdBQUcsRUFBRTtnQkFDVixVQUFVLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDdkIsQ0FBQyxDQUFDO1FBQ0osQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsYUFBYSxDQUFDLG1CQUE0QjtRQUNyRCxrQ0FBa0M7UUFDbEMsSUFBSSxnQkFBZ0IsR0FBVyxFQUFFLENBQUM7UUFDbEMsSUFBSSxtQkFBbUIsRUFBRSxDQUFDO1lBQ3hCLGdCQUFnQixHQUFHLG1CQUFtQixDQUFDO1FBQ3pDLENBQUM7YUFBTSxDQUFDO1lBQ04sSUFBSSxDQUFDO2dCQUNILE1BQU0sb0JBQW9CLEdBQUcsSUFBSSxPQUFPLENBQUMsWUFBWSxDQUFDLFlBQVksRUFBRSxDQUFDO2dCQUNyRSxNQUFNLGNBQWMsR0FBRyxNQUFNLG9CQUFvQixDQUFDLGlCQUFpQixFQUFFLENBQUM7Z0JBQ3RFLElBQUksY0FBYyxFQUFFLENBQUM7b0JBQ25CLGdCQUFnQixHQUFHLGNBQWMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDO2dCQUNqRCxDQUFDO1lBQ0gsQ0FBQztZQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7Z0JBQ2IseUVBQXlFO2dCQUN6RSxnREFBZ0Q7WUFDbEQsQ0FBQztRQUNILENBQUM7UUFFRCxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLGFBQWEsRUFBRTtZQUN6RCxVQUFVLEVBQUUsY0FBYztZQUMxQixhQUFhLEVBQUUsZ0JBQWdCO1lBQy9CLFlBQVksRUFBRSxJQUFJO1lBQ2xCLGVBQWUsRUFBRSxDQUFDLGFBQWEsRUFBRSxhQUFhLENBQUM7WUFDL0MsVUFBVSxFQUFFLEVBQUU7WUFDZCxlQUFlLEVBQUUsS0FBSztTQUN2QixDQUFDLENBQUM7UUFDSCxJQUFJLFFBQVEsQ0FBQyxVQUFVLEtBQUssR0FBRyxFQUFFLENBQUM7WUFDaEMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsMEJBQTBCLENBQUMsQ0FBQztRQUNqRCxDQUFDO2FBQU0sQ0FBQztZQUNOLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLDBCQUEwQixDQUFDLENBQUM7UUFDbEQsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxPQUFPLENBQUMsU0FBaUIsRUFBRSxRQUFnQixFQUFFLE9BQU8sR0FBRyxFQUFFO1FBQ3BFLE1BQU0sVUFBVSxHQUFHLEdBQUcsSUFBSSxDQUFDLFVBQVUsR0FBRyxRQUFRLEVBQUUsQ0FBQztRQUVuRCx5Q0FBeUM7UUFDekMsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxZQUFZLENBQUMsTUFBTSxFQUFFO2FBQzVELEdBQUcsQ0FBQyxVQUFVLENBQUM7YUFDZixNQUFNLENBQUMsY0FBYyxFQUFFLGtCQUFrQixDQUFDO2FBQzFDLE1BQU0sQ0FBQyxpQkFBaUIsRUFBRSxJQUFJLENBQUMsYUFBYSxDQUFDO2FBQzdDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsYUFBYSxDQUFDO2FBQzdCLE9BQU8sQ0FBQyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO1FBRWpDLHVDQUF1QztRQUN2QyxJQUFJLE9BQU8sSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUMvQyxZQUFZLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQzdCLENBQUM7UUFFRCxzQ0FBc0M7UUFDdEMsSUFBSSxRQUFRLENBQUM7UUFDYixRQUFRLFNBQVMsQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDO1lBQ2hDLEtBQUssS0FBSztnQkFDUixRQUFRLEdBQUcsTUFBTSxZQUFZLENBQUMsR0FBRyxFQUFFLENBQUM7Z0JBQ3BDLE1BQU07WUFDUixLQUFLLE1BQU07Z0JBQ1QsUUFBUSxHQUFHLE1BQU0sWUFBWSxDQUFDLElBQUksRUFBRSxDQUFDO2dCQUNyQyxNQUFNO1lBQ1IsS0FBSyxLQUFLO2dCQUNSLFFBQVEsR0FBRyxNQUFNLFlBQVksQ0FBQyxHQUFHLEVBQUUsQ0FBQztnQkFDcEMsTUFBTTtZQUNSLEtBQUssUUFBUTtnQkFDWCxRQUFRLEdBQUcsTUFBTSxZQUFZLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQ3ZDLE1BQU07WUFDUjtnQkFDRSxNQUFNLElBQUksS0FBSyxDQUFDLDRCQUE0QixTQUFTLEVBQUUsQ0FBQyxDQUFDO1FBQzdELENBQUM7UUFFRCxnREFBZ0Q7UUFDaEQsSUFBSSxJQUFJLENBQUM7UUFDVCxNQUFNLFdBQVcsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLGNBQWMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUUzRCxtRkFBbUY7UUFDbkYsZ0RBQWdEO1FBQ2hELE1BQU0sbUJBQW1CLEdBQ3ZCLFFBQVEsQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLENBQUM7WUFDbkMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxjQUFjLENBQUM7WUFDakMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUU5QixJQUFJLFdBQVcsQ0FBQyxRQUFRLENBQUMsa0JBQWtCLENBQUMsSUFBSSxDQUFDLG1CQUFtQixFQUFFLENBQUM7WUFDckUsSUFBSSxHQUFHLE1BQU0sUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQy9CLENBQUM7YUFBTSxDQUFDO1lBQ04sSUFBSSxHQUFHLE1BQU0sUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQzdCLDZFQUE2RTtZQUM3RSxJQUNFLENBQUMsbUJBQW1CO2dCQUNwQixJQUFJO2dCQUNKLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQzlDLENBQUM7Z0JBQ0QsSUFBSSxDQUFDO29CQUNILElBQUksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO2dCQUMxQixDQUFDO2dCQUFDLE1BQU0sQ0FBQztvQkFDUCxnQ0FBZ0M7Z0JBQ2xDLENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQztRQUVELHlEQUF5RDtRQUN6RCxNQUFNLGNBQWMsR0FBRztZQUNyQixVQUFVLEVBQUUsUUFBUSxDQUFDLE1BQU07WUFDM0IsSUFBSSxFQUFFLElBQUk7WUFDVixPQUFPLEVBQUUsUUFBUSxDQUFDLE9BQU87U0FDMUIsQ0FBQztRQUVGLElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxHQUFHLEVBQUUsQ0FBQztZQUM1QixPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3BCLENBQUM7UUFFRCxPQUFPLGNBQWMsQ0FBQztJQUN4QixDQUFDO0lBRU0sS0FBSyxDQUFDLGdCQUFnQixDQUMzQixTQUFpQixFQUNqQixRQUFnQixFQUNoQixVQUFnRCxFQUNoRCxRQUFjO1FBRWQsTUFBTSxVQUFVLEdBQUcsR0FBRyxJQUFJLENBQUMsVUFBVSxHQUFHLFFBQVEsRUFBRSxDQUFDO1FBRW5ELHlDQUF5QztRQUN6QyxNQUFNLFlBQVksR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLFlBQVksQ0FBQyxNQUFNLEVBQUU7YUFDNUQsR0FBRyxDQUFDLFVBQVUsQ0FBQzthQUNmLE1BQU0sQ0FBQyxjQUFjLEVBQUUsa0JBQWtCLENBQUM7YUFDMUMsTUFBTSxDQUFDLGlCQUFpQixFQUFFLElBQUksQ0FBQyxhQUFhLENBQUM7YUFDN0MsTUFBTSxDQUFDLE1BQU0sRUFBRSxhQUFhLENBQUM7YUFDN0IsT0FBTyxDQUFDLEtBQUssQ0FBQzthQUNkLE9BQU8sQ0FBQyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxtQ0FBbUM7UUFFdEYsOENBQThDO1FBQzlDLElBQUksUUFBUSxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ2pELFlBQVksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDOUIsQ0FBQztRQUVELGtFQUFrRTtRQUNsRSxJQUFJLFVBQVUsRUFBRSxDQUFDO1lBQ2YsSUFBSSxPQUFPLEdBQUcsQ0FBQyxDQUFDO1lBQ2hCLE1BQU0sV0FBVyxHQUFHLElBQUksT0FBTyxDQUFDLFdBQVcsQ0FBQyxXQUFXLENBQUM7Z0JBQ3RELGFBQWEsRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLEVBQUU7b0JBQ2hDLElBQUksT0FBTyxHQUFHLElBQUksS0FBSyxDQUFDLEVBQUUsQ0FBQzt3QkFDekIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsT0FBTyxFQUFFLENBQUMsQ0FBQztvQkFDMUMsQ0FBQztvQkFDRCxPQUFPLEVBQUUsQ0FBQztvQkFDVixPQUFPLFFBQVEsQ0FBQztnQkFDbEIsQ0FBQzthQUNGLENBQUMsQ0FBQztZQUVILHlDQUF5QztZQUN6QyxNQUFNLFlBQVksR0FBRyxVQUFVLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDO1lBRWxELCtDQUErQztZQUMvQyxZQUFZLENBQUMsTUFBTSxDQUFDLFlBQVksRUFBRSwwQkFBMEIsQ0FBQyxDQUFDO1FBQ2hFLENBQUM7UUFFRCxzQ0FBc0M7UUFDdEMsSUFBSSxRQUE0QyxDQUFDO1FBQ2pELFFBQVEsU0FBUyxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUM7WUFDaEMsS0FBSyxLQUFLO2dCQUNSLFFBQVEsR0FBRyxNQUFNLFlBQVksQ0FBQyxHQUFHLEVBQUUsQ0FBQztnQkFDcEMsTUFBTTtZQUNSLEtBQUssTUFBTTtnQkFDVCxRQUFRLEdBQUcsTUFBTSxZQUFZLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQ3JDLE1BQU07WUFDUixLQUFLLEtBQUs7Z0JBQ1IsUUFBUSxHQUFHLE1BQU0sWUFBWSxDQUFDLEdBQUcsRUFBRSxDQUFDO2dCQUNwQyxNQUFNO1lBQ1IsS0FBSyxRQUFRO2dCQUNYLFFBQVEsR0FBRyxNQUFNLFlBQVksQ0FBQyxNQUFNLEVBQUUsQ0FBQztnQkFDdkMsTUFBTTtZQUNSO2dCQUNFLE1BQU0sSUFBSSxLQUFLLENBQUMsNEJBQTRCLFNBQVMsRUFBRSxDQUFDLENBQUM7UUFDN0QsQ0FBQztRQUVELE9BQU8sQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBRTdCLDhDQUE4QztRQUM5QyxNQUFNLFNBQVMsR0FBRyxRQUFRLENBQUMsTUFBTSxFQUFFLENBQUM7UUFFcEMsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ2Ysc0RBQXNEO1lBQ3RELE1BQU0sSUFBSSxHQUFHLE1BQU0sUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ25DLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7WUFFbEIsc0NBQXNDO1lBQ3RDLE9BQU87Z0JBQ0wsVUFBVSxFQUFFLFFBQVEsQ0FBQyxNQUFNO2dCQUMzQixJQUFJLEVBQUUsSUFBSTtnQkFDVixPQUFPLEVBQUUsUUFBUSxDQUFDLE9BQU87YUFDMUIsQ0FBQztRQUNKLENBQUM7UUFFRCwwRUFBMEU7UUFDMUUsTUFBTSxVQUFVLEdBQUcsT0FBTyxDQUFDLFdBQVcsQ0FBQyxjQUFjLENBQUMsZ0NBQWdDLENBQUMsU0FBUyxDQUFDLENBQUM7UUFFbEcsNkZBQTZGO1FBQzdGLCtFQUErRTtRQUMvRSxVQUFVLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxHQUFHLEVBQUUsR0FBRSxDQUFDLENBQUMsQ0FBQztRQUVqQyxtQ0FBbUM7UUFDbEMsVUFBa0IsQ0FBQyxVQUFVLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FBQztRQUNoRCxVQUFrQixDQUFDLElBQUksR0FBRyxFQUFFLENBQUMsQ0FBQyxvQkFBb0I7UUFFbkQsT0FBTyxVQUFVLENBQUM7SUFDcEIsQ0FBQztJQUVNLEtBQUssQ0FBQyx3QkFBd0IsQ0FDbkMsU0FBaUIsRUFDakIsUUFBZ0IsRUFDaEIsV0FBb0MsRUFBRTtRQUV0QyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3RDLE1BQU0sT0FBTyxHQUFvQztZQUMvQyxjQUFjLEVBQUUsa0JBQWtCO1lBQ2xDLGdCQUFnQixFQUFFLE1BQU0sQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDO1lBQ3pDLFlBQVksRUFBRSxTQUFTO1lBQ3ZCLFNBQVMsRUFBRSxLQUFLO1lBQ2hCLGlCQUFpQixFQUFFLElBQUksQ0FBQyxhQUFhO1lBQ3JDLE1BQU0sRUFBRSxhQUFhO1NBQ3RCLENBQUM7UUFFRixJQUFJLElBQUksQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQUM7WUFDL0MsT0FBTyxNQUFNLElBQUksQ0FBQyxxQ0FBcUMsQ0FBQyxTQUFTLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQztRQUM5RixDQUFDO1FBRUQsTUFBTSxFQUFFLGFBQWEsRUFBRSxPQUFPLEVBQUUsR0FBRyxJQUFJLENBQUMscUJBQXFCLENBQUMsU0FBUyxFQUFFLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUU1RixPQUFPLE1BQU0sSUFBSSxPQUFPLENBQTZCLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQ3ZFLE1BQU0sT0FBTyxHQUFHLGFBQWEsQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxFQUFFLEVBQUU7Z0JBQzFELElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxJQUFJLENBQUMsQ0FBQyxJQUFJLEdBQUcsRUFBRSxDQUFDO29CQUN0QyxJQUFJLENBQUMsb0JBQW9CLENBQUMsUUFBUSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7d0JBQ3BELE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQywwQ0FBMEMsUUFBUSxDQUFDLFVBQVUsS0FBSyxRQUFRLEVBQUUsQ0FBQyxDQUFDLENBQUM7b0JBQ2xHLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztvQkFDakIsT0FBTztnQkFDVCxDQUFDO2dCQUVELElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxFQUFFLENBQUM7b0JBQ3JCLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQyxpREFBaUQsQ0FBQyxDQUFDLENBQUM7b0JBQ3JFLE9BQU87Z0JBQ1QsQ0FBQztnQkFFRCxPQUFPLENBQUM7b0JBQ04sTUFBTSxFQUFFLElBQUksQ0FBQywrQkFBK0IsQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDLE1BQU0sQ0FBQztvQkFDdkUsS0FBSyxFQUFFLEtBQUssSUFBSSxFQUFFO3dCQUNoQixRQUFRLENBQUMsT0FBTyxFQUFFLENBQUM7d0JBQ25CLFFBQVEsQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLENBQUM7b0JBQzdCLENBQUM7b0JBQ0QsVUFBVSxFQUFFLFFBQVEsQ0FBQyxVQUFVLElBQUksQ0FBQztvQkFDcEMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxPQUFPO2lCQUMxQixDQUFDLENBQUM7WUFDTCxDQUFDLENBQUMsQ0FBQztZQUVILE9BQU8sQ0FBQyxFQUFFLENBQUMsU0FBUyxFQUFFLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsRUFBRTtnQkFDL0MsSUFBSSxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO29CQUNwQixNQUFNLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO2dCQUN2QixDQUFDO2dCQUNELE9BQU8sQ0FBQztvQkFDTixNQUFNLEVBQUUsTUFBTTtvQkFDZCxLQUFLLEVBQUUsS0FBSyxJQUFJLEVBQUU7d0JBQ2hCLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQztvQkFDbkIsQ0FBQztvQkFDRCxVQUFVLEVBQUUsUUFBUSxDQUFDLFVBQVUsSUFBSSxDQUFDO29CQUNwQyxPQUFPLEVBQUUsUUFBUSxDQUFDLE9BQU87aUJBQzFCLENBQUMsQ0FBQztZQUNMLENBQUMsQ0FBQyxDQUFDO1lBRUgsT0FBTyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7WUFDNUIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNwQixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFTyxLQUFLLENBQUMscUNBQXFDLENBQ2pELFNBQWlCLEVBQ2pCLFFBQWdCLEVBQ2hCLE9BQWUsRUFDZixVQUEyQztRQUUzQyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDcEUsTUFBTSxVQUFVLEdBQUksVUFBa0IsQ0FBQyxJQUFJLENBQUM7UUFDNUMsSUFBSSxVQUFVLEVBQUUsT0FBTyxFQUFFLENBQUM7WUFDeEIsT0FBTyxNQUFNLElBQUksQ0FBQywwQ0FBMEMsQ0FDMUQsVUFBVSxFQUNWLFNBQVMsRUFDVCxRQUFRLEVBQ1IsT0FBTyxFQUNQLFVBQVUsQ0FDWCxDQUFDO1FBQ0osQ0FBQztRQUVELE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQy9DLE1BQU0sV0FBVyxHQUFHO1lBQ2xCLEdBQUcsU0FBUyxDQUFDLFdBQVcsRUFBRSxJQUFJLFFBQVEsV0FBVztZQUNqRCxHQUFHLE1BQU0sQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLEVBQUUsRUFBRSxDQUFDLEdBQUcsR0FBRyxLQUFLLEtBQUssRUFBRSxDQUFDO1lBQ3ZFLEVBQUU7WUFDRixPQUFPO1NBQ1IsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFFZixPQUFPLE1BQU0sSUFBSSxPQUFPLENBQTZCLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQ3ZFLElBQUksT0FBTyxHQUFHLEtBQUssQ0FBQztZQUNwQixJQUFJLGNBQWMsR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBRXJDLE1BQU0sb0JBQW9CLEdBQUcsR0FBRyxFQUFFO2dCQUNoQyxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUMsQ0FBQztnQkFDL0IsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsa0