UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

1,015 lines (1,012 loc) 46 kB
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