@zkp2p/reclaim-witness-sdk
Version:
<div> <div> <img src="https://raw.githubusercontent.com/reclaimprotocol/.github/main/assets/banners/Attestor-Core.png" /> </div> </div>
252 lines • 19.7 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeHttpResponseParser = makeHttpResponseParser;
exports.getHttpRequestDataFromTranscript = getHttpRequestDataFromTranscript;
const tls_1 = require("@reclaimprotocol/tls");
const generics_1 = require("../utils/generics");
const redactions_1 = require("../utils/redactions");
const HTTP_HEADER_LINE_END = (0, tls_1.strToUint8Array)('\r\n');
/**
* parses http/1.1 responses
*/
function makeHttpResponseParser() {
/** the HTTP response data */
const res = {
statusCode: 0,
statusMessage: '',
headers: {},
body: new Uint8Array(),
complete: false,
headersComplete: false,
headerIndices: new Map(),
headerEndIdx: 0
};
let remainingBodyBytes = 0;
let isChunked = false;
let remaining = new Uint8Array();
let currentByteIdx = 0;
return {
res,
/**
* Parse the next chunk of data
* @param data the data to parse
*/
onChunk(data) {
var _a, _b;
// concatenate the remaining data from the last chunk
remaining = (0, tls_1.concatenateUint8Arrays)([remaining, data]);
// if we don't have the headers yet, keep reading lines
// as each header is in a line
if (!res.headersComplete) {
for (let line = getLine(); typeof line !== 'undefined'; line = getLine()) {
// first line is the HTTP version, status code & message
if (!res.statusCode) {
const [, statusCode, statusMessage] = line.match(/HTTP\/\d\.\d (\d+) (.*)/) || [];
res.statusCode = Number(statusCode);
res.statusMessage = statusMessage;
res.statusLineEndIndex = currentByteIdx - HTTP_HEADER_LINE_END.length;
}
else if (line === '') { // empty line signifies end of headers
res.headersComplete = true;
res.headerEndIdx = currentByteIdx - 4;
// if the response is chunked, we need to process the body differently
if ((_a = res.headers['transfer-encoding']) === null || _a === void 0 ? void 0 : _a.includes('chunked')) {
isChunked = true;
res.chunks = [];
break;
// if the response has a content-length, we know how many bytes to read
}
else if (res.headers['content-length']) {
remainingBodyBytes = Number(res.headers['content-length']);
break;
}
else {
remainingBodyBytes = -1;
break;
// otherwise,
// no content-length, no chunked transfer encoding
// means wait till the stream ends
// https://stackoverflow.com/a/11376887
}
}
else if (!res.complete) { // parse the header
const [key, value] = line.split(': ');
res.headers[key.toLowerCase()] = value;
res.headerIndices[key.toLowerCase()] = {
fromIndex: currentByteIdx - line.length - HTTP_HEADER_LINE_END.length,
toIndex: currentByteIdx - HTTP_HEADER_LINE_END.length
};
}
else {
throw new Error('got more data after response was complete');
}
}
}
if (res.headersComplete) {
if (remainingBodyBytes) {
readBody();
// if no more body bytes to read,
// and the response was not chunked we're done
if (!remainingBodyBytes && !isChunked) {
res.complete = true;
}
}
if (res.headers['content-length'] === '0') {
res.complete = true;
}
if (isChunked) {
for (let line = getLine(); typeof line !== 'undefined'; line = getLine()) {
if (line === '') {
continue;
}
const chunkSize = Number.parseInt(line, 16);
// if chunk size is 0, we're done
if (!chunkSize) {
res.complete = true;
continue;
}
(_b = res.chunks) === null || _b === void 0 ? void 0 : _b.push({
fromIndex: currentByteIdx,
toIndex: currentByteIdx + chunkSize,
});
// otherwise read the chunk
remainingBodyBytes = chunkSize;
readBody();
// if we read all the data we had,
// but there's still data left,
// break the loop and wait for the next chunk
if (remainingBodyBytes) {
break;
}
}
}
}
},
/**
* Call to prevent further parsing; indicating the end of the request
* Checks that the response is valid & complete, otherwise throws an error
*/
streamEnded() {
if (!res.headersComplete) {
throw new Error('stream ended before headers were complete');
}
if (remaining.length) {
throw new Error('stream ended with remaining data');
}
if (remainingBodyBytes > 0) {
throw new Error('stream ended before all body bytes were received');
}
res.complete = true;
}
};
function readBody() {
if (res.complete) {
throw new Error('got more data after response was complete');
}
if (!res.bodyStartIndex) {
res.bodyStartIndex = currentByteIdx;
}
let bytesToCopy;
if (remainingBodyBytes === -1) {
// all bytes are body bytes
bytesToCopy = remaining.length;
}
else {
// take the number of bytes we need to read, or the number of bytes remaining
// and append to the bytes of the body
bytesToCopy = Math.min(remainingBodyBytes, remaining.length);
remainingBodyBytes -= bytesToCopy;
}
res.body = (0, tls_1.concatenateUint8Arrays)([
res.body,
remaining.slice(0, bytesToCopy)
]);
remaining = remaining.slice(bytesToCopy);
currentByteIdx += bytesToCopy;
}
function getLine() {
// find end of line, if it exists
// otherwise return undefined
const idx = (0, generics_1.findIndexInUint8Array)(remaining, HTTP_HEADER_LINE_END);
if (idx === -1) {
return undefined;
}
const line = (0, generics_1.uint8ArrayToStr)(remaining.slice(0, idx));
remaining = remaining.slice(idx + HTTP_HEADER_LINE_END.length);
currentByteIdx += idx + HTTP_HEADER_LINE_END.length;
return line;
}
}
/**
* Read the HTTP request from a TLS receipt transcript.
* @param receipt the transcript to read from or application messages if they were extracted beforehand
* @returns the parsed HTTP request
*/
function getHttpRequestDataFromTranscript(receipt) {
const clientMsgs = receipt
.filter(s => s.sender === 'client');
// if the first message is redacted, we can't parse it
// as we don't know what the request was
if (clientMsgs[0].message[0] === redactions_1.REDACTION_CHAR_CODE) {
throw new Error('First client message request is redacted. Cannot parse');
}
const request = {
method: '',
url: '',
protocol: '',
headers: {}
};
let requestBuffer = (0, tls_1.concatenateUint8Arrays)(clientMsgs.map(m => m.message));
// keep reading lines until we get to the end of the headers
for (let line = getLine(); typeof line !== 'undefined'; line = getLine()) {
if (line === '') {
break;
}
if (!request.method) {
const [, method, url, protocol] = line.match(/(\w+) (.*) (.*)/) || [];
request.method = method.toLowerCase();
request.url = url;
request.protocol = protocol;
}
else {
let keyIdx = line.indexOf(':');
if (keyIdx === -1) {
keyIdx = line.length - 1;
}
const key = line.slice(0, keyIdx)
.toLowerCase()
.trim();
const value = line.slice(keyIdx + 1)
.trim();
const oldValue = request.headers[key];
if (typeof oldValue === 'string') {
request.headers[key] = [oldValue, value];
}
else if (Array.isArray(oldValue)) {
oldValue.push(value);
}
else {
request.headers[key] = value;
}
}
}
//the rest is request body
if (requestBuffer.length) {
request.body = requestBuffer;
}
if (!request.method) {
throw new Error('Client request is incomplete');
}
return request;
function getLine() {
const idx = (0, generics_1.findIndexInUint8Array)(requestBuffer, HTTP_HEADER_LINE_END);
if (idx === -1) {
return undefined;
}
const line = (0, generics_1.uint8ArrayToStr)(requestBuffer.slice(0, idx));
requestBuffer = requestBuffer
.slice(idx + HTTP_HEADER_LINE_END.length);
return line;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC1wYXJzZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvaHR0cC1wYXJzZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFrREEsd0RBb0xDO0FBT0QsNEVBeUVDO0FBdFRELDhDQUE4RTtBQUc5RSxpREFBMkU7QUFDM0UscURBQTBEO0FBeUMxRCxNQUFNLG9CQUFvQixHQUFHLElBQUEscUJBQWUsRUFBQyxNQUFNLENBQUMsQ0FBQTtBQUVwRDs7R0FFRztBQUNILFNBQWdCLHNCQUFzQjtJQUNyQyw2QkFBNkI7SUFDN0IsTUFBTSxHQUFHLEdBQWlCO1FBQ3pCLFVBQVUsRUFBRSxDQUFDO1FBQ2IsYUFBYSxFQUFFLEVBQUU7UUFDakIsT0FBTyxFQUFFLEVBQUU7UUFDWCxJQUFJLEVBQUUsSUFBSSxVQUFVLEVBQUU7UUFDdEIsUUFBUSxFQUFFLEtBQUs7UUFDZixlQUFlLEVBQUUsS0FBSztRQUN0QixhQUFhLEVBQUMsSUFBSSxHQUFHLEVBQXNCO1FBQzNDLFlBQVksRUFBRSxDQUFDO0tBQ2YsQ0FBQTtJQUVELElBQUksa0JBQWtCLEdBQUcsQ0FBQyxDQUFBO0lBQzFCLElBQUksU0FBUyxHQUFHLEtBQUssQ0FBQTtJQUNyQixJQUFJLFNBQVMsR0FBRyxJQUFJLFVBQVUsRUFBRSxDQUFBO0lBQ2hDLElBQUksY0FBYyxHQUFHLENBQUMsQ0FBQTtJQUV0QixPQUFPO1FBQ04sR0FBRztRQUNIOzs7V0FHUztRQUNULE9BQU8sQ0FBQyxJQUFnQjs7WUFDdkIscURBQXFEO1lBQ3JELFNBQVMsR0FBRyxJQUFBLDRCQUFzQixFQUFDLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUE7WUFDckQsdURBQXVEO1lBQ3ZELDhCQUE4QjtZQUM5QixJQUFHLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxDQUFDO2dCQUN6QixLQUFJLElBQUksSUFBSSxHQUFHLE9BQU8sRUFBRSxFQUFFLE9BQU8sSUFBSSxLQUFLLFdBQVcsRUFBRSxJQUFJLEdBQUcsT0FBTyxFQUFFLEVBQUUsQ0FBQztvQkFDekUsd0RBQXdEO29CQUN4RCxJQUFHLENBQUMsR0FBRyxDQUFDLFVBQVUsRUFBRSxDQUFDO3dCQUNwQixNQUFNLENBQUMsRUFBRSxVQUFVLEVBQUUsYUFBYSxDQUFDLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyx5QkFBeUIsQ0FBQyxJQUFJLEVBQUUsQ0FBQTt3QkFDakYsR0FBRyxDQUFDLFVBQVUsR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUE7d0JBQ25DLEdBQUcsQ0FBQyxhQUFhLEdBQUcsYUFBYSxDQUFBO3dCQUNqQyxHQUFHLENBQUMsa0JBQWtCLEdBQUcsY0FBYyxHQUFHLG9CQUFvQixDQUFDLE1BQU0sQ0FBQTtvQkFDdEUsQ0FBQzt5QkFBTSxJQUFHLElBQUksS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDLHNDQUFzQzt3QkFDOUQsR0FBRyxDQUFDLGVBQWUsR0FBRyxJQUFJLENBQUE7d0JBQzFCLEdBQUcsQ0FBQyxZQUFZLEdBQUcsY0FBYyxHQUFHLENBQUMsQ0FBQTt3QkFDckMsc0VBQXNFO3dCQUN0RSxJQUFHLE1BQUEsR0FBRyxDQUFDLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQywwQ0FBRSxRQUFRLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQzs0QkFDMUQsU0FBUyxHQUFHLElBQUksQ0FBQTs0QkFDaEIsR0FBRyxDQUFDLE1BQU0sR0FBRyxFQUFFLENBQUE7NEJBQ2YsTUFBSzs0QkFDTCx1RUFBdUU7d0JBQ3hFLENBQUM7NkJBQU0sSUFBRyxHQUFHLENBQUMsT0FBTyxDQUFDLGdCQUFnQixDQUFDLEVBQUUsQ0FBQzs0QkFDekMsa0JBQWtCLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFBOzRCQUMxRCxNQUFLO3dCQUNOLENBQUM7NkJBQU0sQ0FBQzs0QkFDUCxrQkFBa0IsR0FBRyxDQUFDLENBQUMsQ0FBQTs0QkFDdkIsTUFBSzs0QkFDTCxhQUFhOzRCQUNiLGtEQUFrRDs0QkFDbEQsa0NBQWtDOzRCQUNsQyx1Q0FBdUM7d0JBQ3hDLENBQUM7b0JBQ0YsQ0FBQzt5QkFBTSxJQUFHLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsbUJBQW1CO3dCQUM3QyxNQUFNLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUE7d0JBQ3JDLEdBQUcsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFBO3dCQUN0QyxHQUFHLENBQUMsYUFBYSxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxHQUFHOzRCQUN0QyxTQUFTLEVBQUMsY0FBYyxHQUFHLElBQUksQ0FBQyxNQUFNLEdBQUcsb0JBQW9CLENBQUMsTUFBTTs0QkFDcEUsT0FBTyxFQUFDLGNBQWMsR0FBRyxvQkFBb0IsQ0FBQyxNQUFNO3lCQUNwRCxDQUFBO29CQUNGLENBQUM7eUJBQU0sQ0FBQzt3QkFDUCxNQUFNLElBQUksS0FBSyxDQUFDLDJDQUEyQyxDQUFDLENBQUE7b0JBQzdELENBQUM7Z0JBQ0YsQ0FBQztZQUNGLENBQUM7WUFFRCxJQUFHLEdBQUcsQ0FBQyxlQUFlLEVBQUUsQ0FBQztnQkFDeEIsSUFBRyxrQkFBa0IsRUFBRSxDQUFDO29CQUN2QixRQUFRLEVBQUUsQ0FBQTtvQkFDVixpQ0FBaUM7b0JBQ2pDLDhDQUE4QztvQkFDOUMsSUFBRyxDQUFDLGtCQUFrQixJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7d0JBQ3RDLEdBQUcsQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFBO29CQUNwQixDQUFDO2dCQUNGLENBQUM7Z0JBRUQsSUFBRyxHQUFHLENBQUMsT0FBTyxDQUFDLGdCQUFnQixDQUFDLEtBQUssR0FBRyxFQUFFLENBQUM7b0JBQzFDLEdBQUcsQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFBO2dCQUNwQixDQUFDO2dCQUVELElBQUcsU0FBUyxFQUFFLENBQUM7b0JBQ2QsS0FBSSxJQUFJLElBQUksR0FBRyxPQUFPLEVBQUUsRUFBRSxPQUFPLElBQUksS0FBSyxXQUFXLEVBQUUsSUFBSSxHQUFHLE9BQU8sRUFBRSxFQUFFLENBQUM7d0JBQ3pFLElBQUcsSUFBSSxLQUFLLEVBQUUsRUFBRSxDQUFDOzRCQUNoQixTQUFRO3dCQUNULENBQUM7d0JBRUQsTUFBTSxTQUFTLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUE7d0JBQzNDLGlDQUFpQzt3QkFDakMsSUFBRyxDQUFDLFNBQVMsRUFBRSxDQUFDOzRCQUNmLEdBQUcsQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFBOzRCQUNuQixTQUFRO3dCQUNULENBQUM7d0JBRUQsTUFBQSxHQUFHLENBQUMsTUFBTSwwQ0FBRSxJQUFJLENBQUM7NEJBQ2hCLFNBQVMsRUFBRSxjQUFjOzRCQUN6QixPQUFPLEVBQUUsY0FBYyxHQUFHLFNBQVM7eUJBQ25DLENBQUMsQ0FBQTt3QkFFRiwyQkFBMkI7d0JBQzNCLGtCQUFrQixHQUFHLFNBQVMsQ0FBQTt3QkFDOUIsUUFBUSxFQUFFLENBQUE7d0JBRVYsa0NBQWtDO3dCQUNsQywrQkFBK0I7d0JBQy9CLDZDQUE2Qzt3QkFDN0MsSUFBRyxrQkFBa0IsRUFBRSxDQUFDOzRCQUN2QixNQUFLO3dCQUNOLENBQUM7b0JBQ0YsQ0FBQztnQkFDRixDQUFDO1lBQ0YsQ0FBQztRQUNGLENBQUM7UUFDRDs7O1dBR1M7UUFDVCxXQUFXO1lBQ1YsSUFBRyxDQUFDLEdBQUcsQ0FBQyxlQUFlLEVBQUUsQ0FBQztnQkFDekIsTUFBTSxJQUFJLEtBQUssQ0FBQywyQ0FBMkMsQ0FBQyxDQUFBO1lBQzdELENBQUM7WUFFRCxJQUFHLFNBQVMsQ0FBQyxNQUFNLEVBQUUsQ0FBQztnQkFDckIsTUFBTSxJQUFJLEtBQUssQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFBO1lBQ3BELENBQUM7WUFFRCxJQUFHLGtCQUFrQixHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUMzQixNQUFNLElBQUksS0FBSyxDQUFDLGtEQUFrRCxDQUFDLENBQUE7WUFDcEUsQ0FBQztZQUVELEdBQUcsQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFBO1FBQ3BCLENBQUM7S0FDRCxDQUFBO0lBRUQsU0FBUyxRQUFRO1FBQ2hCLElBQUcsR0FBRyxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ2pCLE1BQU0sSUFBSSxLQUFLLENBQUMsMkNBQTJDLENBQUMsQ0FBQTtRQUM3RCxDQUFDO1FBRUQsSUFBRyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEVBQUUsQ0FBQztZQUN4QixHQUFHLENBQUMsY0FBYyxHQUFHLGNBQWMsQ0FBQTtRQUNwQyxDQUFDO1FBRUQsSUFBSSxXQUFtQixDQUFBO1FBQ3ZCLElBQUcsa0JBQWtCLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUM5QiwyQkFBMkI7WUFDM0IsV0FBVyxHQUFHLFNBQVMsQ0FBQyxNQUFNLENBQUE7UUFDL0IsQ0FBQzthQUFNLENBQUM7WUFDUCw2RUFBNkU7WUFDN0Usc0NBQXNDO1lBQ3RDLFdBQVcsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLGtCQUFrQixFQUFFLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQTtZQUM1RCxrQkFBa0IsSUFBSSxXQUFXLENBQUE7UUFDbEMsQ0FBQztRQUVELEdBQUcsQ0FBQyxJQUFJLEdBQUcsSUFBQSw0QkFBc0IsRUFBQztZQUNqQyxHQUFHLENBQUMsSUFBSTtZQUNSLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLFdBQVcsQ0FBQztTQUMvQixDQUFDLENBQUE7UUFDRixTQUFTLEdBQUcsU0FBUyxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsQ0FBQTtRQUN4QyxjQUFjLElBQUksV0FBVyxDQUFBO0lBQzlCLENBQUM7SUFFRCxTQUFTLE9BQU87UUFDZixpQ0FBaUM7UUFDakMsNkJBQTZCO1FBQzdCLE1BQU0sR0FBRyxHQUFHLElBQUEsZ0NBQXFCLEVBQUMsU0FBUyxFQUFFLG9CQUFvQixDQUFDLENBQUE7UUFDbEUsSUFBRyxHQUFHLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUNmLE9BQU8sU0FBUyxDQUFBO1FBQ2pCLENBQUM7UUFFRCxNQUFNLElBQUksR0FBRyxJQUFBLDBCQUFlLEVBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQTtRQUNyRCxTQUFTLEdBQUcsU0FBUyxDQUFDLEtBQUssQ0FBQyxHQUFHLEdBQUcsb0JBQW9CLENBQUMsTUFBTSxDQUFDLENBQUE7UUFFOUQsY0FBYyxJQUFJLEdBQUcsR0FBRyxvQkFBb0IsQ0FBQyxNQUFNLENBQUE7UUFFbkQsT0FBTyxJQUFJLENBQUE7SUFDWixDQUFDO0FBQ0YsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxTQUFnQixnQ0FBZ0MsQ0FBQyxPQUErQjtJQUMvRSxNQUFNLFVBQVUsR0FBRyxPQUFPO1NBQ3hCLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLEtBQUssUUFBUSxDQUFDLENBQUE7SUFFcEMsc0RBQXNEO0lBQ3RELHdDQUF3QztJQUN4QyxJQUFHLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssZ0NBQW1CLEVBQUUsQ0FBQztRQUNyRCxNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxDQUFDLENBQUE7SUFDMUUsQ0FBQztJQUVELE1BQU0sT0FBTyxHQUFnQjtRQUM1QixNQUFNLEVBQUUsRUFBRTtRQUNWLEdBQUcsRUFBRSxFQUFFO1FBQ1AsUUFBUSxFQUFFLEVBQUU7UUFDWixPQUFPLEVBQUUsRUFBRTtLQUNYLENBQUE7SUFDRCxJQUFJLGFBQWEsR0FBRyxJQUFBLDRCQUFzQixFQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQTtJQUMxRSw0REFBNEQ7SUFDNUQsS0FBSSxJQUFJLElBQUksR0FBRyxPQUFPLEVBQUUsRUFBRSxPQUFPLElBQUksS0FBSyxXQUFXLEVBQUUsSUFBSSxHQUFHLE9BQU8sRUFBRSxFQUFFLENBQUM7UUFDekUsSUFBRyxJQUFJLEtBQUssRUFBRSxFQUFFLENBQUM7WUFDaEIsTUFBSztRQUNOLENBQUM7UUFFRCxJQUFHLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3BCLE1BQU0sQ0FBQyxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsUUFBUSxDQUFDLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtZQUNyRSxPQUFPLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQyxXQUFXLEVBQUUsQ0FBQTtZQUNyQyxPQUFPLENBQUMsR0FBRyxHQUFHLEdBQUcsQ0FBQTtZQUNqQixPQUFPLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQTtRQUM1QixDQUFDO2FBQU0sQ0FBQztZQUNQLElBQUksTUFBTSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUE7WUFDOUIsSUFBRyxNQUFNLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQztnQkFDbEIsTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFBO1lBQ3pCLENBQUM7WUFFRCxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUM7aUJBQy9CLFdBQVcsRUFBRTtpQkFDYixJQUFJLEVBQUUsQ0FBQTtZQUNSLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQztpQkFDbEMsSUFBSSxFQUFFLENBQUE7WUFDUixNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFBO1lBQ3JDLElBQUcsT0FBTyxRQUFRLEtBQUssUUFBUSxFQUFFLENBQUM7Z0JBQ2pDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsS0FBSyxDQUFDLENBQUE7WUFDekMsQ0FBQztpQkFBTSxJQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztnQkFDbkMsUUFBUSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQTtZQUNyQixDQUFDO2lCQUFNLENBQUM7Z0JBQ1AsT0FBTyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxLQUFLLENBQUE7WUFDN0IsQ0FBQztRQUNGLENBQUM7SUFDRixDQUFDO0lBRUQsMEJBQTBCO0lBQzFCLElBQUcsYUFBYSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ3pCLE9BQU8sQ0FBQyxJQUFJLEdBQUcsYUFBYSxDQUFBO0lBQzdCLENBQUM7SUFFRCxJQUFHLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMsOEJBQThCLENBQUMsQ0FBQTtJQUNoRCxDQUFDO0lBRUQsT0FBTyxPQUFPLENBQUE7SUFFZCxTQUFTLE9BQU87UUFDZixNQUFNLEdBQUcsR0FBRyxJQUFBLGdDQUFxQixFQUFDLGFBQWEsRUFBRSxvQkFBb0IsQ0FBQyxDQUFBO1FBQ3RFLElBQUcsR0FBRyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDZixPQUFPLFNBQVMsQ0FBQTtRQUNqQixDQUFDO1FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBQSwwQkFBZSxFQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUE7UUFDekQsYUFBYSxHQUFHLGFBQWE7YUFDM0IsS0FBSyxDQUFDLEdBQUcsR0FBRyxvQkFBb0IsQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUUxQyxPQUFPLElBQUksQ0FBQTtJQUNaLENBQUM7QUFDRixDQUFDIn0=
;