arblant
FoxyVPN-MultiAcc / app / src / main / java / com / vauth / foxyvpn / vpn / upstream / UpstreamHealthTracker.kt
UpstreamHealthTracker.kt67 lines · 1.6 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
package com.vauth.foxyvpn.vpn.upstream

import java.io.InterruptedIOException
import java.net.SocketTimeoutException

class UpstreamHealthTracker {

    enum class Verdict {

        TARGET_FAILURE,

        SESSION_UNHEALTHY,

        SESSION_UNAUTHENTICATED,
    }

    private val timeoutTargets = LinkedHashSet<String>()

    @Synchronized
    fun observeSuccess() {
        timeoutTargets.clear()
    }

    @Synchronized
    fun reset() {
        timeoutTargets.clear()
    }

    @Synchronized
    fun observeFailure(target: String, cause: Throwable?): Verdict {

        if (invalidatesSession(cause)) {
            timeoutTargets.clear()
            return Verdict.SESSION_UNAUTHENTICATED
        }

        if (!isSilence(cause)) return Verdict.TARGET_FAILURE

        timeoutTargets.add(target)
        if (timeoutTargets.size < MAX_DISTINCT_TIMEOUT_TARGETS) return Verdict.TARGET_FAILURE

        timeoutTargets.clear()
        return Verdict.SESSION_UNHEALTHY
    }

    private fun isSilence(cause: Throwable?): Boolean = when (cause) {
        null -> true
        is UpstreamConnectTimeoutException -> true
        is SocketTimeoutException -> true
        is InterruptedIOException -> true
        else -> false
    }

    private fun invalidatesSession(cause: Throwable?): Boolean {
        val rejection = cause as? UpstreamConnectRejectedException ?: return false
        return rejection.statusCode in SESSION_FATAL_STATUS_CODES
    }

    companion object {

        const val MAX_DISTINCT_TIMEOUT_TARGETS = 10

        val SESSION_FATAL_STATUS_CODES = setOf(401, 403, 407)

        val TARGET_UNREACHABLE_STATUS_CODES = setOf(502, 503, 504)
    }
}