@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
1,015 lines (1,012 loc) • 46 kB
JavaScript
import { EMPTY, Observable, of, throwError } from 'rxjs';
import { catchError, expand, map } from 'rxjs/operators';
import { LogLevel } from '../diagnostics/log-level';
import { Logging } from '../diagnostics/logging';
import { SmeWebTelemetry } from '../diagnostics/sme-web-telemetry';
import { TelemetryEventStates } from '../diagnostics/sme-web-telemetry-models';
import { Disposer } from './disposable';
import { HttpMethod } from './http';
import { Net } from './net';
import { PowerShell } from './powershell';
/**
* PowerShell runspace session state.
*/
var RunspaceSessionState;
(function (RunspaceSessionState) {
/**
* Runspace still active.
*/
RunspaceSessionState[RunspaceSessionState["Active"] = 0] = "Active";
/**
* Runspace already expired.
*/
RunspaceSessionState[RunspaceSessionState["Expired"] = 1] = "Expired";
/**
* No runsapce available for the give node.
* Either this is the first call, or the previous call error out
* or previous runspace was deleted due to expiry.
*/
RunspaceSessionState[RunspaceSessionState["Unavailable"] = 2] = "Unavailable";
})(RunspaceSessionState || (RunspaceSessionState = {}));
/**
* The PowerShellBatchSession class.
*/
export class PowerShellBatchSession {
powerShellBatch;
lifetime;
constructor(powerShellBatch, lifetime) {
this.powerShellBatch = powerShellBatch;
this.lifetime = lifetime;
}
/**
* Dispose the session object.
*/
dispose() {
if (this.lifetime) {
this.lifetime.dispose();
}
}
}
/**
* Class containing methods related to PowerShell runspaces creation/deletion/command using PowerShell Raw API plugin during batch run.
* - It's auto holding the runspace as long as it's used within last 3 minutes.
*/
class PowerShellBatchRaw {
batchConnection;
context;
// 3 minutes session holding time.
static maxDeltaTimeInMs = 3 * 60 * 1000;
nodesToSessionIdsMap = {};
timestampInMs = 0;
markDelete = false;
internalActive = false;
requestedNodesList;
/**
* Initializes a new instance of the PowerShellBatchRaw class.
*
* @param batchConnection The batch connection service.
* @param context The PowerShell batch session Context.
*/
constructor(batchConnection, context) {
this.batchConnection = batchConnection;
this.context = context;
}
/**
* Gets active status of PowerShell execution.
*/
get active() {
return this.internalActive;
}
/**
* Dispose the runspace.
*/
dispose() {
if (!this.active) {
// only close sessions that have been created.
// If a result was cached a component may not
// execute a command and still dispose the session
// when the component is destroyed.
if (Object.keys(this.nodesToSessionIdsMap).length > 0) {
this.close().subscribe();
}
}
else {
this.markDelete = true;
}
}
/**
* Runs the given batch command, and try followup Get calls if all nodes don't complete during the initial batch call.
*
* @param nodesList The nodes list to run batch against.
* @param commandList The list of command body, corresponding to nodesList.
*/
runCommand(nodesList, commandList) {
// take the timestamp only success/healthy case.
// error session would be auto-deleted after expiration time.
this.internalActive = true;
this.requestedNodesList = nodesList;
return this.command(nodesList, commandList)
.pipe(catchError((error) => {
this.internalActive = false;
SmeWebTelemetry.tracePowershellBatchEvent(commandList, TelemetryEventStates.Error, error);
return throwError(() => error);
}), expand((response) => {
this.timestampInMs = Date.now();
const psBatchResponse = this.convertBatchResponseToPowerShellBatchResponse(response);
const incompleteNodes = this.getIncompleteNodes(psBatchResponse);
if (incompleteNodes.length === 0) {
this.internalActive = false;
return EMPTY;
}
// create list of Get URLs for incomplete nodes.
const incompleteNodesUrlList = this.createRelativeUrlListSingleMethod(incompleteNodes, HttpMethod.Get);
// update requested Nodes list, so we can parse the response correctly.
this.requestedNodesList = incompleteNodesUrlList;
return this.batchConnection.get(incompleteNodes, incompleteNodesUrlList, this.context.requestOptions);
}), map((response) => {
const psBatchResponse = this.convertBatchResponseToPowerShellBatchResponse(response);
const errorResponses = psBatchResponse.filter((psResponse) => psResponse.error || psResponse.errors);
if (errorResponses.length > 0) {
SmeWebTelemetry.tracePowershellBatchEvent(commandList, TelemetryEventStates.Error, { response: errorResponses });
}
return psBatchResponse;
}));
}
/**
* Close/Delete the session / runspace map.
*/
close() {
if (Object.keys(this.nodesToSessionIdsMap).length > 0) {
const nodeList = [];
for (const node in this.nodesToSessionIdsMap) {
if (this.nodesToSessionIdsMap.hasOwnProperty(node)) {
nodeList.push(node);
}
}
const nodeUrls = this.createRelativeUrlListSingleMethod(nodeList, HttpMethod.Delete);
return this.batchConnection.delete(nodeList, nodeUrls, this.context.requestOptions)
.pipe(map((responseData) => {
this.nodesToSessionIdsMap = {};
const psBatchResponse = this.convertBatchResponseToPowerShellBatchResponse(responseData);
return psBatchResponse;
}));
}
Logging.log({
level: LogLevel.Warning,
source: 'PowerShellBatch/close',
message: MsftSme.getStrings().MsftSmeShell.Core.Error.PowerShellUnableSessionClose.message
});
return of(null);
}
/**
* Cancel the command.
*/
cancelCommand() {
if (Object.keys(this.nodesToSessionIdsMap).length > 0) {
const nodeList = [];
for (const node in this.nodesToSessionIdsMap) {
if (this.nodesToSessionIdsMap.hasOwnProperty(node)) {
nodeList.push(node);
}
}
const nodeUrls = this.createRelativeUrlListSingleMethod(nodeList, 'CANCEL');
return this.batchConnection.put(nodeList, nodeUrls)
.pipe(map((responseData) => {
this.nodesToSessionIdsMap = {};
const psBatchResponse = this.convertBatchResponseToPowerShellBatchResponse(responseData);
return psBatchResponse;
}));
}
Logging.log({
level: LogLevel.Warning,
source: 'PowerShell',
message: MsftSme.getStrings().MsftSmeShell.Core.Error.PowerShellUnableCancelCommand.message
});
return of(null);
}
/**
* Parse the response array for multi-part response and convert to PowerShellBatchResponse list.
*
* @param responseList The BatchResponse array received for the powershell batch call.
*/
convertBatchResponseToPowerShellBatchResponse(responseList) {
const powershellBatchResponse = [];
for (let itemId = 0; itemId < responseList.length; itemId++) {
const responseItem = responseList[itemId].response;
const nodeName = responseList[itemId].nodeName;
const sequenceNumber = responseList[itemId].sequenceNumber;
const jsonResponse = responseItem.response;
const status = responseItem.status;
const properties = Net.getItemProperties(jsonResponse);
if (status < 400) {
powershellBatchResponse.push({ sequenceNumber, status, nodeName, properties });
}
else {
const responseData = responseItem.response;
if (responseData.error) {
const error = responseData.error;
powershellBatchResponse.push({ sequenceNumber, status, nodeName, properties, error });
Logging.log({
source: 'Batch PowerShell',
level: LogLevel.Error,
message: MsftSme.getStrings().MsftSmeShell.Core.Error.BatchConnection.message
.format(status, error.code, Net.getPowerShellErrorMessage(responseItem.response))
});
}
else if (responseData.errors) {
const errors = responseData.errors;
powershellBatchResponse.push({ sequenceNumber, status, nodeName, properties, errors });
Logging.log({
source: 'Batch PowerShell',
level: LogLevel.Error,
message: Net.getPowerShellErrorMessage(responseItem.response)
});
}
}
}
return powershellBatchResponse;
}
/**
* Initiate command execution. It auto recycles old sessions.
*
* @param nodesList The list of nodes to run commands against
* @param commandList The command body list corresponding to nodesList.
*/
command(nodesList, commandList) {
const nodesSessionStateMap = this.getSessionsStateForNodesList(nodesList);
const methodsList = [];
for (let index = 0; index < nodesList.length; index++) {
const nodeName = nodesList[index];
if (nodesSessionStateMap[nodeName] === RunspaceSessionState.Expired) {
// Delete item from map.
delete this.nodesToSessionIdsMap[nodeName];
}
if (nodesSessionStateMap[nodeName] === RunspaceSessionState.Active) {
// Post method
methodsList.push(HttpMethod.Post);
}
else {
// Put method
methodsList.push(HttpMethod.Put);
}
}
const nodeUrls = this.createRelativeUrlList(nodesList, methodsList);
return this.batchConnection.mixed(nodesList, nodeUrls, commandList, methodsList, this.context.requestOptions);
}
/**
* Check if a valid/non-expired sesison exists for each node in the list.
*
* @param nodesList The nodes list to check valid existing sesion for.
*/
getSessionsStateForNodesList(nodesList) {
const runspaceSessionsState = {};
for (let index = 0; index < nodesList.length; index++) {
const savedSession = this.nodesToSessionIdsMap[nodesList[index]];
if (!savedSession) {
runspaceSessionsState[nodesList[index]] = RunspaceSessionState.Unavailable;
}
else if (this.isSessionEntryExpired(savedSession)) {
runspaceSessionsState[nodesList[index]] = RunspaceSessionState.Expired;
}
else {
runspaceSessionsState[nodesList[index]] = RunspaceSessionState.Active;
}
}
return runspaceSessionsState;
}
/**
* Create a relative url list for PowerShell Post batch call, based on nodes and methods list.
*
* @param nodesList The list of nodes to generate urls for
* @param methodList The http method types map corresponding to nodesList.
*/
createRelativeUrlList(nodesList, methodList) {
const responseUrlList = [];
for (let index = 0; index < nodesList.length; index++) {
const nodeName = nodesList[index];
const method = methodList[index];
// try to get session ids for given Node from the stored map.
const savedSession = this.nodesToSessionIdsMap[nodeName];
const sessionId = (savedSession && !this.isSessionEntryExpired(savedSession)) ? savedSession.sessionId : MsftSme.newGuid();
responseUrlList.push(this.createRelativeUrl(method, sessionId));
}
return responseUrlList;
}
/**
* Create a relative url list for PowerShell batch call, based on provided method.
*
* @param nodesList The list of nodes to generate urls for
* @param method The http method type
*/
createRelativeUrlListSingleMethod(nodesList, method) {
const responseUrlList = [];
for (let index = 0; index < nodesList.length; index++) {
const nodeName = nodesList[index];
// try to get session ids for given Node from the stored map.
const savedSession = this.nodesToSessionIdsMap[nodeName];
const sessionId = (savedSession && !this.isSessionEntryExpired(savedSession)) ? savedSession.sessionId : MsftSme.newGuid();
responseUrlList.push(this.createRelativeUrl(method, sessionId));
}
return responseUrlList;
}
/**
* Create a relative url for the given method and session Id.
*
* @param method The Http method to use for call.
* @param sessionId The PS runspace session Id.
*/
createRelativeUrl(method, sessionId) {
let relativeUrl = '';
if (method === HttpMethod.Delete) {
relativeUrl = Net.powerShellApiSessions.format(sessionId);
}
else if (method === HttpMethod.Put) {
relativeUrl = Net.powerShellApiSessions.format(sessionId);
}
else if (method === HttpMethod.Post) {
relativeUrl = Net.powerShellApiExecuteCommand.format(sessionId);
}
else if (method === HttpMethod.Get) {
relativeUrl = Net.powerShellApiRetrieveOutput.format(sessionId);
}
else if (method === 'CANCEL') {
relativeUrl = Net.powerShellApiCancelCommand.format(sessionId);
}
return relativeUrl;
}
/**
* Check if all indivudual nodes have returned 'completed' result and return the list of nodes which returned 'completed=false'
*
* @param responseArray The response from a PowerShell batch call.
* @return incompleteNodes The incompleteNodes array populated with nodes not completed yet.
*/
getIncompleteNodes(responseArray) {
const incompleteNodes = [];
for (const responseItem of responseArray) {
const properties = responseItem.properties;
// skip the error cases.
if (!properties) {
continue;
}
const sessionId = properties.sessionId;
const creationTimestamp = Date.now();
if (sessionId) {
// keep the PS session GUID
this.nodesToSessionIdsMap[responseItem.nodeName] = { sessionId, creationTimestamp };
}
if (properties.completed && properties.completed.toLowerCase() !== 'true') {
incompleteNodes.push(responseItem.nodeName);
}
}
return incompleteNodes;
}
/**
* Checks if a stored runSpace session for a specific node is expired.
*
* @param rsSessionContext runspace session context.
*/
isSessionEntryExpired(rsSessionContext) {
const now = Date.now();
return rsSessionContext.creationTimestamp !== 0 && (now - rsSessionContext.creationTimestamp) > PowerShellBatchRaw.maxDeltaTimeInMs;
}
}
/**
* The PowerShellbatch class.
*
* - Single instance of PowerShell batch class manages a single single nodes-runspaces map, with a runspace corresponding to each node.
* - It queues coming requests and process one at a time sequentially.
* - If a command is slow and causing with multiple responses, it aggregates response into single Q result.
* - A PowerShellBatch instance should be created through create() function, and it's statically stored/managed into _map collection.
* - Once all lifetime references are gone, it deletes the runspaces map.
* - To dispose the PowerShellBatch instance, it can use lifetime.dispose().
*/
export class PowerShellBatch {
/**
* Static collection of PowerShellbatch objects.
*/
static map = {};
/**
* The context of PowerShellBatch object.
*/
context;
/**
* The queue of PowerShell command requests.
*/
queue = [];
/**
* The reference to PowerShellRaw class object.
*/
raw;
/**
* Current data to return to caller.
*/
currentData = [];
/**
* Current data map to aggregate partial data parts from multiple data responses.
*/
currentDataMap = {};
/**
* Timestamp when last command started.
*/
timestamp;
static create(nodesList, batchConnection, key, lifetime, requestOptions) {
let ps;
if (key && lifetime) {
ps = PowerShellBatch.map[PowerShellBatch.indexName(nodesList, key)];
if (ps) {
ps.addLifetime(lifetime);
return ps;
}
}
ps = new PowerShellBatch(nodesList, batchConnection, key, lifetime, requestOptions);
if (key && lifetime) {
PowerShellBatch.map[PowerShellBatch.indexName(nodesList, key)] = ps;
}
return ps;
}
/**
* Find existing PowerShellBatch object. Create call must be called before to create the PowerShellBatch instance.
*
* @param nodeName The node name.
* @param key The shared key to queue the requests to use the single runspace.
*/
static find(nodesList, key) {
return PowerShellBatch.map[PowerShellBatch.indexName(nodesList, key)];
}
/**
* Create the index name in map collection.
*
* @param nodesList The nodes list targeted by this PowerShellBatch object.
* @param key The shared key to queue the requests to use the single runspace.
*/
static indexName(nodesList, key) {
return nodesList.join(':') + ':' + key;
}
/**
* Initializes a new instance of the PowerShellBatch class.
* (private constructor which shouldn't be called directly.)
*
* @param nodeList The nodes list targeted by this PowerShellBatch object.
* @param batchConnection The batch connection service.
* @param key The shared key to queue the requests to use the single runspace map.
* @param lifetime The lifetime container.
*/
constructor(nodeList, batchConnection, key, lifetime, options) {
this.context = {
key: key,
nodesList: nodeList,
lifetimes: [],
requestOptions: PowerShell.newEndpointOptions(options)
};
this.timestamp = 0;
this.raw = new PowerShellBatchRaw(batchConnection, this.context);
if (key && lifetime) {
lifetime.registerForDispose(new Disposer(() => this.lifetimeDisposer(lifetime)));
this.context.lifetimes.push(lifetime);
}
}
/**
* Run PowerShellBatch command.
*
* @param command The command to run against all nodes in nodesList.
* @param options The options.
* @return observable The result of PowerShell batch command.
*/
runSingleCommand(command, options) {
const commandsList = [];
for (let i = 0; i < this.context.nodesList.length; i++) {
commandsList.push(command);
}
return this.run(commandsList, options);
}
/**
* Run PowerShellBatch command list.
*
* @param commandsList The commands to run against given nodesList.
* @param options The options.
* @return observable The result of PowerShell batch command.
*/
run(commandsList, options) {
if (commandsList.length !== this.context.nodesList.length) {
return EMPTY;
}
const commandBodyList = [];
// wrap command in properties.
for (const command of commandsList) {
commandBodyList.push(Net.createPropertiesJSONString(command));
}
if (this.context.lifetimes.length === 0) {
// no disposer is assigned, force to close the session after every query.
const timeoutMs = options && options.timeoutMs;
if (options) {
options.timeoutMs = timeoutMs;
options.close = true;
}
else {
options = { timeoutMs: timeoutMs, close: true };
}
}
// queue the request.
const observable = this.enqueue(this.context.nodesList, commandBodyList, options);
return observable;
}
/**
* Cancel PowerShellBatch command.
*/
cancel() {
return this.raw.cancelCommand();
}
/**
* Enqueue a command request.
*
* @param nodesList: the node list.
* @param commandBodyList The command.
* @param options The options.
*/
enqueue(nodesList, commandBodyList, options) {
return new Observable((observer) => {
this.queue.push({ nodesList, commandList: commandBodyList, observer, options });
this.dequeue();
});
}
/**
* Dequeue a command request.
*/
dequeue() {
if (this.raw.active) {
return false;
}
const item = this.queue.shift();
if (item) {
this.currentData = [];
this.currentDataMap = {};
this.timestamp = Date.now();
this.raw.runCommand(item.nodesList, item.commandList).subscribe({
next: data => {
this.collect(data, item.options && item.options.timeoutMs, item.options && item.options.partial ? item.observer : null);
},
error: error => {
if (item.options && item.options.close) {
this.raw.close().subscribe();
}
item.observer.error(error);
this.timestamp = 0;
this.dequeue();
},
complete: () => {
if (item.options && item.options.close) {
this.raw.close().subscribe();
}
if (!item.options || !item.options.partial) {
item.observer.next(this.currentData);
}
item.observer.complete();
this.timestamp = 0;
this.dequeue();
}
});
return true;
}
return false;
}
/**
* Collect response results for batch call and aggregate into single object.
*
* @param properties The properties of response object.
* @param timeoutMs The timeout to cancel command.
* @param observer The observer of powershell results.
*/
collect(psResponseList, timeoutMs, observer) {
if (timeoutMs && this.timestamp && (Date.now() - this.timestamp > timeoutMs)) {
// force to cancel the command because of unexpected longer execution.
this.raw.cancelCommand();
this.timestamp = 0;
return;
}
if (observer) {
// return partial data if observer is not null.
observer.next(psResponseList);
this.currentData = psResponseList;
return;
}
// Merge responses from calls which didn't complete in one go.
for (const item of psResponseList) {
// Check if we have saved record from previous call to add to.
if (this.currentDataMap[item.nodeName]) {
// Aggregate Results: If the newly received has results, aggregate them with saved data.
if (item.properties.results) {
let array;
// if any previously received results, use them in aggregation.
if (this.currentDataMap[item.nodeName].properties && this.currentDataMap[item.nodeName].properties.results) {
if (MsftSme.getTypeOf(this.currentDataMap[item.nodeName].properties.results) === 'array') {
array = this.currentDataMap[item.nodeName].properties.results;
}
else {
array = [this.currentDataMap[item.nodeName].properties.results];
}
}
else {
array = [];
}
// Add results from currently received data.
if (MsftSme.getTypeOf(item.properties.results) === 'array') {
item.properties.results.forEach((x) => {
array.push(x);
});
}
else {
array.push(item.properties.results);
}
// Update saved map with the new aggregated data
this.currentDataMap[item.nodeName].properties.results = array;
}
// Aggregate Errors: If the newly received response has errors field, aggregate them with saved data.
if (item.errors) {
let errorsArray;
// if any previously received errors, use them in aggregation.
if (this.currentDataMap[item.nodeName].errors) {
errorsArray = this.currentDataMap[item.nodeName].errors;
}
else {
errorsArray = [];
}
// Add results from currently received data.
item.errors.forEach((x) => {
errorsArray.push(x);
});
// Update saved map with the new aggregated data
this.currentDataMap[item.nodeName].errors = errorsArray;
}
}
else {
// first response/ no saved response. Simply add to map.
this.currentDataMap[item.nodeName] = item;
}
}
this.currentData = this.convertResponseMapDataToList(this.currentDataMap);
}
/**
* Helper method to convert a map data to list
*
* @param nodeMap The map of nodenames to PowerShellBatchResponseItem. Used to track different calls in a batch.
* @return The response data for the calls in a list.
*/
convertResponseMapDataToList(nodeMap) {
const responseList = [];
for (const key in nodeMap) {
if (nodeMap.hasOwnProperty(key)) {
responseList.push(nodeMap[key]);
}
}
return responseList;
}
/**
* Attach lifetime object to disposer when disposing.
*
* @param lifetime The lifetime object.
*/
addLifetime(lifetime) {
const found = MsftSme.find(this.context.lifetimes, (value) => value === lifetime);
if (!found) {
this.context.lifetimes.push(lifetime);
lifetime.registerForDispose(new Disposer(() => this.lifetimeDisposer(lifetime)));
}
}
/**
* Callback when disposing the container of view model.
* If none, reference the PowerShell object. Dispose it. (Delete runspace)
*
* @param lifetime The lifetime object.
*/
lifetimeDisposer(lifetime) {
const found = MsftSme.find(this.context.lifetimes, (value) => value === lifetime);
if (found) {
MsftSme.remove(this.context.lifetimes, lifetime);
if (this.context.lifetimes.length === 0) {
// cancel queue command requests.
this.queue.forEach((value) => {
value.observer.next(null);
value.observer.complete();
});
// delete from the map collection and delete the runspace/session.
delete PowerShellBatch.map[PowerShellBatch.indexName(this.context.nodesList, this.context.key)];
this.raw.dispose();
}
}
}
}
//# sourceMappingURL=powershell-batch.js.map
// SIG // Begin signature block
// SIG // MIIoKwYJKoZIhvcNAQcCoIIoHDCCKBgCAQExDzANBglg
// SIG // hkgBZQMEAgEFADB3BgorBgEEAYI3AgEEoGkwZzAyBgor
// SIG // BgEEAYI3AgEeMCQCAQEEEBDgyQbOONQRoqMAEEvTUJAC
// SIG // AQACAQACAQACAQACAQAwMTANBglghkgBZQMEAgEFAAQg
// SIG // c03rLMjUNl1F36d7n+kaIon56DbQsbhtB0R2XKkZB8Cg
// 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/15n8G9bW1qyVJzEw16UM0xghoNMIIaCQIBATCBlTB+
// SIG // MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
// SIG // bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
// SIG // cm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNy
// SIG // b3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExAhMzAAAE
// SIG // BGx0Bv9XKydyAAAAAAQEMA0GCWCGSAFlAwQCAQUAoIGu
// SIG // MBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisG
// SIG // AQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3
// SIG // DQEJBDEiBCCtAJ5jPkwkVCx8nLogLznNKANWl0pjO0F5
// SIG // T3vmYfyoPTBCBgorBgEEAYI3AgEMMTQwMqAUgBIATQBp
// SIG // AGMAcgBvAHMAbwBmAHShGoAYaHR0cDovL3d3dy5taWNy
// SIG // b3NvZnQuY29tMA0GCSqGSIb3DQEBAQUABIIBAD8DhAZ1
// SIG // xkFjTTl6AaXFV8ves9ccrPJB8qN6TJR4PiCVvZT8nAMM
// SIG // Ml8ukzBTuiH3COYvCbK1ooqQMtnaMoo1S3nPZ4RjEUzc
// SIG // 9oPv1BlKHC3a2ikFUjEtgV6rPa6U2QNEMgLteazwuVVp
// SIG // H2IGSd8Gzk3X9FrmfyACdBbrXdcGBUOkqziX/baTzWs2
// SIG // r9gzdVZnPOSDN6TrYvNAhJKOE8IFR3+9GMrR0gJTLq6S
// SIG // ZBvcuataySUqzg2qR+tNku12/t0peUCV9E1dzmOSdZ9l
// SIG // xbWreWruAf7JHgcMcw2MSQ40xJ7Hco5mTwR1UWwYG1x5
// SIG // KbYFjjJ+K4PWuI6Ld8W+fJ1AGRShgheXMIIXkwYKKwYB
// SIG // BAGCNwMDATGCF4Mwghd/BgkqhkiG9w0BBwKgghdwMIIX
// SIG // bAIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBUgYLKoZIhvcN
// SIG // AQkQAQSgggFBBIIBPTCCATkCAQEGCisGAQQBhFkKAwEw
// SIG // MTANBglghkgBZQMEAgEFAAQg5PORI9AOvwy7qUxk4JHb
// SIG // SAcJaJbk9w2V6iDcNiWv4usCBmeuM89BdxgTMjAyNTAy
// SIG // MjAxNTI4MzkuMDE2WjAEgAIB9KCB0aSBzjCByzELMAkG
// SIG // A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
// SIG // BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
// SIG // dCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0
// SIG // IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNo
// SIG // aWVsZCBUU1MgRVNOOkUwMDItMDVFMC1EOTQ3MSUwIwYD
// SIG // VQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
// SIG // oIIR7TCCByAwggUIoAMCAQICEzMAAAHuBdMCMLKanacA
// SIG // AQAAAe4wDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC
// SIG // VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
// SIG // B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
// SIG // b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
// SIG // U3RhbXAgUENBIDIwMTAwHhcNMjMxMjA2MTg0NTQ0WhcN
// SIG // MjUwMzA1MTg0NTQ0WjCByzELMAkGA1UEBhMCVVMxEzAR
// SIG // BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
// SIG // bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
// SIG // bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3Bl
// SIG // cmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNO
// SIG // OkUwMDItMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3Nv
// SIG // ZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG
// SIG // 9w0BAQEFAAOCAg8AMIICCgKCAgEAvvG8pdeihImvMSku
// SIG // L1S+0RDjkey82Ai1xLVoHqsjlZa87hM/gKAmuLQRhEo2
// SIG // x01xAnjDsD/Uz3imimpX01OV0ho6SYaRsefX8TCaE2Fj
// SIG // 88w9DtkQJcgZjgQZoiw10Q0CS9UbbgI7woi7pVUHojyP
// SIG // Fe/h4U0d/dU2wtW3kscF33SiamNaJ4w2sKgyQJrcLAP4
// SIG // Jql4B8BfX2VnMCkrl4mQU21OX3Jt24YZUTcOXdOC3deW
// SIG // Vs1Zf1Q6f4kXqxqNiLP9FsJ/2t3hjnR6738CG35OpVas
// SIG // GzUBNdTnnZ9rr0YylhMHq1y+9Drg2fLy88a8tMhHb0PJ
// SIG // MvlX6vJnxF0vdO2O6zfx2F+nArAtrKMlxtzsArSwO6NP
// SIG // /pCiWbjqw+R1K0s95H6oA5Zlsuu8/GWT45IgwtXWFtYz
// SIG // e+7eYkpeVqdRygaeyVPEYkSPr2NotXG+V9kRJMN1qzVv
// SIG // 426H1xLPbeG4HfslPLICp/TLVZ0OubOkBu9jP8mlGRth
// SIG // zCN9bZvZqKB9vbzwTvYwzDiLtC8M1E5CFn5YHf7xFn0z
// SIG // XD1hEI+37FrkqFbid7gasDZkUqZkA80nzGiM7srNKb1d
// SIG // YxVqrasMAnGmP1l7G/2sZMQf8wk3R0gVCfE5t4uDzPbJ
// SIG // Irp12PnEqh+fI1pKR22ywNzn7LO3viWzIypk3XI5kpG+
// SIG // aDfKlNcCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBQQiM0/
// SIG // GtncIJ69+8Xftr9f3HamCDAfBgNVHSMEGDAWgBSfpxVd
// SIG // AF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQ
// SIG // hk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
// SIG // L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
// SIG // JTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG
// SIG // CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
// SIG // b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUt
// SIG // U3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB
// SIG // Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4G
// SIG // A1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA
// SIG // d2cgL2thCjlklaQZ2JM1/H/BmY2jrOe+xfaNeAJ4fZSs
// SIG // urUt+MF6D1xMkKdb9YiO6yc2VRu66VM52stp/XLH596e
// SIG // su5GJB6rUroAhpk4ogZMIRX0gcijyNPDJJYLybyk2W+u
// SIG // 98hn6RcD40MGXiOhD4/zgLaWJE+yFF6jJItQkTCSoHmO
// SIG // MFEQnHCLo3VkZKFb+Cd6v/OyhNKj0JgEfX6jDcYyN2Qp
// SIG // VcQOMIjN7TVZUWxfUoKTp41aNz/yOafCXeNYTUlQsf/I
// SIG // 96jO2i0irQ8zhFDbPmbY4c55mYFHe/wFhw4cAR3S+e0y
// SIG // PYe54mZHzmTl53GLCsRuIK8k7IVOhurAGKW6nTBP/v4N
// SIG // bnq+1RiB1LS6t1tAJ5vJQH0vT6rYbJGbeeCRdvAh3bBa
// SIG // v+11QbRZcS/yoHEMpSTZ4mvmp4sVButMlA7dxTBkiSN+
// SIG // MRvTR7M9waaklrnhrSYUOWTdCvI7tLzVYBfg79ObIqz4
// SIG // NH7Uin/RVRAqfd6PKIBePI4fAk/wd9pc9Q+k67pOBM3M
// SIG // OxNTobTjH+wx4DzFn+ljnWJ3/h2kice2U1wibFuaDpDN
// SIG // LC4rcQaUqRnI9mI5zc5wqbBD2WrdIfune7pUWlkeURwF
// SIG // MhRUPY0WuylmjRnRC07Ppx0pWI2HkKSuUEl44oHSpS0D
// SIG // wZV/vczqBgCYaGX66Y6uJ0AwggdxMIIFWaADAgECAhMz
// 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 // ahC0HVUzWLOhcGbyoYIDUDCCAjgCAQEwgfmhgdGkgc4w
// SIG // gcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
// SIG // dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
// SIG // aWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
// SIG // Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNV
// SIG // BAsTHm5TaGllbGQgVFNTIEVTTjpFMDAyLTA1RTAtRDk0
// SIG // NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
// SIG // U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAiKOm1Tb35RcW
// SIG // 1Fgg0N2GCsujvpOggYMwgYCkfjB8MQswCQYDVQQGEwJV
// SIG // UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
// SIG // UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
// SIG // cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
// SIG // dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAOth
// SIG // Q4YwIhgPMjAyNTAyMjAwNjAwMzhaGA8yMDI1MDIyMTA2
// SIG // MDAzOFowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA62FD
// SIG // hgIBADAKAgEAAgIOWwIB/zAHAgEAAgITLjAKAgUA62KV
// SIG // BgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZ
// SIG // CgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqG
// SIG // SIb3DQEBCwUAA4IBAQCJm5tqhfJNGnCU6pzufu7oSZKV
// SIG // kv+qUT6NPmaIzZj91+DmimfBb0YmUjGNsZHYZ+ZZcsRX
// SIG // kTw2s3ZNXOL4R4lwl1wsBp3LeZhHAaaHYdDY4fJAGy5F
// SIG // RPOKH2123IGxiX7HVxJ9cpcgc8n5XjhyYVLDie2DU4E4
// SIG // k+SMqAXEkt9Y+mnPhbyFkwjij9kjLMnRozEem8SSrQNk
// SIG // wpvhm3a1Nv33xw2xGJbO2QW++gn7WtZyN8hQytYCvPpE
// SIG // HQwmlEpURvW+AE3UaWklCnpbF3IlBTmJNxYxkxl2EWZP
// SIG // RJrr1zrZ3TOAUhcChoymljIQT2I5ozMrbqD0wP4/eS7q
// SIG // mD5oCPXlMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
// SIG // VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
// SIG // B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
// SIG // b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
// SIG // U3RhbXAgUENBIDIwMTACEzMAAAHuBdMCMLKanacAAQAA
// SIG // Ae4wDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJ
// SIG // AzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQg
// SIG // euu8YImv6had0eGzrqF2AlXyREvHykEH6poINPn4k/Aw
// SIG // gfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBPUHcU
// SIG // lYX6vlXX/gz7PuRCJAc/aAkvzkH5R5FUYX4wITCBmDCB
// SIG // gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
// SIG // aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
// SIG // ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
// SIG // HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMz
// SIG // AAAB7gXTAjCymp2nAAEAAAHuMCIEIL8alJrYHTS+brqa
// SIG // XihIgQYMTOuyPr8/IrVb9C65hr9FMA0GCSqGSIb3DQEB
// SIG // CwUABIICAI9RU9UDqoT1sMLT03MTytV2aefuUey7dwbB
// SIG // RYg0AaEz+Qz9sHEMkp9HP3ui6rfpo7mrUZXmXFYGiye+
// SIG // U+mADOpsgS2bqjb0GKsjQiFCvp1us3IMOGheZlhYqTRK
// SIG // blrRvsbCdawAsp7yP2vfCV9rjxVcw+tfjhY6gR1084gN
// SIG // UjbRIa7qGhfjsmjpciyFfuR1oJA2FTUO21yQqy3q/TQS
// SIG // /HfPjK+zEpr4Vm7Ps/9bTBbCxAvn4iPtubjAsHU8UOC9
// SIG // Xvumpa2eMyXR0rmLx7aVjX9TzJUeIDKa6bK4DbhIJQu3
// SIG // vwhSZ0qPZt05ojR0cMmDNKQ9LzdESMHiTvdgEURVFTuZ
// SIG // rXrR6xv8s4zbjO4Z8zY4rzuw9g8pRO4AYL/C3jrG1nSY
// SIG // ll8E3dTgfpS/nqFIWkOPKcCJoDt9uM5eJrodHF5tVEnY
// SIG // 0e3007oU7nA6UukPdMjYeQm0V0fOkHo+MszMhQ+qcxZu
// SIG // JjnWnxqVZbRCKg4wm2PgMytiMoNJjHnAKga3ZW5RqjM4
// SIG // k1G7gAhLYDnQm9OAPzJx3E4JQlOSsY3StPM+uLXuudpN
// SIG // 8c8IN2fGKtq7dlKaipbNdE4NbiYtnd0A5D+8yI0axwK7
// SIG // WGgyG5/hMX9x3ScDB+3dRs7MRN3IKLTeWcbegv/5UDhL
// SIG // wt6FN5DUGH+t2lPODRLfVJMPvoS4ht+e
// SIG // End signature block