Private/Cache/CacheStore.ps1
|
Set-StrictMode -Version Latest $script:AzNetSqliteLoaded = $false $script:AzNetSqliteAssembly = $null function Initialize-AzNetSqlite { [CmdletBinding()] param() if ($script:AzNetSqliteLoaded) { return $true } $types = @( 'Microsoft.Data.Sqlite.SqliteConnection, Microsoft.Data.Sqlite', 'Microsoft.Data.Sqlite.SqliteConnection' ) foreach ($typeName in $types) { $t = [type]::GetType($typeName, $false) if ($t) { $script:AzNetSqliteLoaded = $true; $script:AzNetSqliteAssembly = $t.Assembly; return $true } } try { Add-Type -AssemblyName 'Microsoft.Data.Sqlite' -ErrorAction Stop $script:AzNetSqliteLoaded = $true return $true } catch { Write-AzNetLog -Level Verbose -Message "Microsoft.Data.Sqlite not loaded, using JSON cache fallback: $($_.Exception.Message)" return $false } } function Open-AzNetCache { [CmdletBinding()] param([string]$CacheRoot) $dbPath = Get-AzNetCacheDbPath -CacheRoot $CacheRoot $useSqlite = Initialize-AzNetSqlite $store = [pscustomobject]@{ Kind = if ($useSqlite) { 'sqlite' } else { 'json' } Path = $dbPath Root = (Split-Path $dbPath -Parent) Connection = $null } if ($useSqlite) { $conn = New-Object -TypeName 'Microsoft.Data.Sqlite.SqliteConnection' -ArgumentList "Data Source=$dbPath;Mode=ReadWriteCreate;Cache=Shared" $conn.Open() $store.Connection = $conn Invoke-AzNetSqliteNonQuery -Connection $conn -Sql "PRAGMA journal_mode=WAL;" | Out-Null Invoke-AzNetSqliteNonQuery -Connection $conn -Sql "PRAGMA synchronous=NORMAL;" | Out-Null Invoke-AzNetSqliteNonQuery -Connection $conn -Sql "PRAGMA busy_timeout=60000;" | Out-Null Initialize-AzNetCacheSchema -Connection $conn | Out-Null } else { $jsonDir = Join-Path (Split-Path $dbPath -Parent) 'json-cache' New-Item -ItemType Directory -Force -Path $jsonDir | Out-Null $store | Add-Member -NotePropertyName JsonDir -NotePropertyValue $jsonDir } return $store } function Close-AzNetCache { [CmdletBinding()] param([Parameter(Mandatory)]$Store) if ($Store.Kind -eq 'sqlite' -and $Store.Connection) { try { $Store.Connection.Close() } catch { Write-AzNetLog -Level Verbose -Message 'Cache connection close threw' -Data @{ error = $_.Exception.Message } } try { $Store.Connection.Dispose() } catch { Write-AzNetLog -Level Verbose -Message 'Cache connection dispose threw' -Data @{ error = $_.Exception.Message } } $Store.Connection = $null } } function Invoke-AzNetSqliteNonQuery { [CmdletBinding()] param( [Parameter(Mandatory)]$Connection, [Parameter(Mandatory)][string]$Sql, [hashtable]$Parameters ) $cmd = $Connection.CreateCommand() try { $cmd.CommandText = $Sql if ($Parameters) { foreach ($k in $Parameters.Keys) { $p = $cmd.CreateParameter() $p.ParameterName = $k $p.Value = if ($null -eq $Parameters[$k]) { [System.DBNull]::Value } else { $Parameters[$k] } [void]$cmd.Parameters.Add($p) } } return $cmd.ExecuteNonQuery() } finally { $cmd.Dispose() } } function Invoke-AzNetSqliteScalar { [CmdletBinding()] param( [Parameter(Mandatory)]$Connection, [Parameter(Mandatory)][string]$Sql, [hashtable]$Parameters ) $cmd = $Connection.CreateCommand() try { $cmd.CommandText = $Sql if ($Parameters) { foreach ($k in $Parameters.Keys) { $p = $cmd.CreateParameter() $p.ParameterName = $k $p.Value = if ($null -eq $Parameters[$k]) { [System.DBNull]::Value } else { $Parameters[$k] } [void]$cmd.Parameters.Add($p) } } return $cmd.ExecuteScalar() } finally { $cmd.Dispose() } } function Invoke-AzNetSqliteReader { [CmdletBinding()] param( [Parameter(Mandatory)]$Connection, [Parameter(Mandatory)][string]$Sql, [hashtable]$Parameters ) $cmd = $Connection.CreateCommand() try { $cmd.CommandText = $Sql if ($Parameters) { foreach ($k in $Parameters.Keys) { $p = $cmd.CreateParameter() $p.ParameterName = $k $p.Value = if ($null -eq $Parameters[$k]) { [System.DBNull]::Value } else { $Parameters[$k] } [void]$cmd.Parameters.Add($p) } } $rows = [System.Collections.Generic.List[object]]::new() $reader = $cmd.ExecuteReader() try { while ($reader.Read()) { $row = [ordered]@{} for ($i = 0; $i -lt $reader.FieldCount; $i++) { $row[$reader.GetName($i)] = if ($reader.IsDBNull($i)) { $null } else { $reader.GetValue($i) } } [void]$rows.Add([pscustomobject]$row) } } finally { $reader.Dispose() } return $rows } finally { $cmd.Dispose() } } function Initialize-AzNetCacheSchema { [CmdletBinding()] param([Parameter(Mandatory)]$Connection) Invoke-AzNetSqliteNonQuery -Connection $Connection -Sql @' CREATE TABLE IF NOT EXISTS schema_version ( version INTEGER NOT NULL ); '@ | Out-Null $rows = Invoke-AzNetSqliteReader -Connection $Connection -Sql 'SELECT version FROM schema_version LIMIT 1;' if ($rows.Count -eq 0) { Invoke-AzNetSqliteNonQuery -Connection $Connection -Sql 'INSERT INTO schema_version(version) VALUES (2);' | Out-Null } Invoke-AzNetSqliteNonQuery -Connection $Connection -Sql @' CREATE TABLE IF NOT EXISTS resource ( resource_id TEXT PRIMARY KEY, subscription_id TEXT NOT NULL, resource_type TEXT NOT NULL, location TEXT, etag TEXT, content_hash TEXT NOT NULL, body_json TEXT NOT NULL, fetched_at_utc TEXT NOT NULL, expires_at_utc TEXT NOT NULL, source TEXT NOT NULL ); '@ | Out-Null Invoke-AzNetSqliteNonQuery -Connection $Connection -Sql 'CREATE INDEX IF NOT EXISTS ix_resource_sub ON resource(subscription_id);' | Out-Null Invoke-AzNetSqliteNonQuery -Connection $Connection -Sql 'CREATE INDEX IF NOT EXISTS ix_resource_type ON resource(resource_type);' | Out-Null Invoke-AzNetSqliteNonQuery -Connection $Connection -Sql @' CREATE TABLE IF NOT EXISTS edge ( from_id TEXT NOT NULL, to_id TEXT NOT NULL, kind TEXT NOT NULL, properties TEXT, PRIMARY KEY (from_id, to_id, kind) ); '@ | Out-Null Invoke-AzNetSqliteNonQuery -Connection $Connection -Sql @' CREATE TABLE IF NOT EXISTS run ( run_id TEXT PRIMARY KEY, started_at_utc TEXT NOT NULL, ended_at_utc TEXT, parameters_json TEXT NOT NULL, errors_json TEXT ); '@ | Out-Null } function Set-AzNetCacheEntry { [CmdletBinding()] param( [Parameter(Mandatory)]$Store, [Parameter(Mandatory)][string]$ResourceId, [Parameter(Mandatory)][string]$SubscriptionId, [Parameter(Mandatory)][string]$ResourceType, [string]$Location, [string]$ETag, [Parameter(Mandatory)]$Body, [timespan]$Ttl, [string]$Source = 'azure' ) if (-not $Ttl) { $Ttl = Get-AzNetCacheTTL -ResourceType $ResourceType } $canonical = ConvertTo-CanonicalJson -InputObject $Body $hash = Get-Sha256Hash -Text $canonical $fetched = [datetimeoffset]::UtcNow $expires = $fetched.Add($Ttl) if ($Store.Kind -eq 'sqlite') { $tx = $Store.Connection.BeginTransaction() try { Invoke-AzNetSqliteNonQuery -Connection $Store.Connection -Sql @' INSERT INTO resource (resource_id, subscription_id, resource_type, location, etag, content_hash, body_json, fetched_at_utc, expires_at_utc, source) VALUES ($id, $sub, $type, $loc, $etag, $hash, $body, $fetched, $expires, $source) ON CONFLICT(resource_id) DO UPDATE SET subscription_id = excluded.subscription_id, resource_type = excluded.resource_type, location = excluded.location, etag = excluded.etag, content_hash = excluded.content_hash, body_json = excluded.body_json, fetched_at_utc = excluded.fetched_at_utc, expires_at_utc = excluded.expires_at_utc, source = excluded.source; '@ -Parameters @{ '$id' = $ResourceId '$sub' = $SubscriptionId '$type' = $ResourceType '$loc' = $Location '$etag' = $ETag '$hash' = $hash '$body' = $canonical '$fetched' = $fetched.ToString('o') '$expires' = $expires.ToString('o') '$source' = $Source } | Out-Null $tx.Commit() } catch { $tx.Rollback() throw } finally { $tx.Dispose() } } else { $fileName = (Get-Sha256Hash -Text $ResourceId) + '.json' $path = Join-Path $Store.JsonDir $fileName $entry = [pscustomobject]@{ resource_id = $ResourceId subscription_id = $SubscriptionId resource_type = $ResourceType location = $Location etag = $ETag content_hash = $hash body_json = $canonical fetched_at_utc = $fetched.ToString('o') expires_at_utc = $expires.ToString('o') source = $Source } Set-Content -LiteralPath $path -Value ($entry | ConvertTo-Json -Depth 6) -Encoding utf8 } } function Get-AzNetCacheEntry { [CmdletBinding()] param( [Parameter(Mandatory)]$Store, [Parameter(Mandatory)][string]$ResourceId ) if ($Store.Kind -eq 'sqlite') { $rows = Invoke-AzNetSqliteReader -Connection $Store.Connection -Sql @' SELECT resource_id, subscription_id, resource_type, location, etag, content_hash, body_json, fetched_at_utc, expires_at_utc, source FROM resource WHERE resource_id = $id LIMIT 1; '@ -Parameters @{ '$id' = $ResourceId } if ($rows.Count -eq 0) { return $null } return $rows[0] } $fileName = (Get-Sha256Hash -Text $ResourceId) + '.json' $path = Join-Path $Store.JsonDir $fileName if (-not (Test-Path -LiteralPath $path)) { return $null } $raw = Get-Content -LiteralPath $path -Raw return ($raw | ConvertFrom-Json) } function Find-AzNetCacheEntry { [CmdletBinding()] param( [Parameter(Mandatory)]$Store, [string]$SubscriptionId, [string]$ResourceType ) if ($Store.Kind -eq 'sqlite') { $sql = 'SELECT resource_id, subscription_id, resource_type, location, etag, content_hash, body_json, fetched_at_utc, expires_at_utc, source FROM resource WHERE 1=1' $params = @{} if ($SubscriptionId) { $sql += ' AND subscription_id = $sub'; $params['$sub'] = $SubscriptionId } if ($ResourceType) { $sql += ' AND resource_type = $type'; $params['$type'] = $ResourceType } return Invoke-AzNetSqliteReader -Connection $Store.Connection -Sql $sql -Parameters $params } $entries = @() if (-not (Test-Path $Store.JsonDir)) { return $entries } foreach ($f in Get-ChildItem -LiteralPath $Store.JsonDir -Filter '*.json') { $obj = (Get-Content -LiteralPath $f.FullName -Raw | ConvertFrom-Json) if ($SubscriptionId -and $obj.subscription_id -ne $SubscriptionId) { continue } if ($ResourceType -and $obj.resource_type -ne $ResourceType) { continue } $entries += $obj } return $entries } function Remove-AzNetCacheEntry { [CmdletBinding()] param( [Parameter(Mandatory)]$Store, [string]$SubscriptionId, [string]$ResourceType, [timespan]$OlderThan ) if ($Store.Kind -eq 'sqlite') { $sql = 'DELETE FROM resource WHERE 1=1' $params = @{} if ($SubscriptionId) { $sql += ' AND subscription_id = $sub'; $params['$sub'] = $SubscriptionId } if ($ResourceType) { $sql += ' AND resource_type = $type'; $params['$type'] = $ResourceType } if ($OlderThan) { $cutoff = [datetimeoffset]::UtcNow.Subtract($OlderThan) $sql += ' AND fetched_at_utc < $cutoff' $params['$cutoff'] = $cutoff.ToString('o') } return Invoke-AzNetSqliteNonQuery -Connection $Store.Connection -Sql $sql -Parameters $params } $count = 0 foreach ($f in Get-ChildItem -LiteralPath $Store.JsonDir -Filter '*.json' -ErrorAction SilentlyContinue) { $obj = (Get-Content -LiteralPath $f.FullName -Raw | ConvertFrom-Json) $match = $true if ($SubscriptionId -and $obj.subscription_id -ne $SubscriptionId) { $match = $false } if ($ResourceType -and $obj.resource_type -ne $ResourceType) { $match = $false } if ($OlderThan) { $cutoff = [datetimeoffset]::UtcNow.Subtract($OlderThan) if ([datetimeoffset]::Parse($obj.fetched_at_utc) -ge $cutoff) { $match = $false } } if ($match) { Remove-Item -LiteralPath $f.FullName -Force; $count++ } } return $count } function Get-AzNetCachedOrFetch { [CmdletBinding()] param( [Parameter(Mandatory)]$Store, [Parameter(Mandatory)]$RunContext, [Parameter(Mandatory)][string]$ResourceId, [Parameter(Mandatory)][string]$SubscriptionId, [Parameter(Mandatory)][string]$ResourceType, [string]$Location, [Parameter(Mandatory)][scriptblock]$Fetch, [string]$CacheMode = 'UseCache' ) $existing = Get-AzNetCacheEntry -Store $Store -ResourceId $ResourceId if ($existing -and $CacheMode -ne 'Refresh') { $expiresAt = [datetime]::Parse($existing.expires_at_utc).ToUniversalTime() if (Test-AzNetCacheFresh -ExpiresAtUtc $expiresAt -Mode $CacheMode) { $RunContext.CacheStats['hits']++ return ($existing.body_json | ConvertFrom-Json -Depth 20) } } if ($CacheMode -eq 'Offline') { $RunContext.CacheStats['stale']++ return $null } $RunContext.CacheStats['misses']++ $fresh = & $Fetch if ($null -ne $fresh) { $ttl = Get-AzNetCacheTTL -ResourceType $ResourceType Set-AzNetCacheEntry -Store $Store -ResourceId $ResourceId ` -SubscriptionId $SubscriptionId -ResourceType $ResourceType ` -Location $Location -Body $fresh -Ttl $ttl $RunContext.CacheStats['writes']++ } return $fresh } function Get-AzNetCachedList { [CmdletBinding()] param( [Parameter(Mandatory)]$Store, [Parameter(Mandatory)]$RunContext, [Parameter(Mandatory)][string]$SubscriptionId, [Parameter(Mandatory)][string]$ResourceType, [Parameter(Mandatory)][string]$ListKey, [Parameter(Mandatory)][scriptblock]$Fetch, [string]$CacheMode = 'UseCache' ) $manifestKey = Get-AzNetCacheKey -ResourceId "/subscriptions/$SubscriptionId" -Aspect "list::$ListKey" # Offline or UseCache with fresh manifest: replay from cache without hitting Azure. if ($CacheMode -in 'UseCache','Offline') { $manifest = Get-AzNetCacheEntry -Store $Store -ResourceId $manifestKey if ($manifest) { $exp = [datetime]::Parse($manifest.expires_at_utc).ToUniversalTime() $isFresh = Test-AzNetCacheFresh -ExpiresAtUtc $exp -Mode $CacheMode if ($isFresh -or $CacheMode -eq 'Offline') { $ids = @() try { $ids = @($manifest.body_json | ConvertFrom-Json -Depth 5) } catch { $ids = @() } $bodies = [System.Collections.Generic.List[object]]::new() $allPresent = $true foreach ($rid in $ids) { $row = Get-AzNetCacheEntry -Store $Store -ResourceId $rid if (-not $row) { $allPresent = $false; break } try { [void]$bodies.Add(($row.body_json | ConvertFrom-Json -Depth 20)) } catch { $allPresent = $false; break } } if ($allPresent) { $RunContext.CacheStats['hits'] = [int]$RunContext.CacheStats['hits'] + @($bodies).Count return [pscustomobject]@{ Items = @($bodies); FromCache = $true } } } } if ($CacheMode -eq 'Offline') { $RunContext.CacheStats['stale']++ return [pscustomobject]@{ Items = @(); FromCache = $true } } } # Live fetch (Refresh, WriteThrough, UseCache with stale/missing manifest). $fresh = & $Fetch if ($null -eq $fresh) { # Record an empty manifest so subsequent UseCache runs skip the fetch too. Set-AzNetCacheEntry -Store $Store -ResourceId $manifestKey -SubscriptionId $SubscriptionId ` -ResourceType $ResourceType -Location $null -Body (@()) return [pscustomobject]@{ Items = @(); FromCache = $false } } $items = @($fresh) # Record the id manifest for UseCache replay. The caller is responsible for # caching individual resource bodies via Set-AzNetCacheEntry (so the # discoverer's normalization via ConvertTo-AzNetRawBody is preserved). $ids = [System.Collections.Generic.List[string]]::new() foreach ($item in $items) { if ($item -and $item.PSObject.Properties.Name -contains 'Id' -and $item.Id) { [void]$ids.Add([string]$item.Id) } elseif ($item -and $item.PSObject.Properties.Name -contains 'ResourceId' -and $item.ResourceId) { [void]$ids.Add([string]$item.ResourceId) } } Set-AzNetCacheEntry -Store $Store -ResourceId $manifestKey -SubscriptionId $SubscriptionId ` -ResourceType $ResourceType -Location $null -Body (@($ids)) $RunContext.CacheStats['misses']++ return [pscustomobject]@{ Items = $items; FromCache = $false } } function Start-AzNetCacheRun { [CmdletBinding()] param( [Parameter(Mandatory)]$Store, [Parameter(Mandatory)][string]$RunId, [Parameter(Mandatory)][hashtable]$Parameters ) if ($Store.Kind -ne 'sqlite') { return } Invoke-AzNetSqliteNonQuery -Connection $Store.Connection -Sql @' INSERT OR REPLACE INTO run (run_id, started_at_utc, parameters_json) VALUES ($id, $started, $params); '@ -Parameters @{ '$id' = $RunId '$started' = ([datetimeoffset]::UtcNow.ToString('o')) '$params' = ($Parameters | ConvertTo-Json -Depth 6 -Compress) } | Out-Null } function Complete-AzNetCacheRun { [CmdletBinding()] param( [Parameter(Mandatory)]$Store, [Parameter(Mandatory)][string]$RunId, [object[]]$Errors ) if ($Store.Kind -ne 'sqlite') { return } Invoke-AzNetSqliteNonQuery -Connection $Store.Connection -Sql @' UPDATE run SET ended_at_utc = $ended, errors_json = $errs WHERE run_id = $id; '@ -Parameters @{ '$id' = $RunId '$ended' = ([datetimeoffset]::UtcNow.ToString('o')) '$errs' = if ($Errors) { ($Errors | ConvertTo-Json -Depth 6 -Compress) } else { '[]' } } | Out-Null } # SIG # Begin signature block # MII2twYJKoZIhvcNAQcCoII2qDCCNqQCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAb5kZLAfS8qAGu # 7SqyphFWYzzB9m6phIonv91gt9669aCCG0YwggXMMIIDtKADAgECAhBUmNLR1FsZ # lUgTecgRwIeZMA0GCSqGSIb3DQEBDAUAMHcxCzAJBgNVBAYTAlVTMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xSDBGBgNVBAMTP01pY3Jvc29mdCBJZGVu # dGl0eSBWZXJpZmljYXRpb24gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAy # MDAeFw0yMDA0MTYxODM2MTZaFw00NTA0MTYxODQ0NDBaMHcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xSDBGBgNVBAMTP01pY3Jv # c29mdCBJZGVudGl0eSBWZXJpZmljYXRpb24gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRo # b3JpdHkgMjAyMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALORKgeD # Bmf9np3gx8C3pOZCBH8Ppttf+9Va10Wg+3cL8IDzpm1aTXlT2KCGhFdFIMeiVPvH # or+Kx24186IVxC9O40qFlkkN/76Z2BT2vCcH7kKbK/ULkgbk/WkTZaiRcvKYhOuD # PQ7k13ESSCHLDe32R0m3m/nJxxe2hE//uKya13NnSYXjhr03QNAlhtTetcJtYmrV # qXi8LW9J+eVsFBT9FMfTZRY33stuvF4pjf1imxUs1gXmuYkyM6Nix9fWUmcIxC70 # ViueC4fM7Ke0pqrrBc0ZV6U6CwQnHJFnni1iLS8evtrAIMsEGcoz+4m+mOJyoHI1 # vnnhnINv5G0Xb5DzPQCGdTiO0OBJmrvb0/gwytVXiGhNctO/bX9x2P29Da6SZEi3 # W295JrXNm5UhhNHvDzI9e1eM80UHTHzgXhgONXaLbZ7LNnSrBfjgc10yVpRnlyUK # xjU9lJfnwUSLgP3B+PR0GeUw9gb7IVc+BhyLaxWGJ0l7gpPKWeh1R+g/OPTHU3mg # trTiXFHvvV84wRPmeAyVWi7FQFkozA8kwOy6CXcjmTimthzax7ogttc32H83rwjj # O3HbbnMbfZlysOSGM1l0tRYAe1BtxoYT2v3EOYI9JACaYNq6lMAFUSw0rFCZE4e7 # swWAsk0wAly4JoNdtGNz764jlU9gKL431VulAgMBAAGjVDBSMA4GA1UdDwEB/wQE # AwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIftJqhSobyhmYBAcnz1AQ # T2ioojAQBgkrBgEEAYI3FQEEAwIBADANBgkqhkiG9w0BAQwFAAOCAgEAr2rd5hnn # LZRDGU7L6VCVZKUDkQKL4jaAOxWiUsIWGbZqWl10QzD0m/9gdAmxIR6QFm3FJI9c # Zohj9E/MffISTEAQiwGf2qnIrvKVG8+dBetJPnSgaFvlVixlHIJ+U9pW2UYXeZJF # xBA2CFIpF8svpvJ+1Gkkih6PsHMNzBxKq7Kq7aeRYwFkIqgyuH4yKLNncy2RtNwx # AQv3Rwqm8ddK7VZgxCwIo3tAsLx0J1KH1r6I3TeKiW5niB31yV2g/rarOoDXGpc8 # FzYiQR6sTdWD5jw4vU8w6VSp07YEwzJ2YbuwGMUrGLPAgNW3lbBeUU0i/OxYqujY # lLSlLu2S3ucYfCFX3VVj979tzR/SpncocMfiWzpbCNJbTsgAlrPhgzavhgplXHT2 # 6ux6anSg8Evu75SjrFDyh+3XOjCDyft9V77l4/hByuVkrrOj7FjshZrM77nq81YY # uVxzmq/FdxeDWds3GhhyVKVB0rYjdaNDmuV3fJZ5t0GNv+zcgKCf0Xd1WF81E+Al # GmcLfc4l+gcK5GEh2NQc5QfGNpn0ltDGFf5Ozdeui53bFv0ExpK91IjmqaOqu/dk # ODtfzAzQNb50GQOmxapMomE2gj4d8yu8l13bS3g7LfU772Aj6PXsCyM2la+YZr9T # 03u4aUoqlmZpxJTG9F9urJh4iIAGXKKy7aIwggakMIIEjKADAgECAhMzAABzA2ge # zvWGpRrVAAAAAHMDMA0GCSqGSIb3DQEBDAUAMFoxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKzApBgNVBAMTIk1pY3Jvc29mdCBJ # RCBWZXJpZmllZCBDUyBBT0MgQ0EgMDQwHhcNMjYwNDIzMjIxNzAyWhcNMjYwNDI2 # MjIxNzAyWjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExFzAVBgNV # BAcTDlZpcmdpbmlhIEJlYWNoMRQwEgYDVQQKEwtTZXRoIE1JbGxlcjEUMBIGA1UE # AxMLU2V0aCBNSWxsZXIwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDi # ofa8qOqz+DSBEh09HxJlxCDciC4+34MKu7Kms9T9bA8/QHSqECW3yIQyNru8sw1l # Ma2Ll+06JZKMlJQqkyfvQ0mVTI01VDwId+jMK234aU9sFv1eBWECr7YGZiWitJFX # 5G+jOe/gb1weLbEr+zMZDc8JniBf3ZFZqlcJ1ZM6FpmeSd72wLC30jiOO8BAC/Ud # sDgG0F0pxsYoOFO6TndsNZEAJrq++3fZNKs/6qVKWKTH0gztUhrouICkUAwSoMIN # 8tXb386pXAgFNbOBeiRRlOho0UKgZhLkJS1vmaa0h+Dtt33pMHMPblwU/1bxG5Fw # 9t6kl99UE4ttVX6/bHudaRgtutWmI4LjmUqvpdy9gd3+3gk3oBjmCYWZJ7DupwN5 # hwg/yFa1gYvpb/midFn5eIP/b6Xfq/2dQYvXLbFFvzGdzUX4BxrbgzYrZKscFcCf # Z77e8v5GaYzogpUk28cofAoTQeccIN22Vgsmm9z9Y025rL5GTYEsPDca0dBhEcUC # AwEAAaOCAdYwggHSMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMD0GA1Ud # JQQ2MDQGCisGAQQBgjdhAQAGCCsGAQUFBwMDBhwrBgEEAYI3YYHR5Y4pgv7dj0iB # laOJIoHu469rMB0GA1UdDgQWBBShPhVS/1wDO3h/kmNfjd2bHdNXwjAfBgNVHSME # GDAWgBRrJUHe+2t8/RiACi1/j3ZdqnM9uDBnBgNVHR8EYDBeMFygWqBYhlZodHRw # Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBJRCUy # MFZlcmlmaWVkJTIwQ1MlMjBBT0MlMjBDQSUyMDA0LmNybDB0BggrBgEFBQcBAQRo # MGYwZAYIKwYBBQUHMAKGWGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y2VydHMvTWljcm9zb2Z0JTIwSUQlMjBWZXJpZmllZCUyMENTJTIwQU9DJTIwQ0El # MjAwNC5jcnQwVAYDVR0gBE0wSzBJBgRVHSAAMEEwPwYIKwYBBQUHAgEWM2h0dHA6 # Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTAN # BgkqhkiG9w0BAQwFAAOCAgEAWiIv8T27YuG5HzK5ag915ZU/PCqxXAJdW5E4Hn1l # nsNSyoDv5a1cxKptpJlcGiF9Ff7xA6PcmMpzZI4kou7COcsxp7Eb2ZhXBKcdHgqE # CCVdhIMzG8MOOxTPDtcdHlfpIb34s5ecmsHlyEv9/kdV71TOc1fY/FPsw6CpdsYK # +RiC+d5hPEhZvLD8LFn/ttkOltiGcboSmznksFOz+Uy37lNUt2xRuBTXLbYvg220 # tYtCzVCAy8Xv2Y3TWbpqfwXmPFBD/h6YPDzROou9zNM4Rf8Oy5m3C84q9jzkbtGE # Hvp6Nd7/ipAW3LC+5TzSSXBu88f78eK6Bo37KAFitG/gh2iOKEi/uvlDnNc2FJag # VDJMM3ZXAAWghMfnrDWj5pVLSXJoa4L9cnxAlg4t4G3vTkR+xq+QbOX1nxzbmP/i # bFV2fxbhpdJ3OzjjJ6/Ns51MMEBdvnRCI+qIQaUDTa4J2j+qNMwrn7cxaX/4By77 # y62Gzd1Qz50lqSxqCADtUwgEYF78H9dPMtipBzJ0C5tai8e8V9GKLdSzIt90Gx6v # Gou0SpfbFLF+IdCO8SODPPg4o95xeaZtl95yXpDkBt9RNfwVPdfW3EY/vuQgCmWj # qqTbPrrZ9gzNE1MevKd/n/L5gSFgHwYA6OfKbCqM0wBXsbNa1w6ErPhXYY/Cei1O # jJwwggcoMIIFEKADAgECAhMzAAAAFjGSjZICZXuaAAAAAAAWMA0GCSqGSIb3DQEB # DAUAMGMxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xNDAyBgNVBAMTK01pY3Jvc29mdCBJRCBWZXJpZmllZCBDb2RlIFNpZ25pbmcg # UENBIDIwMjEwHhcNMjYwMzI2MTgxMTI5WhcNMzEwMzI2MTgxMTI5WjBaMQswCQYD # VQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYDVQQD # EyJNaWNyb3NvZnQgSUQgVmVyaWZpZWQgQ1MgQU9DIENBIDA0MIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAylX6yNvoCTDP9G0OTlSjXbzgEsy21FDL17n/ # lZe2BrqHz2mR1aN4DBxeYp0/hjEqSHHyGfarV1NVBuvK8vLzW0LTi+DZt9In16ai # NfgcogFiztWE9Fp8xu1zzrqE3nlrDWb+RZo8QrEXgWb8s8swsl2W7tREHycVkx+H # m1MLQIlva6jH/Xg4/8GIYhHzbXiVd2RXomw9s7Qh6/SYRXXfe125wh4EKEyKnNNl # +cZUSrVBgWvvjrRwQY4if7sAZ805KruBY6WY0Hiba5nWvrq9Qk9o35ViAf8qZ+7u # 1fbb1vcCWyWLfx9hLSdBjjVsSWe0xLvI1j4p3Tjt5czz+1Lc0v5lQ1feB7nFmpbZ # rK2us0hvAaBCfOyDPEEm+735vzuNRYWJFL/PViI+REtjuJMcojEn3veQjIrwrmK0 # T9oSr8e3oDzK1oAwwZMTC4KymTvYUTVDJvL5N8OW/UqIBzsiVYcchZvGhV3yMYKg # xeEtIOG4W4Z85Y5kpQi5bpjGXFxRg46RdrTaALt1RhRmLR7U0jVSr2aYAd2+Mp2q # A5Gz3/loOOdt47eFZ3mrAYGYQtbK2SNjQpwgQX4Iy6tOKahCgFhKIcltitvSkpJB # 77eVWhNWnN2LfqMojszEue7V8EAySxry4PzlxTtFTb3Mw53XyH12BMQf2m9j7jEs # HeVSATsCAwEAAaOCAdwwggHYMA4GA1UdDwEB/wQEAwIBhjAQBgkrBgEEAYI3FQEE # AwIBADAdBgNVHQ4EFgQUayVB3vtrfP0YgAotf492XapzPbgwVAYDVR0gBE0wSzBJ # BgRVHSAAMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w # a2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBi # AEMAQTASBgNVHRMBAf8ECDAGAQH/AgEAMB8GA1UdIwQYMBaAFNlBKbAPD2Ns72nX # 9c0pnqRIajDmMHAGA1UdHwRpMGcwZaBjoGGGX2h0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMElEJTIwVmVyaWZpZWQlMjBDb2Rl # JTIwU2lnbmluZyUyMFBDQSUyMDIwMjEuY3JsMH0GCCsGAQUFBwEBBHEwbzBtBggr # BgEFBQcwAoZhaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9N # aWNyb3NvZnQlMjBJRCUyMFZlcmlmaWVkJTIwQ29kZSUyMFNpZ25pbmclMjBQQ0El # MjAyMDIxLmNydDANBgkqhkiG9w0BAQwFAAOCAgEABtVQXlR01UQZY5XGQ9yIjMcD # 8jI0MizWhJ1buZjg5toUQSXx/BrASwE5qxwHPBeO45pOQp6VD4iILgm8OmfylY+A # 7KIqttvDUizC3sBXxjK4u7sDRiyEguXHKfL1HQAwxCLEtnRPkCPTsJA6b917lA+3 # foQIHC1XDDpdQLHxGbbGXp4Rr0mFK5vxbi6tAahBi/RlzOXPh6PavKPlZ/0vhlkD # dsvoJETtebNJCNOZ1Kav3Tg+K4va4FbOrYqRHdGGahoA/gmTYmmVqw0zkGzT53Hd # hfajrFGttJomK7qE+T8CQGiPkEIkxNmSXjCTpDqc4U1IKlTGcGYnRFGSgqrnWnkA # NPFsJ5EDHysh82lPI+PFC3FOIVMLzLL+30rqznvRgHUUAj7xfFnEiuaAx3vFVSTO # Lb+iigpvdR6i8fSWpgYESOkdkn2N57tuhBs57tKwoP++vc/MVpuD1XAtmWi+lZSl # ahadTbDfGKjMn+bfm2xlW9PZ6BSnCRv1MMhpcUZkAZX3gVEMef8rZc2c7BJ4ayRf # X0wH43vI9znV+ZRJ3j0xUC0Zb82RQalF5yHkCr93x0IwvZtn6P2dNQyCP6qd3fC4 # RlVFtAQhtOH0cByTR/Iqqghv6qHzL/pMptgMQQ5x8zYEYy+tCThYgYIrq7y4WEDY # QfeSlqIxQOrIUJ4IJDEwggeeMIIFhqADAgECAhMzAAAAB4ejNKN7pY4cAAAAAAAH # MA0GCSqGSIb3DQEBDAUAMHcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xSDBGBgNVBAMTP01pY3Jvc29mdCBJZGVudGl0eSBWZXJp # ZmljYXRpb24gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMDAeFw0yMTA0 # MDEyMDA1MjBaFw0zNjA0MDEyMDE1MjBaMGMxCzAJBgNVBAYTAlVTMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNDAyBgNVBAMTK01pY3Jvc29mdCBJRCBW # ZXJpZmllZCBDb2RlIFNpZ25pbmcgUENBIDIwMjEwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCy8MCvGYgo4t1UekxJbGkIVQm0Uv96SvjB6yUo92cXdylN # 65Xy96q2YpWCiTas7QPTkGnK9QMKDXB2ygS27EAIQZyAd+M8X+dmw6SDtzSZXyGk # xP8a8Hi6EO9Zcwh5A+wOALNQbNO+iLvpgOnEM7GGB/wm5dYnMEOguua1OFfTUITV # MIK8faxkP/4fPdEPCXYyy8NJ1fmskNhW5HduNqPZB/NkWbB9xxMqowAeWvPgHtpz # yD3PLGVOmRO4ka0WcsEZqyg6efk3JiV/TEX39uNVGjgbODZhzspHvKFNU2K5MYfm # Hh4H1qObU4JKEjKGsqqA6RziybPqhvE74fEp4n1tiY9/ootdU0vPxRp4BGjQFq28 # nzawuvaCqUUF2PWxh+o5/TRCb/cHhcYU8Mr8fTiS15kRmwFFzdVPZ3+JV3s5MulI # f3II5FXeghlAH9CvicPhhP+VaSFW3Da/azROdEm5sv+EUwhBrzqtxoYyE2wmuHKw # s00x4GGIx7NTWznOm6x/niqVi7a/mxnnMvQq8EMse0vwX2CfqM7Le/smbRtsEeOt # bnJBbtLfoAsC3TdAOnBbUkbUfG78VRclsE7YDDBUbgWt75lDk53yi7C3n0WkHFU4 # EZ83i83abd9nHWCqfnYa9qIHPqjOiuAgSOf4+FRcguEBXlD9mAInS7b6V0UaNwID # AQABo4ICNTCCAjEwDgYDVR0PAQH/BAQDAgGGMBAGCSsGAQQBgjcVAQQDAgEAMB0G # A1UdDgQWBBTZQSmwDw9jbO9p1/XNKZ6kSGow5jBUBgNVHSAETTBLMEkGBFUdIAAw # QTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9E # b2NzL1JlcG9zaXRvcnkuaHRtMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8G # A1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUyH7SaoUqG8oZmAQHJ89QEE9oqKIw # gYQGA1UdHwR9MHsweaB3oHWGc2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lv # cHMvY3JsL01pY3Jvc29mdCUyMElkZW50aXR5JTIwVmVyaWZpY2F0aW9uJTIwUm9v # dCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAyMC5jcmwwgcMGCCsGAQUF # BwEBBIG2MIGzMIGBBggrBgEFBQcwAoZ1aHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBJZGVudGl0eSUyMFZlcmlmaWNhdGlv # biUyMFJvb3QlMjBDZXJ0aWZpY2F0ZSUyMEF1dGhvcml0eSUyMDIwMjAuY3J0MC0G # CCsGAQUFBzABhiFodHRwOi8vb25lb2NzcC5taWNyb3NvZnQuY29tL29jc3AwDQYJ # KoZIhvcNAQEMBQADggIBAH8lKp7+1Kvq3WYK21cjTLpebJDjW4ZbOX3HD5ZiG84v # jsFXT0OB+eb+1TiJ55ns0BHluC6itMI2vnwc5wDW1ywdCq3TAmx0KWy7xulAP179 # qX6VSBNQkRXzReFyjvF2BGt6FvKFR/imR4CEESMAG8hSkPYso+GjlngM8JPn/ROU # rTaeU/BRu/1RFESFVgK2wMz7fU4VTd8NXwGZBe/mFPZG6tWwkdmA/jLbp0kNUX7e # lxu2+HtHo0QO5gdiKF+YTYd1BGrmNG8sTURvn09jAhIUJfYNotn7OlThtfQjXqe0 # qrimgY4Vpoq2MgDW9ESUi1o4pzC1zTgIGtdJ/IvY6nqa80jFOTg5qzAiRNdsUvzV # koYP7bi4wLCj+ks2GftUct+fGUxXMdBUv5sdr0qFPLPB0b8vq516slCfRwaktAxK # 1S40MCvFbbAXXpAZnU20FaAoDwqq/jwzwd8Wo2J83r7O3onQbDO9TyDStgaBNlHz # MMQgl95nHBYMelLEHkUnVVVTUsgC0Huj09duNfMaJ9ogxhPNThgq3i8w3DAGZ61A # MeF0C1M+mU5eucj1Ijod5O2MMPeJQ3/vKBtqGZg4eTtUHt/BPjN74SsJsyHqAdXV # S5c+ItyKWg3Eforhox9k3WgtWTpgV4gkSiS4+A09roSdOI4vrRw+p+fL4WrxSK5n # MYIaxzCCGsMCAQEwcTBaMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgSUQgVmVyaWZpZWQgQ1Mg # QU9DIENBIDA0AhMzAABzA2gezvWGpRrVAAAAAHMDMA0GCWCGSAFlAwQCAQUAoIGQ # MBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgor # BgEEAYI3AgEVMCQGCisGAQQBgjcCAQwxFjAUoA6ADABTAEgAQQAyADUANqECgAAw # LwYJKoZIhvcNAQkEMSIEIFCxgSzUypkb7IP40uvu+bZs0qywRb9ukVhChNfe2+D9 # MA0GCSqGSIb3DQEBAQUABIIBgBj+5WuETks3C7wttb6an3sn7un4pJif4D/3Wty9 # csaD5PIa60SJjeJ30JA7hdeuOQPqJVZL952MeagFhD7GNkpiZ6Bz7XYpXA/NnAOH # 3a6ZFsp/DJNx5Th9SHQo61JTjKHZB4SOYvN43s10/j7Y2qvcrAfGqtk3UaXKbT11 # tWG2wQ3IWuq4hKj2yTIMD1jq+3IUIPdyKpgW3/D7WbPx4xg/nc7wm7oKDLu9Hnj8 # kiqmce9Nsvr1HqrVRd1tDVDm35+qeUo5q2PZ+ik+FqXde6Y9RXQu4E6pH/BucCcI # ki49vxlFC2v37O8z8gVsGOVutEdX+SLMj3XHr3plAcarwS1rsdw1HBxN/lUcyOVY # klW70BnFVrl8NMhcsaVbUSh5XI3UPDX2YppdFj4OUekXlf+UzwD0WHgZW8O2n2cc # wQjlJrwPuUAIOj9HE1NSL/o2X6RdqmAAsUkyOKgLssIvjgVHKadEVmZwrCH2iE1Q # tc4r4oGq0RHvJLuv9/yA6L5OZ6GCGBQwghgQBgorBgEEAYI3AwMBMYIYADCCF/wG # CSqGSIb3DQEHAqCCF+0wghfpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFiBgsqhkiG # 9w0BCRABBKCCAVEEggFNMIIBSQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQC # AQUABCABWKLfaTlGxUhh9M3xuiXyDR6Otd4EXtyXiwq+I9TcrQIGaeddbIdxGBMy # MDI2MDQyNDIyMTgzNC4zNzZaMASAAgH0oIHhpIHeMIHbMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj # YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTUwMC0wNUUw # LUQ5NDcxNTAzBgNVBAMTLE1pY3Jvc29mdCBQdWJsaWMgUlNBIFRpbWUgU3RhbXBp # bmcgQXV0aG9yaXR5oIIPITCCB4IwggVqoAMCAQICEzMAAAAF5c8P/2YuyYcAAAAA # AAUwDQYJKoZIhvcNAQEMBQAwdzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjFIMEYGA1UEAxM/TWljcm9zb2Z0IElkZW50aXR5IFZl # cmlmaWNhdGlvbiBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDIwMB4XDTIw # MTExOTIwMzIzMVoXDTM1MTExOTIwNDIzMVowYTELMAkGA1UEBhMCVVMxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFB1 # YmxpYyBSU0EgVGltZXN0YW1waW5nIENBIDIwMjAwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCefOdSY/3gxZ8FfWO1BiKjHB7X55cz0RMFvWVGR3eRwV1w # b3+yq0OXDEqhUhxqoNv6iYWKjkMcLhEFxvJAeNcLAyT+XdM5i2CgGPGcb95WJLiw # 7HzLiBKrxmDj1EQB/mG5eEiRBEp7dDGzxKCnTYocDOcRr9KxqHydajmEkzXHOeRG # wU+7qt8Md5l4bVZrXAhK+WSk5CihNQsWbzT1nRliVDwunuLkX1hyIWXIArCfrKM3 # +RHh+Sq5RZ8aYyik2r8HxT+l2hmRllBvE2Wok6IEaAJanHr24qoqFM9WLeBUSudz # +qL51HwDYyIDPSQ3SeHtKog0ZubDk4hELQSxnfVYXdTGncaBnB60QrEuazvcob9n # 4yR65pUNBCF5qeA4QwYnilBkfnmeAjRN3LVuLr0g0FXkqfYdUmj1fFFhH8k8YBoz # rEaXnsSL3kdTD01X+4LfIWOuFzTzuoslBrBILfHNj8RfOxPgjuwNvE6YzauXi4or # p4Sm6tF245DaFOSYbWFK5ZgG6cUY2/bUq3g3bQAqZt65KcaewEJ3ZyNEobv35Nf6 # xN6FrA6jF9447+NHvCjeWLCQZ3M8lgeCcnnhTFtyQX3XgCoc6IRXvFOcPVrr3D9R # PHCMS6Ckg8wggTrtIVnY8yjbvGOUsAdZbeXUIQAWMs0d3cRDv09SvwVRd61evQID # AQABo4ICGzCCAhcwDgYDVR0PAQH/BAQDAgGGMBAGCSsGAQQBgjcVAQQDAgEAMB0G # A1UdDgQWBBRraSg6NS9IY0DPe9ivSek+2T3bITBUBgNVHSAETTBLMEkGBFUdIAAw # QTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9E # b2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # yH7SaoUqG8oZmAQHJ89QEE9oqKIwgYQGA1UdHwR9MHsweaB3oHWGc2h0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMElkZW50aXR5 # JTIwVmVyaWZpY2F0aW9uJTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5 # JTIwMjAyMC5jcmwwgZQGCCsGAQUFBwEBBIGHMIGEMIGBBggrBgEFBQcwAoZ1aHR0 # cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBJ # ZGVudGl0eSUyMFZlcmlmaWNhdGlvbiUyMFJvb3QlMjBDZXJ0aWZpY2F0ZSUyMEF1 # dGhvcml0eSUyMDIwMjAuY3J0MA0GCSqGSIb3DQEBDAUAA4ICAQBfiHbHfm21WhV1 # 50x4aPpO4dhEmSUVpbixNDmv6TvuIHv1xIs174bNGO/ilWMm+Jx5boAXrJxagRhH # QtiFprSjMktTliL4sKZyt2i+SXncM23gRezzsoOiBhv14YSd1Klnlkzvgs29XNjT # +c8hIfPRe9rvVCMPiH7zPZcw5nNjthDQ+zD563I1nUJ6y59TbXWsuyUsqw7wXZoG # zZwijWT5oc6GvD3HDokJY401uhnj3ubBhbkR83RbfMvmzdp3he2bvIUztSOuFzRq # rLfEvsPkVHYnvH1wtYyrt5vShiKheGpXa2AWpsod4OJyT4/y0dggWi8g/tgbhmQl # ZqDUf3UqUQsZaLdIu/XSjgoZqDjamzCPJtOLi2hBwL+KsCh0Nbwc21f5xvPSwym0 # Ukr4o5sCcMUcSy6TEP7uMV8RX0eH/4JLEpGyae6Ki8JYg5v4fsNGif1OXHJ2IWG+ # 7zyjTDfkmQ1snFOTgyEX8qBpefQbF0fx6URrYiarjmBprwP6ZObwtZXJ23jK3Fg/ # 9uqM3j0P01nzVygTppBabzxPAh/hHhhls6kwo3QLJ6No803jUsZcd4JQxiYHHc+Q # /wAMcPUnYKv/q2O444LO1+n6j01z5mggCSlRwD9faBIySAcA9S8h22hIAcRQqIGE # jolCK9F6nK9ZyX4lhthsGHumaABdWzCCB5cwggV/oAMCAQICEzMAAABWfo+dWAiO # 6WAAAAAAAFYwDQYJKoZIhvcNAQEMBQAwYTELMAkGA1UEBhMCVVMxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFB1Ymxp # YyBSU0EgVGltZXN0YW1waW5nIENBIDIwMjAwHhcNMjUxMDIzMjA0NjUxWhcNMjYx # MDIyMjA0NjUxWjCB2zELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UE # CxMeblNoaWVsZCBUU1MgRVNOOkE1MDAtMDVFMC1EOTQ3MTUwMwYDVQQDEyxNaWNy # b3NvZnQgUHVibGljIFJTQSBUaW1lIFN0YW1waW5nIEF1dGhvcml0eTCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBALSln5v7pdNu/3fEZW/DJ/4NEFL7y6mN # lbMt7SPFNrRUrKU2aJmTg9wR0/C5Efka4TCYG9VYwChTcrGivXC0l4nzxkiAazwo # LPT+MtuJayRJq1ekOc+AZqjISD62YRL2Z1qQkuBzu42Enov58Zgu/9RK/peS4Nz5 # ksW/HdiFXAEcUsNQeJsQelyNJ5HpfcGtXWG9sHxqaH62hZsWTsU/XjYbeCx9EQUl # bnm2umTaY0v9ILX5u6oiIsj+qej0c002zJ1arB51f3f61tMx8fkPkDWecFKipk2S # QfYVPOd/tqV+aw3yt9rjWPf1gTgJs26oKRHUJG4jGr1DMlA0oZsnCL4B3UJ0ttO7 # E4/DPpCS97TnWoT7j6jMLGggoHX8MEMdDvUynuxUr2wBGLNQJ5XQpfyhxmQjlb1D # ao8i9dCS3tP/hg/f8p6lxlhaVzo2rp72f3CkToYzeDOXuscdG9poqnD4ouP4otmY # XimpZSRE+wipaRUobN8MoOhf36I0MULz521g+DcsepYY1o8JqC3MesNRUgrWrywp # ct9wS0UpU1OKilMWmvHe2DexKqZ/VztEmNLpjryhV61h+68ZfvYmonIrXZ005LAJ # 0Y73pHSk95YO5UTH5n2VPL1zYjdFGCc0/RI6o0ZtLjf4dKF8T4TXz2KnhW8j1xhs # c2mFM+s8d6k3AgMBAAGjggHLMIIBxzAdBgNVHQ4EFgQUvrYz8rurWf4eRrMi78s9 # R/hTSFowHwYDVR0jBBgwFoAUa2koOjUvSGNAz3vYr0npPtk92yEwbAYDVR0fBGUw # YzBhoF+gXYZbaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWlj # cm9zb2Z0JTIwUHVibGljJTIwUlNBJTIwVGltZXN0YW1waW5nJTIwQ0ElMjAyMDIw # LmNybDB5BggrBgEFBQcBAQRtMGswaQYIKwYBBQUHMAKGXWh0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwUHVibGljJTIwUlNB # JTIwVGltZXN0YW1waW5nJTIwQ0ElMjAyMDIwLmNydDAMBgNVHRMBAf8EAjAAMBYG # A1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDBmBgNVHSAEXzBd # MFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wCAYGZ4EMAQQCMA0G # CSqGSIb3DQEBDAUAA4ICAQAOA6gFxLDtuo/y2uxYJ/0In4rfMbmXpKmee/mHvrB/ # 4UU2xBIxmK2YLKsEf5VFHyghaW2RfJrGmT0CTkeGGFBFPF8oy65/GNYwkpqMYfZe # 7VokqHPyRQcN+eiQJsxhsXgQNhFksUbk69QLmXup2GjfP8LRZIh3LPIDGncVwbOg # 8VYcruWJ4Sz0JH7pipt5RX7cBO6Ynle39ZbJJpYLAugHkhgsxj2VIAr3B+U7/0Hv # c+2yCJkg90rs4TiMGj/nikE2H+u04n8iSpFkEnRn0wOinLuNZPCweqDyvjC5NY28 # cSucD6i0i+tsYytOEgVxxCUhJ7BbdM8VpMT/5YHo9Q8alJ5q2BHZMb8ykhyAKhVk # mbpf+YSPrycbxT4bDUARJOHErpQ5CUKXHVYv4Jn/5hxTmIQwY7GtebOC/trAYpd1 # 1f0/EYkeukPMWL0y0VsXdnVbKzqAsJ7FOFiHogtCYpwr9VixxIe0Ms6/UUq+JCiS # 1naTWC4YI5KI05hJAIxTu++Ld8Qe3p27yBdBjrFdfcZwlM6vRBisrdIDLmqYSpTY # yfmk6Y1jGQxqPhjirJ6fdx5n7ZpdEsqdxffjN8vsuliRlGaCGSattu4w44xJ3baV # K4fQXT3VSH1SQ/wLvNUc4dOVBwIr6K0NzrPDxCxyIIjnfU1s23YJhs3CC7f3XVUB # ETGCB0YwggdCAgEBMHgwYTELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFB1YmxpYyBSU0EgVGlt # ZXN0YW1waW5nIENBIDIwMjACEzMAAABWfo+dWAiO6WAAAAAAAFYwDQYJYIZIAWUD # BAIBBQCgggSfMBEGCyqGSIb3DQEJEAIPMQIFADAaBgkqhkiG9w0BCQMxDQYLKoZI # hvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI2MDQyNDIyMTgzNFowLwYJKoZIhvcN # AQkEMSIEIKbydwUUGdb4hWLGXbUVZZNL/QQDq7eh986ZbxnSJUDKMIG5BgsqhkiG # 9w0BCRACLzGBqTCBpjCBozCBoAQgtgwzJU2k4/CVd4k4OV56XuAkh+tNeN2fl/aO # TQYDDKgwfDBlpGMwYTELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFB1YmxpYyBSU0EgVGltZXN0 # YW1waW5nIENBIDIwMjACEzMAAABWfo+dWAiO6WAAAAAAAFYwggNhBgsqhkiG9w0B # CRACEjGCA1AwggNMoYIDSDCCA0QwggIsAgEBMIIBCaGB4aSB3jCB2zELMAkGA1UE # BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc # BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0 # IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE1 # MDAtMDVFMC1EOTQ3MTUwMwYDVQQDEyxNaWNyb3NvZnQgUHVibGljIFJTQSBUaW1l # IFN0YW1waW5nIEF1dGhvcml0eaIjCgEBMAcGBSsOAwIaAxUA/3P3KRUqkFmAXl4I # MkSdmW72BBGgZzBlpGMwYTELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFB1YmxpYyBSU0EgVGlt # ZXN0YW1waW5nIENBIDIwMjAwDQYJKoZIhvcNAQELBQACBQDtldBoMCIYDzIwMjYw # NDI0MTEyMDA4WhgPMjAyNjA0MjUxMTIwMDhaMHcwPQYKKwYBBAGEWQoEATEvMC0w # CgIFAO2V0GgCAQAwCgIBAAICIKUCAf8wBwIBAAICEmowCgIFAO2XIegCAQAwNgYK # KwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQAC # AwGGoDANBgkqhkiG9w0BAQsFAAOCAQEAkwZ2awKfhigvSn/7PmProFI5pM57guLm # 0YD0rvbnKbRyyfIwS+3L1SqC5RTukXd9Z/xn6zVtUoDjnliwwihd4/EQtweqxiBp # dsOagzux3DwG7UQS4OE1QoF4C5h7Yy7VLhAFRg4Py9GHfiv0+WM7pn2KYA+knK3z # IhAUcSE6QPcD7BBbmuSvGMQUtvg5kQonpi7KOw+QTjcaXTXrrqlxEcxMUvLp7f1B # U1yQP1Eu78bOrOhPIdyeoZ706FBzSPx9zt6I19BVt8jECy25OsVX9R+KNHPj4emg # YSuWGhVZHm41UlLRwwL4GXJbX+EX7mHSFr8IkEYadgL98eG567pXpjANBgkqhkiG # 9w0BAQEFAASCAgCFzy8VddQJcYVBKuKVbwWUgTb4nZU29qr1qAnYmlNyuZTGKLxF # hgiRX+anmMpiNnZsbOCu2zY3/ILWkMY/1meXKCFuzJS51n7+85aiinMT8jLV+3aK # VLlJ7WaXM8/RiMW6LkmEo/XIQ5DFn86Cmk9k7Q2RBIWGP/9Y/BTjlrisJXH9vvW9 # C8Q8lvvrhx3WcZR5jj3ysh+PKmiQ/g3vQJto9oqElGW41zf+do/PWFGHIz2fUnkw # v0zzozy0F8lp+rW56mQau8w/eQiYtfLMUlAVk8EhemPXbbyETgEjaIAtEe2cj+YK # xUUmf/iJPzj16SuJaH6Xs06Y3NTdPIEerKuYZ+iVp2tKbgzgpeb6ByoYXSg24x1Y # +EfDpMtOcnDuWJQ8qLBqG85pnSZim5w/4F0Z99i115vS9xYiSCuoyM9pCSyx/0Gv # MHJuByatPXpPdqpPZaNB/qJkwEUa9avpxg/jWQhCRwqCxN/zt78uOFGvpQwgN9uY # KNLcsbOGR8jNDrki9LZMDb3JkLLlw9KdL8AROzJQuwOKNEh/wOm8HdvRJLno83WF # 6KETL3ie+CiQCZ6BodVWkk+chQiKka1vz8D/TDJ8XgYo4VD7YPbVbgN4/ICek14s # YF1SqJNt6zJ5AXQu88WjYfUZVheVzME5O/hKsrLc5GqqQ2bh+FYVXwu7Cg== # SIG # End signature block |