@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
914 lines (911 loc) • 45.5 kB
JavaScript
import { from, Subject, throwError } from 'rxjs';
import { filter, map, mergeMap, take, tap } from 'rxjs/operators';
import { CoreEnvironment } from '../data/core-environment';
import { LogLevel } from '../diagnostics/log-level';
import { Logging } from '../diagnostics/logging';
import { RpcAzureOperationType, RpcAzureResponseKey } from '../rpc/azure/rpc-azure-model';
import { RpcAzureRequestClient } from '../rpc/azure/rpc-azure-request-client';
import { ArmTelemetry, AzureConstants } from './azure-constants';
import { AzureLogging } from './azure-logging';
/**
* Azure Manager class. Handles detecting and configuring Azure on a set of servers.
*/
export class AzureManager {
rpc;
http;
watcher;
azureLogging;
AzureGlobal = 'azureglobal';
AzureChina = 'azurechina';
AzureUSGov = 'azureusgov';
/**
* Initializes a new instance of the Azure Manager class
* @param rpc The rpc to forward auth requests to a parent window
*/
constructor(rpc, http) {
this.rpc = rpc;
this.http = http;
this.watcher = new Subject();
this.azureLogging = new AzureLogging();
}
/**
* Initialize the azure manager
*/
initialize() {
// When in Shell (the top window) do not register. Only extension register.
if (MsftSme.isExtension()) {
this.rpc.register(RpcAzureResponseKey.command, this.onRpcResponse.bind(this));
}
}
/**
* Get application from gateway
*/
getAppInfo() {
return this.sendRequest({
requestId: MsftSme.newGuid(),
operation: RpcAzureOperationType.GetAadStatus
});
}
/**
* Open the register Aad dialog
*/
openRegisterAadDialog() {
return this.sendRequest({
requestId: MsftSme.newGuid(),
operation: RpcAzureOperationType.OpenAzureDialog
});
}
/**
* Signs in user.
*/
signIn() {
return this.sendTokenRequest({
requestId: MsftSme.newGuid(),
operation: RpcAzureOperationType.SignIn
});
}
/**
* Signs out user.
*/
signOut() {
return this.sendRequest({
requestId: MsftSme.newGuid(),
operation: RpcAzureOperationType.SignOut
});
}
/**
* Switches user accounts.
*/
switchAccounts() {
throw new Error('To be implemented');
}
/**
* Get user token from azure
*/
getToken() {
return this.sendTokenRequest({
requestId: MsftSme.newGuid(),
operation: RpcAzureOperationType.GetAadToken
});
}
/**
* Get the Azure AD graph token from azure
*/
getGraphToken() {
return this.sendTokenRequest({
requestId: MsftSme.newGuid(),
operation: RpcAzureOperationType.GetAadGraphToken
});
}
/**
* Get the Microsoft graph token from azure
* @param scopes The set of permissions for which the token has to be requested.
* https://docs.microsoft.com/en-us/graph/permissions-reference
* A null value will request default scopes - openid and profile.
*/
getMicrosoftGraphToken(scopes = null) {
return this.sendTokenRequest({
requestId: MsftSme.newGuid(),
operation: RpcAzureOperationType.GetMicrosoftGraphToken,
tokenScopes: scopes
});
}
/**
* Checks to see if user is signed in. Returns true if signed in, else false.
*/
checkUserSignedIn() {
return this.sendTokenRequest({
requestId: MsftSme.newGuid(),
operation: RpcAzureOperationType.CheckUserSignedIn
}).pipe(map(result => {
const isSignedIn = result && result.appInfo && result.appInfo.token;
return !!isSignedIn;
}));
}
/**
* Get Azure subscriptions.
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
getSubscriptions(integrationType) {
let armEndpoint = null;
const url = AzureConstants.azureSubscriptionUrl;
return this.getAppInfo().pipe(mergeMap(({ appInfo }) => {
if (appInfo) {
armEndpoint = this.getArmEndpoint(appInfo.cloudName);
}
else {
return throwError(() => new Error('Gateway is not registered to Azure.'));
}
return this.getRequestWithArmToken(url.format(armEndpoint), integrationType);
}), map(result => {
const response = {
httpStatusCode: result.status,
value: result.response
};
return response;
}));
}
/**
* Check whether a resource group exists or not.
* @param subscriptionId The subscription id.
* @param resourceGroupName The resource group name.
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
checkResourceGroupExists(subscriptionId, resourceGroupName, integrationType) {
let armEndpoint = null;
return this.getAppInfo().pipe(mergeMap(({ appInfo }) => {
if (appInfo) {
armEndpoint = this.getArmEndpoint(appInfo.cloudName);
}
else {
return throwError(() => new Error('Gateway is not registered to Azure.'));
}
return this.getArmToken();
}), mergeMap(token => {
const url = AzureConstants.resourceGroupUrl.format(armEndpoint, subscriptionId, resourceGroupName);
const ajaxRequest = this.createRequestWithHeaders(token, { integrationType: integrationType });
return this.http.head(url, ajaxRequest);
}), map(result => {
return { status: result.status };
}));
}
/**
* Create or update (if it exists) resource group.
* @param subscriptionId The subscription id.
* @param resourceGroupName The resource group name.
* @param body request body. Optional. See definition of ResourceGroupRequestBody for what fields can be inside this body.
* If this is not provided, a default of { \'location\': \'West US\' } will be used.
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
createResourceGroup(subscriptionId, resourceGroupName, body, integrationType) {
let armEndpoint = null;
return this.getAppInfo().pipe(mergeMap(({ appInfo }) => {
if (appInfo) {
armEndpoint = this.getArmEndpoint(appInfo.cloudName);
}
else {
return throwError(() => new Error('Gateway is not registered to Azure.'));
}
const url = AzureConstants.resourceGroupUrl.format(armEndpoint, subscriptionId, resourceGroupName);
if (!body) {
body = { location: 'West US' };
}
return this.putRequestWithArmToken(url, body, integrationType);
}), map(result => {
const response = {
httpStatusCode: result.status,
value: result.response
};
return response;
}));
}
/**
* List all resource groups in a subscription.
* @param subscriptionId The subscription id.
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
getResourceGroups(subscriptionId, integrationType) {
let armEndpoint = null;
return this.getAppInfo().pipe(mergeMap(({ appInfo }) => {
if (appInfo) {
armEndpoint = this.getArmEndpoint(appInfo.cloudName);
}
else {
return throwError(() => new Error('Gateway is not registered to Azure.'));
}
const url = AzureConstants.listResourceGroups.format(armEndpoint, subscriptionId);
return this.getRequestWithArmToken(url, integrationType);
}), map(result => {
const response = {
httpStatusCode: result.status,
value: result.response
};
return response;
}));
}
/**
* Delete resource group.
* @param subscriptionId The subscription id.
* @param resourceGroupName The resource group name.
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
deleteResourceGroup(subscriptionId, resourceGroupName, integrationType) {
let armEndpoint = null;
return this.getAppInfo().pipe(mergeMap(({ appInfo }) => {
if (appInfo) {
armEndpoint = this.getArmEndpoint(appInfo.cloudName);
}
else {
return throwError(() => new Error('Gateway is not registered to Azure.'));
}
const url = AzureConstants.resourceGroupUrl.format(armEndpoint, subscriptionId, resourceGroupName);
return this.deleteRequestWithArmToken(url, integrationType);
}), map(result => {
// result.status will either be 200 or 202.
return { status: result.status };
}));
}
/**
* Get all available geo-locations.
* @param subscriptionId The subscription id.
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
getAllAzureLocations(subscriptionId, integrationType) {
let armEndpoint = null;
return this.getAppInfo().pipe(mergeMap(({ appInfo }) => {
if (appInfo) {
armEndpoint = this.getArmEndpoint(appInfo.cloudName);
}
else {
return throwError(() => new Error('Gateway is not registered to Azure.'));
}
const url = AzureConstants.getAllGeoLocations.format(armEndpoint, subscriptionId);
return this.getRequestWithArmToken(url, integrationType);
}), map(result => {
const response = {
httpStatusCode: result.status,
value: result.response
};
return response;
}));
}
/**
* Deploy resources with Resource Manager templates and Resource Manager REST API.
* @param subscriptionId The subscription id.
* @param resourceGroupName The resource group name.
* @param deploymentName The deployment name.
* @param resourceTemplate The resource template content.
* @param templateParameters The resource template parameters.
* @param deploymentMode The mode that is used to deploy resources.This value can be either Incremental or Complete.
* In Incremental mode, resources are deployed without deleting existing resources that are not included in the template.
In Complete mode, resources are deployed and existing resources in the resource group that are not included in the
template are deleted.
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
DeployAzureTemplate(subscriptionId, resourceGroupName, deploymentName, resourceTemplate, templateParameters, deploymentMode = 'Incremental', integrationType) {
let armEndpoint = null;
return this.getAppInfo().pipe(mergeMap(({ appInfo }) => {
if (appInfo) {
armEndpoint = this.getArmEndpoint(appInfo.cloudName);
}
else {
return throwError(() => new Error('Gateway is not registered to Azure.'));
}
const url = AzureConstants.ArmDeploymentUrl.format(armEndpoint, subscriptionId, resourceGroupName, deploymentName);
const body = {
properties: {
template: JSON.parse(resourceTemplate),
parameters: JSON.parse(templateParameters),
mode: deploymentMode
}
};
return this.putRequestWithArmToken(url, body, integrationType);
}), tap(result => this.logTelemetry(result)), map(result => this.createDeploymentOperationResponse(result)));
}
/**
* Validate resources with Resource Manager templates and Resource Manager REST API.
* @param subscriptionId The subscription id.
* @param resourceGroupName The resource group name.
* @param deploymentName The deployment name.
* @param resourceTemplate The resource template content.
* @param templateParameters The resource template parameters.
* @param deploymentMode The mode that is used to deploy resources.This value can be either Incremental or Complete.
* In Incremental mode, resources are deployed without deleting existing resources that are not included in the template.
In Complete mode, resources are deployed and existing resources in the resource group that are not included in the
template are deleted.
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
validateAzureTemplate(subscriptionId, resourceGroupName, deploymentName, resourceTemplate, templateParameters, deploymentMode = 'Incremental', integrationType) {
let armEndpoint = null;
return this.getAppInfo().pipe(mergeMap(({ appInfo }) => {
if (appInfo) {
armEndpoint = this.getArmEndpoint(appInfo.cloudName);
}
else {
return throwError(() => new Error('Gateway is not registered to Azure.'));
}
const url = AzureConstants.validateAzureTemplate.format(armEndpoint, subscriptionId, resourceGroupName, deploymentName);
const body = {
properties: {
template: JSON.parse(resourceTemplate),
parameters: JSON.parse(templateParameters),
mode: deploymentMode
}
};
return this.postRequestWithArmToken(url, body, integrationType);
}), map(result => this.createDeploymentOperationResponse(result)));
}
/**
* Creates azure request with headers
* @param token bearer token for ARM request
* @param options additional information that can be send in the headers of the request.
* - additionalHeaders any headers besides 'Authorization'
* - integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
* 'x-ms-client-request-id', 'x-ms-return-client-request-id', and 'Accept-Language'.
* @return formatted AjaxRequest for an ARM call
*/
createRequestWithHeaders(token, options) {
const ajaxRequest = { headers: {} };
ajaxRequest.headers = (options && options.additionalHeaders) || {};
ajaxRequest.headers[AzureConstants.authorizationHeaderKey] = AzureConstants.bearer + token;
const integrationType = (options && options.integrationType) || ArmTelemetry.toIntegrationType(MsftSme.self().Init.moduleName);
// TODO: Remove work around. Only intended for the quality release for Jan 2020.
const connectionProperties = options && options.connectionProperties;
ajaxRequest.headers[AzureConstants.clientRequestHeaderKey] =
this.formatClientRequestId(integrationType, this.hexEncodeTelemetryData(connectionProperties));
ajaxRequest.headers[AzureConstants.returnClientRequestHeaderKey] = 'True';
ajaxRequest.headers[AzureConstants.acceptLanguageHeaderKey] = CoreEnvironment.localizationManager.getLocaleId().neutral;
return ajaxRequest;
}
/**
* Gets the arm token.
*/
getArmToken() {
return this.sendTokenRequest({
requestId: MsftSme.newGuid(),
operation: RpcAzureOperationType.GetAadToken
})
.pipe(map(result => {
return result && result.appInfo && result.appInfo.token;
}));
}
// TODO: Remove work around. Only intended for the quality release for Jan 2020.
/**
* Parses connection properties for ARM telemetry data and encode in hex
* @param connectionProperties properties on the current active connection
* @return hex encoded json object.
*/
hexEncodeTelemetryData(connectionProperties) {
if (connectionProperties
&& connectionProperties['operatingSystem']
&& connectionProperties['version']
&& connectionProperties['computerModel']
&& connectionProperties['computerManufacturer']
&& connectionProperties['isS2dEnabled'] != null) {
const telemetryData = {
operatingSystem: connectionProperties['operatingSystem'],
version: connectionProperties['version'],
computerManufacturer: connectionProperties['computerManufacturer'],
computerModel: connectionProperties['computerModel'],
isS2dEnabled: connectionProperties['isS2dEnabled']
};
const stringifiedData = JSON.stringify(telemetryData);
try {
return MsftSme.toHex(stringifiedData);
}
catch {
return null;
}
}
return null;
}
// TODO: Remove work around. Only intended for the quality release for Jan 2020.
formatClientRequestId(integrationType, encodedData) {
let clientRequestId = AzureConstants.clientRequestIdQualifier.format(MsftSme.newGuid());
if (integrationType) {
clientRequestId += AzureConstants.integrationTypeDataParameter.format(integrationType);
}
if (encodedData != null) {
if (encodedData.length < AzureConstants.maxEncodedTelemetryDataLength) {
clientRequestId += AzureConstants.telemetryDataParameter.format(encodedData);
}
else {
clientRequestId += AzureConstants.telemetryDataParameter.format(AzureConstants.telemetryDataInvalidValue);
}
}
// Ordering of parameters is important for parsing in telemetry pipeline.
clientRequestId += AzureConstants.sourceParameter;
return clientRequestId;
}
/**
* Send request to RPC
* @param request RpcAzureOperation
*/
sendRequest(request) {
Logging.log({
level: LogLevel.Debug,
message: 'Sending request to AzureManagerShellService. Request:{0}'.format(JSON.stringify(request)),
source: 'AzureManager'
});
return from(RpcAzureRequestClient.azureRequest(this.rpc, request))
.pipe(mergeMap(() => this.watcher), filter((result) => result.requestId === request.requestId), take(1), map((result) => result));
}
/**
* Send token request to RPC
* @param request RpcAzureOperation
*/
sendTokenRequest(request) {
Logging.log({
level: LogLevel.Debug,
message: 'Sending request to AzureManagerShellService. Request:{0}'.format(JSON.stringify(request)),
source: 'AzureManager'
});
return from(RpcAzureRequestClient.azureRequest(this.rpc, request))
.pipe(mergeMap(() => this.watcher), filter((result) => result.requestId === request.requestId), take(1), map((result) => {
if (result.appInfo) {
return result;
}
else if (result.error) {
throw new Error(result.error);
}
throw new Error(MsftSme.getStrings().MsftSmeShell.Core.Rpc.Azure.Unexpected.Error.message);
}));
}
/**
* Handles Rpc response.
*/
onRpcResponse(data) {
this.watcher.next(data);
return Promise.resolve();
}
/**
* Determine cloud specific ARM endpoint based on the cloud that gateway is registered to.
* @param cloudName cloudName from gateway's app registration.
* @returns Returns cloud specific ARM endpoint.
*/
getArmEndpoint(cloudNameParam) {
const cloudName = MsftSme.replaceAll(cloudNameParam, ' ', '').toLowerCase();
if (cloudName === this.AzureGlobal) {
return AzureConstants.azureGlobalArmEndpoint;
}
else if (cloudName === this.AzureChina) {
return AzureConstants.azureChinaArmEndpoint;
}
else if (cloudName === this.AzureUSGov) {
return AzureConstants.azureUSGovArmEndpoint;
}
}
/**
* Make a GET request using ARM token.
* @param url endpoint for request
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
getRequestWithArmToken(url, integrationType) {
return this.getArmToken().pipe(mergeMap(token => {
const ajaxRequest = this.createRequestWithHeaders(token, { integrationType: integrationType });
return this.http.get(url, ajaxRequest);
}));
}
/**
* Make a POST request using ARM token.
* @param url endpoint for request
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
postRequestWithArmToken(url, body, integrationType) {
return this.getArmToken().pipe(mergeMap(token => {
const ajaxRequest = this.createRequestWithHeaders(token, { integrationType: integrationType });
return this.http.post(url, body, ajaxRequest);
}));
}
/**
* Make a PUT request using ARM token.
* @param url endpoint for request
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
putRequestWithArmToken(url, body, integrationType) {
if (!MsftSme.self().Init.isProduction &&
(url.includes(AzureConstants.azureGlobalArmEndpoint) ||
url.includes(AzureConstants.azureChinaArmEndpoint) ||
url.includes(AzureConstants.azureUSGovArmEndpoint)) &&
url.includes(AzureConstants.resourceDeploymentSubstring)) {
Logging.logWarning('putRequestWithArmToken', 'Use DeployAzure Template to create resource.');
}
return this.getArmToken().pipe(mergeMap(token => {
const ajaxRequest = this.createRequestWithHeaders(token, { integrationType: integrationType });
return this.http.put(url, body, ajaxRequest);
}));
}
/**
* Make a DELETE request using ARM token.
* @param url endpoint for request
* @param integrationType integration type is used to distinguish which Azure tool/service in Windows Admin Center is making a
* Azure resource management (ARM) call. If only one repository has one tool/service, this will be inferred based
* on the name of the module. If a repository has more than one tool/service then the integration type needs to be
* explicitly set.
*/
deleteRequestWithArmToken(url, integrationType) {
return this.getArmToken().pipe(mergeMap(token => {
const ajaxRequest = this.createRequestWithHeaders(token, { integrationType: integrationType });
return this.http.delete(url, ajaxRequest);
}));
}
createDeploymentOperationResponse(result) {
const headers = result.xhr.getAllResponseHeaders();
const headerArray = headers && headers.trim().split(/[\r\n]+/);
const headerMap = new Map();
headerArray.forEach((x) => {
const parts = x.split(': ');
const header = parts && parts.shift();
const value = parts && parts.join(': ');
headerMap.set(header, value);
});
const response = {
httpStatusCode: result.status ? result.status : null,
responseHeaders: headerMap ? headerMap : null,
response: result.response ? result.response : null
};
return response;
}
logTelemetry(response) {
response = response.response ? response.response : response;
if (response && response.properties && response.properties.dependencies) {
let dependentResourceIds = response.properties.dependencies;
dependentResourceIds = dependentResourceIds.map(dependentResourceId => dependentResourceId.id);
this.azureLogging.submitCreateResourceTelemetry(dependentResourceIds);
}
else {
this.azureLogging.submitCreateResourceTelemetry();
}
}
}
//# sourceMappingURL=azure-manager.js.map
// SIG // Begin signature block
// SIG // MIIoKAYJKoZIhvcNAQcCoIIoGTCCKBUCAQExDzANBglg
// SIG // hkgBZQMEAgEFADB3BgorBgEEAYI3AgEEoGkwZzAyBgor
// SIG // BgEEAYI3AgEeMCQCAQEEEBDgyQbOONQRoqMAEEvTUJAC
// SIG // AQACAQACAQACAQACAQAwMTANBglghkgBZQMEAgEFAAQg
// SIG // /pOypVPHWpMaHcYu6rYGnxhjGOGvgqMrTDDs/qYP8oSg
// 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 // DQEJBDEiBCCdlVuGvJZhP6KGR+CPtY0MWFxlHQ3K6jtT
// SIG // rwpuG0KSoTBCBgorBgEEAYI3AgEMMTQwMqAUgBIATQBp
// SIG // AGMAcgBvAHMAbwBmAHShGoAYaHR0cDovL3d3dy5taWNy
// SIG // b3NvZnQuY29tMA0GCSqGSIb3DQEBAQUABIIBACICrSs9
// SIG // 2wDHrqB6oklAQsrZ1AYic4RUsOCjAooQQa9+8U0PfAMX
// SIG // VUpeOphRquX9eYJDqnSS5TM67TbdN1J7qCJrSlT+oVDG
// SIG // G846hYevrVOr/ceK46k8kCAwxuozKYKowR4Mzahu8X3j
// SIG // nh3b2wWI8Y78XPKM2NhILBenVIo7i1f+T89hKlZH3djC
// SIG // lOKycyqiQu4E+VAn/L7NUeEZnI/x3NTf++5EJAPjKR0O
// SIG // /SCxjUB7G7oWgJNLiGQOyX/XBmTAXo+LKnlJaY+tVvic
// SIG // gjWKhCqC46vZxAWoaH4U+S+LFWDKdXzCcIJw5Bjy4vUT
// SIG // /K4pVdXCQ5HohnP4DIHf253jTw6hgheUMIIXkAYKKwYB
// SIG // BAGCNwMDATGCF4Awghd8BgkqhkiG9w0BBwKgghdtMIIX
// SIG // aQIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBUgYLKoZIhvcN
// SIG // AQkQAQSgggFBBIIBPTCCATkCAQEGCisGAQQBhFkKAwEw
// SIG // MTANBglghkgBZQMEAgEFAAQgq9KoFK31MM/5Q4KhAdzf
// SIG // pkWg6hP4kCw4dkMvdsOQfRACBmet86n9FRgTMjAyNTAy
// SIG // MjAxNTI4MzUuNzg5WjAEgAIB9KCB0aSBzjCByzELMAkG
// SIG // A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
// SIG // BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
// SIG // dCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0
// SIG // IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNo
// SIG // aWVsZCBUU1MgRVNOOjhEMDAtMDVFMC1EOTQ3MSUwIwYD
// SIG // VQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
// SIG // oIIR6jCCByAwggUIoAMCAQICEzMAAAHzxQpDrgPMHTEA
// SIG // AQAAAfMwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC
// SIG // VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
// SIG // B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
// SIG // b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
// SIG // U3RhbXAgUENBIDIwMTAwHhcNMjMxMjA2MTg0NjAyWhcN
// SIG // MjUwMzA1MTg0NjAyWjCByzELMAkGA1UEBhMCVVMxEzAR
// SIG // BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
// SIG // bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
// SIG // bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3Bl
// SIG // cmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNO
// SIG // OjhEMDAtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3Nv
// SIG // ZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG
// SIG // 9w0BAQEFAAOCAg8AMIICCgKCAgEA/p+m2uErgfYkjuVj
// SIG // IW54KmAG/s9yH8zaWSFkv7IH14ZS2Jhp7FLaxl9zlXIP
// SIG // vJKyXYsbjVDDu2QDqgmbF1Izs/M3J9WlA+Q9q9j4c1So
// SIG // x7Yr1hoBo+MecKlntUKL97zM/Fh7CrH2nSJVo3wTJ1Sl
// SIG // aJjsm0O/to3OGn849lyUEEphPY0EaAaIA8JqmWpHmJyM
// SIG // dBJjrrnD6+u+E+v2Gkz4iGJRn/l1druqEBwJDBuesWD0
// SIG // IpIrUI4zVhwA3wamwRGqqaWrLcaUTXOIndktcVUMXEBl
// SIG // 45wIHnlW2z2wKBC4W8Ps91XrUcLhBSUc0+oW1hIL8/Sz
// SIG // GD0m4qBy/MPmYlqN8bsN0e3ybKnu6arJ48L54j+7HxNb
// SIG // rX4u5NDUGTKb4jrP/9t/R+ngOiDlbRfMOuoqRO9RGK3E
// SIG // jazhpU5ubqqvrMjtbnWTnijNMWO9vDXBgxap47hT2xBJ
// SIG // uvnrWSn7VPY8Swks6lzlTs3agPDuV2txONY97OzJUxeE
// SIG // OwWK0Jm6caoU737iJWMCNgM3jtzor3HsycAY9hUIE4lR
// SIG // 2nLzEA4EgOxOb8rWpNPjCwZtAHFuCD3q/AOIDhg/aEqa
// SIG // 5sgLtSesBZAa39ko5/onjauhcdLVo/CKYN7kL3LoN+40
// SIG // mnReqta1BGqDyGo2QhlZPqOcJ+q7fnMHSd/URFON2lgs
// SIG // J9Avl8cCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBTDZBX2
// SIG // pRFRDIwNwKaFMfag6w0KJDAfBgNVHSMEGDAWgBSfpxVd
// SIG // AF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQ
// SIG // hk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
// SIG // L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
// SIG // JTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG
// SIG // CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
// SIG // b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUt
// SIG // U3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB
// SIG // Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4G
// SIG // A1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA
// SIG // 38Qcj/zR/u/b3N5YjuHO51zP1ChXAJucOtRcUcT8Ql0V
// SIG // 5YjY2e7A6jT9A81EwVPbUuQ6pKkUoiFdeY+6vHunpYPP
// SIG // 3A9279LFuBqPQDC+JYQOTAYN8MynYoXydBPxyKnB19dZ
// SIG // sLW6U4gtrIAFIe/jmZ2/U8CRO6WxATyUFMcbgokuf69L
// SIG // NkFYqQZov/DBFtniIuJifrxyOQwmgBqKE+ANef+6DY/c
// SIG // 8s0QAU1CAjTa0tfSn68hDeXYeZKjhuEIHGvcOi+wi/kr
// SIG // rk2YtEmfGauuYitoUPCDADlcXsAqQ+JWS+jQ7FTUsATV
// SIG // zlJbMTgDtxtMDU/nAboPxw+NwexNqHVX7Oh9hGAmcVEt
// SIG // a4EXhndrqkMYENsKzLk2+cpDvqnfuJ4Wn//Ujd4HraJr
// SIG // UJ+SM4XwpK2k9Sp2RfEyN8ntWd6Z3q9Ap/6deR+8DcA5
// SIG // AQImftos/TVBHmC3zBpvbxKw1QQ0TIxrBPx6qmO0E0k7
// SIG // Q71O/s2cETxo4mGFBV0/lYJH3R4haSsONl7JtDHy+Wjm
// SIG // t9RcgjNe/6T0yCk0YirAxd+9EsCMGQI1c4g//UIRBQbv
// SIG // aaIxVCzmb87i+YkhCSHKqKVQMHWzXa6GYthzfJ3w48yW
// SIG // vAjE5EHkn0LEKSq/NzoQZhNzBdrM/IKnt5aHNOQ1vCTb
// SIG // 2d9vCabNyyQgC7dK0DyWJzswggdxMIIFWaADAgECAhMz
// 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 // BAsTHm5TaGllbGQgVFNTIEVTTjo4RDAwLTA1RTAtRDk0
// SIG // NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
// SIG // U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAbvoGLNi0YWua
// SIG // RTu/YNy5H8CkZyiggYMwgYCkfjB8MQswCQYDVQQGEwJV
// SIG // UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
// SIG // UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
// SIG // cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
// SIG // dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAOth
// SIG // rBkwIhgPMjAyNTAyMjAxMzI2NDlaGA8yMDI1MDIyMTEz
// SIG // MjY0OVowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA62Gs
// SIG // GQIBADAHAgEAAgIBnjAHAgEAAgIUvzAKAgUA62L9mQIB
// SIG // ADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMC
// SIG // oAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3
// SIG // DQEBCwUAA4IBAQAoTQfdDvZWBMekHKV5gFhVyHjl8oV7
// SIG // 5TqpTteSLKY0Bdi2o4JeRDQ04ABchdUlZkdfu8JYMhep
// SIG // fw/irZeE79ijSJnoKc4++qzsV1mnrz5i53sCcwylzLHD
// SIG // gUOWov375oREEiI+4cu3eELySA7SckfMLsYC6dMfJGNj
// SIG // YUXr5Dgc3zo2Xvv6Ax997nmY7242L4ZrG121sxpHYRCg
// SIG // ghB3uEQFQp0zxTuKQ79tVDc+GBlEO5KzDO+NKZ/WgCfU
// SIG // Dne6aE4/eOSm3Y0c/CrbhdaQ7Azl0k8twq+oNGHstcR9
// SIG // rhHusUcMYhH0gGcYAmPeCkmiSbBZTFjx/HEdN7mu+2aS
// SIG // hR3XMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
// SIG // EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
// SIG // ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
// SIG // dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
// SIG // bXAgUENBIDIwMTACEzMAAAHzxQpDrgPMHTEAAQAAAfMw
// SIG // DQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzEN
// SIG // BgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgM0CJ
// SIG // MUI7+9uYev+7KR6GpjwqlSmnpxI1fvpgyL4U7OUwgfoG
// SIG // CyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAYvNk0i7bh
// SIG // uFZKfMAZiZP0/kQIfONbBv2gzsMYOjti6DCBmDCBgKR+
// SIG // MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
// SIG // dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
// SIG // aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
// SIG // Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
// SIG // 88UKQ64DzB0xAAEAAAHzMCIEIG2uliVrDtmXRDIFDtPg
// SIG // J/wKSOs7YDo2gSjScMwMr06RMA0GCSqGSIb3DQEBCwUA
// SIG // BIICAOvFh2yR9r873JPEHqp1ilty7M05Bopj7xe7CTBu
// SIG // pJ0nDMamDL//o5bjP2FIa53lvtdvc1phKXyYrpyahKSM
// SIG // E/env89uE8HS3Av1xvlA2XgvIPJtJNjByYY9j8yWh4sn
// SIG // z+zlK4AqlluUGfpQ9qZ4qgM4ecrNjqulpR7XQdtrMDby
// SIG // oJarlc1XGp5ZeQSs7SeYzuZrMA2uCXFt9I6Cntahrm/H
// SIG // rh1pprUvxNHWd0j9EshP6j0k9t1JH26kUD9HL1WHwX2L
// SIG // wVAIrMibIfKyZdKQx0MnD7TrKsqxYFynADU18btCCG6x
// SIG // LyuLaNRfa8iRAVRy+UBA2761aYrRRNmwViqHDOIIbpAI
// SIG // 4TjWc71Pz3zV5c+bGYj2y/AH1JIcwxvzgsofMwYLVat4
// SIG // zabY7wPdx86EQuTam1yxmWd6rYdJ9eBzaXQMUs6VTb4p
// SIG // rETiBc/VaqI5UtnQ0n4EeRUWKebId8IzxYfRdvklQUfd
// SIG // vQThTmvAXSM9ysy1Tk1KiBQNAThBDkgbgcdvs3WIqLho
// SIG // c3lhJkRtP3Qcbd0M42Mmt/oOio/XppZphUuftP7u4MQU
// SIG // GzoSyoek375uCfbjIHqpbAtBvN5Sf05izhJhiknuBAs4
// SIG // BY0vvopbvC9AVSDtTJ2qC/Y233AOxJZ5n/JVVwhtp3CM
// SIG // HwDjEaXpbr9nmNY7DyLbfzx+NHm5
// SIG // End signature block