<commit_before>/*
 * This file is part of Blokada.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * Copyright © 2021 Blocka AB. All rights reserved.
 *
 * @author <NAME> (<EMAIL>)
 */

package engine

import model.BlokadaException
import model.Dns
import model.Gateway
import model.PrivateKey
import service.ConnectivityService
import service.DozeService
import utils.Logger
import java.net.DatagramSocket

object PacketLoopService {

    private val log = Logger("PacketLoop")
    private val connectivity = ConnectivityService
    private val doze = DozeService
    private val screenOn = ScreenOnService

    var onCreateSocket = {
        log.e("Created unprotected socket for the packet loop")
        DatagramSocket()
    }

    var onStoppedUnexpectedly = {
        log.e("Thread stopped unexpectedly, but nobody listening")
    }

    private var loop: Pair<PacketLoopConfig, Thread?>? = null
        @Synchronized get
        @Synchronized set

    init {
        // When connectivity is back restart the loop for faster recovery.
        connectivity.onConnectivityChanged = { isConected ->
            if (isConected) maybeRestartLoop()
        }

        // Listen to device wake events and restart the loop if running.
        // This is an attempt to improve the conn issues on some devices after device wake
        // caused by the loop not doing the handshake until it times out.
        screenOn.onScreenOn = {
            maybeRestartLoop()
        }
    }

    private fun maybeRestartLoop() {
        loop?.let {
            val (config, thread) = it
            if (thread == null) {
                log.w("Connectivity back, recreating packet loop")
                loop = createLoop(config)
                startSupportingServices(config)
            } else {
                log.w("Connectivity back, loop was running, restarting it")
                stopUnexpectedly()
            }
        }
    }

    suspend fun startLibreMode(useDoh: Boolean, dns: Dns, tunnelConfig: SystemTunnelConfig) {
        log.v("Requested to start packet loop (libre mode)")
        if (loop != null) {
            log.w("Packet loop thread already running, stopping")
            stop()
        }

        val cfg = PacketLoopConfig(false, dns, useDoh, tunnelConfig)
        loop = createLoop(cfg)
        startSupportingServices(cfg)
    }

    suspend fun startPlusMode(useDoh: Boolean, dns: Dns, tunnelConfig: SystemTunnelConfig,
                              privateKey: PrivateKey, gateway: Gateway) {
        log.v("Requested to start packet loop for gateway: ${gateway.niceName()}")
        if (loop != null) {
            log.w("Packet loop thread already running, stopping")
            stop()
        }

        val cfg = PacketLoopConfig(true, dns, useDoh, tunnelConfig, privateKey, gateway)
        loop = createLoop(cfg)
        startSupportingServices(cfg)
    }

    suspend fun startSlimMode(useDoh: Boolean, dns: Dns, tunnelConfig: SystemTunnelConfig) {
        log.v("Requested to start packet loop (slim mode)")
        if (loop != null) {
            log.w("Packet loop thread already running, stopping")
            stop()
        }

        val cfg = PacketLoopConfig(false, dns, useDoh, tunnelConfig, useFiltering = false)
        loop = createLoop(cfg)
        startSupportingServices(cfg)
    }

    private fun createLoop(config: PacketLoopConfig): Pair<PacketLoopConfig, Thread> {
        val thread = when {
            config.usePlusMode && config.useDoh -> {
                val gw = config.requireGateway()
                PacketLoopForPlusDoh(
                    deviceIn = config.tunnelConfig.deviceIn,
                    deviceOut = config.tunnelConfig.deviceOut,
                    userBoringtunPrivateKey = config.requirePrivateKey(),
                    gatewayId = gw.public_key,
                    gatewayIp = gw.ipv4,
                    gatewayPort = gw.port,
                    createSocket = onCreateSocket,
                    stoppedUnexpectedly = this::stopUnexpectedly
                )
            }
            config.usePlusMode -> {
                val gw = config.requireGateway()
                PacketLoopForPlus(
                    deviceIn = config.tunnelConfig.deviceIn,
                    deviceOut = config.tunnelConfig.deviceOut,
                    userBoringtunPrivateKey = config.requirePrivateKey(),
                    gatewayId = gw.public_key,
                    gatewayIp = gw.ipv4,
                    gatewayPort = gw.port,
                    createSocket = onCreateSocket,
                    stoppedUnexpectedly = this::stopUnexpectedly
                )
            }
            else -> {
                PacketLoopForLibre(
                    deviceIn = config.tunnelConfig.deviceIn,
                    deviceOut = config.tunnelConfig.deviceOut,
                    createSocket = onCreateSocket,
                    stoppedUnexpectedly = this::stopUnexpectedly,
                    filter = config.useFiltering
                )
            }
        }

        thread.start()
        return config to thread
    }

    private fun startSupportingServices(config: PacketLoopConfig) {
        MetricsService.startMetrics(
            onNoConnectivity = this::stopUnexpectedly
        )
    }

    private fun stopUnexpectedly() {
        loop?.let {
            log.w("Packet loop stopped unexpectedly")
            val (config, thread) = it
            if (thread != null) {
                thread.interrupt()
                if (connectivity.isDeviceInOfflineMode()) {
                    log.w("Device is offline, not bringing packet loop back for now")
                    loop = config to null
                } else {
                    log.w("Device is online, bringing packet loop back")
                    loop = createLoop(config)
                    startSupportingServices(config)
                }
            }
        }
    }

    suspend fun stop() {
        log.v("Requested to stop packet loop")
        loop?.let {
            loop = null
            val (_, thread) = it
            thread?.interrupt()
            MetricsService.stopMetrics()
//            log.w("Waiting after stopping packet loop as a workaround")
//            delay(4000)
        }
    }

    fun getStatus() = (loop?.second as? PacketLoopForPlusDoh)?.gatewayId ?:
                        (loop?.second as? PacketLoopForPlus)?.gatewayId

}<commit_msg>android: clean conissues state on unexpected loop stop<commit_after>/*
 * This file is part of Blokada.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * Copyright © 2021 Blocka AB. All rights reserved.
 *
 * @author <NAME> (<EMAIL>)
 */

package engine

import model.BlokadaException
import model.Dns
import model.Gateway
import model.PrivateKey
import service.ConnectivityService
import service.DozeService
import utils.Logger
import java.net.DatagramSocket

object PacketLoopService {

    private val log = Logger("PacketLoop")
    private val connectivity = ConnectivityService
    private val doze = DozeService
    private val screenOn = ScreenOnService

    var onCreateSocket = {
        log.e("Created unprotected socket for the packet loop")
        DatagramSocket()
    }

    var onStoppedUnexpectedly = {
        log.e("Thread stopped unexpectedly, but nobody listening")
    }

    private var loop: Pair<PacketLoopConfig, Thread?>? = null
        @Synchronized get
        @Synchronized set

    init {
        // When connectivity is back restart the loop for faster recovery.
        connectivity.onConnectivityChanged = { isConected ->
            if (isConected) maybeRestartLoop()
        }

        // Listen to device wake events and restart the loop if running.
        // This is an attempt to improve the conn issues on some devices after device wake
        // caused by the loop not doing the handshake until it times out.
        screenOn.onScreenOn = {
            maybeRestartLoop()
        }
    }

    private fun maybeRestartLoop() {
        loop?.let {
            val (config, thread) = it
            if (thread == null) {
                log.w("Connectivity back, recreating packet loop")
                loop = createLoop(config)
                startSupportingServices(config)
            } else {
                log.w("Connectivity back, loop was running, restarting it")
                stopUnexpectedly()
            }
        }
    }

    suspend fun startLibreMode(useDoh: Boolean, dns: Dns, tunnelConfig: SystemTunnelConfig) {
        log.v("Requested to start packet loop (libre mode)")
        if (loop != null) {
            log.w("Packet loop thread already running, stopping")
            stop()
        }

        val cfg = PacketLoopConfig(false, dns, useDoh, tunnelConfig)
        loop = createLoop(cfg)
        startSupportingServices(cfg)
    }

    suspend fun startPlusMode(useDoh: Boolean, dns: Dns, tunnelConfig: SystemTunnelConfig,
                              privateKey: PrivateKey, gateway: Gateway) {
        log.v("Requested to start packet loop for gateway: ${gateway.niceName()}")
        if (loop != null) {
            log.w("Packet loop thread already running, stopping")
            stop()
        }

        val cfg = PacketLoopConfig(true, dns, useDoh, tunnelConfig, privateKey, gateway)
        loop = createLoop(cfg)
        startSupportingServices(cfg)
    }

    suspend fun startSlimMode(useDoh: Boolean, dns: Dns, tunnelConfig: SystemTunnelConfig) {
        log.v("Requested to start packet loop (slim mode)")
        if (loop != null) {
            log.w("Packet loop thread already running, stopping")
            stop()
        }

        val cfg = PacketLoopConfig(false, dns, useDoh, tunnelConfig, useFiltering = false)
        loop = createLoop(cfg)
        startSupportingServices(cfg)
    }

    private fun createLoop(config: PacketLoopConfig): Pair<PacketLoopConfig, Thread> {
        val thread = when {
            config.usePlusMode && config.useDoh -> {
                val gw = config.requireGateway()
                PacketLoopForPlusDoh(
                    deviceIn = config.tunnelConfig.deviceIn,
                    deviceOut = config.tunnelConfig.deviceOut,
                    userBoringtunPrivateKey = config.requirePrivateKey(),
                    gatewayId = gw.public_key,
                    gatewayIp = gw.ipv4,
                    gatewayPort = gw.port,
                    createSocket = onCreateSocket,
                    stoppedUnexpectedly = this::stopUnexpectedly
                )
            }
            config.usePlusMode -> {
                val gw = config.requireGateway()
                PacketLoopForPlus(
                    deviceIn = config.tunnelConfig.deviceIn,
                    deviceOut = config.tunnelConfig.deviceOut,
                    userBoringtunPrivateKey = config.requirePrivateKey(),
                    gatewayId = gw.public_key,
                    gatewayIp = gw.ipv4,
                    gatewayPort = gw.port,
                    createSocket = onCreateSocket,
                    stoppedUnexpectedly = this::stopUnexpectedly
                )
            }
            else -> {
                PacketLoopForLibre(
                    deviceIn = config.tunnelConfig.deviceIn,
                    deviceOut = config.tunnelConfig.deviceOut,
                    createSocket = onCreateSocket,
                    stoppedUnexpectedly = this::stopUnexpectedly,
                    filter = config.useFiltering
                )
            }
        }

        thread.start()
        return config to thread
    }

    private fun startSupportingServices(config: PacketLoopConfig) {
        MetricsService.startMetrics(
            onNoConnectivity = this::stopUnexpectedly
        )
    }

    private fun stopUnexpectedly() {
        loop?.let {
            log.w("Packet loop stopped unexpectedly")
            val (config, thread) = it
            if (thread != null) {
                thread.interrupt()
                MetricsService.stopMetrics()
                if (connectivity.isDeviceInOfflineMode()) {
                    log.w("Device is offline, not bringing packet loop back for now")
                    loop = config to null
                } else {
                    log.w("Device is online, bringing packet loop back")
                    loop = createLoop(config)
                    startSupportingServices(config)
                }
            }
        }
    }

    suspend fun stop() {
        log.v("Requested to stop packet loop")
        loop?.let {
            loop = null
            val (_, thread) = it
            thread?.interrupt()
            MetricsService.stopMetrics()
//            log.w("Waiting after stopping packet loop as a workaround")
//            delay(4000)
        }
    }

    fun getStatus() = (loop?.second as? PacketLoopForPlusDoh)?.gatewayId ?:
                        (loop?.second as? PacketLoopForPlus)?.gatewayId

}
{'fraction_non_alphanumeric': 0.07977122215532811, 'fraction_numerical': 0.002107164358819988, 'mean_word_length': 1.7639351081530783, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 19, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3136924', 'n_tokens_mistral': 3490, 'n_tokens_neox': 3294, 'n_words': 934}