Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ jobs:

- name: Build project
run: ./gradlew assembleDebug
- name: Checks
run: ./gradlew tomlCheck ktlintCheck detektCheck checkSortDependencies projectHealth
- name: Run tests
run: ./gradlew test
- name: Build APK and AAB
Expand Down
6 changes: 6 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,15 @@ dependencies {
annotationProcessor(libs.androidlombock)
annotationProcessor(libs.androidx.room.compiler)

testImplementation(libs.assertj.core)
testImplementation(libs.junit)

androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.room.testing)
androidTestImplementation(libs.androidx.rules)
androidTestImplementation(libs.androidx.runner)
androidTestImplementation(libs.androidx.testing)
androidTestImplementation(libs.assertj.core)
}
java {
toolchain {
Expand Down
86 changes: 86 additions & 0 deletions app/src/androidTest/java/org/fptn/vpn/database/dao/SniDaoTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.fptn.vpn.database.dao;

import static org.assertj.core.api.Assertions.assertThat;

import android.content.Context;

import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.fptn.vpn.database.AppDatabase;
import org.fptn.vpn.database.entity.SniEntity;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Arrays;
import java.util.List;

@RunWith(AndroidJUnit4.class)
public class SniDaoTest {

@Rule
public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();

private AppDatabase db;
private SniDao sniDao;

@Before
public void createDb() {
Context context = ApplicationProvider.getApplicationContext();
db = Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build();
sniDao = db.sniDAO();
}

@After
public void closeDb() {
db.close();
}

@Test
public void insertAndGetAllSni() {
SniEntity sni1 = new SniEntity("sni1", false);
SniEntity sni2 = new SniEntity("sni2", false);
List<SniEntity> snis = Arrays.asList(sni1, sni2);

sniDao.insertAll(snis);

List<SniEntity> allSni = sniDao.getAll();

assertThat(allSni).hasSize(2);
assertThat(allSni.get(0).getSni()).isEqualTo("sni1");
assertThat(allSni.get(1).getSni()).isEqualTo("sni2");
}

@Test
public void insertDuplicateSni() {
SniEntity sni1 = new SniEntity("sni1", false);
SniEntity sni2 = new SniEntity("sni1", false); // Duplicate
List<SniEntity> snis = Arrays.asList(sni1, sni2);

sniDao.insertAll(snis);

List<SniEntity> allSni = sniDao.getAll();

assertThat(allSni).hasSize(1);
assertThat(allSni.get(0).getSni()).isEqualTo("sni1");
}

@Test
public void deleteAll() {
SniEntity sni1 = new SniEntity("sni1", false);
List<SniEntity> snis = List.of(sni1);

sniDao.insertAll(snis);
sniDao.deleteAll();

List<SniEntity> allSni = sniDao.getAll();

assertThat(allSni).isEmpty();
}

}
13 changes: 11 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
android:noHistory="true"
tools:ignore="LockedOrientationActivity">

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
tools:ignore="ForegroundServicesPolicy" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<uses-permission android:name="android.permission.INTERNET" />
Expand All @@ -13,7 +14,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />

<uses-permission
Expand Down Expand Up @@ -109,6 +111,13 @@
android:value="android.service.quicksettings.CATEGORY_CONNECTIVITY" />
</service>

<service
android:name="org.fptn.vpn.services.snichecker.SniCheckerService"
android:exported="false"
android:foregroundServiceType="systemExempted"
android:stopWithTask="false"
tools:ignore="ForegroundServicePermission"> <!--add this to turn off warning-->
</service>
</application>

</manifest>
6 changes: 5 additions & 1 deletion app/src/main/java/org/fptn/vpn/database/AppDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@

import org.fptn.vpn.database.dao.AppInfoDAO;
import org.fptn.vpn.database.dao.ServerDAO;
import org.fptn.vpn.database.dao.SniDao;
import org.fptn.vpn.database.entity.AppInfoEntity;
import org.fptn.vpn.database.entity.ServerEntity;
import org.fptn.vpn.database.entity.SniEntity;

@Database(entities = {ServerEntity.class, AppInfoEntity.class}, version = 1, exportSchema = false)
@Database(entities = {ServerEntity.class, AppInfoEntity.class, SniEntity.class}, version = 2, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {

public static final String FPTN_DATABASE = "FptnDatabase";
Expand All @@ -20,6 +22,8 @@ public abstract class AppDatabase extends RoomDatabase {

public abstract AppInfoDAO appInfoDAO();

public abstract SniDao sniDAO();

private static AppDatabase instance;

public static synchronized AppDatabase getInstance(Context context) {
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/org/fptn/vpn/database/dao/ServerDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public interface ServerDAO {
@Query("SELECT * FROM server_table WHERE censured = :censured")
List<ServerEntity> getServerList(boolean censured);

@Query("SELECT * FROM server_table WHERE censured = :censured")
ListenableFuture<List<ServerEntity>> getServerListAsync(boolean censured);

@Query("UPDATE server_table SET selected = CASE WHEN id = :id THEN 1 ELSE 0 END")
void setSelected(int id);

Expand All @@ -43,4 +46,6 @@ default void deleteAndInsert(List<ServerEntity> servers) {
insertAll(servers);
}

@Query("SELECT * FROM server_table WHERE id = :serverId")
ServerEntity getById(int serverId);
}
44 changes: 44 additions & 0 deletions app/src/main/java/org/fptn/vpn/database/dao/SniDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.fptn.vpn.database.dao;

import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;

import com.google.common.util.concurrent.ListenableFuture;

import org.fptn.vpn.database.entity.SniEntity;

import java.util.List;

@Dao
public interface SniDao {

@Query("SELECT * FROM sni_table")
List<SniEntity> getAll();

@Query("SELECT * FROM sni_table where checked = 0")
List<SniEntity> getAllUnchecked();

@Insert(onConflict = OnConflictStrategy.REPLACE)
ListenableFuture<Void> insertAll(List<SniEntity> sniList);

@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(SniEntity sni);

@Query("DELETE FROM sni_table")
void deleteAll();

@Query("SELECT COUNT(*) FROM sni_table")
int count();

@Query("SELECT COUNT(*) FROM sni_table where checked = 0")
int countUnchecked();

@Query("SELECT * FROM sni_table where checked = 0 limit :limit")
List<SniEntity> getUnchecked(int limit);

@Query("UPDATE sni_table SET checked = 0")
void resetAll();

}
24 changes: 24 additions & 0 deletions app/src/main/java/org/fptn/vpn/database/entity/SniEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.fptn.vpn.database.entity;

import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity(tableName = "sni_table")
public class SniEntity {

@PrimaryKey
@NonNull
private String sni;

private boolean checked = false;
}
4 changes: 3 additions & 1 deletion app/src/main/java/org/fptn/vpn/enums/ConnectionState.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Set;

public enum ConnectionState {
SEARCH_SNI,
DISCONNECTED,
CONNECTING,
CONNECTED,
Expand All @@ -11,7 +12,8 @@ public enum ConnectionState {
private final static Set<ConnectionState> ACTIVE_STATES = Set.of(
CONNECTING,
CONNECTED,
RECONNECTING
RECONNECTING,
SEARCH_SNI
);

public boolean isActiveState() {
Expand Down
36 changes: 36 additions & 0 deletions app/src/main/java/org/fptn/vpn/services/snichecker/SniChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.fptn.vpn.services.snichecker;

import android.util.Log;

import org.fptn.vpn.database.entity.ServerEntity;
import org.fptn.vpn.enums.BypassCensorshipMethod;

import java.util.Random;

public class SniChecker {
private final String TAG = getClass().getSimpleName();
private final ServerEntity selectedServer;
private final BypassCensorshipMethod bypassCensorshipMethod;

public final Random RANDOM = new Random();

public SniChecker(ServerEntity selectedServer, BypassCensorshipMethod bypassCensorshipMethod) {
this.selectedServer = selectedServer;
this.bypassCensorshipMethod = bypassCensorshipMethod;
}

public boolean checkSni(String sni) {
Log.d(TAG, "checkSni: " + sni);

// todo: replace with real check
try {
long sleepTime = RANDOM.nextInt(1000) + 100;
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}

return RANDOM.nextInt(1000) > 950;
}
}
Loading