Skip to content

Commit fa5df9c

Browse files
committed
支持juicity协议
1 parent e707e88 commit fa5df9c

File tree

23 files changed

+554
-36
lines changed

23 files changed

+554
-36
lines changed

app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/6.json

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"formatVersion": 1,
33
"database": {
44
"version": 6,
5-
"identityHash": "3d3db9106a89d6f20ef3fde6e81dbaa9",
5+
"identityHash": "da0920cdc8e1b1a5183ca9bbdc43dd09",
66
"entities": [
77
{
88
"tableName": "proxy_groups",
@@ -80,7 +80,7 @@
8080
},
8181
{
8282
"tableName": "proxy_entities",
83-
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `mieruBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `anyTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)",
83+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `mieruBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `juicityBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `anyTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)",
8484
"fields": [
8585
{
8686
"fieldPath": "id",
@@ -202,6 +202,12 @@
202202
"affinity": "BLOB",
203203
"notNull": false
204204
},
205+
{
206+
"fieldPath": "juicityBean",
207+
"columnName": "juicityBean",
208+
"affinity": "BLOB",
209+
"notNull": false
210+
},
205211
{
206212
"fieldPath": "sshBean",
207213
"columnName": "sshBean",
@@ -266,7 +272,7 @@
266272
},
267273
{
268274
"tableName": "rules",
269-
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `config` TEXT NOT NULL DEFAULT '', `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)",
275+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `config` TEXT NOT NULL DEFAULT '', `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `ruleset` TEXT NOT NULL DEFAULT '', `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)",
270276
"fields": [
271277
{
272278
"fieldPath": "id",
@@ -341,6 +347,13 @@
341347
"affinity": "TEXT",
342348
"notNull": true
343349
},
350+
{
351+
"fieldPath": "ruleset",
352+
"columnName": "ruleset",
353+
"affinity": "TEXT",
354+
"notNull": true,
355+
"defaultValue": "''"
356+
},
344357
{
345358
"fieldPath": "outbound",
346359
"columnName": "outbound",
@@ -367,7 +380,7 @@
367380
"views": [],
368381
"setupQueries": [
369382
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
370-
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3d3db9106a89d6f20ef3fde6e81dbaa9')"
383+
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'da0920cdc8e1b1a5183ca9bbdc43dd09')"
371384
]
372385
}
373-
}
386+
}

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@
182182
<activity
183183
android:name="io.nekohasekai.sagernet.ui.profile.TuicSettingsActivity"
184184
android:configChanges="uiMode" />
185+
<activity
186+
android:name="io.nekohasekai.sagernet.ui.profile.JuicitySettingsActivity"
187+
android:configChanges="uiMode" />
185188
<activity
186189
android:name="io.nekohasekai.sagernet.ui.profile.ChainSettingsActivity"
187190
android:configChanges="uiMode" />

app/src/main/java/io/nekohasekai/sagernet/Constants.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ object Key {
130130
const val SERVER_DISABLE_SNI = "serverDisableSNI"
131131
const val SERVER_REDUCE_RTT = "serverReduceRTT"
132132

133+
const val SERVER_USER_ID = "serverUserId"
134+
const val SERVER_PINNED_CERT_CHAIN_SHA256 = "serverPinnedCertChainSha256"
135+
133136
const val ROUTE_NAME = "routeName"
134137
const val ROUTE_DOMAIN = "routeDomain"
135138
const val ROUTE_IP = "routeIP"

app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ object DataStore : OnPreferenceDataStoreChangeListener {
230230
var serverDisableSNI by profileCacheStore.boolean(Key.SERVER_DISABLE_SNI)
231231
var serverReduceRTT by profileCacheStore.boolean(Key.SERVER_REDUCE_RTT)
232232

233+
var serverUserId by profileCacheStore.string(Key.SERVER_USER_ID)
234+
var serverPinnedCertChainSha256 by profileCacheStore.string(Key.SERVER_PINNED_CERT_CHAIN_SHA256)
235+
233236
var routeName by profileCacheStore.string(Key.ROUTE_NAME)
234237
var routeDomain by profileCacheStore.string(Key.ROUTE_DOMAIN)
235238
var routeIP by profileCacheStore.string(Key.ROUTE_IP)

app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import io.nekohasekai.sagernet.fmt.trojan_go.buildTrojanGoConfig
2727
import io.nekohasekai.sagernet.fmt.trojan_go.toUri
2828
import io.nekohasekai.sagernet.fmt.tuic.TuicBean
2929
import io.nekohasekai.sagernet.fmt.tuic.toUri
30+
import io.nekohasekai.sagernet.fmt.juicity.JuicityBean
31+
import io.nekohasekai.sagernet.fmt.juicity.toUri
3032
import io.nekohasekai.sagernet.fmt.v2ray.*
3133
import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
3234
import io.nekohasekai.sagernet.ktx.app
@@ -64,6 +66,7 @@ data class ProxyEntity(
6466
var naiveBean: NaiveBean? = null,
6567
var hysteriaBean: HysteriaBean? = null,
6668
var tuicBean: TuicBean? = null,
69+
var juicityBean: JuicityBean? = null,
6770
var sshBean: SSHBean? = null,
6871
var wgBean: WireGuardBean? = null,
6972
var shadowTLSBean: ShadowTLSBean? = null,
@@ -90,6 +93,7 @@ data class ProxyEntity(
9093
const val TYPE_TUIC = 20
9194
const val TYPE_MIERU = 21
9295
const val TYPE_ANYTLS = 22
96+
const val TYPE_JUICITY = 23
9397

9498
const val TYPE_CONFIG = 998
9599
const val TYPE_NEKO = 999
@@ -172,6 +176,7 @@ data class ProxyEntity(
172176
TYPE_SSH -> sshBean = KryoConverters.sshDeserialize(byteArray)
173177
TYPE_WG -> wgBean = KryoConverters.wireguardDeserialize(byteArray)
174178
TYPE_TUIC -> tuicBean = KryoConverters.tuicDeserialize(byteArray)
179+
TYPE_JUICITY -> juicityBean = KryoConverters.juicityDeserialize(byteArray)
175180
TYPE_SHADOWTLS -> shadowTLSBean = KryoConverters.shadowTLSDeserialize(byteArray)
176181
TYPE_ANYTLS -> anyTLSBean = KryoConverters.anyTLSDeserialize(byteArray)
177182
TYPE_CHAIN -> chainBean = KryoConverters.chainDeserialize(byteArray)
@@ -193,6 +198,7 @@ data class ProxyEntity(
193198
TYPE_SSH -> "SSH"
194199
TYPE_WG -> "WireGuard"
195200
TYPE_TUIC -> "TUIC"
201+
TYPE_JUICITY -> "Juicity"
196202
TYPE_SHADOWTLS -> "ShadowTLS"
197203
TYPE_ANYTLS -> "AnyTLS"
198204
TYPE_CHAIN -> chainName
@@ -218,6 +224,7 @@ data class ProxyEntity(
218224
TYPE_SSH -> sshBean
219225
TYPE_WG -> wgBean
220226
TYPE_TUIC -> tuicBean
227+
TYPE_JUICITY -> juicityBean
221228
TYPE_SHADOWTLS -> shadowTLSBean
222229
TYPE_ANYTLS -> anyTLSBean
223230
TYPE_CHAIN -> chainBean
@@ -256,6 +263,7 @@ data class ProxyEntity(
256263
is NaiveBean -> toUri()
257264
is HysteriaBean -> toUri()
258265
is TuicBean -> toUri()
266+
is JuicityBean -> toUri()
259267
is AnyTLSBean -> toUri()
260268
is NekoBean -> ""
261269
else -> toUniversalLink()
@@ -355,6 +363,7 @@ data class ProxyEntity(
355363
sshBean = null
356364
wgBean = null
357365
tuicBean = null
366+
juicityBean = null
358367
shadowTLSBean = null
359368
anyTLSBean = null
360369
chainBean = null
@@ -422,6 +431,11 @@ data class ProxyEntity(
422431
tuicBean = bean
423432
}
424433

434+
is JuicityBean -> {
435+
type = TYPE_JUICITY
436+
juicityBean = bean
437+
}
438+
425439
is ShadowTLSBean -> {
426440
type = TYPE_SHADOWTLS
427441
shadowTLSBean = bean
@@ -467,6 +481,7 @@ data class ProxyEntity(
467481
TYPE_SSH -> SSHSettingsActivity::class.java
468482
TYPE_WG -> WireGuardSettingsActivity::class.java
469483
TYPE_TUIC -> TuicSettingsActivity::class.java
484+
TYPE_JUICITY -> JuicitySettingsActivity::class.java
470485
TYPE_SHADOWTLS -> ShadowTLSSettingsActivity::class.java
471486
TYPE_ANYTLS -> AnyTLSSettingsActivity::class.java
472487
TYPE_CHAIN -> ChainSettingsActivity::class.java

app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import androidx.room.Database
55
import androidx.room.Room
66
import androidx.room.RoomDatabase
77
import androidx.room.TypeConverters
8-
import androidx.room.migration.Migration
9-
import androidx.sqlite.db.SupportSQLiteDatabase
108
import dev.matrix.roomigrant.GenerateRoomMigrations
119
import io.nekohasekai.sagernet.Key
1210
import io.nekohasekai.sagernet.SagerNet
@@ -18,7 +16,7 @@ import kotlinx.coroutines.launch
1816

1917
@Database(
2018
entities = [ProxyGroup::class, ProxyEntity::class, RuleEntity::class],
21-
version = 7,
19+
version = 6,
2220
autoMigrations = [
2321
AutoMigration(from = 3, to = 4),
2422
AutoMigration(from = 4, to = 5),
@@ -30,37 +28,12 @@ import kotlinx.coroutines.launch
3028
abstract class SagerDatabase : RoomDatabase() {
3129

3230
companion object {
33-
private val ALL_MIGRATIONS = arrayOf(
34-
object : Migration(6, 7) {
35-
override fun migrate(db: SupportSQLiteDatabase) {
36-
val cursor = db.query("PRAGMA table_info(rules)")
37-
var hasConfig = false
38-
var hasRuleset = false
39-
while (cursor.moveToNext()) {
40-
val columnName = cursor.getString(cursor.getColumnIndex("name"))
41-
when (columnName) {
42-
"config" -> hasConfig = true
43-
"ruleset" -> hasRuleset = true
44-
}
45-
}
46-
cursor.close()
47-
48-
if (!hasConfig) {
49-
db.execSQL("ALTER TABLE `rules` ADD COLUMN `config` TEXT NOT NULL DEFAULT ''")
50-
}
51-
if (!hasRuleset) {
52-
db.execSQL("ALTER TABLE `rules` ADD COLUMN `ruleset` TEXT NOT NULL DEFAULT ''")
53-
}
54-
}
55-
}
56-
)
57-
5831
@OptIn(DelicateCoroutinesApi::class)
5932
@Suppress("EXPERIMENTAL_API_USAGE")
6033
val instance by lazy {
6134
SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs()
6235
Room.databaseBuilder(SagerNet.application, SagerDatabase::class.java, Key.DB_PROFILE)
63-
.addMigrations(*ALL_MIGRATIONS)
36+
// .addMigrations(*SagerDatabase_Migrations.build())
6437
.setJournalMode(JournalMode.TRUNCATE)
6538
.allowMainThreadQueries()
6639
.enableMultiInstanceInvalidation()

app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import io.nekohasekai.sagernet.fmt.ssh.SSHBean
1919
import io.nekohasekai.sagernet.fmt.ssh.buildSingBoxOutboundSSHBean
2020
import io.nekohasekai.sagernet.fmt.tuic.TuicBean
2121
import io.nekohasekai.sagernet.fmt.tuic.buildSingBoxOutboundTuicBean
22+
import io.nekohasekai.sagernet.fmt.juicity.JuicityBean
23+
import io.nekohasekai.sagernet.fmt.juicity.buildSingBoxOutboundJuicityBean
2224
import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean
2325
import io.nekohasekai.sagernet.fmt.v2ray.buildSingBoxOutboundStandardV2RayBean
2426
import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
@@ -357,7 +359,7 @@ fun buildConfig(
357359
// internal outbound
358360

359361
currentOutbound = when (bean) {
360-
is ConfigBean -> CustomSingBoxOption(bean.config)
362+
is ConfigBean -> CustomSingBoxOption(bean.config) as SingBoxOption
361363

362364
is ShadowTLSBean -> // before StandardV2RayBean
363365
buildSingBoxOutboundShadowTLSBean(bean)
@@ -371,6 +373,9 @@ fun buildConfig(
371373
is TuicBean ->
372374
buildSingBoxOutboundTuicBean(bean)
373375

376+
is JuicityBean ->
377+
buildSingBoxOutboundJuicityBean(bean)
378+
374379
is SOCKSBean ->
375380
buildSingBoxOutboundSocksBean(bean)
376381

app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.nekohasekai.sagernet.fmt.trojan.TrojanBean;
2424
import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean;
2525
import io.nekohasekai.sagernet.fmt.tuic.TuicBean;
26+
import io.nekohasekai.sagernet.fmt.juicity.JuicityBean;
2627
import io.nekohasekai.sagernet.fmt.v2ray.VMessBean;
2728
import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean;
2829
import io.nekohasekai.sagernet.ktx.KryosKt;
@@ -137,6 +138,12 @@ public static TuicBean tuicDeserialize(byte[] bytes) {
137138
return deserialize(new TuicBean(), bytes);
138139
}
139140

141+
@TypeConverter
142+
public static JuicityBean juicityDeserialize(byte[] bytes) {
143+
if (JavaUtil.isEmpty(bytes)) return null;
144+
return deserialize(new JuicityBean(), bytes);
145+
}
146+
140147
@TypeConverter
141148
public static ShadowTLSBean shadowTLSDeserialize(byte[] bytes) {
142149
if (JavaUtil.isEmpty(bytes)) return null;

app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ object TypeMap : HashMap<String, Int>() {
1616
this["ssh"] = ProxyEntity.TYPE_SSH
1717
this["wg"] = ProxyEntity.TYPE_WG
1818
this["tuic"] = ProxyEntity.TYPE_TUIC
19+
this["juicity"] = ProxyEntity.TYPE_JUICITY
1920
this["anytls"] = ProxyEntity.TYPE_ANYTLS
2021
this["neko"] = ProxyEntity.TYPE_NEKO
2122
this["config"] = ProxyEntity.TYPE_CONFIG
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package io.nekohasekai.sagernet.fmt.juicity;
2+
3+
import androidx.annotation.NonNull;
4+
5+
import com.esotericsoftware.kryo.io.ByteBufferInput;
6+
import com.esotericsoftware.kryo.io.ByteBufferOutput;
7+
8+
import org.jetbrains.annotations.NotNull;
9+
10+
import io.nekohasekai.sagernet.fmt.AbstractBean;
11+
import io.nekohasekai.sagernet.fmt.KryoConverters;
12+
13+
public class JuicityBean extends AbstractBean {
14+
15+
public String uuid;
16+
public String password;
17+
public String sni;
18+
public String pinnedCertchainSha256;
19+
public Boolean allowInsecure;
20+
21+
@Override
22+
public void initializeDefaultValues() {
23+
super.initializeDefaultValues();
24+
if (uuid == null) uuid = "";
25+
if (password == null) password = "";
26+
if (sni == null) sni = "";
27+
if (pinnedCertchainSha256 == null) pinnedCertchainSha256 = "";
28+
if (allowInsecure == null) allowInsecure = false;
29+
}
30+
31+
@Override
32+
public void serialize(ByteBufferOutput output) {
33+
output.writeInt(1);
34+
super.serialize(output);
35+
output.writeString(uuid);
36+
output.writeString(password);
37+
output.writeString(sni);
38+
output.writeString(pinnedCertchainSha256);
39+
output.writeBoolean(allowInsecure);
40+
}
41+
42+
@Override
43+
public void deserialize(ByteBufferInput input) {
44+
int version = input.readInt();
45+
super.deserialize(input);
46+
uuid = input.readString();
47+
password = input.readString();
48+
sni = input.readString();
49+
pinnedCertchainSha256 = input.readString();
50+
allowInsecure = input.readBoolean();
51+
}
52+
53+
@Override
54+
public boolean canTCPing() {
55+
return false;
56+
}
57+
58+
@NotNull
59+
@Override
60+
public JuicityBean clone() {
61+
return KryoConverters.deserialize(new JuicityBean(), KryoConverters.serialize(this));
62+
}
63+
64+
public static final Creator<JuicityBean> CREATOR = new CREATOR<JuicityBean>() {
65+
@NonNull
66+
@Override
67+
public JuicityBean newInstance() {
68+
return new JuicityBean();
69+
}
70+
71+
@Override
72+
public JuicityBean[] newArray(int size) {
73+
return new JuicityBean[size];
74+
}
75+
};
76+
}

0 commit comments

Comments
 (0)