TrafficUtil.psm1

Import-Module $PSScriptRoot\Logger.psm1
Import-Module $PSScriptRoot\ConfigManager.psm1

class TrafficEndpoint {
    [string] $VmName = "VmName"
    [string] $HostName = "HostName"
    [pscredential] $VmCredential = $null
    [pscredential] $hostCredential = $null
    [string] $IpAddress = "0.0.0.0"
    [int] $Port = 5001
    [string] $PublicIP = $null

    [string] ToString() {
        return "vm:$($this.VmName) ip:$($this.IpAddress):$($this.Port) pip:$($this.PublicIP)"
    }
}

class TrafficPattern {
    [int] $Connections = 10
    [int] $Duration = 1
    [int] $Iterations = 1
    # 10 GB
    [int64] $TransferSizeInBytes = 10737418240
    [string] $Protocol = "TCP"
    [uint64] $BitsPerSecond = 0
    [uint64] $FrameRate = 0
    [uint64] $BufferDepth = 0
    [uint64] $StreamLength = 0
    [string] $PushPattern = "pushpull"

    [string] ToString() {
        if("UDP" -eq $this.Protocol) {
            return "udp cxn:$($this.Connections) iters:$($this.Iterations) xfer:$($this.TransferSizeInBytes) bps:$($this.BitsPerSecond) fr:$($this.FrameRate) bd:$($this.BufferDepth)"
        }
        if("TCP" -eq $this.Protocol) {
            return "tcp cxn:$($this.Connections) iters:$($this.Iterations)bps:$($this.BitsPerSecond) bd:$($this.BufferDepth) ptrn:$($this.PushPattern)"
        }
        return ""
    }
}

class WorkloadSummary {
    [int] $TotalConnections = 0
    [int] $FailedConnections = 0
    [int] $SuccessfulConnections = 0
    [int] $TotalBytesTransferred = 0
}

Set-Variable VNET_DIP -Value 0 -Option Constant
Set-Variable PRIV_VIP -Value 1 -Option Constant
Set-Variable PUB_VIP -Value 2 -Option Constant
Set-Variable ILB_VIP -Value 3 -Option Constant
Set-Variable CLIENT_IP -Value 4 -Option Constant
Set-Variable DIPS -Value "DIPS" -Option Constant
Set-Variable VIP_IP -Value "VIP_IP" -Option Constant
Set-Variable VIP_PORT -Value "VIP_PORT" -Option Constant
Set-Variable VIP_PROTOCOL -Value "VIP_PROTOCOL" -Option Constant
Set-Variable VIP_TYPE -Value "VIP_TYPE" -Option Constant
Set-Variable DIP_IP -Value "DIP_IP" -Option Constant
Set-Variable DIP_PORT -Value "DIP_PORT" -Option Constant
Set-Variable DIP_VMNAME -Value "DIP_VMNAME" -Option Constant
Set-Variable FRONTEND_IP_CONFIG -Value "FRONTEND_IP_CONFIG" -Option Constant
Set-Variable BACKEND_IP_CONFIG -Value "BACKEND_IP_CONFIG" -Option Constant
Set-Variable INBOUND_NAT_TRAFFIC_RULES -Value "INBOUND_NAT_TRAFFIC_RULES" -Option Constant
Set-Variable LOADBALANCER_TRAFFIC_RULES -Value "LOADBALANCER_TRAFFIC_RULES" -Option Constant
Set-Variable OUTBOUND_NAT_TRAFFIC_RULES -Value "OUTBOUND_NAT_TRAFFIC_RULES" -Option Constant
Set-Variable DIP_IPCONFIG -Value "DIP_IPCONFIG" -Option Constant
Set-Variable CLIENT_VMNAME -Value "InternetClient" -Option Constant
Set-Variable DIP_VM_HOSTNAME -Value "DIP_VM_HOSTNAME" -Option Constant
Set-Variable TRAFFIC_MAX_ALLOWED_FAILURE_PERCENT -Value 5 -Option Constant
Set-Variable DEFAULT_CTSTRAFFICPORT -Value 5001 -Option Constant


$script:ctsTrafficFolderPath = "C:\tools\" 
$script:ctsTrafficPath = "C:\tools\ctsTraffic.exe"

<#
    This function will create a new traffic pattern object
    The traffic pattern object will be used to run traffic between two VMs
#>

function New-TrafficPattern {
    param(
        [int] $connections,
        [int] $duration,
        [int] $iterations,
        [int64] $TransferSizeInBytes,
        [string] $protocol = "TCP",
        [int64] $bitsPerSecond = 0,
        [int64] $frameRate = 0,
        [int64] $bufferDepth = 0,
        [uint64] $streamLength = 0,

        [ValidateSet("push", "pullpush","pull","duplex", "")]
        [string] $pushPattern = "pushpull"

    )

    $pattern = New-Object TrafficPattern
    $pattern.Connections = $connections
    $pattern.Duration = $duration
    $pattern.Iterations = $iterations
    $pattern.TransferSizeInBytes = $TransferSizeInBytes
    $pattern.Protocol = $protocol
    $pattern.BitsPerSecond = $bitsPerSecond
    $pattern.FrameRate = $frameRate
    $pattern.BufferDepth = $bufferDepth
    $pattern.PushPattern = $pushPattern
    $pattern.StreamLength = $streamLength
    
    return $pattern
}



<#
    Creates a new traffic endpoint object. Endpoints can run/receieve traffic between two VMs
 
    endpointType : 0 - Vnet DIP
                   1 - Private IP Address
                   2 - PublicIP
                   3 - ILB
                   4 - Client IP
#>

function New-TrafficEndpoint {

    param(
        [string] $vmName,
        [string] $hostName,
        [pscredential] $vmCredential,
        [pscredential] $hostCredential,
        [string] $ipAddress,
        [int] $port,
        [bool] $resolveHostName = $false,
        [string] $publicIp = $null,

        [ValidateSet(0,1,2,3,4)]
        [parameter(Mandatory=$true)]
        [int] $endpointType
    )

    if($resolveHostName -eq $true) {
        $hostName = Resolve-HostName -hostName $hostName -hostCred $hostCredential -vmName $vmName -Force $true
    }

    $endpoint = New-Object TrafficEndpoint
    $endpoint.VmName = $vmName
    $endpoint.HostName = $hostName
    $endpoint.VmCredential = $vmCredential
    $endpoint.HostCredential = $hostCredential
    $endpoint.IpAddress = $ipAddress
    $endpoint.Port = $port
    $endpoint.PublicIP = $publicIp

    return $endpoint
}


<#
    Checks if the VM has ctstraffic installed and physically ready to run traffic workloads
    - Checks if the VM is powered on
    - Checks if the VM has ctstraffic installed
    - Checks if the VM has a valid IP address
    - Checks if the VM has a ctstraffic is enabled on firewall
#>

function Init-VmForCtstraffic {

    param(
        [object[]] $trafficEndpoints,
        [pscredential] $hostCred,
        [pscredential] $vmCred
    )

    [bool] $isCluster = (Test-IsCluster)

    #test if ctstraffic is available or not, if not, download it
    if( $isCluster) {
        $csvPath = get-csv
        $ctsTrafficClusterPath = Join-Path -Path $csvPath -ChildPath "\WorkloadTools\CtsTraffic.exe"
        
    } else {
        $ctsTrafficClusterPath  = $script:ctsTrafficPath
    }
     
    if(-not (Test-Path $script:ctsTrafficPath) -or -not (Test-Path $ctsTrafficClusterPath)) {
        Write-TraceLog "Init-VmForCtstraffic: ctstraffic not found, downloading it"
            
        #download ctstraiffic from github
        Start-BitsTransfer https://github.com/microsoft/ctsTraffic/raw/master/Releases/2.0.3.2/x64/ctsTraffic.exe $script:ctsTrafficPath
        
       
        if($ctsTrafficClusterPath  -ne $script:ctsTrafficPath) {
            mkdir "$csvPath\WorkloadTools\" -ErrorAction SilentlyContinue
            copy "$script:ctsTrafficPath" "$csvPath\WorkloadTools\" -Verbose -ErrorAction SilentlyContinue
            
        }

        Write-SDNExpressLog "Copied ctstraffic.exe from $script:ctsTrafficPath to $path" 
    }
    
    foreach($endpoint in $trafficEndpoints) {

        # log all properties of endpoint

        Write-SDNExpressLog -Message "Copy-VMFile $($endpoint.VmName) `
            Host:$($endpoint.HostName) `
            ToolPath:$script:ctsTrafficPath `
            SourcePath:$script:ctsTrafficPath `
            DestPath:$script:ctsTrafficPath `
            "


        # stop any previous running instances of ctstraffic
        Invoke-PowershellCommandOnVm -vmName $endpoint.VmName `
                                     -hostName $endpoint.HostName `
                                     -cmd "Stop-Process -Name CtsTraffic -Force -ErrorAction SilentlyContinue" `
                                     -hostCred $hostCred `
                                     -vmCred $vmCred

        [string] $ctsTrafficLocalMachineSourcePath = $script:ctsTrafficPath

          if($isCluster) {

            $ctsTrafficLocalMachineSourcePath = $ctsTrafficClusterPath 
        }


        $ctsEnableFirewall = "`$rule = New-NetFirewallRule -DisplayName ""Ctstraffic (Inbound)"" -Direction Inbound -Program $script:ctsTrafficPath -Action Allow"
        $ctsEnableFirewall += ";`$rule = New-NetFirewallRule -DisplayName ""Ctstraffic (Outbound)"" -Direction Outbound -Program $script:ctstrafficPath -Action Allow"

        $hostCred = Get-TurnKeySdnCred
        $vmCred = Get-TurnKeySdnWorkloadVmCred

        $results = Invoke-PowershellCommandOnVm -vmName $endpoint.VmName `
                                     -hostName $endpoint.HostName `
                                     -cmd $ctsEnableFirewall `
                                     -hostCred $hostCred `
                                     -vmCred $vmCred


        # ctstraffic.exe is detected as malware by windows defender, so we need to disable it
        # it also does full FS scan which slows deployment considerably

        $disableDefender = "Set-MpPreference -DisableRealtimeMonitoring `$true;"
        $disableDefender += "Set-MpPreference -DisableBehaviorMonitoring `$true;"
        $disableDefender += "Set-MpPreference -DisableBlockAtFirstSeen `$true;"
        $disableDefender += "Add-MpPreference -ExclusionPath $script:ctsTrafficFolderPath;"
        Invoke-PowershellCommandOnVm -vmName $endpoint.VmName `
            -hostName $endpoint.HostName `
            -cmd $disableDefender `
            -hostCred $hostCred `
            -vmCred $vmCred
        # now copy ctstraffic to the vm
        Copy-VMFile -VMName $endpoint.VmName ` `
                                     -SourcePath $ctsTrafficLocalMachineSourcePath `
                                     -DestinationPath $script:ctsTrafficPath `
                                     -FileSource Host `
                                     -ComputerName $endpoint.HostName `
                                     -CreateFullPath `
                                     -Force
    }
}



<#
Runs traffic between given endpoints
- Completes a full mesh (NXN endpoints with the traffic pattern provided), with one at a time.
- Assumes that appropriate firewall rules are enabled on the VMs (From SDN)
#>

function Start-IntraVmTraffic {
    param(
        [object[]] $trafficEndpoints,
        [TrafficPattern[]] $trafficPatterns,
        [pscredential] $hostCred,
        [pscredential] $vmCred
    )

    Write-FunctionEntryWithParams -FunctionName $MyInvocation.MyCommand.Name -boundparameters $psboundparameters -UnboundArguments $MyINvocation.UnboundArguments -ParamSet $psCmdlet

    # todo : (optimization) this can be sped up if we keep the destination running and go over the targets, this
    # way the init time can be saved (needs to handle different outFileNames for each target)
    foreach($trafficPattern in $trafficPatterns) {
        foreach($srcEndpoint in $trafficEndpoints) {
            foreach($dstEndpoint in $trafficEndpoints) {

                if($srcEndpoint -eq $dstEndpoint) {
                    continue
                }
                if($srcEndpoint.VmName -like "*ilb_client*" -or $dstEndpoint.VmName -like "*ilb_client*") {
                    Write-TraceLog "Start-IntraVmTraffic: ILB & clients are skipped from EW traffic tests."
                    continue
                }

                $guid = New-Guid 

                Write-TraceLog "Start-IntraVmTraffic: East West Traffic ($guid) $($srcEndpoint.VmName) $($dstEndpoint.VmName) starting.."  -Warning
                LogTrafficInfo -listenEndpoint $dstEndpoint -sendEndpoint $srcEndpoint -trafficPattern $trafficPattern

                $outFileName = "IntraVM-"+((New-Guid).Guid.ToString()).Substring(0,8) + ".csv"
                $errorFileName = "IntraVM-"+((New-Guid).Guid.ToString()).Substring(0,8) + ".err"

                Start-CtstrafficListener -listerningPort $dstEndpoint.Port `
                                        -vmName $dstEndpoint.VmName `
                                        -hostName $dstEndpoint.HostName `
                                        -vmCred $vmCred `
                                        -hostCred $hostCred `
                                        -trafficPattern $trafficPattern `
                                        -outFileName $outFileName `
                                        -uri $uri `
                                        -errorFileName $errorFileName `
                                        -isILB $false

                Start-CtstrafficSender -targetIpAddress $dstEndpoint.IpAddress `
                                        -targetPort $dstEndpoint.Port `
                                        -hostCred $hostCred `
                                        -vmCred $vmCred `
                                        -vmName $srcEndpoint.VmName `
                                        -hostName $srcEndpoint.HostName `
                                        -trafficPattern $trafficPattern `
                                        -outFileName $outFileName `
                                        -uri $uri `
                                        -wait $true

                $srcWorkloadResult = Wait-ForWorloadCompletion -vmName $srcEndpoint.VmName `
                                        -hostName $srcEndpoint.HostName `
                                        -hostCred $hostCred `
                                        -vmCred $vmCred `
                                        -outFileName $outFileName

                Test-WorkloadResult -workloadResults $srcWorkloadResult -maxFailedPercentage $TRAFFIC_MAX_ALLOWED_FAILURE_PERCENT -EndpointName $srcEndpoint.VmName

                $dstWorkloadResult = Wait-ForWorloadCompletion -vmName $dstEndpoint.VmName `
                                        -hostName $dstEndpoint.HostName `
                                        -hostCred $hostCred `
                                        -vmCred $vmCred `
                                        -outFileName $outFileName `
                                        -force $true

                Test-WorkloadResult -workloadResults $dstWorkloadResult -maxFailedPercentage $TRAFFIC_MAX_ALLOWED_FAILURE_PERCENT -EndpointName $dstEndpoint.VmName

                Write-Host "Start-IntraVmTraffic: East West Traffic ($guid) $($srcEndpoint.VmName) $($dstEndpoint.VmName) PASSED!"
            }
        }
    }
}

<#
    Runs SLB Traffic on a given set of endpoints.
 
    The traffic pattern is defined by the following parameters:
    - RulesToTarget : The type of rules to target on the load balancer. Valid values are:
        - LoadbalancerRules : All load balancer rules
        - Inbound : All inbound NAT rules
        - Outbound : All outbound NAT rules
 
    The caller must create the metadata json which describes the topology of DIPs and VIPs before calling this method.
    Use the helper Get-EndpointsFromLoadBalancer to extract the metadata from a given loadbalancer and pass it to this method to initiate traffic.
 
    NOTE: the Get-EndpointsFromLoadBalancer does not detect if probes are down for the given endpoints. Please disable probes before calling this method.
    For inbound/lb rules the server instances are
 
#>

function Start-SLBTraffic {
    param(
        [ValidateSet("LoadbalancerRules", "Inbound", "Outbound")]
        [string] $TargetType,
        [object] $vips,
        [TrafficPattern[]] $trafficPatterns,
        [pscredential] $hostCred,
        [pscredential] $vmCred,
        [string] $uri
    )

    if($TargetType -eq "LoadbalancerRules") {

        $vipInfo = $vips[$LOADBALANCER_TRAFFIC_RULES]

        if($null -eq $vipInfo -or $vipInfo.Count -eq 0) {
            throw "No loadbalancer rules found"
        }

        foreach($vipEndpoint in $vipInfo) {
            foreach($trafficPattern in $trafficPatterns) {

                    Write-TraceLog "Start-SLBTraffic:(lb) $vipEndpoint TRAFFICPATTERN: $trafficPattern"

                    if($vipEndpoint[$VIP_PROTOCOL] -ne $trafficPattern.Protocol -and $vipEndpoint[$VIP_PROTOCOL] -ne "All") {

                        Write-TraceLog "Start-SLBTraffic:(lb) Skipping as protocol does not match $($vipEndpoint[$VIP_PROTOCOL]) $($trafficPattern.Protocol)"
                        continue;
                    }

                    Start-SLBLBRuleTraffic -vipEndpoint $vipEndpoint `
                                        -trafficPattern $trafficPattern `
                                        -hostCred $hostCred `
                                        -vmCred $vmCred `
                                        -uri $uri
            }
        }

    }

    elseif($TargetType -eq "Inbound") {

        $vipInfo = $vips[$INBOUND_NAT_TRAFFIC_RULES]

        foreach($vipEndpoint in $vipInfo) {
            foreach($trafficPattern in $trafficPatterns) {

                Write-TraceLog "Start-SLBTraffic:(inbound) $vipEndpoint TRAFFICPATTERN: $trafficPattern"

                if($vipEndpoint[$VIP_PROTOCOL] -ne $trafficPattern.Protocol -and $vipEndpoint[$VIP_PROTOCOL] -ne "All") {
                    Write-TraceLog "Start-SLBTraffic:(inbound) Skipping as protocol does not match $($vipEndpoint[$VIP_PROTOCOL]) $($trafficPattern.Protocol)"
                    continue;
                }

                Start-SLBInboundTraffic -vipEndpoint $vipEndpoint `
                                        -trafficPattern $trafficPattern `
                                        -hostCred $hostCred `
                                        -vmCred $vmCred `
                                        -uri $uri
            }
        }

    }

    elseif($TargetType -eq "Outbound") {

        $vipInfo = $vips[$OUTBOUND_NAT_TRAFFIC_RULES]

        foreach($vipEndpoint in $vipInfo) {
            foreach($trafficPattern in $trafficPatterns) {

                Write-TraceLog "Start-SLBTraffic:(outbound) $vipEndpoint TRAFFICPATTERN: $trafficPattern"

                if($vipEndpoint[$VIP_PROTOCOL] -ne $trafficPattern.Protocol -and $vipEndpoint[$VIP_PROTOCOL] -ne "All") {
                    Write-TraceLog "Start-SLBTraffic:(outbound) Skipping as protocol does not match $($vipEndpoint[$VIP_PROTOCOL]) $($trafficPattern.Protocol)"
                    continue;
                }

                Start-SLBOutboundTraffic -vipEndpoint $vipEndpoint `
                                        -trafficPattern $trafficPattern `
                                        -hostCred $hostCred `
                                        -vmCred $vmCred `
                                        -uri $uri
            }
        }

    } else {
        throw "Invalid TargetType parameter"
    }
}

<#
 
 Gets a suitable endpoint to run the client traffic on, if its ILB, it will pick up a VNET location (for ILB: The ILB client NIC is used)
 for everything else, it will use the PublicIP on the ILB CLient
 
 todo: make this configurable to run the workload in different locations
 
 #>

function Get-SLBClientEndpoint {
    param(
        [object[]] $dips,
        [int] $vipType,
        [string] $uri,
        [pscredential] $hostCred,
        [pscredential] $vmCred,
        [string] $port
    )

    if($dips -eq $null -or $dips.Count -eq 0) {
        throw "Update-DipInfoIfILBEnabledDips: Dips is null or empty"
    }

    $ipConfigTokens = $dips[0][$DIP_IPCONFIG].split("/")
    $ipConfig = Get-NetworkControllerNetworkInterfaceIpConfiguration -NetworkInterfaceId $ipConfigTokens[2] -ResourceId $ipConfigTokens[4] -ConnectionUri $uri -PassInnerException
    $subnetNameResourceRef = $ipConfig.properties.Subnet.resourceRef
    $subnetNameResRefTokens = $subnetNameResourceRef.split("/")
    $subnetName = $subnetNameResRefTokens[4]

    $vmName = "$($subnetName)_ilb_client"

    $nic = Get-NetworkControllerNetworkInterface -ResourceId $vmName -ConnectionUri $uri -PassInnerException
    if($nic -eq $null) {
        throw "Could not find NIC $vmName"
    }


    # for ILB cases, use the ILB VNET IP Address for traffic
    if($vipInfo[$VIP_TYPE] -eq 3) {

        New-TrafficEndpoint -vmName $vmName `
            -hostName $null `
            -vmCredential $vmCred `
            -hostCredential $hostCred `
            -ipAddress $nic.properties.IpConfigurations[0].properties.privateIpAddress `
            -port 5001 `
            -endpointType $CLIENT_IP `
            -resolveHostName $true `
            -publicIp $null

    } else {
         # for other LB scenarios use the PublicIP address for traffic
        # get public ip from the ilb client nic

        $publicIpResRef = $nic.properties.IpConfigurations[0].properties.PublicIPAddress.resourceRef
        $publicIpResRefTokens = $publicIpResRef.split("/")
        $publicIpResource = Get-NetworkControllerPublicIPAddress -ResourceId $publicIpResRefTokens[2] -ConnectionUri $uri -PassInnerException
        $publicIpAddress = $publicIpResource.properties.IpAddress

            New-TrafficEndpoint -vmName $vmName `
                -hostName $null `
                -vmCredential $vmCred `
                -hostCredential $hostCred `
                -ipAddress $null `
                -port 5001 `
                -endpointType $CLIENT_IP `
                -resolveHostName $true `
                -publicIp $publicIpAddress

    }

}

function Start-SLBInboundTraffic {

    param(
        [object] $vipEndpoint,
        [TrafficPattern] $trafficPattern,
        [pscredential] $hostCred,
        [pscredential] $vmCred,
        [string] $uri
    )

    # todo : this can be sped up if we pre resolve the host names for all the DIPs
    $dipHostName = Resolve-HostName -hostName $vipEndpoint[$DIPS][0][$DIP_VMNAME] `
        -hostCred $hostCred `
        -vmName $vipEndpoint[$DIPS][0][$DIP_VMNAME] `
        -Force $true

    # select the best client based on the VIP type
    $clientEndpoint = Get-SLBClientEndpoint -dips $vipEndpoint[$DIPS] `
                                            -vipType $vipEndpoint[$VIP_TYPE] `
                                            -uri $uri `
                                            -hostCred $hostCred `
                                            -vmCred $vmCred `
                                            -port $vipEndpoint[$VIP_PORT]

    $guid = New-Guid
    $outFileName = ($guid.ToString()).Substring(0,8) + ".csv"

    Write-TraceLog "----------------------------------------------"
    Write-TraceLog "Start-SLBInboundTraffic: Starting INBOUND Traffic ($($guid))" -Warning
    Write-TraceLog "Start-SLBInboundTraffic: `t VIP : $($vipEndpoint[$VIP_IP]):$($vipEndpoint[$VIP_PORT]) "
    Write-TraceLog "Start-SLBInboundTraffic: `t VIPTYPE : $($vipEndpoint[$VIP_TYPE])"
    foreach($dipInfo in $vipEndpoint[$DIPS]) {
        Write-TraceLog "Start-SLBInboundTraffic: `t `t DIP: $($dipInfo[$DIP_IP]):$($vipEndpoint[$DIP_PORT]) vm($($dipInfo[$DIP_VMNAME]))" 
    }
    Write-TraceLog "Start-SLBInboundTraffic: `t CLIENT : $($clientEndpoint.vmName):$($clientEndpoint.IpAddress) "
    Write-TraceLog "Start-SLBInboundTraffic: `t TRAFFIC : $($trafficPattern.ToString())"
    Write-TraceLog "----------------------------------------------"


    # set up dip to recv
    Start-CtstrafficListener -listerningPort $vipEndpoint[$DIP_PORT] `
                             -vmName $vipEndpoint["DIPS"][0][$DIP_VMNAME] `
                             -hostName $dipHostName `
                             -vmCred $vmCred `
                             -hostCred $hostCred `
                             -outFileName $outFileName `
                             -trafficPattern $trafficPattern


    Start-CtstrafficSender  -vmName $clientEndpoint.VmName `
                            -hostName $clientEndpoint.HostName `
                            -targetIpAddress $vipEndpoint[$VIP_IP] `
                            -targetPort $vipEndpoint[$VIP_PORT] `
                            -hostCred $hostCred `
                            -vmCred $vmCred `
                            -ctsTrafficPathLocal $script:ctsTrafficPath `
                            -outFileName $outFileName `
                            -trafficPattern $trafficPattern

    # once the sender is complete, the receiver can be forced killed

    $workloadResultsOnDip = Wait-ForWorloadCompletion -vmName $vipEndpoint["DIPS"][0][$DIP_VMNAME] `
                                                -hostName $dipHostName `
                                                -hostCred $hostCred `
                                                -vmCred $vmCred `
                                                -outFileName $outFileName `
                                                -force $true
    Test-WorkloadResult -workloadResults $workloadResultsOnDip -maxFailedPercentage $TRAFFIC_MAX_ALLOWED_FAILURE_PERCENT -EndpointName $vipEndpoint["DIPS"][0][$DIP_VMNAME]



    $workloadResultClient = Wait-ForWorloadCompletion -vmName $clientEndpoint.VmName  `
                                                -hostName  $clientEndpoint.HostName `
                                                -hostCred $hostCred `
                                                -vmCred $vmCred `
                                                -outFileName $outFileName
    Test-WorkloadResult -workloadResults $workloadResultClient -maxFailedPercentage $TRAFFIC_MAX_ALLOWED_FAILURE_PERCENT -EndpointName $clientEndpoint.VmName

    Write-TraceLog "Start-SLBInboundTraffic: END --- "
}

function Test-WorkloadResult {
    param(
        [object] $workloadResults,
        [int] $maxFailedPercentage = 10,
        [string] $EndpointName
    )

    if($workloadResults -eq $null) {
        throw "Workload result is null"
    }
    if($workloadResults.count -eq 0) {
        throw "Workload result is empty"
    }

    [int] $total = 0
    [int] $failed = 0
    [int] $successful = 0

    foreach($result in $workloadResults) {

        if($result.Result -eq "Failed") {
            $failed += 1
        } else {
            $successful += 1
        }
        $total += 1
    }

    if($total -eq 0) {
        throw "Workload result is empty"
    }

    $failedPercentage = $failed / $total * 100
    if($failedPercentage -gt $maxFailedPercentage) {
        throw "Workload failed for more than 10% of the connections"
    }

    Write-TraceLog "Test-WorkloadResult: ----------SUMMARY for ($EndpointName)--------------------" -Warning
    Write-TraceLog "Test-WorkloadResult: Connections Total:$total Failed:$failed Successful:$successful Failed %:$failedPercentage" -Warning
    Write-TraceLog "Test-WorkloadResult: ----------SUMMARY for ($EndpointName)--------------------" -Warning

    return $true

}


function Start-SLBLBRuleTraffic {

    param(
        [object] $vipEndpoint,
        [TrafficPattern] $trafficPattern,
        [pscredential] $hostCred,
        [pscredential] $vmCred,
        [string] $uri
    )

    # select the best client based on the VIP type
    $clientEndpoint = Get-SLBClientEndpoint -dips $vipEndpoint[$DIPS] `
                                            -vipType $vipEndpoint[$VIP_TYPE] `
                                            -uri $uri `
                                            -hostCred $hostCred `
                                            -vmCred $vmCred `
                                            -port $vipEndpoint[$VIP_PORT]

    $guid = New-Guid
    $outFileName = ($guid.ToString()).Substring(0,8) + ".csv"

    Write-TraceLog "----------------------------------------------"
    Write-TraceLog "Start-SLBLBRuleTraffic: Starting LB Rule Traffic ($($guid))"  -Warning
    Write-TraceLog "Start-SLBLBRuleTraffic: `t VIP : $($vipEndpoint[$VIP_IP]):$($vipEndpoint[$VIP_PORT]) "
    Write-TraceLog "Start-SLBLBRuleTraffic: `t VIPTYPE : $($vipEndpoint[$VIP_TYPE])"
    foreach($dipInfo in $vipEndpoint[$DIPS]) {
        Write-TraceLog "Start-SLBLBRuleTraffic: `t `t DIP: $($dipInfo[$DIP_IP]):$($vipEndpoint[$DIP_PORT]) vm($($dipInfo[$DIP_VMNAME]))" 
    }
    Write-TraceLog "Start-SLBLBRuleTraffic: `t CLIENT : $($clientEndpoint.vmName):$($clientEndpoint.IpAddress):$($clientEndpoint.port) publicIp:$($clientEndpoint.PublicIP)"
    Write-TraceLog "----------------------------------------------"

    foreach($dipInfo in $vipEndpoint[$DIPS]) {

        Start-CtstrafficListener -listerningPort $vipEndpoint[$DIP_PORT] `
                                -vmName $dipInfo[$DIP_VMNAME] `
                                -hostName $null `
                                -vmCred $vmCred `
                                -hostCred $hostCred `
                                -outFileName $outFileName `
                                -trafficPattern $trafficPattern
    }


    Start-CtstrafficSender -vmName $clientEndpoint.VmName `
                        -hostName $clientEndpoint.HostName `
                        -targetIpAddress $vipEndpoint[$VIP_IP] `
                        -targetPort $vipEndpoint[$VIP_PORT] `
                        -hostCred $clientEndpoint.HostCredential `
                        -vmCred $clientEndpoint.VmCredential `
                        -outFileName $outFileName `
                        -trafficPattern $trafficPattern

    foreach($dipInfo in $vipEndpoint[$DIPS]) {

        # once the sender is complete, the receiver can be forced killed
        $workloadResultsOnDip = Wait-ForWorloadCompletion -vmName $dipInfo[$DIP_VMNAME]  `
                            -hostName $null `
                            -hostCred $hostCred `
                            -vmCred $vmCred `
                            -outFileName $outFileName `
                            -force $true

        Test-WorkloadResult -workloadResults $workloadResultsOnDip -maxFailedPercentage $TRAFFIC_MAX_ALLOWED_FAILURE_PERCENT -EndpointName $dipInfo[$DIP_VMNAME]

    }


    $workloadResultClient = Wait-ForWorloadCompletion -vmName $clientEndpoint.VmName `
            -hostName $clientEndpoint.HostName `
            -hostCred $clientEndpoint.HostCredential `
            -vmCred $clientEndpoint.VmCredential `
            -outFileName $outFileName
    Test-WorkloadResult -workloadResults $workloadResultClient -maxFailedPercentage $TRAFFIC_MAX_ALLOWED_FAILURE_PERCENT -EndpointName $clientEndpoint.VmName

    Write-TraceLog "Start-SLBLBRuleTraffic: LB Rule Traffic ($($guid)) PASSED"
    Write-TraceLog "----------------------------------------------"

}



function Start-SLBOutboundTraffic {
    param(
        [object] $vipEndpoint,
        [TrafficPattern] $trafficPattern,
        [pscredential] $hostCred,
        [pscredential] $vmCred,
        [string] $uri
    )

    Write-TraceLog "Start-SLBOutboundTraffic: $dipInfo $vipInfo"


    # for Get-SLBClientEndpoint gives us the ILB client VM which has a PublicIP on it, so it should work from there
    $clientEndpoint = Get-SLBClientEndpoint -dips $vipEndpoint[$DIPS] `
                                            -vipType $vipEndpoint[$VIP_TYPE] `
                                            -uri $uri `
                                            -hostCred $hostCred `
                                            -vmCred $vmCred `
                                            -port $vipEndpoint[$VIP_PORT]

    $vipPort = 5001
    $guid = New-Guid
    $outFileName = ($guid.ToString()).Substring(0,8) + ".csv"

    Write-TraceLog "----------------------------------------------"
    Write-TraceLog "Start-SLBOutboundTraffic: Starting OUTBOUND LB Rule Traffic ($($guid))"  -Warning
    Write-TraceLog "Start-SLBOutboundTraffic: `t VIP : $($vipEndpoint[$VIP_IP]):$($vipEndpoint[$VIP_PORT]) "
    Write-TraceLog "Start-SLBOutboundTraffic: `t VIPTYPE : $($vipEndpoint[$VIP_TYPE])"
    foreach($dipInfo in $vipEndpoint[$DIPS]) {
        Write-TraceLog "Start-SLBOutboundTraffic: `t `t DIP: $($dipInfo[$DIP_IP]):$($vipEndpoint[$DIP_PORT]) vm($($dipInfo[$DIP_VMNAME]))" 
    }
    Write-TraceLog "Start-SLBOutboundTraffic: `t CLIENT : $($clientEndpoint.vmName) IP $($clientEndpoint.IpAddress) PORT;$($clientEndpoint.port) PUBLICIP:$($clientEndpoint.PublicIP)"
    Write-TraceLog "----------------------------------------------"

    # set up client to recv
    Start-CtstrafficListener -listerningPort  $vipPort  `
                             -vmName $clientEndpoint.VmName `
                             -hostName $clientEndpoint.HostName `
                             -vmCred $vmCred `
                             -hostCred $hostCred `
                             -outFileName $outFileName `
                             -trafficPattern $trafficPattern

    # start senders
    foreach($dipInfo in $vipEndpoint[$DIPS]) {

        Start-CtstrafficSender -targetIpAddress $clientEndpoint.PublicIP `
                               -targetPort $vipPort `
                               -hostCred $hostCred `
                               -vmCred $vmCred `
                                -outFileName $outFileName `
                                -vmName $dipInfo[$DIP_VMNAME] `
                                -trafficPattern $trafficPattern

    }

    # wait for senders
    $workloadResults = @()
    foreach($dipInfo in $vipEndpoint[$DIPS]) {

         $workloadResult = Wait-ForWorloadCompletion -vmName $dipInfo[$DIP_VMNAME] `
                                  -hostName "" `
                                  -hostCred $hostCred `
                                  -vmCred $vmCred `
                                  -outFileName $outFileName `
                                  -force $true

        # test workload results per DIP
        Test-WorkloadResult -workloadResults $workloadResult -maxFailedPercentage $TRAFFIC_MAX_ALLOWED_FAILURE_PERCENT -EndpointName $dipInfo[$DIP_VMNAME]
        $workloadResults += $workloadResult

    }

    # also validate the total workload results
    Test-WorkloadResult -workloadResults $workloadResult -maxFailedPercentage $TRAFFIC_MAX_ALLOWED_FAILURE_PERCENT -EndpointName "SLB-OUTBOUND-DIPS_SUMMARY"

    # validate workload results on the oubound receiver
    $workloadResultClient = Wait-ForWorloadCompletion -vmName $clientEndpoint.VmName `
        -hostName $clientEndpoint.HostName `
        -hostCred $clientEndpoint.HostCredential `
        -vmCred $clientEndpoint.VmCredential `
        -outFileName $outFileName `
        -force $true

    Test-WorkloadResult -workloadResults $workloadResult -maxFailedPercentage $TRAFFIC_MAX_ALLOWED_FAILURE_PERCENT -EndpointName $clientEndpoint.VmName

    Write-TraceLog "Start-SLBOutboundTraffic: Passed"
}

function Start-Traffic {
    param(
        [ValidateSet("Listen", "Target")]
        [string] $Role,

        [object] $trafficPattern,

        [object] $currEndpoint,
        [object] $targetEndpoint,

        [pscredential] $hostCred,
        [pscredential] $vmCred
    )

    # note:
    # currEndpoint contains context on where the instance of ctstraffic is going to be run (either listen or target)
    # targetEndpoint contains context on where the traffic is going to be sent to, its required for sender to know where to send the traffic to

    $cmd = "Stop-Process -Name CtsTraffic -Force -ErrorAction SilentlyContinue;"
    if($Role -eq "Listen") {
        $cmd +=  "Start-Process " + $script:ctsTrafficPath + " -ArgumentList ""-listen:* -port:$($targetEndpoint.Port)""";
    } else {
        $cmd +=  "Start-Process -FilePath " + $script:ctsTrafficPath + " -Wait -ArgumentList ""-target:$($targetEndpoint.IpAddress) -port:$($targetEndpoint.Port) -protocol:tcp -connections:$($trafficPattern.Connections) -iterations:$($trafficPattern.Iterations)""";
    }

    Write-TraceLog -Message "Start-Traffic ($Role): $cmd"

    Invoke-PowershellCommandOnVmInternal -vmName $currEndpoint.VmName `
        -hostName $currEndpoint.HostName `
        -cmd $cmd `
        -hostCredentials $hostCred `
        -vmCredentials  $vmCred
}

function Resolve-HostName {

    param(
        [string] $hostName,
        [pscredential] $hostCred,
        [string] $vmName,
        [bool] $Force = $false
    )

    if(-not [string]::IsNullOrEmpty($hostName) -and $Force -eq $false) {
        Write-TraceLog "Resolve-HostName: HostName is not null or empty, returning $hostName"
        return $hostName
    }
    if([string]::IsNullOrEmpty($vmName)) {
        Write-TraceLog "Resolve-HostName: VMName is null, returning $hostName"
        return $hostName;
    }

    $nodes = (get-clusternode).Name
    foreach($node in $nodes) {

        $vm = Get-VM -VMName $vmName -ComputerName $node -ErrorAction SilentlyContinue
        if($vm -ne $null) {

            Write-TraceLog "Resolve-HostName: Resolved VM $vmName on node $node"
            return $node
        }
    }
    throw "Could not resolve host name for vm $vmName"
}

function Wait-ForWorloadCompletion {

    param(
        [string] $vmName,
        [string] $hostName,
        [pscredential] $hostCred,
        [pscredential] $vmCred,
        [string] $outFileName,
        [int] $timeoutInSeconds = 3600,
        [string] $ctstrafficPathLocal = $script:ctsTrafficPath,
        [bool] $force = $false
    )

    # Write-FunctionEntryWithParams -FunctionName "Wait-ForWorloadCompletion" -BoundParameters $PSBoundParameters -UnboundArguments $args

    # todo : avoid resolution of VM to speed things up a bit
    $hostName = Resolve-HostName -hostName $hostName -hostCred $hostCred -vmName $vmName -Force $false
    Write-TraceLog "Wait-ForWorloadCompletion: host:$hostName vm:$vmName.."

    $ctsTrafficFolderPath = $ctstrafficPathLocal.Substring(0, $ctstrafficPathLocal.LastIndexOf("\"))
    $csvFilePath = Join-Path $ctsTrafficFolderPath $outFileName

    if([string]::IsNullOrEmpty($vmName) -and [string]::IsNullOrEmpty($hostName)) {

        # wait for ctstraffic.exe to complete or kill and exit
        $done = $false
        [System.Diagnostics.Stopwatch]$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
        $stopwatch.Start()

        while(-not $done  -and $stopwatch.Elapsed.TotalSeconds -lt $timeoutInSeconds -and $force -eq $false) {

            Write-Host "Waiting for ctstraffic to complete"
            $p = Get-Process -Name Ctstraffic -ErrorAction SilentlyContinue

            if($null -eq $p) {
                $done = $true
                break
            }
        }

        if(-not $done) {
            Stop-Process -Name CtsTraffic -Force -ErrorAction SilentlyContinue
        }
        if(-not $force -and -not $done) {
            throw "Workload did not complete in time"
        }

        $WorkloadResult = Get-Content -Path $csvFilePath | ConvertFrom-Csv
        Write-TraceLog "Wait-ForWorloadCompletion: host:$hostName vm:$vmName..done"
        return $WorkloadResult
    }

    # the following is only if we are running traffic from the host (which is running turnkey)
    $done = $false
    [System.Diagnostics.Stopwatch]$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    $stopwatch.Start()

    while(-not $done -and $stopwatch.Elapsed.TotalSeconds -lt $timeoutInSeconds -and $force -eq $false) {

        Write-Host "Waiting for ctstraffic to complete"
        $cmd = "Get-Process -Name Ctstraffic -ErrorAction SilentlyContinue"

        $p = Invoke-PowershellCommandOnVm -vmName $vmName `
                                          -hostName $hostName `
                                          -cmd $cmd `
                                          -hostCred $hostCred `
                                          -vmCred $vmCred

        if($null -eq $p) {
            $done = $true
            break
        }
    }

    if ($force -or -not $done) {

        Invoke-PowershellCommandOnVm -vmName $vmName `
                -hostName $hostName `
                -cmd "Stop-Process -Name CtsTraffic -Force -ErrorAction SilentlyContinue" `
                -hostCred $hostCred `
                -vmCred $vmCred
    }

    if (-not $done -and -not $force) {
        throw "Workload did not complete in time"
    }

    $cmd = "Get-Content -Path $csvFilePath | ConvertFrom-Csv"
    $WorkloadResult = Invoke-PowershellCommandOnVm -vmName $vmName `
                                                   -hostName $hostName `
                                                   -cmd $cmd `
                                                   -hostCred $hostCred `
                                                   -vmCred $vmCred

    return $WorkloadResult
}

function Unblock-AllPortsOnVm {
    param(
        [string] $nicResourceId,
        [string] $restName
    )

    $uri = "https://$restName";

    $vnic = Get-NetworkControllerNetworkInterface -ResourceId $nicResourceId -ConnectionUri $uri -PassInnerException
    if($vnic.properties.IpConfigurations -eq $null -or $vnic.properties.IpConfigurations.Count -eq 0) {
        throw "No IP configuration found on the NIC"
    }
    if($vnic.properties.IpConfigurations.Count -gt 1) {
        throw "Multiple IP configurations found on the NIC"
    }

    # create the NICs ACL
    $nicAclResourceId = $nicResourceId + "_ACL"
    $newNicAcl = New-Object Microsoft.Windows.NetworkController.AccessControlList
    $newNicAcl.properties = New-Object Microsoft.Windows.NetworkController.AccessControlListProperties
    $newNicAcl.properties.AclRules = @()
    $newNicAclRuleIdBase = "ACL_RULE_";
    $newNicAcl.properties.AclRules  += GetAclRule -resourceId $($newNicAclRuleIdBase + 1) `
                                            -protocol "All" `
                                            -action "Allow" `
                                            -sourcePrefix "*" `
                                            -destPrefix "*" `
                                            -type "Inbound" `
                                            -priority 1000

    $newNicAcl.properties.AclRules  += GetAclRule -resourceId $($newNicAclRuleIdBase + 2) `
                                            -protocol "All" `
                                            -action "Allow" `
                                            -sourcePrefix "*" `
                                            -destPrefix "*" `
                                            -type "Outbound" `
                                            -priority 1000
                                            
    $newAcl = New-NetworkControllerAccessControlList -ResourceId $nicAclResourceId -ConnectionUri $uri -Properties $newNicAcl.properties -PassInnerException -Force
    Write-TraceLog "Created/updated allow all acl $nicAclResourceId, resourceRef : $($newAcl.resourceRef)"

    #bind this ACL to the NIC
    $vnic.properties.IpConfigurations[0].Properties.AccessControlList = $newAcl
    $nic = New-NetworkControllerNetworkInterface -ResourceId $nicResourceId -ConnectionUri $uri -Properties $vnic.properties -PassInnerException -Force
    Write-TraceLog "Applied unblock-all ports ACL for NIC $nicResourceId"
}

function GetAclRule
{
    param(
        [parameter(Mandatory=$true)] [String]$resourceId,
        [parameter(Mandatory=$true)] [String]$protocol,
        [parameter(Mandatory=$true)] [String]$action,
        [parameter(Mandatory=$true)] [String]$sourcePrefix,
        [parameter(Mandatory=$true)] [String]$destPrefix,
        [parameter(Mandatory=$true)] [String]$type,
        [parameter(Mandatory=$true)] [int]$priority
        )

    $ruleproperties = new-object Microsoft.Windows.NetworkController.AclRuleProperties
    $ruleproperties.Protocol = $protocol
    $ruleproperties.SourcePortRange = "*"
    $ruleproperties.DestinationPortRange = "*"
    $ruleproperties.Action = $action
    $ruleproperties.SourceAddressPrefix = $sourcePrefix
    $ruleproperties.DestinationAddressPrefix = $destPrefix
    $ruleproperties.Priority = $priority
    $ruleproperties.Type = $type
    $ruleproperties.Logging = "Enabled"

    $aclrule = new-object Microsoft.Windows.NetworkController.AclRule
    $aclrule.Properties = $ruleproperties
    $aclrule.ResourceId = $resourceId
    return $aclrule
}

function Get-EndpointsFromLoadBalancer  {
    param(
        [string] $lbResourceId,
        [pscredential] $hostCred,
        [pscredential] $vmCred,
        [string] $uri
    )

    $lb = Get-NetworkControllerLoadBalancer -ResourceId $lbResourceId -ConnectionUri $uri -PassInnerException
    if($lb.properties.frontendIPConfigurations -eq $null -or $lb.properties.frontendIPConfigurations.Count -eq 0) {
        return $lbEndpoints
    }
    if( $lb.properties.frontendIPConfigurations.Count -gt 1) {
        throw "Multiple frontend IP configurations found on the load balancer"
    }

    # all Traffic rules
    $inboundNATTrafficRules = @()
    $loadbalancerTrafficRules = @()
    $outboundNATTrafficRules = @()

    # parse and create workload patterns for all INBOUND NAT RULES
    foreach($inboundNatRule in $lb.properties.inboundNatRules) {
        $inboundNatTraffic = @{}
        if( $inboundNatRule.properties.backendIpConfiguration -eq $null -or $inboundNatRule.properties.backendIpConfiguration.Count -eq 0) {
            continue
        }
        $inboundNatTraffic["frontendIpConfig"] = $inboundNatRule.properties.frontendIpConfigurations[0].resourceRef
        $inboundNatTraffic["VIP_PORT"] = $inboundNatRule.properties.frontendPort
        $inboundNatTraffic["VIP_PROTOCOL"] = $inboundNatRule.properties.protocol
        $inboundNatTraffic["backendIpConfig"] = $inboundNatRule.properties.backendIpConfiguration.resourceRef
        $inboundNatTraffic["DIP_PORT"] = $inboundNatRule.properties.backendPort
        $inboundNATTrafficRules += $inboundNatTraffic
        $inboundNatTraffic["DIPS"] = @()
    }
    # resolve all DIPs to IPs
    foreach($inboundNatTraffic in $inboundNATTrafficRules) {

        $dipInfo = @{}
        $ipConfigTokens = $inboundNatTraffic["backendIpConfig"].split("/")
        $ipConfig = Get-NetworkControllerNetworkInterfaceIpConfiguration -NetworkInterfaceId $ipConfigTokens[2] -ResourceId $ipConfigTokens[4] -ConnectionUri $uri -PassInnerException
        $dipInfo["DIP_IP"] = $ipConfig.properties.PrivateIPAddress
        $dipInfo["DIP_VMNAME"] = $ipConfigTokens[2]
        $dipInfo[$DIP_IPCONFIG] = $ipConfig.resourceRef
        $dipInfo["DIP_PORT"] = $inboundNatTraffic["DIP_PORT"]
        $inboundNatTraffic["DIPS"] += $dipInfo
    }
    # resolve all VIPs
    foreach($inboundNatTraffic in $inboundNATTrafficRules) {

        $vipInfo = GetVIPInfoFromFroneEndIpConfig -feConfigResourceRef $inboundNatTraffic["frontendIpConfig"] -uri $uri
        $inboundNatTraffic["VIP_IP"] = $vipInfo["VIP_IP"]
        $inboundNatTraffic["VIP_TYPE"] = $vipInfo["VIP_TYPE"]
    }

    # parse and create workload patterns for all LB RULES
    foreach($lbRule in $lb.properties.loadBalancingRules) {
        $lbTrafficRule = @{}
        $lbTrafficRule["frontendIpConfig"] = $lbRule.properties.frontendIpConfigurations[0].resourceRef
        $lbTrafficRule["VIP_PORT"] = $lbRule.properties.frontendPort
        $lbTrafficRule["VIP_PROTOCOL"] = $lbRule.properties.protocol
        $lbTrafficRule["DIPS"] = @()

        #resolve DIPs
        $bePoolTokens = $lbRule.properties.backendAddressPool.resourceRef.split("/")
        $bePool = Get-NetworkControllerLoadBalancerBackendAddressPool -LoadBalancerId $bePoolTokens[2] -ResourceId $bePoolTokens[4] -ConnectionUri $uri -PassInnerException
        foreach($bePoolIpConfig in $bePool.properties.BackendIPConfigurations) {

            $ipConfigTokens = $bePoolIpConfig.resourceRef.split("/")
            $ipConfig = Get-NetworkControllerNetworkInterfaceIpConfiguration -NetworkInterfaceId $ipConfigTokens[2] -ResourceId $ipConfigTokens[4] -ConnectionUri $uri -PassInnerException
            $dipInfo = @{}
            $dipInfo["DIP_IP"] = $ipConfig.properties.PrivateIPAddress
            $dipInfo["DIP_VMNAME"] = $ipConfigTokens[2]
            $dipInfo[$DIP_IPCONFIG] = $ipConfig.resourceRef
            $lbTrafficRule["DIPS"] += $dipInfo

        }
        $lbTrafficRule["DIP_PORT"] = $lbRule.properties.backendPort
        $loadbalancerTrafficRules += $lbTrafficRule
    }
    # resolve all VIPs
    foreach($lbTrafficRule in $loadbalancerTrafficRules) {

        $vipInfo = GetVIPInfoFromFroneEndIpConfig -feConfigResourceRef $lbTrafficRule["frontendIpConfig"] -uri $uri
        $lbTrafficRule["VIP_IP"] = $vipInfo["VIP_IP"]
        $lbTrafficRule["VIP_TYPE"] = $vipInfo["VIP_TYPE"]
    }

    # parse and create workload patterns for all OUTBOUND NAT RULES
    foreach($outboundNatRule in $lb.properties.outboundNatRules) {
        $outboundNatTraffic = @{}
        $outboundNatTraffic["frontendIpConfig"] = $outboundNatRule.properties.frontendIpConfigurations[0].resourceRef
        $outboundNatTraffic["VIP_PORT"] = 0
        $outboundNatTraffic["VIP_PROTOCOL"] = "TCP"
        $outboundNatTraffic["DIPS"] = @()

        # resolve be pool to ipconfigurations (DIPS)
        $bePoolTokens = $outboundNatRule.properties.backendAddressPool.resourceRef.split("/")
        $bePool = Get-NetworkControllerLoadBalancerBackendAddressPool -LoadBalancerId $bePoolTokens[2] -ResourceId $bePoolTokens[4] -ConnectionUri $uri -PassInnerException
        foreach($bePoolIpConfig in $bePool.properties.BackendIPConfigurations) {
            $ipConfigTokens = $bePoolIpConfig.resourceRef.split("/")
            $ipConfig = Get-NetworkControllerNetworkInterfaceIpConfiguration -NetworkInterfaceId $ipConfigTokens[2] -ResourceId $ipConfigTokens[4] -ConnectionUri $uri -PassInnerException
            $dipInfo = @{}
            $dipInfo["DIP_IP"] = $ipConfig.properties.PrivateIPAddress
            $dipInfo["DIP_VMNAME"] = $ipConfigTokens[2]
            $dipInfo[$DIP_IPCONFIG] = $ipConfig.resourceRef
            $outboundNatTraffic["DIPS"] += $dipInfo
        }
        $outboundNatTraffic["DIP_PORT"] = $outboundNatRule.properties.backendPort
        $outboundNATTrafficRules += $outboundNatTraffic
    }
    # resolve all VIPs
    foreach($outboundNatTraffic in $outboundNATTrafficRules) {

        $vipInfo = GetVIPInfoFromFroneEndIpConfig -feConfigResourceRef $outboundNatTraffic["frontendIpConfig"] -uri $uri
        $outboundNatTraffic["VIP_IP"] = $vipInfo["VIP_IP"]
        $outboundNatTraffic["VIP_TYPE"] = $vipInfo["VIP_TYPE"]
    }

    # Add all traffic rules to the endpoint
    $out = @{}
    $out["INBOUND_NAT_TRAFFIC_RULES"] = $inboundNATTrafficRules
    $out["LOADBALANCER_TRAFFIC_RULES"] = $loadbalancerTrafficRules
    $out["OUTBOUND_NAT_TRAFFIC_RULES"] = $outboundNATTrafficRules
    return $out
}

function GetVIPInfoFromFroneEndIpConfig {
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory=$true)]
        [string] $feConfigResourceRef,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory=$false)]
        [string] $uri
    )

    $feConfigTokens = $feConfigResourceRef.split("/")

    $feConfig = Get-NetworkControllerLoadBalancerFrontendIpConfiguration -LoadBalancerId $feConfigTokens[2] -ResourceId $feConfigTokens[4] -ConnectionUri $uri -PassInnerException
    $vipInfo = @{}
    if($null -ne $feConfig.properties.PublicIPAddress) {

        $publicIpResRef = $feConfig.properties.PublicIPAddress.resourceRef;
        $publicIpResRef = $publicIpResRef.Substring($publicIpResRef.LastIndexOf("/") + 1)
        $publicIPResource = Get-NetworkControllerPublicIpAddress -ResourceId $publicIpResRef -ConnectionUri $uri -PassInnerException
        $vipInfo["VIP_IP"] = $publicIPResource.properties.IpAddress
        $vipInfo["VIP_TYPE"] = 2; # public IP
    } else {
        $vipInfo["VIP_IP"] = $feConfig.properties.PrivateIPAddress
        # check if the subnet is vnet or lnet
        if($feConfig.Properties.Subnet.resourceRef.StartsWith("/virtualNetworks/")) {
            $vipInfo["VIP_TYPE"] = 3 ; # ILB
        } else {
            $vipInfo["VIP_TYPE"] = 1 ; # private IP
        }
    }
    return $vipInfo
}


function Stop-CtstrafficListener {

    paramn(
        [string] $vmName,
        [string] $hostName,
        [pscredential] $vmCred,
        [pscredential] $hostCred
    )
    Write-FunctionEntryWithParams -FunctionName $MyInvocation.MyCommand.Name -boundparameters $psboundparameters -UnboundArguments $MyINvocation.UnboundArguments -ParamSet $psCmdlet

    # hostName will be empty for hostName as the client is the local machine
    if([string]::IsNullOrEmpty($hostName)) {
        Stop-Process -Name Ctstraffic -Verbose -Force

        Write-traceLog "Stopped ctstraffic listener on $hostName "
        return
    }

    $hostName = Resolve-HostName -hostName $hostName -hostCred $hostCred -vmName $vmName

    # run on remote machine
    $cmd = "Start-Process -FilePath $ctsTrafficPath -ArgumentList ""-listen:* -port:$($listerningPort)"""
    invoke-powershellcommandonvm -vmName $vmName `
                                -hostName $hostName `
                                -cmd $cmd `
                                -hostCred $hostCred `
                                -vmCred $vmCred

}
function Start-CtstrafficListener {

    param(
        [int] $listerningPort,
        [string] $vmName,
        [string] $hostName,
        [pscredential] $vmCred,
        [pscredential] $hostCred,
        [string] $uri,
        [string] $outFileName,
        [TrafficPattern] $trafficPattern,
        [string] $errorFileName = "error.log"
    )

    [string] $ctsTrafficPath = $script:ctsTrafficPath
    # Write-FunctionEntryWithParams -FunctionName $MyInvocation.MyCommand.Name -boundparameters $psboundparameters -UnboundArguments $MyINvocation.UnboundArguments -ParamSet $psCmdlet

    if($null -eq $ctsTrafficPath -or $ctsTrafficPath -eq "") {
        $ctsTrafficPath = $script:ctsTrafficPath
    }
    $ctsFolder = $ctsTrafficPath.Substring(0, $ctsTrafficPath.LastIndexOf("\"));

    if($listerningPort -eq 0) {

        # for HA ports, the port value may be set to 0 , override it in that case
        $listerningPort = $DEFAULT_CTSTRAFFICPORT
        write-TraceLog "Start-CtstrafficListener: Defaulting to port $listerningPort"
    }

    write-TraceLog "Start-CtstrafficListener: Starting ctstraffic ($ctsTrafficPath) listener on $hostName $vmName, ctsfolder : $ctsFolder port:$listerningPort"

    # use local machine if host and vmname are null
    if([string]::IsNullOrEmpty($hostName) -and [string]::IsNullOrEmpty($vmName)) {

        if(-not [string]::IsNullOrEmpty($outFileName)) {
            $cmd = "-listen:* -port:$($listerningPort) -connectionFileName:$ctsFolder\$outFileName -transfer:$($trafficPattern.transferSizeInBytes)"
        } else {
            $cmd = "-listen:* -port:$($listerningPort) -transfer:$($trafficPattern.transferSizeInBytes)"
        }

        if(-not [string]::IsNullOREmpty($trafficPattern.PushPattern)) {
            $cmd += " -pattern:$($trafficPattern.PushPattern)"
        }

        Start-Process -FilePath $ctsTrafficPath -ArgumentList $cmd -Verbose
        Write-traceLog "Started ctstraffic listener on $hostName with command $cmd"
        return
    }

    if([string]::isNullOrEmpty($hostName)) {

        $hostName = Resolve-HostName -hostName $hostName -hostCred $hostCred -vmName $vmName -Force $true
    }


    # build cts traffic parameter list

    $cmd = "Stop-Process -Name CtsTraffic -Force -ErrorAction SilentlyContinue;"
    $cmd += "del $ctsFolder\*.csv -Force;"
    $cmd += "`$sw = [System.Diagnostics.Stopwatch]::new();`$sw.Start();"

    $cmd += "`$p = Start-Process -FilePath $ctsTrafficPath -PassThru -ArgumentList """
    $cmd += " -listen:* -port:$($listerningPort)"

    if($trafficPattern.Protocol -eq "UDP") {
        $cmd += " -protocol:udp"
        $cmd += " -BitsPerSecond:$($trafficPattern.BitsPerSecond)"
        $cmd += " -FrameRate:$($trafficPattern.FrameRate)"
        $cmd += " -StreamLength:$($trafficPattern.StreamLength)"
        
    } else {
        $cmd += " -protocol:tcp"
        if($trafficPattern.transferSizeInBytes -gt 0) {
            $cmd += " -transfer:$($trafficPattern.transferSizeInBytes)"
        }
        if(-not [string]::IsNullOREmpty($trafficPattern.PushPattern)) {
            $cmd += " -pattern:$($trafficPattern.PushPattern)"
        }
    }

    if(-not [string]::IsNullOrEmpty($outFileName)) {
        $cmd += " -connectionFileName:$ctsFolder\$outFileName"
    }
    if(-not [string]::IsNullOrEmpty($errorFileName)) {
        $cmd += " -errorFileName:$ctsFolder\$errorFileName"
    }

    $cmd += "`";" + [System.Environment]::NewLine # end the -argument list

    # detect failures in the command and throw exceptions

    # for listerner, wait for 2 seconds to see if the process has exited (bad params or something)
    $cmd += [System.Environment]::NewLine + "while(`$p.HasExited -eq `$false -and `$sw.Elapsed.TotalSeconds -lt 2) {Start-Sleep -Seconds 1};"
    # if process is alive, means it seems stable, proceed
    $cmd += [System.Environment]::NewLine + "if(`$p.HasExited -eq `$false ) { return };"
    # if process is dead, even if it exited with 0 error code and capture / throw error
    # if ctstraffic does not generate the error file for some reason then throw a generic error or process return code
    $cmd += [System.Environment]::NewLine + "if(`$p.HasExited -eq `$true ) {`$c = get-content $ctsFolder\$errorFileName -ErrorAction SilentlyContinue; if(-not [string]::IsNullOrEmpty(`$c)) { throw `$c } else { throw ""No error log found. CtsTraffic Exit With code `$p.exitCode""} };"

    invoke-powershellcommandonvm -vmName $vmName `
                                -hostName $hostName `
                                -cmd $cmd `
                                -hostCred $hostCred `
                                -vmCred $vmCred
}

function Start-CtstrafficSender {

    param(
        [string] $targetIpAddress,
        [int] $targetPort,
        [string] $vmName,
        [string] $hostName,
        [pscredential] $vmCred,
        [pscredential] $hostCred,
        [string] $uri,
        [string] $ctsTrafficPath = $script:ctsTrafficPath,
        [string] $outFileName,
        [bool] $Wait = $true,
        [TrafficPattern] $trafficPattern
   )

    #Write-FunctionEntryWithParams -FunctionName $MyInvocation.MyCommand.Name -boundparameters $psboundparameters -UnboundArguments $MyINvocation.UnboundArguments -ParamSet $psCmdlet

    if($null -eq $ctsTrafficPath -or $ctsTrafficPath -eq "") {
        $ctsTrafficPath = $script:ctsTrafficPath
    }

    $ctsFolder = $ctsTrafficPath.Substring(0, $ctsTrafficPath.LastIndexOf("\"));
    $errorFileName = "$ctsFolder\error.log"

    write-TraceLog "Start-CtstrafficSender Starting ctstraffic ($ctsTrafficPath) sender on $hostName $vmName, ctsfolder : $ctsFolder"

    if($targetPort -eq 0) {
        # for HA ports, the port value may be set to 0 , override it in that case
        $targetPort = $DEFAULT_CTSTRAFFICPORT
        write-TraceLog "Start-CtstrafficSender: Defaulting to port $targetPort"
    }

    # use local machine if host and vmname are null
    if([string]::IsNullOrEmpty($hostName) -and [string]::IsNullOrEmpty($vmName)) {

        # note: this is only to test it locally, this will not be used finally
        $cmd = " -target:$($targetIpAddress) -port:$($targetPort) -protocol:$($trafficPattern.protocol) -connections:$($trafficPattern.connections) -iterations:$($trafficPattern.iterations) -transfer:$($trafficPattern.transferSizeInBytes)"
        if(-not [string]::IsNullOrEmpty($outFileName)) {
            $cmd += " -connectionFileName:$ctsFolder\$outFileName "
        }

        if(-not [string]::IsNullOREmpty($trafficPattern.PushPattern)) {
            $cmd += " -pattern:$($trafficPattern.PushPattern)"
        }
        

        Start-Process -FilePath $ctsTrafficPath -ArgumentList $cmd -Verbose -Wait
        Write-traceLog "Started ctstraffic listener on local machine with command $cmd"
        return
    }

    if([string]::isNullOrEmpty($hostName)) {

        $hostName = Resolve-HostName -hostName $hostName -hostCred $hostCred -vmName $vmName -Force $true
    }

    # run on remote machine
    $cmd = "Stop-Process -Name CtsTraffic -Force -ErrorAction SilentlyContinue;"
    $cmd += "del $ctsFolder\*.csv -Force;`$sw = [System.Diagnostics.Stopwatch]::new();`$sw.Start();"

    $cmd += "`$p = Start-Process -FilePath $ctsTrafficPath -ArgumentList """

    if($trafficPattern.Protocol -eq "UDP") {
        $cmd += " -protocol:udp"
        $cmd += " -BitsPerSecond:$($trafficPattern.BitsPerSecond)"
        $cmd += " -FrameRate:$($trafficPattern.FrameRate)"
        $cmd += " -StreamLength:$($trafficPattern.StreamLength)"
        
    } else {
        $cmd += " -protocol:tcp"
        if($trafficPattern.transferSizeInBytes -gt 0) {
            $cmd += " -transfer:$($trafficPattern.transferSizeInBytes)"
        }
        if(-not [string]::IsNullOREmpty($trafficPattern.PushPattern)) {
            $cmd += " -pattern:$($trafficPattern.PushPattern)"
        }
    }
    $cmd += " -target:$($targetIpAddress) -port:$($targetPort) -connections:$($trafficPattern.connections) -iterations:$($trafficPattern.iterations) "

    if(-not [string]::IsNullOrEmpty($outFileName)) {
        $cmd += " -connectionFileName:$ctsFolder\$($outFileName) -errorfilename:$errorFileName"
    } 
    $cmd += """"

    $cmd += " -PassThru;"
    $cmd += "while(`$p.HasExited -eq `$false -and `$sw.Elapsed.TotalMinutes -lt 90) {Start-Sleep -Seconds 15};"
    $cmd += "if(`$p.HasExited -eq `$false ) { `$p.Kill(); throw ""Ctstraffic did not complete on time"" };"
    $cmd += "if(`$p.HasExited -eq `$true -and `$p.exitCode -ne 0) {`$c = get-content $errorFileName; throw `$c };"
    $cmd += "if(`$p.HasExited -eq `$true -and `$p.exitCode -eq 0) { return };"

    invoke-powershellcommandonvm -vmName $vmName `
                                -hostName $hostName `
                                -cmd $cmd `
                                -hostCred $hostCred `
                                -vmCred $vmCred

}



function Get-TrafficPattens {

    # [CmdletBinding()]
    # param (
    # [Parameter()]
    # [ValidateSet("TCP", "UDP")]
    # $Protocol,
    # [Parameter()]
    # [ValidateSet("ShortBursts", "LongRunning")
    # [string] $ProfileType
    # )
    # todo : implement different pattern types based on profile

    # for HLK , we just hardcode some default patterns for now
    $trafficPatterns = @()

    $OneKBInBytes = 1024
    $OneMBInBytes = 1048576
    $OneGBInBytes = 1073741824
    # few long runnning connections
    $trafficPatterns += New-TrafficPattern -connections 5 `
                                        -duration 60 `
                                        -iterations 2 `
                                        -TransferSizeInBytes ($OneGBInBytes * 2) `
                                        -protocol "TCP"
    # # medium set of connections
    # $trafficPatterns += New-TrafficPattern -connections 20 `
    # -duration 60 `
    # -iterations 20 `
    # -TransferSizeInBytes ($OneMBInBytes * 2) `
    # -protocol "TCP"
    
    # udp connections

    # 10mbps stream over 1000 frames
    $mbps10 = 25000000
    $fr = 60
    $trafficPatterns += New-TrafficPattern -connections 5 `
                                        -duration 60 `
                                        -iterations 2 `
                                        -protocol "UDP" `
                                        -bitsPerSecond $mbps10 `
                                        -FrameRate $fr `
                                        -BufferDepth 1 `
                                        -pushPattern "" `
                                        -streamLength 100

    return $trafficPatterns
}


<#
    Triggers a flurry of live mirgation operations of the given VMs.
    The VMs are represented by DIP endpoints, which have a VM name and IP address.
#>

function Start-SDNVmMigrationValidation {

    param(
        [int] $percentageOfVmsToMove = 50,
        [object[]] $trafficEndpoints
    )

    # calculate how many migrations need to be performed
    [int] $numOfVmsToMove = $trafficEndpoints.Count * $percentageOfVmsToMove / 100

    # track vms already attempted to move
    $vmsMoved = @()

    # track vms that failed to move
    $vmsFailedToMove = @()

    # track vms that moved successfully
    $vmsMovedSuccessfully = @()

    $done = $false

    do {

        [int] $breaker = 0
        $vmToMove = $null
        # find a target vm to move
        $vmToMove = Get-Random -InputObject $trafficEndpoints

        if($null -eq $vmToMove) {
            throw "Could not find a VM to move"
        }

        # move the vm
        try {

            # resolve host for the VM
            $hostName = Resolve-HostName -vmName $vmToMove.VmName -hostCred $vmToMove.HostCredential -vmCred $vmToMove.VmCredential
            $allHostnames = Get-ClusterNode | ?{$_.State -eq "UP" -and $_.Name -ne $hostName}
            $target = Get-Random -InputObject $allHostnames

            # for now using only live migration
            # $i = Get-Random -Maximum 4
            # if($i -eq 0) {
            # $migrationType = "Quick"
            # }
            # if($i -eq 1) {
            # $migrationType = "Live"
            # }
            # if($i -eq 2) {
            # $migrationType = "Shutdown"
            # }
            # if($i -eq 3) {
            # $migrationType = "TurnOff"
            # }

            Write-TraceLog "Start-SDNVmMigrationValidation: Moving VM $($vmToMove.VmName) from host $hostName to host $($target.Name)"

            Move-ClusterVirtualMachineRole -Name $vmToMove.VMName -Node $target.Name -MigrationType Live

            $vmsMovedSuccessfully += $vmToMov

            if($vmsMovedSuccessfully.Count -ge $numOfVmsToMove) {
                $done = $true
            }

        } catch {
            Write-TraceLog "Start-SDNVmMigrationValidation: Failed to move VM $($vmToMove.VmName) from host $hostName to host $($target.Name) Error : $_"
            $vmsFailedToMove += $vmToMove

            # give up on 2nd failure
            if($vmsFailedToMove.Count -ge 2) {
                throw "Multiple live migrations failure, please check your configuration and run the test again"
            }
            continue
        }
    }until($done)

    Write-TraceLog "Start-SDNVmMigrationValidation: Successfully moved $numOfVmsToMove VMs"
}

function Initialize-TrafficUtil {

    param([string] $ctsTrafficPath = $script:ctsTrafficPath)
    
    if(Test-Path $script:ctsTrafficPath) {
        Write-Host "ctsTraffic already available at $ctsTrafficPath, skipping download"
        return
    }

    Start-BitsTransfer https://github.com/microsoft/ctsTraffic/raw/master/Releases/2.0.3.2/x64/ctsTraffic.exe $ctsTrafficPath  -Verbose
}

function Test-TenantVirtualMachine {
    param(
        [string] $vmName,
        [string] $hostName,
        [pscredential] $vmCred,
        [pscredential] $hostCred
    )
    
    Write-FunctionEntryWithParams -FunctionName $MyInvocation.MyCommand.Name -boundparameters $psboundparameters -UnboundArguments $MyINvocation.UnboundArguments -ParamSet $psCmdlet

    $cmd = "`$process = Get-Process -Name winlogon; if(`$null -ne `$process) { return `$true } else { return `$false }"

    $hostCred = Get-TurnKeySdnCred
    $vmCred = Get-TurnKeySdnWorkloadVmCred

    $retryCount = 0
    $maxRetryCount = 10

    do {
        try {
            $result = Invoke-PowershellCommandOnVm -vmName $vmName `
                                                -hostName $hostName `
                                                -cmd $cmd `
                                                -hostCred $hostCred `
                                                -vmCred $vmCred 
        } catch {
            Write-TraceLog "Test-TenantVirtualMachine: Failed to connect to VM $vmName, retrying... (retry: $retryCount)"
            Write-TraceLog "Test-TenantVirtualMachine: Error : $_"
            $result = $false
        }

        if($result -eq $true) {
            return $true
        }
        $retryCount += 1
        Start-Sleep -Seconds 15

    } while($retryCount -lt $maxRetryCount)

    if($retryCount -ge $maxRetryCount) {
        throw "Failed to connect to VM $vmName"
    }
    Write-TraceLog "Test-TenantVirtualMachine: VM $vmName is available"
}

function LogTrafficInfo {
    param(
        [TrafficEndpoint] $listenEndpoint,
        [TrafficEndpoint] $sendEndpoint,
        [TrafficPattern] $trafficPattern
    )
    Write-TraceLog  "`t listenEndpoint: $($listenEndpoint.ToString())"
    Write-TraceLog  "`t sendEndpoint: $($sendEndpoint.ToString())"
    Write-TraceLog  "`t traffic: $($trafficPattern.ToString())"
}
# SIG # Begin signature block
# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCntbZiDgAdzI9u
# eRrDzVey8uZXTv16A4l1NCdpSDF7gaCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIOzmdgviIAGlv/YEMOtK0N2C
# Uxl+cPB7NFqWNdWjIcPfMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAQh/vgbLeBSskv4zubC9PLO9+4VcwyG12qaIyfFxsEyZMVDsSg83pgq2I
# 9iCq2LfNuDmd+VzqzFYvaljx2/g9FDXMct9E1ao8uyUJzU9AzUTgYeERYJMWyesJ
# bfwqZQQcqbmXxsamGqMjfM1UHXnv26KcqrzFRU964eB6QRiQxuxtchv+0pBrkJ/g
# MEc2uXi8jSPlRNCClhARiw5XH1CgNy4NsJlHwJ4EG5xHo6LSHibMzwNHbUQ5Ovji
# LBg6FbZ/1lPcT09qTegbxC46cmAbkni8DIaZKOvS9rmmdwuCmd2ZTKf8f6YRiUqw
# nc2ud9WODCTm9IA3i4mHG1o3WaM5LKGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC
# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCzPWDdeYzDfYZmmCx6lhX409nhdKfCmzfBuBWQP8V7mwIGZmsT2369
# GBMyMDI0MDYyNDIyMjMyNy43MDlaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHqMIIHIDCCBQigAwIBAgITMwAAAe3hX8vV96VdcwABAAAB7TANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1
# NDFaFw0yNTAzMDUxODQ1NDFaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCoMMJskrrqapycLxPC1H7zD7g88NpbEaQ6SjcTIRbz
# CVyYQNsz8TaL1pqFTEAPL1X7ojL4/EaEW+UjNqZs/ayMyW4YIpFPZP2x4FBMVCdd
# seF2i+aMMjDHi0LcTQZxM2s3mFMrCZAWSfLYXYDIimFBz8j0oLWGy3VgLmBTKM4x
# Lqv7DZUz8B2SoAmbEtp62ngSl0hOoN73SFwE+Y24SvGQMWhykpG+vXDwcpWvwDe+
# TgnrLR7ATRFXN5JS26dm2yy6SYFMRYnME3dMHCQ/UQIQQNC8nLmIvdKkAoWEMXtJ
# sGEo3QrM2S2SBv4PpHRzRukzTtP+UAceGxM9JyrwUQP5OCEmW6YchEyRDSwP4hU9
# f7B0Ayh14Pw9vJo7jewNjeMPIkmneyLSi0ruv2ox/xRGtcJ9yBNC5BaRktjz7stP
# aojR+PDA2fuBtCo8xKlkt53mUb7AY+CZHHqhLm76pdMF6BHv2TvwlVBeQRN22Xja
# VVRwCgjgJnNewt7PejcrpUn0qHLgLq+1BN1DzYukWkTr7wT0zl0iXr+NtqUkWSOn
# WRfe8N21tB6uv3VkW8nFdChtbbZZz24peLtJEZuNrN8Xf9PTPMzZXDJBI1EciR/9
# 1QcGoZFmVbFVb2rUIAs01+ZkewvbhmGVDefX9oZG4/K4gGUsTvTW+r1JZMxUT2Mw
# qQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFM4b8Oz33hAqBEfKlAZf0NKh4CIZMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCd1gK2Rd+eGL0eHi+iE6/qDY8sbbsO4ema
# ncp6KPN+xq5ZAatiBR4jmRRhm+9Vik0Fo0DLWi/N28bFI7dXYw09p3vCipbjy4Eo
# ifm0Nud7/4U30i9+7RvW7XOQ3rx37+U7vq9lk6yYpGCNp0jlJ188/CuRPgqJnfq5
# EdeafH2AoG46hKWTeB7DuXasGt6spJOenGedSre34MWZqeTIQ0raOItZnFuGDy4+
# xoD1qRz2QW+u2gCHaG8AQjhYUM4uTi9t6kttj6c7Xamr2zrWuceDhz7sKLttLTJ7
# ws5YrA2I8cTlbMAf2KW0GVjKbYGd+LZGduEK7/7fs4GUkMqc51FsNdG1n+zgc7zH
# u2oGGeCBg4s8ZR0ZFyx7jsgm9sSFCKQ5CsbAvlr/60Ndk5TeMR8Js2kNUicu2CqZ
# 03833TsvTgk7iD1KLgfS16HEvjN6m4VKJKgjJ7OJJzabtS4JQgUnJrIZfyosk4D1
# 8rZni9pUwN03WgTmd10WTwiZOu4g8Un6iKcPMY/iFqTu4ntkzFUxBBpbFG6k1CIN
# ZmoirEWmCtG3lyZ2IddmjtIefTkIvGWb4Jxzz7l2m/E2kGOixDJHsahZVmwsoNvh
# y5ku/inU++dXHzw+hlvqTSFT89rIFVhcmsWPDJPNRSSpMhoJ33V2Za/lkKcbkUM0
# SbQgS9qsdzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN
# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjg5MDAtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDu
# HayKTCaYsYxJh+oWTx6uVPFw+aCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6iQR9TAiGA8yMDI0MDYyNDE1NDAz
# N1oYDzIwMjQwNjI1MTU0MDM3WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDqJBH1
# AgEAMAcCAQACAgdZMAcCAQACAhUQMAoCBQDqJWN1AgEAMDYGCisGAQQBhFkKBAIx
# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI
# hvcNAQELBQADggEBAFL/BunoNvo48Vg9QW3DBEhtdxz6yK0XRgO+oJYZsPikBqsz
# /OLtOUF4m0ovcj/Hv5q+hHdIrwHdlHcPHsne1Qu11oUabQg+I+GGaxkZ6zYgN9iA
# eN9CJ379vzLE7iNUPNWeeWbhMfIUQjgC68E9fH/OZIlOFfmVFMK2rS4OYIAQRiP8
# On7DKQMaqYB4FqlFQFv9DFTV33Z3+nS3KdJKqI+RopPZi1ZciUIKZE3XsTXQVEgo
# MsQD/c9lYU23AsFHDP6uUuIA5GCH5W3LMQFL+AeR3IJb3p6Ar4SG7msZ9DuH6at+
# fWbjIxwaeCRPQ5KCTH38/rl1F2S7BJ1vqAK7D5ExggQNMIIECQIBATCBkzB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAe3hX8vV96VdcwABAAAB7TAN
# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G
# CSqGSIb3DQEJBDEiBCDFxNYv6K8xxnEHBPlMSPmTLINMRp6c5IPKYCMlcLxNzTCB
# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EII0uDWg0CFseKxK3A16l1wrIwrsS
# DrXZ6xSf0F4xbMo5MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAHt4V/L1felXXMAAQAAAe0wIgQgkyoY0Em5So9zHYj/7Mi8fh9EIkoW
# l9y9pT7Gkqg0ZWQwDQYJKoZIhvcNAQELBQAEggIAGkqgu5dqitbnUC34h/eWnyoM
# nAQzZnCfymSmWajTzk1MwD/RKONELSyftD1J8/wY6kXTGO/l3pCTzkeGbwvD86mD
# iPxicvRrxcIi/UwxJ8TR4ucWWywCdVY87fcfuQ4c2OuaMpo6nOjNrW6n57f9gXGR
# purqCF5xNNUwIbO0NcxVlEXv1R30MQcp+qPfguys3i7Y/yZJr16aYlGm868fKem4
# RUjkqjuL0PUWcCE15M/vNzHbhLusR3iHiv5AH1Nx0jDUPcSkntFSM579Q4cMyVyd
# 7aHZ5u4yWUowofMKeKUk+CBmR+3CDRrlhf2Z7eyu13FPerZgtUvZmZqOp5cxWEqV
# 2pfdvZm3YKPgwTZ4oSiZqiJuP0/wealK/8LAXue41NZJCMfrvnb03IE26VDaKqnd
# rhMSiiGWvrqSDAhihW37rsq9pvexT9opMV38FuEQq/blocA4y5njV8YzIZHl/YYm
# Rn+7H4SXRyJgqQVU8wqkbYh+EqfN+Yx8FYL2Hnl5YPw1kDJ4WLfW31NtdfQs8Dtl
# Jcu8Fkzpp/MKa0QGAFAALEDJDKiNpP+eS39e8d16bmNzWcX0gxs7z2tKd0CQxWAv
# K2UcO1Yr2ZA9JYt4qzJuHJ4OBUJ25BuCHjMSiffS/lj5aBQrGOA8HTVnej8R/sSQ
# LXt9EjVZEjV8+WkGIIk=
# SIG # End signature block