Commit 668740fc authored by Mygod's avatar Mygod

Add unit tests for AclMatcher

Also fixes a few bugs.
Fixes #2411.
parent 6f216b6e
/*******************************************************************************
* *
* Copyright (C) 2020 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2020 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.acl
import androidx.test.core.app.ApplicationProvider
import com.github.shadowsocks.Core
import com.github.shadowsocks.utils.parseNumericAddress
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.BeforeClass
import org.junit.Test
class AclMatcherTest {
companion object {
@BeforeClass
@JvmStatic
fun setup() {
Core.app = ApplicationProvider.getApplicationContext()
}
}
@Test
fun emptyFile() {
runBlocking {
AclMatcher().apply {
init("".reader())
Assert.assertFalse(shouldBypassIpv4(ByteArray(4)))
Assert.assertFalse(shouldBypassIpv6(ByteArray(16)))
Assert.assertNull(shouldBypass("www.google.com"))
}
}
}
@Test
fun basic() {
runBlocking {
AclMatcher().apply {
init(AclTest.INPUT1.reader())
Assert.assertFalse(shouldBypassIpv4("0.1.2.3".parseNumericAddress()!!.address))
Assert.assertTrue(shouldBypassIpv4("1.0.1.2".parseNumericAddress()!!.address))
Assert.assertFalse(shouldBypassIpv4("1.0.3.2".parseNumericAddress()!!.address))
Assert.assertFalse(shouldBypassIpv6("::".parseNumericAddress()!!.address))
Assert.assertTrue(shouldBypassIpv6("2020::2020".parseNumericAddress()!!.address))
Assert.assertFalse(shouldBypassIpv6("fe80::2020".parseNumericAddress()!!.address))
Assert.assertTrue(shouldBypass("4tern.com") == true)
Assert.assertTrue(shouldBypass("www.4tern.com") == true)
Assert.assertNull(shouldBypass("www.google.com"))
}
}
}
}
...@@ -25,9 +25,10 @@ import org.junit.Test ...@@ -25,9 +25,10 @@ import org.junit.Test
class AclTest { class AclTest {
companion object { companion object {
private const val INPUT1 = """[proxy_all] const val INPUT1 = """[proxy_all]
[bypass_list] [bypass_list]
1.0.1.0/24 1.0.1.0/24
2000::/8
(?:^|\.)4tern\.com${'$'} (?:^|\.)4tern\.com${'$'}
""" """
} }
......
...@@ -30,6 +30,7 @@ import android.net.ConnectivityManager ...@@ -30,6 +30,7 @@ import android.net.ConnectivityManager
import android.os.Build import android.os.Build
import android.os.UserManager import android.os.UserManager
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.work.Configuration import androidx.work.Configuration
...@@ -59,6 +60,7 @@ object Core { ...@@ -59,6 +60,7 @@ object Core {
const val TAG = "Core" const val TAG = "Core"
lateinit var app: Application lateinit var app: Application
@VisibleForTesting set
lateinit var configureIntent: (Context) -> PendingIntent lateinit var configureIntent: (Context) -> PendingIntent
val activity by lazy { app.getSystemService<ActivityManager>()!! } val activity by lazy { app.getSystemService<ActivityManager>()!! }
val connectivity by lazy { app.getSystemService<ConnectivityManager>()!! } val connectivity by lazy { app.getSystemService<ConnectivityManager>()!! }
......
...@@ -23,6 +23,7 @@ package com.github.shadowsocks.acl ...@@ -23,6 +23,7 @@ package com.github.shadowsocks.acl
import android.util.Log import android.util.Log
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
import com.github.shadowsocks.net.Subnet import com.github.shadowsocks.net.Subnet
import java.io.Reader
import java.net.Inet4Address import java.net.Inet4Address
import java.net.Inet6Address import java.net.Inet6Address
import kotlin.system.measureNanoTime import kotlin.system.measureNanoTime
...@@ -56,7 +57,8 @@ class AclMatcher : AutoCloseable { ...@@ -56,7 +57,8 @@ class AclMatcher : AutoCloseable {
private var subnetsIpv6 = emptyList<Subnet.Immutable>() private var subnetsIpv6 = emptyList<Subnet.Immutable>()
private var bypass = false private var bypass = false
suspend fun init(id: String) { suspend fun init(id: String) = init(Acl.getFile(id).bufferedReader())
suspend fun init(reader: Reader) {
fun Sequence<Subnet>.dedup() = sequence { fun Sequence<Subnet>.dedup() = sequence {
val iterator = map { it.toImmutable() }.sortedWith(Subnet.Immutable).iterator() val iterator = map { it.toImmutable() }.sortedWith(Subnet.Immutable).iterator()
var current: Subnet.Immutable? = null var current: Subnet.Immutable? = null
...@@ -70,7 +72,7 @@ class AclMatcher : AutoCloseable { ...@@ -70,7 +72,7 @@ class AclMatcher : AutoCloseable {
check(handle == 0L) check(handle == 0L)
handle = init() handle = init()
val time = measureNanoTime { val time = measureNanoTime {
val (bypass, subnets) = Acl.parse(Acl.getFile(id).bufferedReader(), { val (bypass, subnets) = Acl.parse(reader, {
check(addBypassDomain(handle, it)) check(addBypassDomain(handle, it))
}, { }, {
check(addProxyDomain(handle, it)) check(addProxyDomain(handle, it))
......
...@@ -44,6 +44,18 @@ bool addDomain(JNIEnv *env, stringstream &domains, jstring regex) { ...@@ -44,6 +44,18 @@ bool addDomain(JNIEnv *env, stringstream &domains, jstring regex) {
return true; return true;
} }
const char *buildRE2(stringstream &domains, RE2 *&out, const RE2::Options &options) {
if (domains.rdbuf()->in_avail()) {
out = new RE2(domains.str(), options);
domains.clear();
if (!out->ok()) return out->error().c_str();
} else {
delete out;
out = nullptr;
}
return nullptr;
}
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter" #pragma clang diagnostic ignored "-Wunused-parameter"
extern "C" { extern "C" {
...@@ -75,16 +87,13 @@ Java_com_github_shadowsocks_acl_AclMatcher_build(JNIEnv *env, jclass clazz, jlon ...@@ -75,16 +87,13 @@ Java_com_github_shadowsocks_acl_AclMatcher_build(JNIEnv *env, jclass clazz, jlon
jlong memory_limit) { jlong memory_limit) {
if (!handle) return env->NewStringUTF("AclMatcher closed"); if (!handle) return env->NewStringUTF("AclMatcher closed");
auto matcher = reinterpret_cast<AclMatcher *>(handle); auto matcher = reinterpret_cast<AclMatcher *>(handle);
if (matcher->bypassDomains || matcher->proxyDomains) return env->NewStringUTF("already built");
RE2::Options options; RE2::Options options;
options.set_max_mem(memory_limit); options.set_max_mem(memory_limit);
options.set_never_capture(true); options.set_never_capture(true);
matcher->bypassDomains = new RE2(matcher->bypassDomainsBuilder.str(), options); const char *e = ::buildRE2(matcher->bypassDomainsBuilder, matcher->bypassDomains, options);
matcher->bypassDomainsBuilder.clear(); if (e) return env->NewStringUTF(e);
if (!matcher->bypassDomains->ok()) return env->NewStringUTF(matcher->bypassDomains->error().c_str()); e = ::buildRE2(matcher->proxyDomainsBuilder, matcher->proxyDomains, options);
matcher->proxyDomains = new RE2(matcher->proxyDomainsBuilder.str(), options); if (e) return env->NewStringUTF(e);
matcher->proxyDomainsBuilder.clear();
if (!matcher->proxyDomains->ok()) return env->NewStringUTF(matcher->proxyDomains->error().c_str());
return nullptr; return nullptr;
} }
...@@ -95,8 +104,8 @@ Java_com_github_shadowsocks_acl_AclMatcher_matchHost(JNIEnv *env, jclass clazz, ...@@ -95,8 +104,8 @@ Java_com_github_shadowsocks_acl_AclMatcher_matchHost(JNIEnv *env, jclass clazz,
auto matcher = reinterpret_cast<const AclMatcher *>(handle); auto matcher = reinterpret_cast<const AclMatcher *>(handle);
const char *hostChars = env->GetStringUTFChars(host, nullptr); const char *hostChars = env->GetStringUTFChars(host, nullptr);
jint result = 0; jint result = 0;
if (RE2::FullMatch(hostChars, *matcher->bypassDomains)) result = 1; if (matcher->bypassDomains && RE2::PartialMatch(hostChars, *matcher->bypassDomains)) result = 1;
else if (RE2::FullMatch(hostChars, *matcher->proxyDomains)) result = 2; else if (matcher->proxyDomains && RE2::PartialMatch(hostChars, *matcher->proxyDomains)) result = 2;
env->ReleaseStringUTFChars(host, hostChars); env->ReleaseStringUTFChars(host, hostChars);
return result; return result;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment