@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
858 lines (855 loc) • 40.1 kB
JavaScript
import { NativeDeferred } from '../data/native-q';
import { Net } from '../data/net';
import { LogLevel } from '../diagnostics/log-level';
import { Logging } from '../diagnostics/logging';
import { EnvironmentModule } from '../manifest/environment-modules';
import { RpcBase, RpcMessagePacketType, RpcOutboundCommands, RpcType } from './rpc-base';
import { RpcOutbound } from './rpc-outbound';
import { RpcSeekKey, RpcSeekMode } from './seek/rpc-seek-model';
/**
* RpcChannel class.
* - Both Shell and Module creates one instance to present itself.
*/
export class RpcChannel extends RpcBase {
// Current Rpc mode.
rpcMode;
// Signature of the gateway running instance.
signature;
// RpcShell/RpcModule collection.
rpcCollection = new Map();
sequence = 0;
deferredQueue = new Map();
global = window;
inboundHandlers;
listenerFunction;
webpackInvalid = false;
/**
* Initiates a new instance of the RpcChannel class.
*
* @param name the public name of itself.
* @param origin the origin url of itself.
* @param signature the signature of the gateway running instance.
*/
constructor(name, origin, signature) {
super(null, name, origin, RpcType.Channel);
this.signature = signature;
if (MsftSme.isShell()) {
this.rpcMode = 0 /* RpcMode.Shell */;
this.depth = 0;
}
else {
this.rpcMode = 1 /* RpcMode.Module */;
this.depth = null;
}
}
/**
* Sets the rpc inbound handlers to use when creating for seek command.
*/
set rpcInboundHandlers(handlers) {
this.inboundHandlers = handlers;
}
/**
* Register Inbound/Outbound.
*
* @param rpcObject the RpcInbound/RpcOutbound class instance.
* @param type the type of rpc object.
*/
registerRpc(rpcObject, type) {
if (rpcObject.type !== type) {
const message = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcTypeNoMatch.message;
throw new Error(message.format('registerRpc'));
}
this.addToCollection(rpcObject);
}
/**
* Unregister module with subName
*
* @param name the name of module.
* @param subName the subName.
* @return RpcBase the rpc object.
*/
unregisterRpc(name, subName, type) {
// unregister it by both origin and name.
const rpcObject = this.getFromCollection(name, subName, true);
if (rpcObject.type !== type) {
const message = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcTypeNoMatch.message;
throw new Error(message.format('unregisterRpc'));
}
if (rpcObject) {
this.removeFromCollection(rpcObject);
return rpcObject;
}
else {
const message = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcNotFoundModule.message;
throw new Error(message.format(name, subName));
}
}
/**
* Get Rpc object by module with subName for Inbound.
*
* @param name the name of module.
* @param subName the subName.
* @param type the type of rpc object.
* @param exact the matching type forced.
* @return RpcBase the rpc object.
*/
getRpc(name, subName, type, nullOk = false) {
const rpcObject = this.getFromCollection(name, subName, true);
if (rpcObject && rpcObject.type !== type) {
if (nullOk) {
return null;
}
const message = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcTypeNoMatch.message;
throw new Error(message.format('getRpc'));
}
// return null if it cannot find.
return rpcObject;
}
/**
* Get all Rpc objects for the specified type.
*/
getAllRpc(type) {
const results = [];
this.rpcCollection.forEach((subCollection) => {
subCollection.forEach((rpc) => {
if (rpc.type === type) {
results.push(rpc);
}
});
});
return results;
}
/**
* Get RpcInbound/RpcOutbound object for module name and module sub name.
* If it doesn't configure subName yet, it returns it so the channel set it up.
*
* @param name the module name.
* @param subName the sub name of the iframe object.
* @return RpcBase the matched Rpc object.
*/
getFromCollection(name, subName, exact) {
const subCollection = this.rpcCollection.get(name);
if (subCollection == null) {
return null;
}
return subCollection.find(value => (!exact && value.subName == null) || value.subName === subName);
}
removeFromCollection(rpcObject) {
const subCollection = this.rpcCollection.get(rpcObject.name);
if (subCollection == null) {
return null;
}
const results = MsftSme.remove(subCollection, rpcObject);
if (subCollection.length === 0) {
// remove the entry if it's empty.
this.rpcCollection.delete(rpcObject.name);
}
if (results && results.length === 1) {
return results[0];
}
return null;
}
addToCollection(rpcObject) {
let subCollection = this.rpcCollection.get(rpcObject.name);
if (subCollection == null) {
subCollection = [rpcObject];
this.rpcCollection.set(rpcObject.name, subCollection);
}
else {
subCollection.push(rpcObject);
}
}
/**
* Start the message listener.
*/
start() {
this.listenerFunction = (ev) => this.listener(ev);
this.global.addEventListener('message', this.listenerFunction);
}
/**
* Stop the message listener.
*/
stop() {
this.global.removeEventListener('message', this.listenerFunction);
}
/**
* Post the message with retry delay.
*
* @param target the RpcToModule or RpcToShell object.
* @param message the message packet.
* @param count the retry count.
* @param delay the interval milliseconds.
* @return Promise<T> the promise object.
*/
retryPost(target, message, count, delay) {
if (target == null || target.window == null) {
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcTargetWindowNotConfigured.message;
throw new Error(message2);
}
const deferred = new NativeDeferred();
const lastSequence = this.sequence;
this.deferredQueue[this.sequence] = deferred;
message.srcName = this.name;
message.srcSubName = this.subName;
message.srcDepth = this.depth;
message.destName = target.name;
message.destSubName = target.subName;
message.signature = this.signature;
message.sequence = this.sequence;
message.type = RpcMessagePacketType.Request; // post
this.sequence++;
const header = `Retry ${RpcMessagePacketType[message.type]} "${message.command}" to ${message.destName}!${message.destSubName}`;
this.debugLogRpcMessage(message, header);
target.window.postMessage(message, target.origin);
const timer = setInterval(() => {
if (deferred.isPending) {
if (--count < 0) {
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcExpiredRetry.message;
clearInterval(timer);
deferred.reject(message2.format(message.command));
if (this.deferredQueue[lastSequence]) {
delete this.deferredQueue[lastSequence];
}
return;
}
target.window.postMessage(message, target.origin);
return;
}
clearInterval(timer);
}, delay);
return deferred.promise;
}
/**
* Post the request message.
*
* @param target the RpcToModule or RpcToShell object.
* @param message the message packet.
* @param timeout the timeout. (10 seconds at default)
* @return Promise<TResult> the promise object.
*/
post(target, message, timeout) {
let ignoreTimeout = false;
if (timeout === -1) {
ignoreTimeout = true;
timeout = null;
}
timeout = timeout || 10 * 1000; // 10 seconds
if (target == null || target.window == null) {
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcTargetWindowNotConfigured.message;
throw new Error(message2);
}
const deferred = new NativeDeferred();
const lastSequence = this.sequence;
this.deferredQueue[this.sequence] = deferred;
message.srcName = this.name;
message.srcSubName = this.subName;
message.srcDepth = this.depth;
message.destName = target.name;
message.destSubName = target.subName;
message.signature = this.signature;
message.sequence = this.sequence;
message.type = RpcMessagePacketType.Request; // post
this.sequence++;
const header = `${RpcMessagePacketType[message.type]} "${message.command}" to ${message.destName}!${message.destSubName}`;
this.debugLogRpcMessage(message, header);
target.window.postMessage(message, target.origin);
setTimeout(() => {
if (deferred.isPending) {
if (ignoreTimeout) {
deferred.resolve();
}
else {
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcExpired.message;
deferred.reject(message2.format(this.name, this.subName, target.name, target.subName, message.command, message.type));
}
}
if (this.deferredQueue[lastSequence]) {
delete this.deferredQueue[lastSequence];
}
}, timeout);
return deferred.promise;
}
/**
* Validate the target window if exist by sending null packet.
*
* @param target the target Rpc object.
* @return boolean if false, it remove the target from the list.
*/
validate(target) {
try {
target.window.postMessage({ validate: 'validate' }, target.origin);
return true;
}
catch (error) {
this.removeFromCollection(target);
return false;
}
}
/**
* Log the debug message.
* @param message the message object.
* @param header the header string (used for the log group header).
*/
debugLog(message, header) {
Logging.log({ source: 'rpc', message: message, level: LogLevel.Debug, consoleGroupHeader: header });
}
/**
* Process and log and rpc message.
* @param message the rpc message packet
* @param header the header string (used for the log group header).
*/
debugLogRpcMessage(message, header) {
const logMessage = { ...message };
if (message.command === RpcOutboundCommands[RpcOutboundCommands.Init]) {
// Why is this hidden?
logMessage.data = '(hidden...)';
}
this.debugLog(logMessage, header);
}
/**
* The listen handler.
*
* @param messageEvent the Rpc message event.
*/
listener(messageEvent) {
// ignore any shell hosting messages (don't handle them at all)
if (MsftSme.isShell()) {
const type = MsftSme.getValue(messageEvent, 'data.type');
if (typeof type === 'string' && type.startsWith('msft-sme-shell-host')) {
return;
}
}
// We are operating as an iframe, any message we get, we should format to the parent to handle
if (MsftSme.isExtension() &&
window &&
window.parent &&
messageEvent.source === window.self &&
messageEvent.data &&
(messageEvent.data.type === 'webpackInvalid' ||
messageEvent.data.type === 'webpackOk')) {
// The rpc channel is an iframe, we need to send a message to the parent for the parent to reload after code change
// window.parent references the parent window, postMessage sends a message to the parent
// the '*' refers to sending the message to any origin, this can introduce potential security
// concerns so we only send messages of type 'webpackInvalid' and 'webpackOk'
window.parent.postMessage(messageEvent.data, '*');
}
if (
// Extension windows will only trust messages from there parent window
(MsftSme.isExtension() && messageEvent.source !== window.parent)
// Shell window only trusts extension origins on rpc
|| (MsftSme.isShell() && !EnvironmentModule.isExtensionOrigin(messageEvent.origin))) {
// if we don't trust the origin, then just log a message
this.debugLog('RPC listener received message from untrusted sender: {0}'.format(messageEvent));
return;
}
if (messageEvent.data && messageEvent.data.type) {
// Watch for webpack reloads coming from the iframe
if (messageEvent.data.type === 'webpackInvalid') {
// Webpack is invalid, 1st message
this.webpackInvalid = true;
}
else if (this.webpackInvalid && messageEvent.data.type === 'webpackOk') {
// Webpack is okay, 2nd message, we can reload
this.webpackInvalid = false;
location.reload();
}
}
// if the message if malformed, ignore it.
if (!messageEvent.data || !messageEvent.data.command) {
// ignore null event.
this.debugLog('RPC listener received malformed message from sender: {0}'.format(messageEvent));
return;
}
const message = messageEvent.data;
const header = `${RpcMessagePacketType[message.type]} "${message.command}" from ${message.srcName}!${message.srcSubName}`;
this.debugLogRpcMessage(message, header);
if (message.signature !== this.signature) {
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcSignatureError.message;
throw new Error(message2);
}
// accept shell seek query
if (message.destName !== this.name) {
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcUnexpectedDestination.message;
throw new Error(message2.format(message.destName));
}
let target = this.getFromCollection(message.srcName, message.srcSubName, false);
if (!target) {
// unknown request was received.
if (message.type === RpcMessagePacketType.Request
&& message.command === RpcOutboundCommands[RpcOutboundCommands.Ping]) {
target = this.getFromCollection('*', '*', true);
if (target) {
// keep remote window object to respond.
// current channel is child, and target is parent.
// target could be shell or a parent module.
// remove the rpcInbound object once and re-register back again with new name.
this.removeFromCollection(target);
target.name = message.srcName;
target.subName = message.srcSubName;
target.window = messageEvent.source;
target.origin = messageEvent.origin;
target.depth = message.srcDepth;
this.subName = message.destSubName;
this.depth = message.srcDepth + 1;
this.registerRpc(target, RpcType.Inbound);
}
}
}
// Seek to create or delete RpcInbound on the shell to access a child call.
if (message.command === RpcSeekKey.command
&& this.name === EnvironmentModule.nameOfShell
&& message.type === RpcMessagePacketType.Request) {
const seekData = message.data;
if (seekData.mode === RpcSeekMode.Create) {
if (target) {
// update window object.
target.subName = message.srcSubName;
target.window = messageEvent.source;
target.depth = message.srcDepth;
}
else {
target = new RpcOutbound(this, message.srcName, messageEvent.origin);
target.subName = message.srcSubName;
target.window = messageEvent.source;
target.depth = message.srcDepth;
target.registerAll(this.inboundHandlers);
this.registerRpc(target, RpcType.Outbound);
}
}
else if (seekData.mode === RpcSeekMode.Delete && target) {
this.removeFromCollection(target);
}
}
if (!target) {
// ignore older/unknown response packet. current channel no longer watching it for response, but treat new request as an error.
if (message.type === RpcMessagePacketType.Request) {
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcUnexpectedEvent.message;
throw new Error(message2.format(message.srcName, message.srcSubName));
}
return;
}
let deferred;
switch (message.type) {
case RpcMessagePacketType.Request: // post: processing response/error.
target.handle(message.command, message.version, message.srcName, message.srcSubName, message.data).then((data) => {
message.data = data;
return this.response(target, message);
}, error => {
let logMessage = '';
let logStack = '';
if (typeof error === 'string') {
message.data = error;
logMessage = error;
}
else {
message.data = {};
if (error && error.xhr) {
const netError = Net.getErrorMessage(error);
message.data.message = netError;
logMessage = netError;
}
else if (error.message) {
message.data.message = error.message;
logMessage = error.message;
}
if (error.stack) {
message.data.stack = error.stack;
logStack = error.stack;
}
}
Logging.log({
source: 'RpcChannel',
level: LogLevel.Error,
message: logMessage,
stack: logStack
});
// telemetry with predefined view/action name.
Logging.trace({
view: 'sme-generic-error',
instance: 'rpc-channel',
action: 'exceptionLog',
data: { stack: '' }
});
return this.error(target, message);
});
break;
case RpcMessagePacketType.Response: // response: received result with success.
deferred = this.deferredQueue[message.sequence];
if (!deferred) {
if (message.command === RpcOutboundCommands[RpcOutboundCommands.Ping]) {
// ping can be sent multiple times and deferred could be settled already.
break;
}
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcUnexpectedSequence.message;
throw new Error(message2);
}
delete this.deferredQueue[message.sequence];
deferred.resolve(message.data);
break;
case RpcMessagePacketType.Error: // error: received result with error.
deferred = this.deferredQueue[message.sequence];
if (!deferred) {
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcUnexpectedErrorSequence.message;
throw new Error(message2);
}
delete this.deferredQueue[message.sequence];
deferred.reject(message.data);
break;
}
}
/**
* Sending response message.
*
* @param target the RpcToModule or RpcToShell object.
* @param message the Rpc message packet.
*/
response(target, message) {
if (target == null || target.window == null) {
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcTargetWindowNotConfigured.message;
throw new Error(message2);
}
message.srcName = this.name;
message.srcSubName = this.subName;
message.srcDepth = this.depth;
message.destName = target.name;
message.destSubName = target.subName;
message.signature = this.signature;
message.type = RpcMessagePacketType.Response; // response
target.window.postMessage(message, target.origin);
}
/**
* Sending error message.
*
* @param target the RpcToModule or RpcToShell object.
* @param message the Rpc message packet.
*/
error(target, message) {
if (target == null || target.window == null) {
const message2 = MsftSme.getStrings().MsftSmeShell.Core.Error.RpcTargetWindowNotConfigured.message;
throw new Error(message2);
}
message.srcName = this.name;
message.srcSubName = this.subName;
message.srcDepth = this.depth;
message.destName = target.name;
message.destSubName = target.subName;
message.signature = this.signature;
message.type = RpcMessagePacketType.Error; // error
target.window.postMessage(message, target.origin);
}
}
//# sourceMappingURL=rpc-channel.js.map
// SIG // Begin signature block
// SIG // MIIoKAYJKoZIhvcNAQcCoIIoGTCCKBUCAQExDzANBglg
// SIG // hkgBZQMEAgEFADB3BgorBgEEAYI3AgEEoGkwZzAyBgor
// SIG // BgEEAYI3AgEeMCQCAQEEEBDgyQbOONQRoqMAEEvTUJAC
// SIG // AQACAQACAQACAQACAQAwMTANBglghkgBZQMEAgEFAAQg
// SIG // gMddnM6x+pxDfrPC5ABqaA63Cb9R7CJXLo3ipVhRITmg
// SIG // gg12MIIF9DCCA9ygAwIBAgITMwAABARsdAb/VysncgAA
// SIG // AAAEBDANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQGEwJV
// SIG // UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
// SIG // UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
// SIG // cmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBT
// SIG // aWduaW5nIFBDQSAyMDExMB4XDTI0MDkxMjIwMTExNFoX
// SIG // DTI1MDkxMTIwMTExNFowdDELMAkGA1UEBhMCVVMxEzAR
// SIG // BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
// SIG // bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
// SIG // bjEeMBwGA1UEAxMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
// SIG // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
// SIG // tCg32mOdDA6rBBnZSMwxwXegqiDEUFlvQH9Sxww07hY3
// SIG // w7L52tJxLg0mCZjcszQddI6W4NJYb5E9QM319kyyE0l8
// SIG // EvA/pgcxgljDP8E6XIlgVf6W40ms286Cr0azaA1f7vaJ
// SIG // jjNhGsMqOSSSXTZDNnfKs5ENG0bkXeB2q5hrp0qLsm/T
// SIG // WO3oFjeROZVHN2tgETswHR3WKTm6QjnXgGNj+V6rSZJO
// SIG // /WkTqc8NesAo3Up/KjMwgc0e67x9llZLxRyyMWUBE9co
// SIG // T2+pUZqYAUDZ84nR1djnMY3PMDYiA84Gw5JpceeED38O
// SIG // 0cEIvKdX8uG8oQa047+evMfDRr94MG9EWwIDAQABo4IB
// SIG // czCCAW8wHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYB
// SIG // BQUHAwMwHQYDVR0OBBYEFPIboTWxEw1PmVpZS+AzTDwo
// SIG // oxFOMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQLExVNaWNy
// SIG // b3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAx
// SIG // Mis1MDI5MjMwHwYDVR0jBBgwFoAUSG5k5VAF04KqFzc3
// SIG // IrVtqMp1ApUwVAYDVR0fBE0wSzBJoEegRYZDaHR0cDov
// SIG // L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWlj
// SIG // Q29kU2lnUENBMjAxMV8yMDExLTA3LTA4LmNybDBhBggr
// SIG // BgEFBQcBAQRVMFMwUQYIKwYBBQUHMAKGRWh0dHA6Ly93
// SIG // d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWlj
// SIG // Q29kU2lnUENBMjAxMV8yMDExLTA3LTA4LmNydDAMBgNV
// SIG // HRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQCI5g/S
// SIG // KUFb3wdUHob6Qhnu0Hk0JCkO4925gzI8EqhS+K4umnvS
// SIG // BU3acsJ+bJprUiMimA59/5x7WhJ9F9TQYy+aD9AYwMtb
// SIG // KsQ/rst+QflfML+Rq8YTAyT/JdkIy7R/1IJUkyIS6srf
// SIG // G1AKlX8n6YeAjjEb8MI07wobQp1F1wArgl2B1mpTqHND
// SIG // lNqBjfpjySCScWjUHNbIwbDGxiFr93JoEh5AhJqzL+8m
// SIG // onaXj7elfsjzIpPnl8NyH2eXjTojYC9a2c4EiX0571Ko
// SIG // mhENF3RtR25A7/X7+gk6upuE8tyMy4sBkl2MUSF08U+E
// SIG // 2LOVcR8trhYxV1lUi9CdgEU2CxODspdcFwxdT1+G8YNc
// SIG // gzHyjx3BNSI4nOZcdSnStUpGhCXbaOIXfvtOSfQX/UwJ
// SIG // oruhCugvTnub0Wna6CQiturglCOMyIy/6hu5rMFvqk9A
// SIG // ltIJ0fSR5FwljW6PHHDJNbCWrZkaEgIn24M2mG1M/Ppb
// SIG // /iF8uRhbgJi5zWxo2nAdyDBqWvpWxYIoee/3yIWpquVY
// SIG // cYGhJp/1I1sq/nD4gBVrk1SKX7Do2xAMMO+cFETTNSJq
// SIG // fTSSsntTtuBLKRB5mw5qglHKuzapDiiBuD1Zt4QwxA/1
// SIG // kKcyQ5L7uBayG78kxlVNNbyrIOFH3HYmdH0Pv1dIX/Mq
// SIG // 7avQpAfIiLpOWwcbjzCCB3owggVioAMCAQICCmEOkNIA
// SIG // AAAAAAMwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYT
// SIG // AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
// SIG // EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
// SIG // cG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290
// SIG // IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTEx
// SIG // MDcwODIwNTkwOVoXDTI2MDcwODIxMDkwOVowfjELMAkG
// SIG // A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
// SIG // BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
// SIG // dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0
// SIG // IENvZGUgU2lnbmluZyBQQ0EgMjAxMTCCAiIwDQYJKoZI
// SIG // hvcNAQEBBQADggIPADCCAgoCggIBAKvw+nIQHC6t2G6q
// SIG // ghBNNLrytlghn0IbKmvpWlCquAY4GgRJun/DDB7dN2vG
// SIG // EtgL8DjCmQawyDnVARQxQtOJDXlkh36UYCRsr55JnOlo
// SIG // XtLfm1OyCizDr9mpK656Ca/XllnKYBoF6WZ26DJSJhIv
// SIG // 56sIUM+zRLdd2MQuA3WraPPLbfM6XKEW9Ea64DhkrG5k
// SIG // NXimoGMPLdNAk/jj3gcN1Vx5pUkp5w2+oBN3vpQ97/vj
// SIG // K1oQH01WKKJ6cuASOrdJXtjt7UORg9l7snuGG9k+sYxd
// SIG // 6IlPhBryoS9Z5JA7La4zWMW3Pv4y07MDPbGyr5I4ftKd
// SIG // gCz1TlaRITUlwzluZH9TupwPrRkjhMv0ugOGjfdf8NBS
// SIG // v4yUh7zAIXQlXxgotswnKDglmDlKNs98sZKuHCOnqWbs
// SIG // YR9q4ShJnV+I4iVd0yFLPlLEtVc/JAPw0XpbL9Uj43Bd
// SIG // D1FGd7P4AOG8rAKCX9vAFbO9G9RVS+c5oQ/pI0m8GLhE
// SIG // fEXkwcNyeuBy5yTfv0aZxe/CHFfbg43sTUkwp6uO3+xb
// SIG // n6/83bBm4sGXgXvt1u1L50kppxMopqd9Z4DmimJ4X7Iv
// SIG // hNdXnFy/dygo8e1twyiPLI9AN0/B4YVEicQJTMXUpUMv
// SIG // dJX3bvh4IFgsE11glZo+TzOE2rCIF96eTvSWsLxGoGyY
// SIG // 0uDWiIwLAgMBAAGjggHtMIIB6TAQBgkrBgEEAYI3FQEE
// SIG // AwIBADAdBgNVHQ4EFgQUSG5k5VAF04KqFzc3IrVtqMp1
// SIG // ApUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYD
// SIG // VR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
// SIG // BBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0f
// SIG // BFMwUTBPoE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQu
// SIG // Y29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0
// SIG // MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRS
// SIG // MFAwTgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9z
// SIG // b2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx
// SIG // MV8yMDExXzAzXzIyLmNydDCBnwYDVR0gBIGXMIGUMIGR
// SIG // BgkrBgEEAYI3LgMwgYMwPwYIKwYBBQUHAgEWM2h0dHA6
// SIG // Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvZG9jcy9w
// SIG // cmltYXJ5Y3BzLmh0bTBABggrBgEFBQcCAjA0HjIgHQBM
// SIG // AGUAZwBhAGwAXwBwAG8AbABpAGMAeQBfAHMAdABhAHQA
// SIG // ZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEA
// SIG // Z/KGpZjgVHkaLtPYdGcimwuWEeFjkplCln3SeQyQwWVf
// SIG // Liw++MNy0W2D/r4/6ArKO79HqaPzadtjvyI1pZddZYSQ
// SIG // fYtGUFXYDJJ80hpLHPM8QotS0LD9a+M+By4pm+Y9G6XU
// SIG // tR13lDni6WTJRD14eiPzE32mkHSDjfTLJgJGKsKKELuk
// SIG // qQUMm+1o+mgulaAqPyprWEljHwlpblqYluSD9MCP80Yr
// SIG // 3vw70L01724lruWvJ+3Q3fMOr5kol5hNDj0L8giJ1h/D
// SIG // Mhji8MUtzluetEk5CsYKwsatruWy2dsViFFFWDgycSca
// SIG // f7H0J/jeLDogaZiyWYlobm+nt3TDQAUGpgEqKD6CPxNN
// SIG // ZgvAs0314Y9/HG8VfUWnduVAKmWjw11SYobDHWM2l4bf
// SIG // 2vP48hahmifhzaWX0O5dY0HjWwechz4GdwbRBrF1HxS+
// SIG // YWG18NzGGwS+30HHDiju3mUv7Jf2oVyW2ADWoUa9WfOX
// SIG // pQlLSBCZgB/QACnFsZulP0V3HjXG0qKin3p6IvpIlR+r
// SIG // +0cjgPWe+L9rt0uX4ut1eBrs6jeZeRhL/9azI2h15q/6
// SIG // /IvrC4DqaTuv/DDtBEyO3991bWORPdGdVk5Pv4BXIqF4
// SIG // ETIheu9BCrE/+6jMpF3BoYibV3FWTkhFwELJm3ZbCoBI
// SIG // a/15n8G9bW1qyVJzEw16UM0xghoKMIIaBgIBATCBlTB+
// SIG // MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
// SIG // bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
// SIG // cm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNy
// SIG // b3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExAhMzAAAE
// SIG // BGx0Bv9XKydyAAAAAAQEMA0GCWCGSAFlAwQCAQUAoIGu
// SIG // MBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisG
// SIG // AQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3
// SIG // DQEJBDEiBCACN6B8iNijpDrGyfqERL4UFjFZvBhThTW4
// SIG // 1tl73i/4WzBCBgorBgEEAYI3AgEMMTQwMqAUgBIATQBp
// SIG // AGMAcgBvAHMAbwBmAHShGoAYaHR0cDovL3d3dy5taWNy
// SIG // b3NvZnQuY29tMA0GCSqGSIb3DQEBAQUABIIBAA2oTKnp
// SIG // gmHPY+ht9odaCEauSgoH0Dawv2xHFiQGhKL4xPwvK+Sb
// SIG // XLrXRwUquJEfgiALECox47BtujNYbZHBouB/JSOtWc9O
// SIG // rKXIfTZc/YQo5/JaLLU0xIbXoBT4DjSlXt46Xfm3a6ci
// SIG // zHu1fGVLZhEk+Zzx/VvxOGhKsKw3b6HY2Z/wj4K0UMw+
// SIG // 079ET/FzTytUH16xh0axQgisjUEmebuJsnbRgoIB3aj6
// SIG // cIpmZsLOJo1ByqNIep9i5p4/cIWyIYFVUDdNmV7RbZ1m
// SIG // fWj2heP0xs7Z6AbNkkjo6LPDVyn4JM0wFO3dakJoHbMT
// SIG // QYZ+ZIAihp5NNbnt1bRt2NmfbzmhgheUMIIXkAYKKwYB
// SIG // BAGCNwMDATGCF4Awghd8BgkqhkiG9w0BBwKgghdtMIIX
// SIG // aQIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBUgYLKoZIhvcN
// SIG // AQkQAQSgggFBBIIBPTCCATkCAQEGCisGAQQBhFkKAwEw
// SIG // MTANBglghkgBZQMEAgEFAAQgoq3Fhu5Sj7Z/rng6euk3
// SIG // OkRra52lr1bvJ3zoCjCgthwCBmet45UxDhgTMjAyNTAy
// SIG // MjAxNTI4MzYuMjI1WjAEgAIB9KCB0aSBzjCByzELMAkG
// SIG // A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
// SIG // BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
// SIG // dCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0
// SIG // IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNo
// SIG // aWVsZCBUU1MgRVNOOjkyMDAtMDVFMC1EOTQ3MSUwIwYD
// SIG // VQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
// SIG // oIIR6jCCByAwggUIoAMCAQICEzMAAAHnLo8vkwtPG+kA
// SIG // AQAAAecwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC
// SIG // VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
// SIG // B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
// SIG // b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
// SIG // U3RhbXAgUENBIDIwMTAwHhcNMjMxMjA2MTg0NTE5WhcN
// SIG // MjUwMzA1MTg0NTE5WjCByzELMAkGA1UEBhMCVVMxEzAR
// SIG // BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
// SIG // bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
// SIG // bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3Bl
// SIG // cmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNO
// SIG // OjkyMDAtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3Nv
// SIG // ZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG
// SIG // 9w0BAQEFAAOCAg8AMIICCgKCAgEAwlefL+CLkOufVzzN
// SIG // Q7WljL/fx0VAuZHYhBfPWAT+v0Z+5I6jJGeREnpn+RJY
// SIG // uAi7UFUnn0aRdY+0uSyyorDFjhkWi3GlWxk33JiNbzES
// SIG // dbczMAjSKAqv78vFh/EHVdQfwG+bCvkPciL8xsOO031z
// SIG // xPEZa2rsCv3vp1p8DLdOtGpBGYiSc9VYdS4UmCmoj/Wd
// SIG // txGZhhEwlooJCm3LgJ4b4d8qzGvPbgX2nh0GRBxkKnbJ
// SIG // DOPBAXFklnaYkkgYgMcoR1JG5J5fTz87Qf0lMc0WY1M1
// SIG // h4PW39ZqmdHCIgFgtBIyuzjYZUHykkR1SyizT6Zd//lC
// SIG // +F43NGL3anPPIDi1K//OE/f8Sua/Nrpb0adgPP2q/XBu
// SIG // Fu+udLimgMUQJoC+ISoCF+f9GiALG8qiTmujiBkhfWvg
// SIG // 315dS6UDzSke/drHBe7Yw+VqsCLon0vWFIhzL0S44ypN
// SIG // Ekglf5qVwtAaD5JOWrH8a6yWwrCXjx0jhG5aSc0Zs2j+
// SIG // jjF8EXK2+01xUDrE5CrqpFr72CD71cwuvFDPjLJCz5Xd
// SIG // XqnTjjCu0m239rRkmX9/ojsFkDHFlwfYMOYCtwCGCtPF
// SIG // pCSbssz6n4rYLm3UQpmK/QlbDTrlvsBw2BoXIiQxdi5K
// SIG // 45BVI1HF0iCXfX9rLGIrWfQrqxle+AUHH68Y75NS/I77
// SIG // Te5rpSMCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBTP/uCY
// SIG // gJ82OHaRH/2Za4dSu96PWDAfBgNVHSMEGDAWgBSfpxVd
// SIG // AF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQ
// SIG // hk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
// SIG // L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
// SIG // JTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG
// SIG // CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
// SIG // b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUt
// SIG // U3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB
// SIG // Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4G
// SIG // A1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA
// SIG // dKHw25PpZVotXAup7H4nuSbadPaOm+gEQqb7Qz6tihT/
// SIG // oYvlDTT+yxnIirnJKlwpgUxSIXwXhksb5OsnKJHUK9/N
// SIG // eaRDmmFk5x70NPvISsvOq9ReK3wbuKBweXE8tPE+KIax
// SIG // vzmBvwf4DZ89Dper+7v6hI8+PM12emZcShsmcCpimVmg
// SIG // Xdg2BMMyqXS5AcbOgOnp1mUdI2PquRXW1eOYIRkyoEq+
// SIG // RAgDpyw+J4ycH4yKtJkWVsA2UKF7SUmlR0rtpR0C92Bx
// SIG // BYpLp21EyXzXwQyy+xr/rE5kYg2ZMuTgMaCxtoGk37oh
// SIG // W36Zknz3IJeQjlM3zEJ86Sn1+vhZCNEEDb7j6VrA1PLE
// SIG // frp4tlZg6O65qia6JuIoYFTXS2jHzVKrwS+WYkitc5mh
// SIG // CwSfWvmDoxOaZkmq1ubBm5+4lZBdlvSUCDh+rRlixSUu
// SIG // R7N+s2oZKB4fIg/ety3ho2apBbrCmlFu9sjI/8sU3hhA
// SIG // zqCK9+ZMF8a9VLvs5Lq9svhbjWNKGY6ac6feQFtZXoT9
// SIG // MWjvqAVdV372grq/weT1QKdsc66LDBFHAMKSaYqPlWHy
// SIG // Lnxo+5nl3BkGFgPFJq/CugLqPiZY/CHhUupUryoakKZn
// SIG // QcwDBqjzkCrdTsN2V8XoSu7wIopt2YgC5TNCueOpNLGa
// SIG // 8XWT4KZs+zvMPYBy7smQEHswggdxMIIFWaADAgECAhMz
// SIG // AAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUA
// SIG // MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
// SIG // Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
// SIG // TWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylN
// SIG // aWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3Jp
// SIG // dHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAx
// SIG // ODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
// SIG // YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
// SIG // VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
// SIG // BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
// SIG // MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
// SIG // 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1
// SIG // V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeF
// SIG // RiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDc
// SIG // wUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus
// SIG // 9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130
// SIG // /o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHI
// SIG // NSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTes
// SIG // y+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGp
// SIG // F1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+
// SIG // /NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fz
// SIG // pk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNO
// SIG // wTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLi
// SIG // Mxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5
// SIG // UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q
// SIG // BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6H
// SIG // XtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG
// SIG // CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYE
// SIG // FCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSf
// SIG // pxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEG
// SIG // DCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRw
// SIG // Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3Mv
// SIG // UmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUH
// SIG // AwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYD
// SIG // VR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
// SIG // BBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0f
// SIG // BE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQu
// SIG // Y29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0
// SIG // XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBK
// SIG // BggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQu
// SIG // Y29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w
// SIG // Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1Vffwq
// SIG // reEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1
// SIG // OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpT
// SIG // Td2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinL
// SIG // btg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l
// SIG // 9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJ
// SIG // w7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2Fz
// SIG // Lixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7
// SIG // hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY
// SIG // 3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFX
// SIG // SVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFU
// SIG // a2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz
// SIG // /gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/
// SIG // AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1
// SIG // ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328
// SIG // y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG
// SIG // ahC0HVUzWLOhcGbyoYIDTTCCAjUCAQEwgfmhgdGkgc4w
// SIG // gcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
// SIG // dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
// SIG // aWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
// SIG // Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNV
// SIG // BAsTHm5TaGllbGQgVFNTIEVTTjo5MjAwLTA1RTAtRDk0
// SIG // NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
// SIG // U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAs3IE5xmrEsHv
// SIG // 3a7vnD3tTRf78EOggYMwgYCkfjB8MQswCQYDVQQGEwJV
// SIG // UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
// SIG // UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
// SIG // cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
// SIG // dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAOth
// SIG // nAMwIhgPMjAyNTAyMjAxMjE4MTFaGA8yMDI1MDIyMTEy
// SIG // MTgxMVowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA62Gc
// SIG // AwIBADAHAgEAAgISVTAHAgEAAgITLzAKAgUA62LtgwIB
// SIG // ADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMC
// SIG // oAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3
// SIG // DQEBCwUAA4IBAQB/Go/n5DB53ySFShKauo/5PAMMhslf
// SIG // oiTMki8OKjSRACXWD4yBJHquXs9hboyZNcK2yZsQVGBy
// SIG // 6PAG+GgLHghfMINGswafHk8cSsyfxz6jqAOmW7Y59uLV
// SIG // JL1wTwJZK9rW8Uk/+pxXCtBH/zkCERr8JPYys9AZiMK0
// SIG // +ypgY0a4M7sti62pzUtQihoCQaRGSqvZ5/dmqIb9yuIB
// SIG // JKUz3HyxkK9TmkgoJPJC7fIp9/obu1jCzJJiVREDPgpy
// SIG // 6ijIj/Tl+pPWZvghugJLjQm1mK21BoM0RdM8AyDJDnwo
// SIG // Yj29UhzWX8rMs4S3+eWqMvCC3Jk/PC4m6oOyrixlRhtc
// SIG // RacaMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
// SIG // EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
// SIG // ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
// SIG // dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
// SIG // bXAgUENBIDIwMTACEzMAAAHnLo8vkwtPG+kAAQAAAecw
// SIG // DQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzEN
// SIG // BgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgr13C
// SIG // M6MOW3PBSIrujBey44tTgOaTUpZqTqiYavSHXbQwgfoG
// SIG // CyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDlNl0NdmqG
// SIG // /Q3gxVzPVBR3hF++Bb9AAb0DBu6gudZzrTCBmDCBgKR+
// SIG // MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
// SIG // dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
// SIG // aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
// SIG // Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
// SIG // 5y6PL5MLTxvpAAEAAAHnMCIEIF2FP3R/vAZxCsgAJaEN
// SIG // 0whyGAjsHSLuIPTxdaiAHJKeMA0GCSqGSIb3DQEBCwUA
// SIG // BIICALjqkeTXrr6OWEQUXSrbje/XcpDgUMo7lIYyYo2G
// SIG // fT5FocpzaV/BtDxVOjndULaD4Oi8N6cVUbv3FXa93jJh
// SIG // p9TlGTOsiVF5cS977CuwjyKPTsP0T02khfHgQFQvvWSG
// SIG // zh9bogQ98+yq9WmFsOvEf5jOy/u7Dsl7W9fDQGl89Nel
// SIG // 9q7QqopyEFt2kyQUEn/YskS+F2jDnTp0E1QZykDHnBDr
// SIG // PHD2y3Gy5fq+3yAYxEqfyuVneYpAd2xyqQEW5EmY3Om/
// SIG // TEjZWnQTVUK0zIjbuKRSOwCd6gdv6jGEFcbpMbJGj/tb
// SIG // lpYVNVJLtF9IeRkVzpKk674jseFzjV0W7V2ErUMlWq5E
// SIG // 1EdEYUkiUEyYZg0SdwH2qCa0bww58Ojh5ApUs46J6TIY
// SIG // pKwwaICJC+r+pqFmtdGkS+3PmpRJIaxhvh97C60KU1pp
// SIG // On0qMfW/0TBDMRaQLTh3Q9CyrJCzuobV2iOGb2CHU6N4
// SIG // osW1tKb41d32YQYVRcVIHpRDZQknuPt/zgy4rAyWziHw
// SIG // MwiHzwRoZPxzv+lmIbKFXkK7lhWHWnsSMQwcORcZDtQk
// SIG // 3Q+ENlf7nXF1EXvKBD1US9t8gJ5jDxKvhbV92UjjBPUz
// SIG // NaxLMupqjjTGP1v8gBeN3tVZQaCxc4fKB36BznPbaiC4
// SIG // FZa0N9R4JIY2QHt7JmwHVFj/BnZt
// SIG // End signature block