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