UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

863 lines (860 loc) 38.8 kB
import { EMPTY, merge, Observable, of, ReplaySubject, Subject } from 'rxjs'; import { delay, expand, multicast, refCount, switchMap } from 'rxjs/operators'; import { Logging } from '../diagnostics/logging'; /** * Query Cache class. * - Create a cache entry by "create" call with creator object. * - Subscribe with options to get query result. * - Refresh data on-demand and interval. * - Dispose the resource when it's done. * - Recover and re-subscribe with the same handlers if necessary after an error. * * TData the data type of observable responds. * TParams the options parameters to pass the creator to create new observable. */ export class QueryCache { create; serializeParams; destroy; /** * Internal instance id counter. * (set id number to be 0 to bring back older behavior) */ static id = 10000; /** * Delay clean up time (10 seconds) */ static delayTime = 10 * 1000; /** * Repeater is parked state. */ static repeaterParked = -1; /** * Cached data collection with observable ReplaySubject. */ cachedData; /** * Current options, passed from the fetch call. */ options; /** * Current observer of create observable. */ observer; /** * Key string serialized by TParams. */ key; /** * Delay clean timer object. */ delayTimer; /** * Auto fetch options. */ autoFetchOptions; /** * Repeater context to cycle repeating observable. */ repeaterContext; /** * Instance id of query cache for internal debugging. */ id = '{0}-{1}'.format(MsftSme.self().Init.moduleName, ++QueryCache.id); /** * The tracing name. */ name; /** * The start time of tracing. */ traceTime; /** * Initializes a new instance of the QueryCache. * * @param create the function to create a new observable with specified parameters. * @param serializeParams the function to generate serialized string from specified parameters. (optional) * @param destroy the function to clean up any residue after all reference was gone. (optional) */ constructor(create, serializeParams, destroy) { this.create = create; this.serializeParams = serializeParams; this.destroy = destroy; } /** * Use the QueryCache inside of Worker or background tab view. * setInterval time lost accuracy within background. Use setTimeout recursive query by expand observable operator. * Call the static function once before creating new instance of QueryCache, probably set at app.component.ts file. */ static useExpandInterval() { QueryCache.id = 0; } /** * Create or return the cached observable. * * @param autoFetchOptions the options parameter auto-fetch when subscribe is called. * @return Observable<TData> the observable object. */ createObservable(autoFetchOptions) { // for recovery scenario, this.cachedData.subscribers is retained. // clear them here if no publish ever made. if (this.cachedData && this.cachedData.publish) { if (autoFetchOptions) { // schedule the fetch immediately after current call stack. setTimeout(() => { // cancel auto fetch if already unsubscribed. if (this.cachedData && this.cachedData.publish) { this.fetch(autoFetchOptions); } }); } return this.cachedData.publish; } const fetch = new Subject(); const refresh = new Subject(); const apply = new Subject(); const subscribers = []; const publish = this.createPublishObservable(fetch, refresh, apply, subscribers); this.cachedData = { fetch, refresh, publish, apply, subscribers }; this.autoFetchOptions = autoFetchOptions; return publish; } /** * Unsubscribe any remained subscribers and dispose all remained resources. * - clearCache() is not necessary to be called if all subscriptions are unsubscribe'ed properly. */ clearCache() { if (!this.cachedData) { return; } if (this.cachedData.subscribers) { const tempSubscribers = this.cachedData.subscribers.slice(0); for (const context of tempSubscribers) { context.subscription.unsubscribe(); } } this.cleanup(false); } /** * Fetch with new query options. * - The fetch starts new query when cache is empty, current cache doesn't match the key generated by * serializedParams function which was configured at the constructor, interval was changed (ex. * changing zero interval to active interval or other way around), or the first subscriber like when * delayClean comes back active subscription. * * @param options the options with interval, delay clean, recovery and parameters. */ fetch(options) { if (!this.cachedData) { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.QueryCacheFetchOrder.message; Logging.logError('QueryCache', message); throw new Error(message); } if (this.cachedData.fetch) { // send new request when cache doesn't match the key, interval was changed, or single subscriber. const key = this.serializeParams ? this.serializeParams(options.params) : ''; if (!this.options || this.key !== key || this.options.interval !== options.interval || this.key == null) { this.key = key; this.cachedData.fetch.next(options); } } else { Logging.logWarning('QueryCache', MsftSme.getStrings().MsftSmeShell.Core.Error.QueryCacheFetchErrorOnce.message); } } /** * Refresh the query cache with last options and parameters provided on the fetch call. */ refresh() { if (!this.cachedData) { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.QueryCacheRefreshOrder.message; Logging.logError('QueryCache', message); throw new Error(message); } if (this.cachedData.refresh) { this.cachedData.refresh.next(undefined); } else { Logging.logWarning('QueryCache', MsftSme.getStrings().MsftSmeShell.Core.Error.QueryCacheRefreshErrorOnce.message); } } /** * Recover the observable and subscription. * - Recover can be used to resubscribe when the observable got any error situation. The observable would * be unsubscribed state when it got an error response, this function allows to resubscribe without * recreate observable and subscription. * * @param autoFetch if true auto fetch after re-subscribed. */ recover(autoFetch) { if (!this.cachedData) { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.QueryCacheRecoverNoCachedResource.message; Logging.logError('QueryCache', message); throw new Error(message); } if (!this.options || !this.options.enableRecovery) { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.QueryCacheRecoverMissingRecoveryOption.message; Logging.logError('QueryCache', message); throw new Error(message); } const tempCachedData = this.cachedData; if (tempCachedData.refresh) { tempCachedData.refresh.complete(); } if (tempCachedData.fetch) { tempCachedData.fetch.complete(); } if (tempCachedData.apply) { tempCachedData.apply.complete(); } for (const context of this.cachedData.subscribers) { // call original unsubscribe function. context.subscription.unsubscribe(); } // recreate new publish observable and subscribe to original set of handlers. const tempSubscribers = this.cachedData.subscribers.slice(0); const fetch = new Subject(); const refresh = new Subject(); const apply = new Subject(); const subscribers = []; const publish = this.createPublishObservable(fetch, refresh, apply, subscribers); this.cachedData = { fetch, refresh, publish, apply, subscribers }; for (const context of tempSubscribers) { this.cachedData.publish.subscribe(context.next, context.error, context.complete, context.subscription); } if (autoFetch) { fetch.next(this.options); } else { this.options = {}; } } /** * Apply instant data to the query cache. The data will be delivered to the subscriber immediately. * * @param data the data to apply to the replay. */ apply(data) { if (this.cachedData.apply) { this.cachedData.apply.next(data); } } /** * Enable tracing of this query cache instance. * * @param name the name of query cache. */ trace(name) { this.name = name; this.traceTime = Date.now(); this.log(`tracing ${this.name}: ${this.id}`); } log(message) { const time = ((Date.now() - this.traceTime)) / 1000; Logging.debug(`QC: ${this.id} ${this.name}: ${time}sec: ${message}`); } createPublishObservable(fetch, refresh, apply, subscribers) { const publish = // start data query when new fetch is requested. fetch .pipe( // switch map unsubscribe previous fetch observable tree, and re-create new one. switchMap(options => { // remember last options. this.options = options || {}; // merge output from expand-delay object and refresh object. return merge( // submit initial query. this.repeat(), // refresh to trigger new observable. refresh.pipe(switchMap(() => this.create(this.options.params))), // apply data. apply); }), // multicast the result so multiple subscribers can share. // and Replay last result if later subscriber looks for the result. multicast(new ReplaySubject(1)), // keep the reference count so publish observable active. refCount()); // override subscribe call to keep track subscription. publish.subscribe = ((next, error, complete, recoverSubscription) => { if (this.delayTimer) { clearTimeout(this.delayTimer); this.delayTimer = null; if (this.options && this.options.interval) { this.options.interval = null; } } // override default handler with No-OP call if not set. // this allows all subscribe call to be get called when an error reported. next = next || MsftSme.noop; error = error || MsftSme.noop; const context = { next, error, complete, errorCount: 0, unsubscribeCount: 0 }; const hookError = (errorData) => { context.errorCount++; error(errorData); }; context.subscription = Object.getPrototypeOf(publish).subscribe.call(publish, next, hookError, complete); // hook up old subscription so old subscriber can unsubscribe properly. if (recoverSubscription) { recoverSubscription.unsubscribe = () => context.subscription.unsubscribe(); } // add subscribers to the inventory before adding to the un-subscription list. add() could call unsubscribe immediately. subscribers.push(context); // add internal unsubscribe to the original subscription. context.subscription.add(this.internalUnsubscribe.bind(this, context)); // if createObservable() is called with autoFetchOptions, it start fetching data immediately when subscribe() is called. if (this.autoFetchOptions) { this.fetch(this.autoFetchOptions); this.autoFetchOptions = null; } if (this.name) { this.log(`subscribe count=${subscribers.length}`); } return context.subscription; }); return publish; } internalUnsubscribe(context) { context.unsubscribeCount++; if (context.unsubscribeCount > 1) { // ignore if it's called twice and more. return; } if (!this.cachedData) { return; } const options = this.options || {}; if (options.enableRecovery) { // indicating normal unsubscribe call, so delete it. if (context.errorCount === 0) { const contextIndex = this.cachedData.subscribers.indexOf(context); if (contextIndex >= 0) { this.cachedData.subscribers.splice(contextIndex, 1); } } // on the recovery mode, retains all subscriber context, and don't clean up. // if there no active subscription, make cleanup call. const findAny = this.cachedData.subscribers.find(item => !item.subscription.closed); if (!findAny) { if (options.delayClean) { this.delayTimer = setTimeout(() => { this.cleanup(true); this.delayTimer = null; }, QueryCache.delayTime); } else { this.cleanup(true); } } return; } // non recovery mode. const index = this.cachedData.subscribers.indexOf(context); if (index >= 0) { this.cachedData.subscribers.splice(index, 1); } if (this.cachedData.subscribers.length === 0) { if (options.delayClean) { this.delayTimer = setTimeout(() => { this.cleanup(false); this.delayTimer = null; }, QueryCache.delayTime); } else { this.cleanup(false); } } } cleanup(recovery) { if (this.name) { this.log('unsubscribed'); } this.repeaterStop(); if (!this.cachedData) { return; } if (this.cachedData.fetch) { this.cachedData.fetch.complete(); this.cachedData.fetch = null; } if (this.cachedData.refresh) { this.cachedData.refresh.complete(); this.cachedData.refresh = null; } if (this.cachedData.apply) { this.cachedData.apply.complete(); this.cachedData.apply = null; } this.cachedData.publish = null; this.key = null; // on recovery cleanup, retain subscribers and options. if (!recovery) { this.cachedData.subscribers = null; this.cachedData = null; this.options = {}; } if (this.destroy) { this.destroy(); } } /** * Repeat observable. * Using free running interval to avoid setTimeout recursive or Observable.expand recursive stacks. * It reduces the usage of browser memory but use more frequent timer event as pulse in the code. */ repeat() { if (QueryCache.id < 10000) { // Legacy code pattern which use expand/recursive/setTimeout // submit initial query. return this.create(this.options.params) // expand to re-query next interval .pipe(expand((result) => { if (!this.options.interval || this.options.interval <= 0) { // stop the interval delay. return EMPTY; } // return new observable after the delay. return of(result) .pipe( // delay for the interval. delay(this.options.interval), // complete previous observable and switch to new observable. switchMap(() => this.create(this.options.params))); })); } this.repeaterStop(); if (!this.options.interval) { // single observable. (no repeat) return this.create(this.options.params); } // create new observable to repeat calling repeaterCreate() function. return new Observable((observer) => { this.observer = observer; this.repeaterInitialize(); this.repeaterStart(); return () => { // either closed by unsubscribe or observer is completed. this.observer?.complete(); this.observer = null; this.repeaterStop(); }; }); } repeaterInitialize() { if (this.options.interval < 1000) { // min 1 sec interval. this.options.interval = 1000; } // 100 or 200 or 1000 pulse. const pulse = this.options.interval <= 5000 ? 100 : (this.options.interval <= 10000 ? 200 : 1000); const cycle = Math.floor(this.options.interval / pulse); this.repeaterContext = { counter: 0, request: QueryCache.repeaterParked, timer: 0, pulse, cycle, verify: 0 }; } repeaterStart() { // run first query. this.repeaterCreate(); // start interval counter is incremented but cycled by cycle number. // if it hits request === counter, call this.repeaterCreate(); this.repeaterContext.timer = setInterval(this.repeaterCheck.bind(this), this.repeaterContext.pulse); } repeaterStop() { if (!this.repeaterContext) { return; } if (this.repeaterContext.timer) { clearInterval(this.repeaterContext.timer); this.repeaterContext.timer = null; } this.repeaterContext.request = QueryCache.repeaterParked; this.repeaterContext.verify = 0; this.repeaterContext.counter = 0; } repeaterCreate() { if (!this.observer) { this.repeaterStop(); return; } if (!this.options.interval) { if (this.observer) { this.observer.complete(); this.observer = null; } this.repeaterStop(); return; } // subscribe to the create observable with params passed. this.create(this.options.params) .subscribe({ next: data => { this.observer?.next(data); }, error: error => { this.observer?.error(error); this.repeaterStop(); }, complete: () => { if (!this.options.interval) { // complete if the interval was reset. if (this.observer) { this.observer.complete(); this.observer = null; } this.repeaterStop(); return; } if (this.name) { this.log(`repeat: request=${this.repeaterContext.request}, counter=${this.repeaterContext.counter}`); } // set next request counter number if it's parked. if (this.repeaterContext.request === QueryCache.repeaterParked) { this.repeaterContext.request = this.repeaterContext.counter; } } }); } repeaterCheck() { if (this.repeaterContext.request === QueryCache.repeaterParked) { return; } // update cycle counter this.repeaterContext.counter = ++this.repeaterContext.counter % this.repeaterContext.cycle; // update verify counter which starts from 0 to cycle. this.repeaterContext.verify++; // check if it hits a cycle. if (this.repeaterContext.counter === this.repeaterContext.request) { // wait for repeatCreate observable to complete. reset verify counter first before activate the observable. this.repeaterContext.request = QueryCache.repeaterParked; this.repeaterContext.verify = 0; this.repeaterCreate(); } else if (this.repeaterContext.verify > this.repeaterContext.cycle + 1) { throw new Error('QueryCache: Cycle counter running over. cycle={0}, counter={1}, request={2}, verify={3}'.format(this.repeaterContext.cycle, this.repeaterContext.counter, this.repeaterContext.request, this.repeaterContext.verify)); } } } //# sourceMappingURL=query-cache.js.map // SIG // Begin signature block // SIG // MIIoKwYJKoZIhvcNAQcCoIIoHDCCKBgCAQExDzANBglg // SIG // hkgBZQMEAgEFADB3BgorBgEEAYI3AgEEoGkwZzAyBgor // SIG // BgEEAYI3AgEeMCQCAQEEEBDgyQbOONQRoqMAEEvTUJAC // SIG // AQACAQACAQACAQACAQAwMTANBglghkgBZQMEAgEFAAQg // SIG // NBQaPAf3cBYaFqPecGo3fm4R1ipMzL4SblAaNOKyuyGg // 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 // DQEJBDEiBCDn/ZQbg9z7/Vgz/6K3ZCzzoV7SqqO5mt3n // SIG // JQoYULVs5zBCBgorBgEEAYI3AgEMMTQwMqAUgBIATQBp // SIG // AGMAcgBvAHMAbwBmAHShGoAYaHR0cDovL3d3dy5taWNy // SIG // b3NvZnQuY29tMA0GCSqGSIb3DQEBAQUABIIBAD3JdWGz // SIG // omifcq8yXPTqAh9MZi0GPLyRCZs49v7WPz37nnZIvDZj // SIG // AF8FiwJFPOH4VDf3mWwG6v8ETCB9N/h4zt0p6LsKMnyY // SIG // t8Ge7PfQvAY3YPPGvwxgy1CzBeqqvfTxbqvMKYKKmQSZ // SIG // 6DnCH/I8g66iy8qtdMfNY1DZJo6yn7iATe2HvAKaiksr // SIG // 8XMtIYAVT5OSFDknFy+O9v2O77pE/E0+Z340F8fCW/BI // SIG // MYeF1az/lD3KMHBSxPSrLMv5r6cmDibY1ZMMPes1fM9c // SIG // kOju1tSV7TwSBZFa2HWlFSDD3EKIVPI7MyCHk9n0JcUL // SIG // nB+K4LJB0taACoVr/sYkOYmMpPmhgheXMIIXkwYKKwYB // SIG // BAGCNwMDATGCF4Mwghd/BgkqhkiG9w0BBwKgghdwMIIX // SIG // bAIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBUgYLKoZIhvcN // SIG // AQkQAQSgggFBBIIBPTCCATkCAQEGCisGAQQBhFkKAwEw // SIG // MTANBglghkgBZQMEAgEFAAQg2zALxNvEzsQ/DbOgtgDD // SIG // JEVv8L+efx7rZLvVkdxFwT0CBmet6zVgrBgTMjAyNTAy // SIG // MjAxNTI4MzMuNjAxWjAEgAIB9KCB0aSBzjCByzELMAkG // SIG // A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO // SIG // BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m // SIG // dCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0 // SIG // IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNo // SIG // aWVsZCBUU1MgRVNOOkEwMDAtMDVFMC1EOTQ3MSUwIwYD // SIG // VQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl // SIG // oIIR7TCCByAwggUIoAMCAQICEzMAAAHr4BhstbbvOO0A // SIG // AQAAAeswDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC // SIG // VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT // SIG // B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw // SIG // b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt // SIG // U3RhbXAgUENBIDIwMTAwHhcNMjMxMjA2MTg0NTM0WhcN // SIG // MjUwMzA1MTg0NTM0WjCByzELMAkGA1UEBhMCVVMxEzAR // SIG // BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v // SIG // bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv // SIG // bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3Bl // SIG // cmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNO // SIG // OkEwMDAtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3Nv // SIG // ZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG // SIG // 9w0BAQEFAAOCAg8AMIICCgKCAgEAwRVoIdpW4Fd3iadN // SIG // aKomhQbmGzXO4UippLbydeTawfwwW6FKMPFjzkz8W5+4 // SIG // HJiDhpsCZHfk8hceyjp868Z6Ad4br7/dX2blLoCLCk5w // SIG // L4NgVP53ze2c5/SpNZqbidu0usVAx+KHRYl+dSAnCpeh // SIG // BuHMSoHAwIp4oU/Ma6CVlQEy+6fG2358LHNaYoWZnLyL // SIG // mBp29U2PbZ6XQoVq/RAEbgqN04kRozNi6eKYk9pQ+YZ3 // SIG // d1Whk3qTasmpKZAhldPnCvFbvx5CGXb8vs+RC96I03RS // SIG // y+byfSAKIFn91wLt3e0qRWmqHosdHtaueQA/eGcAz/os // SIG // 6i2nbAUd7c46tkX6wjS/k5ov42pUbaPyem4eHz4RxE5w // SIG // wu/E9cn11EHRrZif7rSPwDcYux1fIAD84nfU2IzD22Kh // SIG // vMucc/oCP0hco/mirRx1pisxFz7bV8wHHsSdRB+8G7ol // SIG // ZN7BKzyvTC4NV2+oTORyFgNIxAGYShMneYR9lzIm82pG // SIG // 6drNhCUFmrEHOAzGhdRLENQs4ApQ2CGBuq1IbnXyO5PC // SIG // /SighLn0WyuZXUWDQKnXa/8kiX7mb9z0t/r7Q+l+qtR+ // SIG // FDpowynY6Ft6rOyUTGZh/X5BZDM2+mEs6+nl9S6GJtz6 // SIG // ztSXmuN0mM5Qd08/ODr7lUlezXInVbTaomXllqVY32r0 // SIG // fiY/yTkCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBR0ngWs // SIG // 1lXMbuKk/TuY09gfqgHq4TAfBgNVHSMEGDAWgBSfpxVd // SIG // AF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQ // SIG // hk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz // SIG // L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB // SIG // JTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG // SIG // CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j // SIG // b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUt // SIG // U3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB // SIG // Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4G // SIG // A1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA // SIG // g3TfL6D3fAvlVmT9/lvO3P0G3W1itLDrfWeJBDlp4Oyp // SIG // oflg9i5zyUySiBGsZ4jnLfcDICfMkMsEfFh4Azr28Kna // SIG // rC1GjODa3q7SOhSPa4Y4XmisTTZwWcx2Sw8JZC/bwhA3 // SIG // vUXNHRklXeQYNwlpJ1d7r1WrteBeeREk1iATWkEvQqaN // SIG // jqc93EYAGFX2ixRmwKzXEb0lr0lG3iNiA6kcQuMQW0Yj // SIG // UPtah1wwj59IRrF3y/spw2Z3An7Mza5YGU9uF4Ib082D // SIG // B3F4qC1WKP9h5MqMOnSO7lCyWysS1/MB4bIsK4lyAwp4 // SIG // y1bBtBOW0fNkIHLHhIcW1NndUVR3ELZFBO1vc8Wamev4 // SIG // z5mqI2YF0Dt9148Th2GFWvwV3CLrvEjMz44wAG7o8E2s // SIG // KWsywb/fey0QdGTmzXJCWMkEKRE0n5Td+o1vs+0f5xsi // SIG // akWdx7WdZV1tX+sxAgHj/vXcup5nAq1XDqm0B1+2a/Fj // SIG // 3IIRyQAA5ZuRMT4ecYtbTUZPouhdmvUqU3kJ2Vz+dMPi // SIG // aE8SEkKu7wYo9p4rQLEi2lXjKqD4vjV5U1DWdjXbWxa+ // SIG // iIq/WSvbn2s9xcX7w2aN+ubyzqM5kDnv2fqbuL2Ocz5r // SIG // TYlSHEJxcuyWTomVQyOWyHcEEWotqrhyiepbVHbItx4z // SIG // Z4nrhO9n0+HlocbZpzeR2AgwggdxMIIFWaADAgECAhMz // 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 // BAsTHm5TaGllbGQgVFNTIEVTTjpBMDAwLTA1RTAtRDk0 // SIG // NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg // SIG // U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAgAaJdbtcMMGI // SIG // FLVKMDJ6mL27pd6ggYMwgYCkfjB8MQswCQYDVQQGEwJV // SIG // UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH // SIG // UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv // SIG // cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T // SIG // dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAOth // SIG // o6MwIhgPMjAyNTAyMjAxMjUwNDNaGA8yMDI1MDIyMTEy // SIG // NTA0M1owdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA62Gj // SIG // owIBADAKAgEAAgIGhQIB/zAHAgEAAgISljAKAgUA62L1 // SIG // IwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZ // SIG // CgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqG // SIG // SIb3DQEBCwUAA4IBAQCqmdoVIcQkdywsk3DlsCG8CNkD // SIG // HmucLSQKhujaX4Icn/UCAeCGwMX0gc7+7MvpP4eoAvcT // SIG // m2Eb0Ufp3TbU9RZtqiubFlYv+w0dMJNUI37vTO7+WWO6 // SIG // 1KwAZ5SylTtZv9D5Ptn/jrjxKSvWU9DUdTSyscln1UCZ // SIG // lm6KbsNpaLuUYvnP3yL7cFYgLZ3m8FdLHSC9MnmTsOke // SIG // fhDB7PU42AmyZh7zIYzHHfpAcMRzPZdRE9PPud3OL6Pl // SIG // wT7GF13SdmXlZRx6QZKx9a6NFgOoG3Ww7wCyXkBf01IS // SIG // 1RX3/RZESocEXDpQNCgoUrpIOpXnO5pubrRc0NVbwpTY // SIG // zVNOIbtbMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC // SIG // VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT // SIG // B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw // SIG // b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt // SIG // U3RhbXAgUENBIDIwMTACEzMAAAHr4BhstbbvOO0AAQAA // SIG // AeswDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJ // SIG // AzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQg // SIG // ZJSnmFFY2GSwYt8Kz2Gjmc8D2Je57nb4RLqPsrs+r04w // SIG // gfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDOt2u+ // SIG // X2kD4X/EgQ07ZNg0lICG3Ys17M++odSXYSws+DCBmDCB // SIG // gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo // SIG // aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK // SIG // ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT // SIG // HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMz // SIG // AAAB6+AYbLW27zjtAAEAAAHrMCIEIJh7GNfZ8w9h49KT // SIG // d4HB2/FaX9VTJAqI4Jon3cRbosDVMA0GCSqGSIb3DQEB // SIG // CwUABIICABQxT/Pwhai6358uyRC+bB8pMmoEgsfSsI+H // SIG // ZrpTyY1phDDw1Y+b9IBA0EeTWMmcJIrAP/mhC8cWsXX+ // SIG // BcBDSIn2U+ytDbRNwZq5vaS/KO6OeozpO5OxteQctnT8 // SIG // nwJyXJ+auLi5W6gcbAG7B5tXYFajNnYtkEwrCgXC7MkP // SIG // ooL6WKZn2hXUWHOONbjFp/xcMugjhNMFBnxYt1/Jghgh // SIG // eM4ufKuK7O1CTCuZMnDQHA7V/EVIS9RkQ1hbacLrUAS4 // SIG // ePTXOTTvLVn++JM2Swim/4xydoHvsDq3nD0FyAW+5gUI // SIG // LeOKRlUft78aY+3jso7r+LH4E+8uVu2s9gIDJzGr8gEN // SIG // ENT9jzsTArAvAcGRI4tjWktNEjYeIp6FmtZ3xXcFmg0k // SIG // iKKGCidsBvgQpDgshOXAz+mV5e/cj7+YpjK2dRt4KikL // SIG // pVA1gnVv2wakazjPu3n4dMC6voJuDO1Z6Xuz1CL+hfCU // SIG // BNreSGNQpJ33sVaSkwdLel/WdQZBShPuSjRgmFmZYyTn // SIG // KXfmEfCU8ulAImgGa2/ccBmqgEkvFYklEfZqg1JInVJP // SIG // 58/jDvZJ6tS86IVaIGr2FtZq3LgH6DzzXSZf3D+s57q8 // SIG // HuZ8vrR4m7eciGKZDjbs9fc3McowkSMaFldTeB8dGRyj // SIG // xWyvBtGRuNVdIn44uL9dZSlrr3aRwDki // SIG // End signature block