@@ -297,6 +308,18 @@
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
+
+
+
+
+
+
+
+
diff --git a/app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetServiceCallback.aidl b/app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetServiceCallback.aidl
index f0382acc1..cb41c2bd7 100644
--- a/app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetServiceCallback.aidl
+++ b/app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetServiceCallback.aidl
@@ -6,7 +6,7 @@ import io.nekohasekai.sagernet.aidl.TrafficData;
oneway interface ISagerNetServiceCallback {
void stateChanged(int state, String profileName, String msg);
void missingPlugin(String profileName, String pluginName);
- void routeAlert(int type, String routeName);
void cbSpeedUpdate(in SpeedDisplayData stats);
void cbTrafficUpdate(in TrafficData stats);
+ void cbSelectorUpdate(long id);
}
diff --git a/app/src/main/assets/analysis.txt b/app/src/main/assets/analysis.txt
deleted file mode 100644
index a4f7cc732..000000000
--- a/app/src/main/assets/analysis.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-domain:appcenter.ms
-domain:app-measurement.com
-domain:firebase.io
-domain:crashlytics.com
-domain:google-analytics.com
\ No newline at end of file
diff --git a/app/src/main/assets/proxy_packagename.txt b/app/src/main/assets/proxy_packagename.txt
new file mode 100644
index 000000000..28768cf02
--- /dev/null
+++ b/app/src/main/assets/proxy_packagename.txt
@@ -0,0 +1,390 @@
+amanita_design.samorost3.gp
+android
+au.com.shiftyjelly.pocketcasts
+bbc.mobile.news.ww
+be.mygod.vpnhotspot
+ch.protonmail.android
+cm.aptoide.pt
+co.wanqu.android
+com.alphainventor.filemanager
+com.amazon.kindle
+com.amazon.mshop.android.shopping
+com.android.chrome
+com.android.providers.downloads
+com.android.providers.downloads.ui
+com.android.providers.telephony
+com.android.settings
+com.android.vending
+com.android6park.m6park
+com.apkpure.aegon
+com.apkupdater
+com.app.pornhub
+com.arthurivanets.owly
+com.asahi.tida.tablet
+com.authy.authy
+com.avmovie
+com.ballistiq.artstation
+com.binance.dev
+com.bitly.app
+com.brave.browser
+com.brave.browser_beta
+com.breel.wallpapers18
+com.bvanced.android.youtube
+com.chrome.beta
+com.chrome.canary
+com.chrome.dev
+com.cl.newt66y
+com.cradle.iitc_mobile
+org.exarhteam.iitc_mobile
+com.cygames.shadowverse
+com.dcard.freedom
+com.devhd.feedly
+com.devolver.reigns2
+com.discord
+com.downloader.video.tumblr
+com.driverbrowser
+com.dropbox.android
+com.duolingo
+com.duckduckgo.mobile.android
+com.dv.adm
+com.estrongs.android.pop
+com.estrongs.android.pop.pro
+com.evernote
+com.facebook.katana
+com.facebook.lite
+com.facebook.mlite
+com.facebook.orca
+com.facebook.services
+com.facebook.system
+com.fastaccess.github
+com.felixfilip.scpae
+com.fireproofstudios.theroom4
+com.firstrowria.pushnotificationtester
+com.flyersoft.moonreaderp
+com.fooview.android.fooview
+com.fvd.eversync
+com.gameloft.android.anmp.glofta8hm
+com.gameloft.android.anmp.glofta9hm
+com.gianlu.aria2app
+com.github.yeriomin.yalpstore
+com.google.android.apps.adm
+com.google.android.apps.books
+com.google.android.apps.docs
+com.google.android.apps.docs.editors.sheets
+com.google.android.apps.docs.editors.docs
+com.google.android.apps.docs.editors.slides
+com.google.android.apps.fitness
+com.google.android.apps.googleassistant
+com.google.android.apps.googlevoice
+com.google.android.apps.hangoutsdialer
+com.google.android.apps.inbox
+com.google.android.apps.magazines
+com.google.android.apps.maps
+com.google.android.apps.nbu.files
+com.google.android.apps.paidtasks
+com.google.android.apps.pdfviewer
+com.google.android.apps.photos
+com.google.android.apps.plus
+com.google.android.apps.translate
+com.google.android.gm
+com.google.android.gms
+com.google.android.gms.setup
+com.google.android.googlequicksearchbox
+com.google.android.gsf
+com.google.android.gsf.login
+com.google.android.ims
+com.google.android.inputmethod.latin
+com.google.android.instantapps.supervisor
+com.google.android.keep
+com.google.android.music
+com.google.android.ogyoutube
+com.google.android.partnersetup
+com.google.android.play.games
+com.google.android.street
+com.google.android.syncadapters.calendar
+com.google.android.syncadapters.contacts
+com.google.android.talk
+com.google.android.tts
+com.google.android.videos
+com.google.android.youtube
+com.google.ar.lens
+com.google.android.apps.authenticator2
+com.hochan.coldsoup
+com.ifttt.ifttt
+com.imgur.mobile
+com.innologica.inoreader
+com.instagram.android
+com.instagram.lite
+com.instapaper.android
+com.jarvanh.vpntether
+com.kapp.youtube.final
+com.klinker.android.twitter_l
+com.lastpass.lpandroid
+com.linecorp.linelite
+com.lingodeer
+com.ltnnews.news
+com.mediapods.tumbpods
+com.mgoogle.android.gms
+com.microsoft.emmx
+com.microsoft.office.powerpoint
+com.microsoft.skydrive
+com.mixplorer
+com.msd.consumerchinese
+com.msd.professionalchinese
+com.mss2011c.sharehelper
+com.netflix.mediaclient
+com.newin.nplayer.pro
+com.nianticlabs.ingress.prime.qa
+com.nianticproject.ingress
+com.ninefolders.hd3
+com.ninegag.android.app
+com.nintendo.zara
+com.nytimes.cn
+com.oasisfeng.island
+com.ocnt.liveapp.hw
+com.orekie.search
+com.patreon.android
+com.paypal.android.p2pmobile
+com.perol.asdpl.pixivez
+com.pinterest
+com.popularapp.periodcalendar
+com.popularapp.videodownloaderforinstagram
+com.pushbullet.android
+com.quoord.tapatalkpro.activity
+com.quora.android
+com.rayark.cytus2
+com.rayark.implosion
+com.rayark.pluto
+com.reddit.frontpage
+com.resilio.sync
+com.rhmsoft.edit
+com.rubenmayayo.reddit
+com.sec.android.app.sbrowser
+com.sec.android.app.sbrowser.beta
+com.shanga.walli
+com.simplehabit.simplehabitapp
+com.slack
+com.snaptube.premium
+com.sololearn
+com.sonelli.juicessh
+com.sparkslab.dcardreader
+com.spotify.music
+com.spotify.lite
+com.tencent.huatuo
+com.termux
+com.teslacoilsw.launcher
+com.theinitium.news
+com.thomsonreuters.reuters
+com.thunkable.android.hritvik00.freenom
+com.topjohnwu.magisk
+com.tripadvisor.tripadvisor
+com.tumblr
+com.twitter.android
+com.u91porn
+com.u9porn
+com.ubisoft.dance.justdance2015companion
+com.udn.news
+com.utopia.pxview
+com.valvesoftware.android.steam.community
+com.vanced.manager
+com.vanced.android.youtube
+com.vanced.android.apps.youtube.music
+com.mgoogle.android.gms
+com.vimeo.android.videoapp
+com.vivaldi.browser
+com.vivaldi.browser.snapshot
+com.vkontakte.android
+com.whatsapp
+com.wire
+com.wuxiangai.refactor
+com.xda.labs
+com.xvideos.app
+com.yahoo.mobile.client.android.superapp
+com.yandex.browser
+com.yandex.browser.beta
+com.yandex.browser.alpha
+com.z28j.feel
+com.zhiliaoapp.musically
+con.medium.reader
+de.apkgrabber
+de.robv.android.xposed.installer
+dk.tacit.android.foldersync.full
+es.rafalense.telegram.themes
+es.rafalense.themes
+flipboard.app
+fm.moon.app
+fr.gouv.etalab.mastodon
+github.tornaco.xposedmoduletest
+idm.internet.download.manager
+idm.internet.download.manager.plus
+io.github.javiewer
+io.github.skyhacker2.magnetsearch
+io.va.exposed
+it.mvilla.android.fenix2
+jp.bokete.app.android
+jp.naver.line.android
+jp.pxv.android
+luo.speedometergpspro
+m.cna.com.tw.App
+mark.via.gp
+me.tshine.easymark
+net.teeha.android.url_shortener
+net.tsapps.appsales
+onion.fire
+org.fdroid.fdroid
+org.freedownloadmanager.fdm
+org.kustom.widget
+org.mozilla.fennec_aurora
+org.mozilla.fenix
+org.mozilla.fenix.nightly
+org.mozilla.firefox
+org.mozilla.firefox_beta
+org.mozilla.focus
+org.schabi.newpipe
+org.telegram.messenger
+org.telegram.multi
+org.telegram.plus
+org.thunderdog.challegram
+org.torproject.android
+org.torproject.torbrowser_alpha
+org.wikipedia
+org.xbmc.kodi
+pl.zdunex25.updater
+tv.twitch.android.app
+tw.com.gamer.android.activecenter
+videodownloader.downloadvideo.downloader
+uk.co.bbc.learningenglish
+com.ted.android
+de.danoeh.antennapod
+com.kiwibrowser.browser
+nekox.messenger
+com.nextcloud.client
+com.aurora.store
+com.aurora.adroid
+chat.simplex.app
+im.vector.app
+network.loki.messenger
+eu.siacs.conversations
+xyz.nextalone.nagram
+de.danoeh.antennapod
+net.programmierecke.radiodroid2
+im.fdx.v2ex
+ml.docilealligator.infinityforreddit
+com.bytemyth.ama
+app.vanadium.browser
+com.cakewallet.cake_wallet
+org.purplei2p.i2pd
+dk.tacit.android.foldersync.lite
+com.nononsenseapps.feeder
+com.m2049r.xmrwallet
+com.paypal.android.p2pmobile
+com.google.android.apps.googlevoice
+com.readdle.spark
+org.torproject.torbrowser
+com.deepl.mobiletranslator
+com.microsoft.bing
+com.keylesspalace.tusky
+com.ottplay.ottplay
+ru.iptvremote.android.iptv.pro
+jp.naver.line.android
+com.xmflsct.app.tooot
+com.forem.android
+app.revanced.android.youtube
+app.rvx.android.youtube
+app.rvx.android.apps.youtube.music
+com.mgoogle.android.gms
+com.pionex.client
+vip.mytokenpocket
+im.token.app
+com.linekong.mars24
+com.feixiaohao
+com.aicoin.appandroid
+com.binance.dev
+com.kraken.trade
+com.okinc.okex.gp
+com.authy.authy
+air.com.rosettastone.mobile.CoursePlayer
+com.blizzard.bma
+com.amazon.kindle
+com.google.android.apps.fitness
+net.tsapps.appsales
+com.wemesh.android
+com.google.android.apps.googleassistant
+allen.town.focus.reader
+me.hyliu.fluent_reader_lite
+com.aljazeera.mobile
+com.ft.news
+de.marmaro.krt.ffupdater
+myradio.radio.fmradio.liveradio.radiostation
+com.google.earth
+eu.kanade.tachiyomi.j2k
+com.audials
+com.microsoft.skydrive
+com.mb.android.tg
+com.melodis.midomiMusicIdentifier.freemium
+com.foxnews.android
+ch.threema.app
+com.briarproject.briar.android
+foundation.e.apps
+com.valvesoftware.android.steam.friendsui
+com.imback.yeetalk
+so.onekey.app.wallet
+com.xc3fff0e.xmanager
+meditofoundation.medito
+com.picol.client
+com.streetwriters.notesnook
+shanghai.panewsApp.com
+org.coursera.android
+com.positron_it.zlib
+com.blizzard.messenger
+com.javdb.javrocket
+com.picacomic.fregata
+com.fxl.chacha
+me.proton.android.drive
+com.lastpass.lpandroid
+com.tradingview.tradingviewapp
+com.deviantart.android.damobile
+com.fusionmedia.investing
+com.ewa.ewaapp
+com.duolingo
+com.hellotalk
+io.github.huskydg.magisk
+com.jsy.xpgbox
+com.hostloc.app.hostloc
+com.dena.pokota
+com.vitorpamplona.amethyst
+com.zhiliaoapp.musically
+us.spotco.fennec_dos
+com.fongmi.android.tv
+com.pocketprep.android.itcybersecurity
+com.cloudtv
+com.glassdoor.app
+com.indeed.android.jobsearch
+com.linkedin.android
+com.github.tvbox.osc.bh
+com.example.douban
+com.sipnetic.app
+com.microsoft.rdc.androidx
+org.zwanoo.android.speedtest
+com.sonelli.juicessh
+com.scmp.newspulse
+org.lsposed.manager
+mnn.Android
+com.thomsonretuers.reuters
+com.guardian
+com.ttxapps.onesyncv2
+org.fcitx.fcitx5.android.updater
+com.instagram.barcelona
+com.deniscerri.ytdl
+jp.pokemon.pokemonsleep
+com.github.android
+com.openai.chatgpt
+mega.privacy.android.app
+com.taptap.global
+tw.com.gamer.android.animad
+com.microsoft.copilot
+com.google.android.apps.aiwallpapers
+ai.x.grok
+com.google.android.apps.weather
+com.metrolist.music
+com.google.android.apps.youtube.creator
\ No newline at end of file
diff --git a/app/src/main/assets/yacd.version.txt b/app/src/main/assets/yacd.version.txt
index 56a6051ca..e440e5c84 100644
--- a/app/src/main/assets/yacd.version.txt
+++ b/app/src/main/assets/yacd.version.txt
@@ -1 +1 @@
-1
\ No newline at end of file
+3
\ No newline at end of file
diff --git a/app/src/main/assets/yacd.zip b/app/src/main/assets/yacd.zip
index 46d2b59b6..5fc55e2e0 100644
Binary files a/app/src/main/assets/yacd.zip and b/app/src/main/assets/yacd.zip differ
diff --git a/app/src/main/java/com/wireguard/crypto/Curve25519.java b/app/src/main/java/com/wireguard/crypto/Curve25519.java
deleted file mode 100644
index 55f2809af..000000000
--- a/app/src/main/java/com/wireguard/crypto/Curve25519.java
+++ /dev/null
@@ -1,497 +0,0 @@
-/*
- * Copyright © 2016 Southern Storm Software, Pty Ltd.
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.crypto;
-
-import androidx.annotation.Nullable;
-
-import java.util.Arrays;
-
-/**
- * Implementation of Curve25519 ECDH.
- *
- * This implementation was imported to WireGuard from noise-java:
- * https://github.com/rweather/noise-java
- *
- * This implementation is based on that from arduinolibs:
- * https://github.com/rweather/arduinolibs
- *
- * Differences in this version are due to using 26-bit limbs for the
- * representation instead of the 8/16/32-bit limbs in the original.
- *
- * References: http://cr.yp.to/ecdh.html, RFC 7748
- */
-@SuppressWarnings({"MagicNumber", "NonConstantFieldWithUpperCaseName", "SuspiciousNameCombination"})
-public final class Curve25519 {
- // Numbers modulo 2^255 - 19 are broken up into ten 26-bit words.
- private static final int NUM_LIMBS_255BIT = 10;
- private static final int NUM_LIMBS_510BIT = 20;
-
- private final int[] A;
- private final int[] AA;
- private final int[] B;
- private final int[] BB;
- private final int[] C;
- private final int[] CB;
- private final int[] D;
- private final int[] DA;
- private final int[] E;
- private final long[] t1;
- private final int[] t2;
- private final int[] x_1;
- private final int[] x_2;
- private final int[] x_3;
- private final int[] z_2;
- private final int[] z_3;
-
- /**
- * Constructs the temporary state holder for Curve25519 evaluation.
- */
- private Curve25519() {
- // Allocate memory for all of the temporary variables we will need.
- x_1 = new int[NUM_LIMBS_255BIT];
- x_2 = new int[NUM_LIMBS_255BIT];
- x_3 = new int[NUM_LIMBS_255BIT];
- z_2 = new int[NUM_LIMBS_255BIT];
- z_3 = new int[NUM_LIMBS_255BIT];
- A = new int[NUM_LIMBS_255BIT];
- B = new int[NUM_LIMBS_255BIT];
- C = new int[NUM_LIMBS_255BIT];
- D = new int[NUM_LIMBS_255BIT];
- E = new int[NUM_LIMBS_255BIT];
- AA = new int[NUM_LIMBS_255BIT];
- BB = new int[NUM_LIMBS_255BIT];
- DA = new int[NUM_LIMBS_255BIT];
- CB = new int[NUM_LIMBS_255BIT];
- t1 = new long[NUM_LIMBS_510BIT];
- t2 = new int[NUM_LIMBS_510BIT];
- }
-
- /**
- * Conditional swap of two values.
- *
- * @param select Set to 1 to swap, 0 to leave as-is.
- * @param x The first value.
- * @param y The second value.
- */
- private static void cswap(int select, final int[] x, final int[] y) {
- select = -select;
- for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
- final int dummy = select & (x[index] ^ y[index]);
- x[index] ^= dummy;
- y[index] ^= dummy;
- }
- }
-
- /**
- * Evaluates the Curve25519 curve.
- *
- * @param result Buffer to place the result of the evaluation into.
- * @param offset Offset into the result buffer.
- * @param privateKey The private key to use in the evaluation.
- * @param publicKey The public key to use in the evaluation, or null
- * if the base point of the curve should be used.
- */
- public static void eval(final byte[] result, final int offset,
- final byte[] privateKey, @Nullable final byte[] publicKey) {
- final Curve25519 state = new Curve25519();
- try {
- // Unpack the public key value. If null, use 9 as the base point.
- Arrays.fill(state.x_1, 0);
- if (publicKey != null) {
- // Convert the input value from little-endian into 26-bit limbs.
- for (int index = 0; index < 32; ++index) {
- final int bit = (index * 8) % 26;
- final int word = (index * 8) / 26;
- final int value = publicKey[index] & 0xFF;
- if (bit <= (26 - 8)) {
- state.x_1[word] |= value << bit;
- } else {
- state.x_1[word] |= value << bit;
- state.x_1[word] &= 0x03FFFFFF;
- state.x_1[word + 1] |= value >> (26 - bit);
- }
- }
-
- // Just in case, we reduce the number modulo 2^255 - 19 to
- // make sure that it is in range of the field before we start.
- // This eliminates values between 2^255 - 19 and 2^256 - 1.
- state.reduceQuick(state.x_1);
- state.reduceQuick(state.x_1);
- } else {
- state.x_1[0] = 9;
- }
-
- // Initialize the other temporary variables.
- Arrays.fill(state.x_2, 0); // x_2 = 1
- state.x_2[0] = 1;
- Arrays.fill(state.z_2, 0); // z_2 = 0
- System.arraycopy(state.x_1, 0, state.x_3, 0, state.x_1.length); // x_3 = x_1
- Arrays.fill(state.z_3, 0); // z_3 = 1
- state.z_3[0] = 1;
-
- // Evaluate the curve for every bit of the private key.
- state.evalCurve(privateKey);
-
- // Compute x_2 * (z_2 ^ (p - 2)) where p = 2^255 - 19.
- state.recip(state.z_3, state.z_2);
- state.mul(state.x_2, state.x_2, state.z_3);
-
- // Convert x_2 into little-endian in the result buffer.
- for (int index = 0; index < 32; ++index) {
- final int bit = (index * 8) % 26;
- final int word = (index * 8) / 26;
- if (bit <= (26 - 8))
- result[offset + index] = (byte) (state.x_2[word] >> bit);
- else
- result[offset + index] = (byte) ((state.x_2[word] >> bit) | (state.x_2[word + 1] << (26 - bit)));
- }
- } finally {
- // Clean up all temporary state before we exit.
- state.destroy();
- }
- }
-
- /**
- * Subtracts two numbers modulo 2^255 - 19.
- *
- * @param result The result.
- * @param x The first number to subtract.
- * @param y The second number to subtract.
- */
- private static void sub(final int[] result, final int[] x, final int[] y) {
- int index;
- int borrow;
-
- // Subtract y from x to generate the intermediate result.
- borrow = 0;
- for (index = 0; index < NUM_LIMBS_255BIT; ++index) {
- borrow = x[index] - y[index] - ((borrow >> 26) & 0x01);
- result[index] = borrow & 0x03FFFFFF;
- }
-
- // If we had a borrow, then the result has gone negative and we
- // have to add 2^255 - 19 to the result to make it positive again.
- // The top bits of "borrow" will be all 1's if there is a borrow
- // or it will be all 0's if there was no borrow. Easiest is to
- // conditionally subtract 19 and then mask off the high bits.
- borrow = result[0] - ((-((borrow >> 26) & 0x01)) & 19);
- result[0] = borrow & 0x03FFFFFF;
- for (index = 1; index < NUM_LIMBS_255BIT; ++index) {
- borrow = result[index] - ((borrow >> 26) & 0x01);
- result[index] = borrow & 0x03FFFFFF;
- }
- result[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
- }
-
- /**
- * Adds two numbers modulo 2^255 - 19.
- *
- * @param result The result.
- * @param x The first number to add.
- * @param y The second number to add.
- */
- private void add(final int[] result, final int[] x, final int[] y) {
- int carry = x[0] + y[0];
- result[0] = carry & 0x03FFFFFF;
- for (int index = 1; index < NUM_LIMBS_255BIT; ++index) {
- carry = (carry >> 26) + x[index] + y[index];
- result[index] = carry & 0x03FFFFFF;
- }
- reduceQuick(result);
- }
-
- /**
- * Destroy all sensitive data in this object.
- */
- private void destroy() {
- // Destroy all temporary variables.
- Arrays.fill(x_1, 0);
- Arrays.fill(x_2, 0);
- Arrays.fill(x_3, 0);
- Arrays.fill(z_2, 0);
- Arrays.fill(z_3, 0);
- Arrays.fill(A, 0);
- Arrays.fill(B, 0);
- Arrays.fill(C, 0);
- Arrays.fill(D, 0);
- Arrays.fill(E, 0);
- Arrays.fill(AA, 0);
- Arrays.fill(BB, 0);
- Arrays.fill(DA, 0);
- Arrays.fill(CB, 0);
- Arrays.fill(t1, 0L);
- Arrays.fill(t2, 0);
- }
-
- /**
- * Evaluates the curve for every bit in a secret key.
- *
- * @param s The 32-byte secret key.
- */
- private void evalCurve(final byte[] s) {
- int sposn = 31;
- int sbit = 6;
- int svalue = s[sposn] | 0x40;
- int swap = 0;
-
- // Iterate over all 255 bits of "s" from the highest to the lowest.
- // We ignore the high bit of the 256-bit representation of "s".
- while (true) {
- // Conditional swaps on entry to this bit but only if we
- // didn't swap on the previous bit.
- final int select = (svalue >> sbit) & 0x01;
- swap ^= select;
- cswap(swap, x_2, x_3);
- cswap(swap, z_2, z_3);
- swap = select;
-
- // Evaluate the curve.
- add(A, x_2, z_2); // A = x_2 + z_2
- square(AA, A); // AA = A^2
- sub(B, x_2, z_2); // B = x_2 - z_2
- square(BB, B); // BB = B^2
- sub(E, AA, BB); // E = AA - BB
- add(C, x_3, z_3); // C = x_3 + z_3
- sub(D, x_3, z_3); // D = x_3 - z_3
- mul(DA, D, A); // DA = D * A
- mul(CB, C, B); // CB = C * B
- add(x_3, DA, CB); // x_3 = (DA + CB)^2
- square(x_3, x_3);
- sub(z_3, DA, CB); // z_3 = x_1 * (DA - CB)^2
- square(z_3, z_3);
- mul(z_3, z_3, x_1);
- mul(x_2, AA, BB); // x_2 = AA * BB
- mulA24(z_2, E); // z_2 = E * (AA + a24 * E)
- add(z_2, z_2, AA);
- mul(z_2, z_2, E);
-
- // Move onto the next lower bit of "s".
- if (sbit > 0) {
- --sbit;
- } else if (sposn == 0) {
- break;
- } else if (sposn == 1) {
- --sposn;
- svalue = s[sposn] & 0xF8;
- sbit = 7;
- } else {
- --sposn;
- svalue = s[sposn];
- sbit = 7;
- }
- }
-
- // Final conditional swaps.
- cswap(swap, x_2, x_3);
- cswap(swap, z_2, z_3);
- }
-
- /**
- * Multiplies two numbers modulo 2^255 - 19.
- *
- * @param result The result.
- * @param x The first number to multiply.
- * @param y The second number to multiply.
- */
- private void mul(final int[] result, final int[] x, final int[] y) {
- // Multiply the two numbers to create the intermediate result.
- long v = x[0];
- for (int i = 0; i < NUM_LIMBS_255BIT; ++i) {
- t1[i] = v * y[i];
- }
- for (int i = 1; i < NUM_LIMBS_255BIT; ++i) {
- v = x[i];
- for (int j = 0; j < (NUM_LIMBS_255BIT - 1); ++j) {
- t1[i + j] += v * y[j];
- }
- t1[i + NUM_LIMBS_255BIT - 1] = v * y[NUM_LIMBS_255BIT - 1];
- }
-
- // Propagate carries and convert back into 26-bit words.
- v = t1[0];
- t2[0] = ((int) v) & 0x03FFFFFF;
- for (int i = 1; i < NUM_LIMBS_510BIT; ++i) {
- v = (v >> 26) + t1[i];
- t2[i] = ((int) v) & 0x03FFFFFF;
- }
-
- // Reduce the result modulo 2^255 - 19.
- reduce(result, t2, NUM_LIMBS_255BIT);
- }
-
- /**
- * Multiplies a number by the a24 constant, modulo 2^255 - 19.
- *
- * @param result The result.
- * @param x The number to multiply by a24.
- */
- private void mulA24(final int[] result, final int[] x) {
- final long a24 = 121665;
- long carry = 0;
- for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
- carry += a24 * x[index];
- t2[index] = ((int) carry) & 0x03FFFFFF;
- carry >>= 26;
- }
- t2[NUM_LIMBS_255BIT] = ((int) carry) & 0x03FFFFFF;
- reduce(result, t2, 1);
- }
-
- /**
- * Raise x to the power of (2^250 - 1).
- *
- * @param result The result. Must not overlap with x.
- * @param x The argument.
- */
- private void pow250(final int[] result, final int[] x) {
- // The big-endian hexadecimal expansion of (2^250 - 1) is:
- // 03FFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
- //
- // The naive implementation needs to do 2 multiplications per 1 bit and
- // 1 multiplication per 0 bit. We can improve upon this by creating a
- // pattern 0000000001 ... 0000000001. If we square and multiply the
- // pattern by itself we can turn the pattern into the partial results
- // 0000000011 ... 0000000011, 0000000111 ... 0000000111, etc.
- // This averages out to about 1.1 multiplications per 1 bit instead of 2.
-
- // Build a pattern of 250 bits in length of repeated copies of 0000000001.
- square(A, x);
- for (int j = 0; j < 9; ++j)
- square(A, A);
- mul(result, A, x);
- for (int i = 0; i < 23; ++i) {
- for (int j = 0; j < 10; ++j)
- square(A, A);
- mul(result, result, A);
- }
-
- // Multiply bit-shifted versions of the 0000000001 pattern into
- // the result to "fill in" the gaps in the pattern.
- square(A, result);
- mul(result, result, A);
- for (int j = 0; j < 8; ++j) {
- square(A, A);
- mul(result, result, A);
- }
- }
-
- /**
- * Computes the reciprocal of a number modulo 2^255 - 19.
- *
- * @param result The result. Must not overlap with x.
- * @param x The argument.
- */
- private void recip(final int[] result, final int[] x) {
- // The reciprocal is the same as x ^ (p - 2) where p = 2^255 - 19.
- // The big-endian hexadecimal expansion of (p - 2) is:
- // 7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFEB
- // Start with the 250 upper bits of the expansion of (p - 2).
- pow250(result, x);
-
- // Deal with the 5 lowest bits of (p - 2), 01011, from highest to lowest.
- square(result, result);
- square(result, result);
- mul(result, result, x);
- square(result, result);
- square(result, result);
- mul(result, result, x);
- square(result, result);
- mul(result, result, x);
- }
-
- /**
- * Reduce a number modulo 2^255 - 19.
- *
- * @param result The result.
- * @param x The value to be reduced. This array will be
- * modified during the reduction.
- * @param size The number of limbs in the high order half of x.
- */
- private void reduce(final int[] result, final int[] x, final int size) {
- // Calculate (x mod 2^255) + ((x / 2^255) * 19) which will
- // either produce the answer we want or it will produce a
- // value of the form "answer + j * (2^255 - 19)". There are
- // 5 left-over bits in the top-most limb of the bottom half.
- int carry = 0;
- int limb = x[NUM_LIMBS_255BIT - 1] >> 21;
- x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
- for (int index = 0; index < size; ++index) {
- limb += x[NUM_LIMBS_255BIT + index] << 5;
- carry += (limb & 0x03FFFFFF) * 19 + x[index];
- x[index] = carry & 0x03FFFFFF;
- limb >>= 26;
- carry >>= 26;
- }
- if (size < NUM_LIMBS_255BIT) {
- // The high order half of the number is short; e.g. for mulA24().
- // Propagate the carry through the rest of the low order part.
- for (int index = size; index < NUM_LIMBS_255BIT; ++index) {
- carry += x[index];
- x[index] = carry & 0x03FFFFFF;
- carry >>= 26;
- }
- }
-
- // The "j" value may still be too large due to the final carry-out.
- // We must repeat the reduction. If we already have the answer,
- // then this won't do any harm but we must still do the calculation
- // to preserve the overall timing. The "j" value will be between
- // 0 and 19, which means that the carry we care about is in the
- // top 5 bits of the highest limb of the bottom half.
- carry = (x[NUM_LIMBS_255BIT - 1] >> 21) * 19;
- x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
- for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
- carry += x[index];
- result[index] = carry & 0x03FFFFFF;
- carry >>= 26;
- }
-
- // At this point "x" will either be the answer or it will be the
- // answer plus (2^255 - 19). Perform a trial subtraction to
- // complete the reduction process.
- reduceQuick(result);
- }
-
- /**
- * Reduces a number modulo 2^255 - 19 where it is known that the
- * number can be reduced with only 1 trial subtraction.
- *
- * @param x The number to reduce, and the result.
- */
- private void reduceQuick(final int[] x) {
- // Perform a trial subtraction of (2^255 - 19) from "x" which is
- // equivalent to adding 19 and subtracting 2^255. We add 19 here;
- // the subtraction of 2^255 occurs in the next step.
- int carry = 19;
- for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
- carry += x[index];
- t2[index] = carry & 0x03FFFFFF;
- carry >>= 26;
- }
-
- // If there was a borrow, then the original "x" is the correct answer.
- // If there was no borrow, then "t2" is the correct answer. Select the
- // correct answer but do it in a way that instruction timing will not
- // reveal which value was selected. Borrow will occur if bit 21 of
- // "t2" is zero. Turn the bit into a selection mask.
- final int mask = -((t2[NUM_LIMBS_255BIT - 1] >> 21) & 0x01);
- final int nmask = ~mask;
- t2[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
- for (int index = 0; index < NUM_LIMBS_255BIT; ++index)
- x[index] = (x[index] & nmask) | (t2[index] & mask);
- }
-
- /**
- * Squares a number modulo 2^255 - 19.
- *
- * @param result The result.
- * @param x The number to square.
- */
- private void square(final int[] result, final int[] x) {
- mul(result, x, x);
- }
-}
diff --git a/app/src/main/java/com/wireguard/crypto/Ed25519.java b/app/src/main/java/com/wireguard/crypto/Ed25519.java
deleted file mode 100644
index a60babfbb..000000000
--- a/app/src/main/java/com/wireguard/crypto/Ed25519.java
+++ /dev/null
@@ -1,2508 +0,0 @@
-/*
- * Copyright © 2020 WireGuard LLC. All Rights Reserved.
- * Copyright 2017 Google Inc.
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.crypto;
-
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.util.Arrays;
-
-/**
- * Implementation of Ed25519 signature verification.
- *
- *
This implementation is based on the ed25519/ref10 implementation in NaCl.
- *
- * It implements this twisted Edwards curve:
- *
- *
- * -x^2 + y^2 = 1 + (-121665 / 121666 mod 2^255-19)*x^2*y^2
- *
- *
- * @see Bernstein D.J., Birkner P., Joye M., Lange
- * T., Peters C. (2008) Twisted Edwards Curves
- * @see Hisil H., Wong K.KH., Carter G., Dawson E.
- * (2008) Twisted Edwards Curves Revisited
- */
-public final class Ed25519 {
-
- // d = -121665 / 121666 mod 2^255-19
- private static final long[] D;
- // 2d
- private static final long[] D2;
- // 2^((p-1)/4) mod p where p = 2^255-19
- private static final long[] SQRTM1;
-
- /**
- * Base point for the Edwards twisted curve = (x, 4/5) and its exponentiations. B_TABLE[i][j] =
- * (j+1)*256^i*B for i in [0, 32) and j in [0, 8). Base point B = B_TABLE[0][0]
- */
- private static final CachedXYT[][] B_TABLE;
- private static final CachedXYT[] B2;
-
- private static final BigInteger P_BI =
- BigInteger.valueOf(2).pow(255).subtract(BigInteger.valueOf(19));
- private static final BigInteger D_BI =
- BigInteger.valueOf(-121665).multiply(BigInteger.valueOf(121666).modInverse(P_BI)).mod(P_BI);
- private static final BigInteger D2_BI = BigInteger.valueOf(2).multiply(D_BI).mod(P_BI);
- private static final BigInteger SQRTM1_BI =
- BigInteger.valueOf(2).modPow(P_BI.subtract(BigInteger.ONE).divide(BigInteger.valueOf(4)), P_BI);
-
- private Ed25519() {
- }
-
- private static class Point {
- private BigInteger x;
- private BigInteger y;
- }
-
- private static BigInteger recoverX(BigInteger y) {
- // x^2 = (y^2 - 1) / (d * y^2 + 1) mod 2^255-19
- BigInteger xx =
- y.pow(2)
- .subtract(BigInteger.ONE)
- .multiply(D_BI.multiply(y.pow(2)).add(BigInteger.ONE).modInverse(P_BI));
- BigInteger x = xx.modPow(P_BI.add(BigInteger.valueOf(3)).divide(BigInteger.valueOf(8)), P_BI);
- if (!x.pow(2).subtract(xx).mod(P_BI).equals(BigInteger.ZERO)) {
- x = x.multiply(SQRTM1_BI).mod(P_BI);
- }
- if (x.testBit(0)) {
- x = P_BI.subtract(x);
- }
- return x;
- }
-
- private static Point edwards(Point a, Point b) {
- Point o = new Point();
- BigInteger xxyy = D_BI.multiply(a.x.multiply(b.x).multiply(a.y).multiply(b.y)).mod(P_BI);
- o.x =
- (a.x.multiply(b.y).add(b.x.multiply(a.y)))
- .multiply(BigInteger.ONE.add(xxyy).modInverse(P_BI))
- .mod(P_BI);
- o.y =
- (a.y.multiply(b.y).add(a.x.multiply(b.x)))
- .multiply(BigInteger.ONE.subtract(xxyy).modInverse(P_BI))
- .mod(P_BI);
- return o;
- }
-
- private static byte[] toLittleEndian(BigInteger n) {
- byte[] b = new byte[32];
- byte[] nBytes = n.toByteArray();
- System.arraycopy(nBytes, 0, b, 32 - nBytes.length, nBytes.length);
- for (int i = 0; i < b.length / 2; i++) {
- byte t = b[i];
- b[i] = b[b.length - i - 1];
- b[b.length - i - 1] = t;
- }
- return b;
- }
-
- private static CachedXYT getCachedXYT(Point p) {
- return new CachedXYT(
- Field25519.expand(toLittleEndian(p.y.add(p.x).mod(P_BI))),
- Field25519.expand(toLittleEndian(p.y.subtract(p.x).mod(P_BI))),
- Field25519.expand(toLittleEndian(D2_BI.multiply(p.x).multiply(p.y).mod(P_BI))));
- }
-
- static {
- Point b = new Point();
- b.y = BigInteger.valueOf(4).multiply(BigInteger.valueOf(5).modInverse(P_BI)).mod(P_BI);
- b.x = recoverX(b.y);
-
- D = Field25519.expand(toLittleEndian(D_BI));
- D2 = Field25519.expand(toLittleEndian(D2_BI));
- SQRTM1 = Field25519.expand(toLittleEndian(SQRTM1_BI));
-
- Point bi = b;
- B_TABLE = new CachedXYT[32][8];
- for (int i = 0; i < 32; i++) {
- Point bij = bi;
- for (int j = 0; j < 8; j++) {
- B_TABLE[i][j] = getCachedXYT(bij);
- bij = edwards(bij, bi);
- }
- for (int j = 0; j < 8; j++) {
- bi = edwards(bi, bi);
- }
- }
- bi = b;
- Point b2 = edwards(b, b);
- B2 = new CachedXYT[8];
- for (int i = 0; i < 8; i++) {
- B2[i] = getCachedXYT(bi);
- bi = edwards(bi, b2);
- }
- }
-
- private static final int PUBLIC_KEY_LEN = Field25519.FIELD_LEN;
- private static final int SIGNATURE_LEN = Field25519.FIELD_LEN * 2;
-
- /**
- * Defines field 25519 function based on curve25519-donna C
- * implementation (mostly identical).
- *
- * Field elements are written as an array of signed, 64-bit limbs (an array of longs), least
- * significant first. The value of the field element is:
- *
- *
- * x[0] + 2^26·x[1] + 2^51·x[2] + 2^77·x[3] + 2^102·x[4] + 2^128·x[5] + 2^153·x[6] + 2^179·x[7] +
- * 2^204·x[8] + 2^230·x[9],
- *
- *
- * i.e. the limbs are 26, 25, 26, 25, ... bits wide.
- */
- private static final class Field25519 {
- /**
- * During Field25519 computation, the mixed radix representation may be in different forms:
- *
- * - Reduced-size form: the array has size at most 10.
- *
- Non-reduced-size form: the array is not reduced modulo 2^255 - 19 and has size at most
- * 19.
- *
- *
- * TODO(quannguyen):
- *
- * - Clarify ill-defined terminologies.
- *
- The reduction procedure is different from DJB's paper
- * (http://cr.yp.to/ecdh/curve25519-20060209.pdf). The coefficients after reducing degree and
- * reducing coefficients aren't guaranteed to be in range {-2^25, ..., 2^25}. We should check to
- * see what's going on.
- *
- Consider using method mult() everywhere and making product() private.
- *
- */
-
- static final int FIELD_LEN = 32;
- static final int LIMB_CNT = 10;
- private static final long TWO_TO_25 = 1 << 25;
- private static final long TWO_TO_26 = TWO_TO_25 << 1;
-
- private static final int[] EXPAND_START = {0, 3, 6, 9, 12, 16, 19, 22, 25, 28};
- private static final int[] EXPAND_SHIFT = {0, 2, 3, 5, 6, 0, 1, 3, 4, 6};
- private static final int[] MASK = {0x3ffffff, 0x1ffffff};
- private static final int[] SHIFT = {26, 25};
-
- /**
- * Sums two numbers: output = in1 + in2
- *
- * On entry: in1, in2 are in reduced-size form.
- */
- static void sum(long[] output, long[] in1, long[] in2) {
- for (int i = 0; i < LIMB_CNT; i++) {
- output[i] = in1[i] + in2[i];
- }
- }
-
- /**
- * Sums two numbers: output += in
- *
- * On entry: in is in reduced-size form.
- */
- static void sum(long[] output, long[] in) {
- sum(output, output, in);
- }
-
- /**
- * Find the difference of two numbers: output = in1 - in2
- * (note the order of the arguments!).
- *
- * On entry: in1, in2 are in reduced-size form.
- */
- static void sub(long[] output, long[] in1, long[] in2) {
- for (int i = 0; i < LIMB_CNT; i++) {
- output[i] = in1[i] - in2[i];
- }
- }
-
- /**
- * Find the difference of two numbers: output = in - output
- * (note the order of the arguments!).
- *
- * On entry: in, output are in reduced-size form.
- */
- static void sub(long[] output, long[] in) {
- sub(output, in, output);
- }
-
- /**
- * Multiply a number by a scalar: output = in * scalar
- */
- static void scalarProduct(long[] output, long[] in, long scalar) {
- for (int i = 0; i < LIMB_CNT; i++) {
- output[i] = in[i] * scalar;
- }
- }
-
- /**
- * Multiply two numbers: out = in2 * in
- *
- * output must be distinct to both inputs. The inputs are reduced coefficient form,
- * the output is not.
- *
- * out[x] <= 14 * the largest product of the input limbs.
- */
- static void product(long[] out, long[] in2, long[] in) {
- out[0] = in2[0] * in[0];
- out[1] = in2[0] * in[1]
- + in2[1] * in[0];
- out[2] = 2 * in2[1] * in[1]
- + in2[0] * in[2]
- + in2[2] * in[0];
- out[3] = in2[1] * in[2]
- + in2[2] * in[1]
- + in2[0] * in[3]
- + in2[3] * in[0];
- out[4] = in2[2] * in[2]
- + 2 * (in2[1] * in[3] + in2[3] * in[1])
- + in2[0] * in[4]
- + in2[4] * in[0];
- out[5] = in2[2] * in[3]
- + in2[3] * in[2]
- + in2[1] * in[4]
- + in2[4] * in[1]
- + in2[0] * in[5]
- + in2[5] * in[0];
- out[6] = 2 * (in2[3] * in[3] + in2[1] * in[5] + in2[5] * in[1])
- + in2[2] * in[4]
- + in2[4] * in[2]
- + in2[0] * in[6]
- + in2[6] * in[0];
- out[7] = in2[3] * in[4]
- + in2[4] * in[3]
- + in2[2] * in[5]
- + in2[5] * in[2]
- + in2[1] * in[6]
- + in2[6] * in[1]
- + in2[0] * in[7]
- + in2[7] * in[0];
- out[8] = in2[4] * in[4]
- + 2 * (in2[3] * in[5] + in2[5] * in[3] + in2[1] * in[7] + in2[7] * in[1])
- + in2[2] * in[6]
- + in2[6] * in[2]
- + in2[0] * in[8]
- + in2[8] * in[0];
- out[9] = in2[4] * in[5]
- + in2[5] * in[4]
- + in2[3] * in[6]
- + in2[6] * in[3]
- + in2[2] * in[7]
- + in2[7] * in[2]
- + in2[1] * in[8]
- + in2[8] * in[1]
- + in2[0] * in[9]
- + in2[9] * in[0];
- out[10] =
- 2 * (in2[5] * in[5] + in2[3] * in[7] + in2[7] * in[3] + in2[1] * in[9] + in2[9] * in[1])
- + in2[4] * in[6]
- + in2[6] * in[4]
- + in2[2] * in[8]
- + in2[8] * in[2];
- out[11] = in2[5] * in[6]
- + in2[6] * in[5]
- + in2[4] * in[7]
- + in2[7] * in[4]
- + in2[3] * in[8]
- + in2[8] * in[3]
- + in2[2] * in[9]
- + in2[9] * in[2];
- out[12] = in2[6] * in[6]
- + 2 * (in2[5] * in[7] + in2[7] * in[5] + in2[3] * in[9] + in2[9] * in[3])
- + in2[4] * in[8]
- + in2[8] * in[4];
- out[13] = in2[6] * in[7]
- + in2[7] * in[6]
- + in2[5] * in[8]
- + in2[8] * in[5]
- + in2[4] * in[9]
- + in2[9] * in[4];
- out[14] = 2 * (in2[7] * in[7] + in2[5] * in[9] + in2[9] * in[5])
- + in2[6] * in[8]
- + in2[8] * in[6];
- out[15] = in2[7] * in[8]
- + in2[8] * in[7]
- + in2[6] * in[9]
- + in2[9] * in[6];
- out[16] = in2[8] * in[8]
- + 2 * (in2[7] * in[9] + in2[9] * in[7]);
- out[17] = in2[8] * in[9]
- + in2[9] * in[8];
- out[18] = 2 * in2[9] * in[9];
- }
-
- /**
- * Reduce a field element by calling reduceSizeByModularReduction and reduceCoefficients.
- *
- * @param input An input array of any length. If the array has 19 elements, it will be used as
- * temporary buffer and its contents changed.
- * @param output An output array of size LIMB_CNT. After the call |output[i]| < 2^26 will hold.
- */
- static void reduce(long[] input, long[] output) {
- long[] tmp;
- if (input.length == 19) {
- tmp = input;
- } else {
- tmp = new long[19];
- System.arraycopy(input, 0, tmp, 0, input.length);
- }
- reduceSizeByModularReduction(tmp);
- reduceCoefficients(tmp);
- System.arraycopy(tmp, 0, output, 0, LIMB_CNT);
- }
-
- /**
- * Reduce a long form to a reduced-size form by taking the input mod 2^255 - 19.
- *
- * On entry: |output[i]| < 14*2^54
- * On exit: |output[0..8]| < 280*2^54
- */
- static void reduceSizeByModularReduction(long[] output) {
- // The coefficients x[10], x[11],..., x[18] are eliminated by reduction modulo 2^255 - 19.
- // For example, the coefficient x[18] is multiplied by 19 and added to the coefficient x[8].
- //
- // Each of these shifts and adds ends up multiplying the value by 19.
- //
- // For output[0..8], the absolute entry value is < 14*2^54 and we add, at most, 19*14*2^54 thus,
- // on exit, |output[0..8]| < 280*2^54.
- output[8] += output[18] << 4;
- output[8] += output[18] << 1;
- output[8] += output[18];
- output[7] += output[17] << 4;
- output[7] += output[17] << 1;
- output[7] += output[17];
- output[6] += output[16] << 4;
- output[6] += output[16] << 1;
- output[6] += output[16];
- output[5] += output[15] << 4;
- output[5] += output[15] << 1;
- output[5] += output[15];
- output[4] += output[14] << 4;
- output[4] += output[14] << 1;
- output[4] += output[14];
- output[3] += output[13] << 4;
- output[3] += output[13] << 1;
- output[3] += output[13];
- output[2] += output[12] << 4;
- output[2] += output[12] << 1;
- output[2] += output[12];
- output[1] += output[11] << 4;
- output[1] += output[11] << 1;
- output[1] += output[11];
- output[0] += output[10] << 4;
- output[0] += output[10] << 1;
- output[0] += output[10];
- }
-
- /**
- * Reduce all coefficients of the short form input so that |x| < 2^26.
- *
- * On entry: |output[i]| < 280*2^54
- */
- static void reduceCoefficients(long[] output) {
- output[10] = 0;
-
- for (int i = 0; i < LIMB_CNT; i += 2) {
- long over = output[i] / TWO_TO_26;
- // The entry condition (that |output[i]| < 280*2^54) means that over is, at most, 280*2^28 in
- // the first iteration of this loop. This is added to the next limb and we can approximate the
- // resulting bound of that limb by 281*2^54.
- output[i] -= over << 26;
- output[i + 1] += over;
-
- // For the first iteration, |output[i+1]| < 281*2^54, thus |over| < 281*2^29. When this is
- // added to the next limb, the resulting bound can be approximated as 281*2^54.
- //
- // For subsequent iterations of the loop, 281*2^54 remains a conservative bound and no
- // overflow occurs.
- over = output[i + 1] / TWO_TO_25;
- output[i + 1] -= over << 25;
- output[i + 2] += over;
- }
- // Now |output[10]| < 281*2^29 and all other coefficients are reduced.
- output[0] += output[10] << 4;
- output[0] += output[10] << 1;
- output[0] += output[10];
-
- output[10] = 0;
- // Now output[1..9] are reduced, and |output[0]| < 2^26 + 19*281*2^29 so |over| will be no more
- // than 2^16.
- long over = output[0] / TWO_TO_26;
- output[0] -= over << 26;
- output[1] += over;
- // Now output[0,2..9] are reduced, and |output[1]| < 2^25 + 2^16 < 2^26. The bound on
- // |output[1]| is sufficient to meet our needs.
- }
-
- /**
- * A helpful wrapper around {@ref Field25519#product}: output = in * in2.
- *
- * On entry: |in[i]| < 2^27 and |in2[i]| < 2^27.
- *
- * The output is reduced degree (indeed, one need only provide storage for 10 limbs) and
- * |output[i]| < 2^26.
- */
- static void mult(long[] output, long[] in, long[] in2) {
- long[] t = new long[19];
- product(t, in, in2);
- // |t[i]| < 2^26
- reduce(t, output);
- }
-
- /**
- * Square a number: out = in**2
- *
- * output must be distinct from the input. The inputs are reduced coefficient form, the output is
- * not.
- *
- * out[x] <= 14 * the largest product of the input limbs.
- */
- private static void squareInner(long[] out, long[] in) {
- out[0] = in[0] * in[0];
- out[1] = 2 * in[0] * in[1];
- out[2] = 2 * (in[1] * in[1] + in[0] * in[2]);
- out[3] = 2 * (in[1] * in[2] + in[0] * in[3]);
- out[4] = in[2] * in[2]
- + 4 * in[1] * in[3]
- + 2 * in[0] * in[4];
- out[5] = 2 * (in[2] * in[3] + in[1] * in[4] + in[0] * in[5]);
- out[6] = 2 * (in[3] * in[3] + in[2] * in[4] + in[0] * in[6] + 2 * in[1] * in[5]);
- out[7] = 2 * (in[3] * in[4] + in[2] * in[5] + in[1] * in[6] + in[0] * in[7]);
- out[8] = in[4] * in[4]
- + 2 * (in[2] * in[6] + in[0] * in[8] + 2 * (in[1] * in[7] + in[3] * in[5]));
- out[9] = 2 * (in[4] * in[5] + in[3] * in[6] + in[2] * in[7] + in[1] * in[8] + in[0] * in[9]);
- out[10] = 2 * (in[5] * in[5]
- + in[4] * in[6]
- + in[2] * in[8]
- + 2 * (in[3] * in[7] + in[1] * in[9]));
- out[11] = 2 * (in[5] * in[6] + in[4] * in[7] + in[3] * in[8] + in[2] * in[9]);
- out[12] = in[6] * in[6]
- + 2 * (in[4] * in[8] + 2 * (in[5] * in[7] + in[3] * in[9]));
- out[13] = 2 * (in[6] * in[7] + in[5] * in[8] + in[4] * in[9]);
- out[14] = 2 * (in[7] * in[7] + in[6] * in[8] + 2 * in[5] * in[9]);
- out[15] = 2 * (in[7] * in[8] + in[6] * in[9]);
- out[16] = in[8] * in[8] + 4 * in[7] * in[9];
- out[17] = 2 * in[8] * in[9];
- out[18] = 2 * in[9] * in[9];
- }
-
- /**
- * Returns in^2.
- *
- * On entry: The |in| argument is in reduced coefficients form and |in[i]| < 2^27.
- *
- * On exit: The |output| argument is in reduced coefficients form (indeed, one need only provide
- * storage for 10 limbs) and |out[i]| < 2^26.
- */
- static void square(long[] output, long[] in) {
- long[] t = new long[19];
- squareInner(t, in);
- // |t[i]| < 14*2^54 because the largest product of two limbs will be < 2^(27+27) and SquareInner
- // adds together, at most, 14 of those products.
- reduce(t, output);
- }
-
- /**
- * Takes a little-endian, 32-byte number and expands it into mixed radix form.
- */
- static long[] expand(byte[] input) {
- long[] output = new long[LIMB_CNT];
- for (int i = 0; i < LIMB_CNT; i++) {
- output[i] = ((((long) (input[EXPAND_START[i]] & 0xff))
- | ((long) (input[EXPAND_START[i] + 1] & 0xff)) << 8
- | ((long) (input[EXPAND_START[i] + 2] & 0xff)) << 16
- | ((long) (input[EXPAND_START[i] + 3] & 0xff)) << 24) >> EXPAND_SHIFT[i]) & MASK[i & 1];
- }
- return output;
- }
-
- /**
- * Takes a fully reduced mixed radix form number and contract it into a little-endian, 32-byte
- * array.
- *
- * On entry: |input_limbs[i]| < 2^26
- */
- @SuppressWarnings("NarrowingCompoundAssignment")
- static byte[] contract(long[] inputLimbs) {
- long[] input = Arrays.copyOf(inputLimbs, LIMB_CNT);
- for (int j = 0; j < 2; j++) {
- for (int i = 0; i < 9; i++) {
- // This calculation is a time-invariant way to make input[i] non-negative by borrowing
- // from the next-larger limb.
- int carry = -(int) ((input[i] & (input[i] >> 31)) >> SHIFT[i & 1]);
- input[i] = input[i] + (carry << SHIFT[i & 1]);
- input[i + 1] -= carry;
- }
-
- // There's no greater limb for input[9] to borrow from, but we can multiply by 19 and borrow
- // from input[0], which is valid mod 2^255-19.
- {
- int carry = -(int) ((input[9] & (input[9] >> 31)) >> 25);
- input[9] += (carry << 25);
- input[0] -= (carry * 19);
- }
-
- // After the first iteration, input[1..9] are non-negative and fit within 25 or 26 bits,
- // depending on position. However, input[0] may be negative.
- }
-
- // The first borrow-propagation pass above ended with every limb except (possibly) input[0]
- // non-negative.
- //
- // If input[0] was negative after the first pass, then it was because of a carry from input[9].
- // On entry, input[9] < 2^26 so the carry was, at most, one, since (2**26-1) >> 25 = 1. Thus
- // input[0] >= -19.
- //
- // In the second pass, each limb is decreased by at most one. Thus the second borrow-propagation
- // pass could only have wrapped around to decrease input[0] again if the first pass left
- // input[0] negative *and* input[1] through input[9] were all zero. In that case, input[1] is
- // now 2^25 - 1, and this last borrow-propagation step will leave input[1] non-negative.
- {
- int carry = -(int) ((input[0] & (input[0] >> 31)) >> 26);
- input[0] += (carry << 26);
- input[1] -= carry;
- }
-
- // All input[i] are now non-negative. However, there might be values between 2^25 and 2^26 in a
- // limb which is, nominally, 25 bits wide.
- for (int j = 0; j < 2; j++) {
- for (int i = 0; i < 9; i++) {
- int carry = (int) (input[i] >> SHIFT[i & 1]);
- input[i] &= MASK[i & 1];
- input[i + 1] += carry;
- }
- }
-
- {
- int carry = (int) (input[9] >> 25);
- input[9] &= 0x1ffffff;
- input[0] += 19 * carry;
- }
-
- // If the first carry-chain pass, just above, ended up with a carry from input[9], and that
- // caused input[0] to be out-of-bounds, then input[0] was < 2^26 + 2*19, because the carry was,
- // at most, two.
- //
- // If the second pass carried from input[9] again then input[0] is < 2*19 and the input[9] ->
- // input[0] carry didn't push input[0] out of bounds.
-
- // It still remains the case that input might be between 2^255-19 and 2^255. In this case,
- // input[1..9] must take their maximum value and input[0] must be >= (2^255-19) & 0x3ffffff,
- // which is 0x3ffffed.
- int mask = gte((int) input[0], 0x3ffffed);
- for (int i = 1; i < LIMB_CNT; i++) {
- mask &= eq((int) input[i], MASK[i & 1]);
- }
-
- // mask is either 0xffffffff (if input >= 2^255-19) and zero otherwise. Thus this conditionally
- // subtracts 2^255-19.
- input[0] -= mask & 0x3ffffed;
- input[1] -= mask & 0x1ffffff;
- for (int i = 2; i < LIMB_CNT; i += 2) {
- input[i] -= mask & 0x3ffffff;
- input[i + 1] -= mask & 0x1ffffff;
- }
-
- for (int i = 0; i < LIMB_CNT; i++) {
- input[i] <<= EXPAND_SHIFT[i];
- }
- byte[] output = new byte[FIELD_LEN];
- for (int i = 0; i < LIMB_CNT; i++) {
- output[EXPAND_START[i]] |= input[i] & 0xff;
- output[EXPAND_START[i] + 1] |= (input[i] >> 8) & 0xff;
- output[EXPAND_START[i] + 2] |= (input[i] >> 16) & 0xff;
- output[EXPAND_START[i] + 3] |= (input[i] >> 24) & 0xff;
- }
- return output;
- }
-
- /**
- * Computes inverse of z = z(2^255 - 21)
- *
- * Shamelessly copied from agl's code which was shamelessly copied from djb's code. Only the
- * comment format and the variable namings are different from those.
- */
- static void inverse(long[] out, long[] z) {
- long[] z2 = new long[Field25519.LIMB_CNT];
- long[] z9 = new long[Field25519.LIMB_CNT];
- long[] z11 = new long[Field25519.LIMB_CNT];
- long[] z2To5Minus1 = new long[Field25519.LIMB_CNT];
- long[] z2To10Minus1 = new long[Field25519.LIMB_CNT];
- long[] z2To20Minus1 = new long[Field25519.LIMB_CNT];
- long[] z2To50Minus1 = new long[Field25519.LIMB_CNT];
- long[] z2To100Minus1 = new long[Field25519.LIMB_CNT];
- long[] t0 = new long[Field25519.LIMB_CNT];
- long[] t1 = new long[Field25519.LIMB_CNT];
-
- square(z2, z); // 2
- square(t1, z2); // 4
- square(t0, t1); // 8
- mult(z9, t0, z); // 9
- mult(z11, z9, z2); // 11
- square(t0, z11); // 22
- mult(z2To5Minus1, t0, z9); // 2^5 - 2^0 = 31
-
- square(t0, z2To5Minus1); // 2^6 - 2^1
- square(t1, t0); // 2^7 - 2^2
- square(t0, t1); // 2^8 - 2^3
- square(t1, t0); // 2^9 - 2^4
- square(t0, t1); // 2^10 - 2^5
- mult(z2To10Minus1, t0, z2To5Minus1); // 2^10 - 2^0
-
- square(t0, z2To10Minus1); // 2^11 - 2^1
- square(t1, t0); // 2^12 - 2^2
- for (int i = 2; i < 10; i += 2) { // 2^20 - 2^10
- square(t0, t1);
- square(t1, t0);
- }
- mult(z2To20Minus1, t1, z2To10Minus1); // 2^20 - 2^0
-
- square(t0, z2To20Minus1); // 2^21 - 2^1
- square(t1, t0); // 2^22 - 2^2
- for (int i = 2; i < 20; i += 2) { // 2^40 - 2^20
- square(t0, t1);
- square(t1, t0);
- }
- mult(t0, t1, z2To20Minus1); // 2^40 - 2^0
-
- square(t1, t0); // 2^41 - 2^1
- square(t0, t1); // 2^42 - 2^2
- for (int i = 2; i < 10; i += 2) { // 2^50 - 2^10
- square(t1, t0);
- square(t0, t1);
- }
- mult(z2To50Minus1, t0, z2To10Minus1); // 2^50 - 2^0
-
- square(t0, z2To50Minus1); // 2^51 - 2^1
- square(t1, t0); // 2^52 - 2^2
- for (int i = 2; i < 50; i += 2) { // 2^100 - 2^50
- square(t0, t1);
- square(t1, t0);
- }
- mult(z2To100Minus1, t1, z2To50Minus1); // 2^100 - 2^0
-
- square(t1, z2To100Minus1); // 2^101 - 2^1
- square(t0, t1); // 2^102 - 2^2
- for (int i = 2; i < 100; i += 2) { // 2^200 - 2^100
- square(t1, t0);
- square(t0, t1);
- }
- mult(t1, t0, z2To100Minus1); // 2^200 - 2^0
-
- square(t0, t1); // 2^201 - 2^1
- square(t1, t0); // 2^202 - 2^2
- for (int i = 2; i < 50; i += 2) { // 2^250 - 2^50
- square(t0, t1);
- square(t1, t0);
- }
- mult(t0, t1, z2To50Minus1); // 2^250 - 2^0
-
- square(t1, t0); // 2^251 - 2^1
- square(t0, t1); // 2^252 - 2^2
- square(t1, t0); // 2^253 - 2^3
- square(t0, t1); // 2^254 - 2^4
- square(t1, t0); // 2^255 - 2^5
- mult(out, t1, z11); // 2^255 - 21
- }
-
-
- /**
- * Returns 0xffffffff iff a == b and zero otherwise.
- */
- private static int eq(int a, int b) {
- a = ~(a ^ b);
- a &= a << 16;
- a &= a << 8;
- a &= a << 4;
- a &= a << 2;
- a &= a << 1;
- return a >> 31;
- }
-
- /**
- * returns 0xffffffff if a >= b and zero otherwise, where a and b are both non-negative.
- */
- private static int gte(int a, int b) {
- a -= b;
- // a >= 0 iff a >= b.
- return ~(a >> 31);
- }
- }
-
- // (x = 0, y = 1) point
- private static final CachedXYT CACHED_NEUTRAL = new CachedXYT(
- new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- new long[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
- private static final PartialXYZT NEUTRAL = new PartialXYZT(
- new XYZ(new long[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
- new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
- new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0});
-
- /**
- * Projective point representation (X:Y:Z) satisfying x = X/Z, y = Y/Z
- *
- * Note that this is referred as ge_p2 in ref10 impl.
- * Also note that x = X, y = Y and z = Z below following Java coding style.
- *
- * See
- * Koyama K., Tsuruoka Y. (1993) Speeding up Elliptic Cryptosystems by Using a Signed Binary
- * Window Method.
- *
- * https://hyperelliptic.org/EFD/g1p/auto-twisted-projective.html
- */
- private static class XYZ {
-
- final long[] x;
- final long[] y;
- final long[] z;
-
- XYZ() {
- this(new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT]);
- }
-
- XYZ(long[] x, long[] y, long[] z) {
- this.x = x;
- this.y = y;
- this.z = z;
- }
-
- XYZ(XYZ xyz) {
- x = Arrays.copyOf(xyz.x, Field25519.LIMB_CNT);
- y = Arrays.copyOf(xyz.y, Field25519.LIMB_CNT);
- z = Arrays.copyOf(xyz.z, Field25519.LIMB_CNT);
- }
-
- XYZ(PartialXYZT partialXYZT) {
- this();
- fromPartialXYZT(this, partialXYZT);
- }
-
- /**
- * ge_p1p1_to_p2.c
- */
- static XYZ fromPartialXYZT(XYZ out, PartialXYZT in) {
- Field25519.mult(out.x, in.xyz.x, in.t);
- Field25519.mult(out.y, in.xyz.y, in.xyz.z);
- Field25519.mult(out.z, in.xyz.z, in.t);
- return out;
- }
-
- /**
- * Encodes this point to bytes.
- */
- byte[] toBytes() {
- long[] recip = new long[Field25519.LIMB_CNT];
- long[] x = new long[Field25519.LIMB_CNT];
- long[] y = new long[Field25519.LIMB_CNT];
- Field25519.inverse(recip, z);
- Field25519.mult(x, this.x, recip);
- Field25519.mult(y, this.y, recip);
- byte[] s = Field25519.contract(y);
- s[31] = (byte) (s[31] ^ (getLsb(x) << 7));
- return s;
- }
-
-
- /**
- * Best effort fix-timing array comparison.
- *
- * @return true if two arrays are equal.
- */
- private static boolean bytesEqual(final byte[] x, final byte[] y) {
- if (x == null || y == null) {
- return false;
- }
- if (x.length != y.length) {
- return false;
- }
- int res = 0;
- for (int i = 0; i < x.length; i++) {
- res |= x[i] ^ y[i];
- }
- return res == 0;
- }
-
- /**
- * Checks that the point is on curve
- */
- boolean isOnCurve() {
- long[] x2 = new long[Field25519.LIMB_CNT];
- Field25519.square(x2, x);
- long[] y2 = new long[Field25519.LIMB_CNT];
- Field25519.square(y2, y);
- long[] z2 = new long[Field25519.LIMB_CNT];
- Field25519.square(z2, z);
- long[] z4 = new long[Field25519.LIMB_CNT];
- Field25519.square(z4, z2);
- long[] lhs = new long[Field25519.LIMB_CNT];
- // lhs = y^2 - x^2
- Field25519.sub(lhs, y2, x2);
- // lhs = z^2 * (y2 - x2)
- Field25519.mult(lhs, lhs, z2);
- long[] rhs = new long[Field25519.LIMB_CNT];
- // rhs = x^2 * y^2
- Field25519.mult(rhs, x2, y2);
- // rhs = D * x^2 * y^2
- Field25519.mult(rhs, rhs, D);
- // rhs = z^4 + D * x^2 * y^2
- Field25519.sum(rhs, z4);
- // Field25519.mult reduces its output, but Field25519.sum does not, so we have to manually
- // reduce it here.
- Field25519.reduce(rhs, rhs);
- // z^2 (y^2 - x^2) == z^4 + D * x^2 * y^2
- return bytesEqual(Field25519.contract(lhs), Field25519.contract(rhs));
- }
- }
-
- /**
- * Represents extended projective point representation (X:Y:Z:T) satisfying x = X/Z, y = Y/Z,
- * XY = ZT
- *
- * Note that this is referred as ge_p3 in ref10 impl.
- * Also note that t = T below following Java coding style.
- *
- * See
- * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited.
- *
- * https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html
- */
- private static class XYZT {
-
- final XYZ xyz;
- final long[] t;
-
- XYZT() {
- this(new XYZ(), new long[Field25519.LIMB_CNT]);
- }
-
- XYZT(XYZ xyz, long[] t) {
- this.xyz = xyz;
- this.t = t;
- }
-
- XYZT(PartialXYZT partialXYZT) {
- this();
- fromPartialXYZT(this, partialXYZT);
- }
-
- /**
- * ge_p1p1_to_p2.c
- */
- private static XYZT fromPartialXYZT(XYZT out, PartialXYZT in) {
- Field25519.mult(out.xyz.x, in.xyz.x, in.t);
- Field25519.mult(out.xyz.y, in.xyz.y, in.xyz.z);
- Field25519.mult(out.xyz.z, in.xyz.z, in.t);
- Field25519.mult(out.t, in.xyz.x, in.xyz.y);
- return out;
- }
-
- /**
- * Decodes {@code s} into an extented projective point.
- * See Section 5.1.3 Decoding in https://tools.ietf.org/html/rfc8032#section-5.1.3
- */
- private static XYZT fromBytesNegateVarTime(byte[] s) throws GeneralSecurityException {
- long[] x = new long[Field25519.LIMB_CNT];
- long[] y = Field25519.expand(s);
- long[] z = new long[Field25519.LIMB_CNT];
- z[0] = 1;
- long[] t = new long[Field25519.LIMB_CNT];
- long[] u = new long[Field25519.LIMB_CNT];
- long[] v = new long[Field25519.LIMB_CNT];
- long[] vxx = new long[Field25519.LIMB_CNT];
- long[] check = new long[Field25519.LIMB_CNT];
- Field25519.square(u, y);
- Field25519.mult(v, u, D);
- Field25519.sub(u, u, z); // u = y^2 - 1
- Field25519.sum(v, v, z); // v = dy^2 + 1
-
- long[] v3 = new long[Field25519.LIMB_CNT];
- Field25519.square(v3, v);
- Field25519.mult(v3, v3, v); // v3 = v^3
- Field25519.square(x, v3);
- Field25519.mult(x, x, v);
- Field25519.mult(x, x, u); // x = uv^7
-
- pow2252m3(x, x); // x = (uv^7)^((q-5)/8)
- Field25519.mult(x, x, v3);
- Field25519.mult(x, x, u); // x = uv^3(uv^7)^((q-5)/8)
-
- Field25519.square(vxx, x);
- Field25519.mult(vxx, vxx, v);
- Field25519.sub(check, vxx, u); // vx^2-u
- if (isNonZeroVarTime(check)) {
- Field25519.sum(check, vxx, u); // vx^2+u
- if (isNonZeroVarTime(check)) {
- throw new GeneralSecurityException("Cannot convert given bytes to extended projective "
- + "coordinates. No square root exists for modulo 2^255-19");
- }
- Field25519.mult(x, x, SQRTM1);
- }
-
- if (!isNonZeroVarTime(x) && (s[31] & 0xff) >> 7 != 0) {
- throw new GeneralSecurityException("Cannot convert given bytes to extended projective "
- + "coordinates. Computed x is zero and encoded x's least significant bit is not zero");
- }
- if (getLsb(x) == ((s[31] & 0xff) >> 7)) {
- neg(x, x);
- }
-
- Field25519.mult(t, x, y);
- return new XYZT(new XYZ(x, y, z), t);
- }
- }
-
- /**
- * Partial projective point representation ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
- *
- * Note that this is referred as complete form in the original ref10 impl (ge_p1p1).
- * Also note that t = T below following Java coding style.
- *
- * Although this has the same types as XYZT, it is redefined to have its own type so that it is
- * readable and 1:1 corresponds to ref10 impl.
- *
- * Can be converted to XYZT as follows:
- * X1 = X * T = x * Z * T = x * Z1
- * Y1 = Y * Z = y * T * Z = y * Z1
- * Z1 = Z * T = Z * T
- * T1 = X * Y = x * Z * y * T = x * y * Z1 = X1Y1 / Z1
- */
- private static class PartialXYZT {
-
- final XYZ xyz;
- final long[] t;
-
- PartialXYZT() {
- this(new XYZ(), new long[Field25519.LIMB_CNT]);
- }
-
- PartialXYZT(XYZ xyz, long[] t) {
- this.xyz = xyz;
- this.t = t;
- }
-
- PartialXYZT(PartialXYZT other) {
- xyz = new XYZ(other.xyz);
- t = Arrays.copyOf(other.t, Field25519.LIMB_CNT);
- }
- }
-
- /**
- * Corresponds to the caching mentioned in the last paragraph of Section 3.1 of
- * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited.
- * with Z = 1.
- */
- private static class CachedXYT {
-
- final long[] yPlusX;
- final long[] yMinusX;
- final long[] t2d;
-
- /**
- * Creates a cached XYZT with Z = 1
- *
- * @param yPlusX y + x
- * @param yMinusX y - x
- * @param t2d 2d * xy
- */
- CachedXYT(long[] yPlusX, long[] yMinusX, long[] t2d) {
- this.yPlusX = yPlusX;
- this.yMinusX = yMinusX;
- this.t2d = t2d;
- }
-
- CachedXYT(CachedXYT other) {
- yPlusX = Arrays.copyOf(other.yPlusX, Field25519.LIMB_CNT);
- yMinusX = Arrays.copyOf(other.yMinusX, Field25519.LIMB_CNT);
- t2d = Arrays.copyOf(other.t2d, Field25519.LIMB_CNT);
- }
-
- // z is one implicitly, so this just copies {@code in} to {@code output}.
- void multByZ(long[] output, long[] in) {
- System.arraycopy(in, 0, output, 0, Field25519.LIMB_CNT);
- }
-
- /**
- * If icopy is 1, copies {@code other} into this point. Time invariant wrt to icopy value.
- */
- void copyConditional(CachedXYT other, int icopy) {
- copyConditional(yPlusX, other.yPlusX, icopy);
- copyConditional(yMinusX, other.yMinusX, icopy);
- copyConditional(t2d, other.t2d, icopy);
- }
-
- /**
- * Conditionally copies a reduced-form limb arrays {@code b} into {@code a} if {@code icopy} is 1,
- * but leave {@code a} unchanged if 'iswap' is 0. Runs in data-invariant time to avoid
- * side-channel attacks.
- *
- *
NOTE that this function requires that {@code icopy} be 1 or 0; other values give wrong
- * results. Also, the two limb arrays must be in reduced-coefficient, reduced-degree form: the
- * values in a[10..19] or b[10..19] aren't swapped, and all all values in a[0..9],b[0..9] must
- * have magnitude less than Integer.MAX_VALUE.
- */
- static void copyConditional(long[] a, long[] b, int icopy) {
- int copy = -icopy;
- for (int i = 0; i < Field25519.LIMB_CNT; i++) {
- int x = copy & (((int) a[i]) ^ ((int) b[i]));
- a[i] = ((int) a[i]) ^ x;
- }
- }
- }
-
- private static class CachedXYZT extends CachedXYT {
-
- private final long[] z;
-
- CachedXYZT() {
- this(new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT]);
- }
-
- /**
- * ge_p3_to_cached.c
- */
- CachedXYZT(XYZT xyzt) {
- this();
- Field25519.sum(yPlusX, xyzt.xyz.y, xyzt.xyz.x);
- Field25519.sub(yMinusX, xyzt.xyz.y, xyzt.xyz.x);
- System.arraycopy(xyzt.xyz.z, 0, z, 0, Field25519.LIMB_CNT);
- Field25519.mult(t2d, xyzt.t, D2);
- }
-
- /**
- * Creates a cached XYZT
- *
- * @param yPlusX Y + X
- * @param yMinusX Y - X
- * @param z Z
- * @param t2d 2d * (XY/Z)
- */
- CachedXYZT(long[] yPlusX, long[] yMinusX, long[] z, long[] t2d) {
- super(yPlusX, yMinusX, t2d);
- this.z = z;
- }
-
- @Override
- public void multByZ(long[] output, long[] in) {
- Field25519.mult(output, in, z);
- }
- }
-
- /**
- * Addition defined in Section 3.1 of
- * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited.
- *
- * Please note that this is a partial of the operation listed there leaving out the final
- * conversion from PartialXYZT to XYZT.
- *
- * @param extended extended projective point input
- * @param cached cached projective point input
- */
- private static void add(PartialXYZT partialXYZT, XYZT extended, CachedXYT cached) {
- long[] t = new long[Field25519.LIMB_CNT];
-
- // Y1 + X1
- Field25519.sum(partialXYZT.xyz.x, extended.xyz.y, extended.xyz.x);
-
- // Y1 - X1
- Field25519.sub(partialXYZT.xyz.y, extended.xyz.y, extended.xyz.x);
-
- // A = (Y1 - X1) * (Y2 - X2)
- Field25519.mult(partialXYZT.xyz.y, partialXYZT.xyz.y, cached.yMinusX);
-
- // B = (Y1 + X1) * (Y2 + X2)
- Field25519.mult(partialXYZT.xyz.z, partialXYZT.xyz.x, cached.yPlusX);
-
- // C = T1 * 2d * T2 = 2d * T1 * T2 (2d is written as k in the paper)
- Field25519.mult(partialXYZT.t, extended.t, cached.t2d);
-
- // Z1 * Z2
- cached.multByZ(partialXYZT.xyz.x, extended.xyz.z);
-
- // D = 2 * Z1 * Z2
- Field25519.sum(t, partialXYZT.xyz.x, partialXYZT.xyz.x);
-
- // X3 = B - A
- Field25519.sub(partialXYZT.xyz.x, partialXYZT.xyz.z, partialXYZT.xyz.y);
-
- // Y3 = B + A
- Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.y);
-
- // Z3 = D + C
- Field25519.sum(partialXYZT.xyz.z, t, partialXYZT.t);
-
- // T3 = D - C
- Field25519.sub(partialXYZT.t, t, partialXYZT.t);
- }
-
- /**
- * Based on the addition defined in Section 3.1 of
- * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited.
- *
- * Please note that this is a partial of the operation listed there leaving out the final
- * conversion from PartialXYZT to XYZT.
- *
- * @param extended extended projective point input
- * @param cached cached projective point input
- */
- private static void sub(PartialXYZT partialXYZT, XYZT extended, CachedXYT cached) {
- long[] t = new long[Field25519.LIMB_CNT];
-
- // Y1 + X1
- Field25519.sum(partialXYZT.xyz.x, extended.xyz.y, extended.xyz.x);
-
- // Y1 - X1
- Field25519.sub(partialXYZT.xyz.y, extended.xyz.y, extended.xyz.x);
-
- // A = (Y1 - X1) * (Y2 + X2)
- Field25519.mult(partialXYZT.xyz.y, partialXYZT.xyz.y, cached.yPlusX);
-
- // B = (Y1 + X1) * (Y2 - X2)
- Field25519.mult(partialXYZT.xyz.z, partialXYZT.xyz.x, cached.yMinusX);
-
- // C = T1 * 2d * T2 = 2d * T1 * T2 (2d is written as k in the paper)
- Field25519.mult(partialXYZT.t, extended.t, cached.t2d);
-
- // Z1 * Z2
- cached.multByZ(partialXYZT.xyz.x, extended.xyz.z);
-
- // D = 2 * Z1 * Z2
- Field25519.sum(t, partialXYZT.xyz.x, partialXYZT.xyz.x);
-
- // X3 = B - A
- Field25519.sub(partialXYZT.xyz.x, partialXYZT.xyz.z, partialXYZT.xyz.y);
-
- // Y3 = B + A
- Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.y);
-
- // Z3 = D - C
- Field25519.sub(partialXYZT.xyz.z, t, partialXYZT.t);
-
- // T3 = D + C
- Field25519.sum(partialXYZT.t, t, partialXYZT.t);
- }
-
- /**
- * Doubles {@code p} and puts the result into this PartialXYZT.
- *
- * This is based on the addition defined in formula 7 in Section 3.3 of
- * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited.
- *
- * Please note that this is a partial of the operation listed there leaving out the final
- * conversion from PartialXYZT to XYZT and also this fixes a typo in calculation of Y3 and T3 in
- * the paper, H should be replaced with A+B.
- */
- private static void doubleXYZ(PartialXYZT partialXYZT, XYZ p) {
- long[] t0 = new long[Field25519.LIMB_CNT];
-
- // XX = X1^2
- Field25519.square(partialXYZT.xyz.x, p.x);
-
- // YY = Y1^2
- Field25519.square(partialXYZT.xyz.z, p.y);
-
- // B' = Z1^2
- Field25519.square(partialXYZT.t, p.z);
-
- // B = 2 * B'
- Field25519.sum(partialXYZT.t, partialXYZT.t, partialXYZT.t);
-
- // A = X1 + Y1
- Field25519.sum(partialXYZT.xyz.y, p.x, p.y);
-
- // AA = A^2
- Field25519.square(t0, partialXYZT.xyz.y);
-
- // Y3 = YY + XX
- Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.x);
-
- // Z3 = YY - XX
- Field25519.sub(partialXYZT.xyz.z, partialXYZT.xyz.z, partialXYZT.xyz.x);
-
- // X3 = AA - Y3
- Field25519.sub(partialXYZT.xyz.x, t0, partialXYZT.xyz.y);
-
- // T3 = B - Z3
- Field25519.sub(partialXYZT.t, partialXYZT.t, partialXYZT.xyz.z);
- }
-
- /**
- * Doubles {@code p} and puts the result into this PartialXYZT.
- */
- private static void doubleXYZT(PartialXYZT partialXYZT, XYZT p) {
- doubleXYZ(partialXYZT, p.xyz);
- }
-
- /**
- * Compares two byte values in constant time.
- */
- private static int eq(int a, int b) {
- int r = ~(a ^ b) & 0xff;
- r &= r << 4;
- r &= r << 2;
- r &= r << 1;
- return (r >> 7) & 1;
- }
-
- /**
- * This is a constant time operation where point b*B*256^pos is stored in {@code t}.
- * When b is 0, t remains the same (i.e., neutral point).
- *
- * Although B_TABLE[32][8] (B_TABLE[i][j] = (j+1)*B*256^i) has j values in [0, 7], the select
- * method negates the corresponding point if b is negative (which is straight forward in elliptic
- * curves by just negating y coordinate). Therefore we can get multiples of B with the half of
- * memory requirements.
- *
- * @param t neutral element (i.e., point 0), also serves as output.
- * @param pos in B[pos][j] = (j+1)*B*256^pos
- * @param b value in [-8, 8] range.
- */
- private static void select(CachedXYT t, int pos, byte b) {
- int bnegative = (b & 0xff) >> 7;
- int babs = b - (((-bnegative) & b) << 1);
-
- t.copyConditional(B_TABLE[pos][0], eq(babs, 1));
- t.copyConditional(B_TABLE[pos][1], eq(babs, 2));
- t.copyConditional(B_TABLE[pos][2], eq(babs, 3));
- t.copyConditional(B_TABLE[pos][3], eq(babs, 4));
- t.copyConditional(B_TABLE[pos][4], eq(babs, 5));
- t.copyConditional(B_TABLE[pos][5], eq(babs, 6));
- t.copyConditional(B_TABLE[pos][6], eq(babs, 7));
- t.copyConditional(B_TABLE[pos][7], eq(babs, 8));
-
- long[] yPlusX = Arrays.copyOf(t.yMinusX, Field25519.LIMB_CNT);
- long[] yMinusX = Arrays.copyOf(t.yPlusX, Field25519.LIMB_CNT);
- long[] t2d = Arrays.copyOf(t.t2d, Field25519.LIMB_CNT);
- neg(t2d, t2d);
- CachedXYT minust = new CachedXYT(yPlusX, yMinusX, t2d);
- t.copyConditional(minust, bnegative);
- }
-
- /**
- * Computes {@code a}*B
- * where a = a[0]+256*a[1]+...+256^31 a[31] and
- * B is the Ed25519 base point (x,4/5) with x positive.
- *
- * Preconditions:
- * a[31] <= 127
- *
- * @throws IllegalStateException iff there is arithmetic error.
- */
- @SuppressWarnings("NarrowingCompoundAssignment")
- private static XYZ scalarMultWithBase(byte[] a) {
- byte[] e = new byte[2 * Field25519.FIELD_LEN];
- for (int i = 0; i < Field25519.FIELD_LEN; i++) {
- e[2 * i + 0] = (byte) (((a[i] & 0xff) >> 0) & 0xf);
- e[2 * i + 1] = (byte) (((a[i] & 0xff) >> 4) & 0xf);
- }
- // each e[i] is between 0 and 15
- // e[63] is between 0 and 7
-
- // Rewrite e in a way that each e[i] is in [-8, 8].
- // This can be done since a[63] is in [0, 7], the carry-over onto the most significant byte
- // a[63] can be at most 1.
- int carry = 0;
- for (int i = 0; i < e.length - 1; i++) {
- e[i] += carry;
- carry = e[i] + 8;
- carry >>= 4;
- e[i] -= carry << 4;
- }
- e[e.length - 1] += carry;
-
- PartialXYZT ret = new PartialXYZT(NEUTRAL);
- XYZT xyzt = new XYZT();
- // Although B_TABLE's i can be at most 31 (stores only 32 4bit multiples of B) and we have 64
- // 4bit values in e array, the below for loop adds cached values by iterating e by two in odd
- // indices. After the result, we can double the result point 4 times to shift the multiplication
- // scalar by 4 bits.
- for (int i = 1; i < e.length; i += 2) {
- CachedXYT t = new CachedXYT(CACHED_NEUTRAL);
- select(t, i / 2, e[i]);
- add(ret, XYZT.fromPartialXYZT(xyzt, ret), t);
- }
-
- // Doubles the result 4 times to shift the multiplication scalar 4 bits to get the actual result
- // for the odd indices in e.
- XYZ xyz = new XYZ();
- doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
- doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
- doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
- doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret));
-
- // Add multiples of B for even indices of e.
- for (int i = 0; i < e.length; i += 2) {
- CachedXYT t = new CachedXYT(CACHED_NEUTRAL);
- select(t, i / 2, e[i]);
- add(ret, XYZT.fromPartialXYZT(xyzt, ret), t);
- }
-
- // This check is to protect against flaws, i.e. if there is a computation error through a
- // faulty CPU or if the implementation contains a bug.
- XYZ result = new XYZ(ret);
- if (!result.isOnCurve()) {
- throw new IllegalStateException("arithmetic error in scalar multiplication");
- }
- return result;
- }
-
- @SuppressWarnings("NarrowingCompoundAssignment")
- private static byte[] slide(byte[] a) {
- byte[] r = new byte[256];
- // Writes each bit in a[0..31] into r[0..255]:
- // a = a[0]+256*a[1]+...+256^31*a[31] is equal to
- // r = r[0]+2*r[1]+...+2^255*r[255]
- for (int i = 0; i < 256; i++) {
- r[i] = (byte) (1 & ((a[i >> 3] & 0xff) >> (i & 7)));
- }
-
- // Transforms r[i] as odd values in [-15, 15]
- for (int i = 0; i < 256; i++) {
- if (r[i] != 0) {
- for (int b = 1; b <= 6 && i + b < 256; b++) {
- if (r[i + b] != 0) {
- if (r[i] + (r[i + b] << b) <= 15) {
- r[i] += r[i + b] << b;
- r[i + b] = 0;
- } else if (r[i] - (r[i + b] << b) >= -15) {
- r[i] -= r[i + b] << b;
- for (int k = i + b; k < 256; k++) {
- if (r[k] == 0) {
- r[k] = 1;
- break;
- }
- r[k] = 0;
- }
- } else {
- break;
- }
- }
- }
- }
- }
- return r;
- }
-
- /**
- * Computes {@code a}*{@code pointA}+{@code b}*B
- * where a = a[0]+256*a[1]+...+256^31*a[31].
- * and b = b[0]+256*b[1]+...+256^31*b[31].
- * B is the Ed25519 base point (x,4/5) with x positive.
- *
- * Note that execution time varies based on the input since this will only be used in verification
- * of signatures.
- */
- private static XYZ doubleScalarMultVarTime(byte[] a, XYZT pointA, byte[] b) {
- // pointA, 3*pointA, 5*pointA, 7*pointA, 9*pointA, 11*pointA, 13*pointA, 15*pointA
- CachedXYZT[] pointAArray = new CachedXYZT[8];
- pointAArray[0] = new CachedXYZT(pointA);
- PartialXYZT t = new PartialXYZT();
- doubleXYZT(t, pointA);
- XYZT doubleA = new XYZT(t);
- for (int i = 1; i < pointAArray.length; i++) {
- add(t, doubleA, pointAArray[i - 1]);
- pointAArray[i] = new CachedXYZT(new XYZT(t));
- }
-
- byte[] aSlide = slide(a);
- byte[] bSlide = slide(b);
- t = new PartialXYZT(NEUTRAL);
- XYZT u = new XYZT();
- int i = 255;
- for (; i >= 0; i--) {
- if (aSlide[i] != 0 || bSlide[i] != 0) {
- break;
- }
- }
- for (; i >= 0; i--) {
- doubleXYZ(t, new XYZ(t));
- if (aSlide[i] > 0) {
- add(t, XYZT.fromPartialXYZT(u, t), pointAArray[aSlide[i] / 2]);
- } else if (aSlide[i] < 0) {
- sub(t, XYZT.fromPartialXYZT(u, t), pointAArray[-aSlide[i] / 2]);
- }
- if (bSlide[i] > 0) {
- add(t, XYZT.fromPartialXYZT(u, t), B2[bSlide[i] / 2]);
- } else if (bSlide[i] < 0) {
- sub(t, XYZT.fromPartialXYZT(u, t), B2[-bSlide[i] / 2]);
- }
- }
-
- return new XYZ(t);
- }
-
- /**
- * Returns true if {@code in} is nonzero.
- *
- * Note that execution time might depend on the input {@code in}.
- */
- private static boolean isNonZeroVarTime(long[] in) {
- long[] inCopy = new long[in.length + 1];
- System.arraycopy(in, 0, inCopy, 0, in.length);
- Field25519.reduceCoefficients(inCopy);
- byte[] bytes = Field25519.contract(inCopy);
- for (byte b : bytes) {
- if (b != 0) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the least significant bit of {@code in}.
- */
- private static int getLsb(long[] in) {
- return Field25519.contract(in)[0] & 1;
- }
-
- /**
- * Negates all values in {@code in} and store it in {@code out}.
- */
- private static void neg(long[] out, long[] in) {
- for (int i = 0; i < in.length; i++) {
- out[i] = -in[i];
- }
- }
-
- /**
- * Computes {@code in}^(2^252-3) mod 2^255-19 and puts the result in {@code out}.
- */
- private static void pow2252m3(long[] out, long[] in) {
- long[] t0 = new long[Field25519.LIMB_CNT];
- long[] t1 = new long[Field25519.LIMB_CNT];
- long[] t2 = new long[Field25519.LIMB_CNT];
-
- // z2 = z1^2^1
- Field25519.square(t0, in);
-
- // z8 = z2^2^2
- Field25519.square(t1, t0);
- for (int i = 1; i < 2; i++) {
- Field25519.square(t1, t1);
- }
-
- // z9 = z1*z8
- Field25519.mult(t1, in, t1);
-
- // z11 = z2*z9
- Field25519.mult(t0, t0, t1);
-
- // z22 = z11^2^1
- Field25519.square(t0, t0);
-
- // z_5_0 = z9*z22
- Field25519.mult(t0, t1, t0);
-
- // z_10_5 = z_5_0^2^5
- Field25519.square(t1, t0);
- for (int i = 1; i < 5; i++) {
- Field25519.square(t1, t1);
- }
-
- // z_10_0 = z_10_5*z_5_0
- Field25519.mult(t0, t1, t0);
-
- // z_20_10 = z_10_0^2^10
- Field25519.square(t1, t0);
- for (int i = 1; i < 10; i++) {
- Field25519.square(t1, t1);
- }
-
- // z_20_0 = z_20_10*z_10_0
- Field25519.mult(t1, t1, t0);
-
- // z_40_20 = z_20_0^2^20
- Field25519.square(t2, t1);
- for (int i = 1; i < 20; i++) {
- Field25519.square(t2, t2);
- }
-
- // z_40_0 = z_40_20*z_20_0
- Field25519.mult(t1, t2, t1);
-
- // z_50_10 = z_40_0^2^10
- Field25519.square(t1, t1);
- for (int i = 1; i < 10; i++) {
- Field25519.square(t1, t1);
- }
-
- // z_50_0 = z_50_10*z_10_0
- Field25519.mult(t0, t1, t0);
-
- // z_100_50 = z_50_0^2^50
- Field25519.square(t1, t0);
- for (int i = 1; i < 50; i++) {
- Field25519.square(t1, t1);
- }
-
- // z_100_0 = z_100_50*z_50_0
- Field25519.mult(t1, t1, t0);
-
- // z_200_100 = z_100_0^2^100
- Field25519.square(t2, t1);
- for (int i = 1; i < 100; i++) {
- Field25519.square(t2, t2);
- }
-
- // z_200_0 = z_200_100*z_100_0
- Field25519.mult(t1, t2, t1);
-
- // z_250_50 = z_200_0^2^50
- Field25519.square(t1, t1);
- for (int i = 1; i < 50; i++) {
- Field25519.square(t1, t1);
- }
-
- // z_250_0 = z_250_50*z_50_0
- Field25519.mult(t0, t1, t0);
-
- // z_252_2 = z_250_0^2^2
- Field25519.square(t0, t0);
- for (int i = 1; i < 2; i++) {
- Field25519.square(t0, t0);
- }
-
- // z_252_3 = z_252_2*z1
- Field25519.mult(out, t0, in);
- }
-
- /**
- * Returns 3 bytes of {@code in} starting from {@code idx} in Little-Endian format.
- */
- private static long load3(byte[] in, int idx) {
- long result;
- result = (long) in[idx] & 0xff;
- result |= (long) (in[idx + 1] & 0xff) << 8;
- result |= (long) (in[idx + 2] & 0xff) << 16;
- return result;
- }
-
- /**
- * Returns 4 bytes of {@code in} starting from {@code idx} in Little-Endian format.
- */
- private static long load4(byte[] in, int idx) {
- long result = load3(in, idx);
- result |= (long) (in[idx + 3] & 0xff) << 24;
- return result;
- }
-
- /**
- * Input:
- * s[0]+256*s[1]+...+256^63*s[63] = s
- *
- * Output:
- * s[0]+256*s[1]+...+256^31*s[31] = s mod l
- * where l = 2^252 + 27742317777372353535851937790883648493.
- * Overwrites s in place.
- */
- private static void reduce(byte[] s) {
- // Observation:
- // 2^252 mod l is equivalent to -27742317777372353535851937790883648493 mod l
- // Let m = -27742317777372353535851937790883648493
- // Thus a*2^252+b mod l is equivalent to a*m+b mod l
- //
- // First s is divided into chunks of 21 bits as follows:
- // s0+2^21*s1+2^42*s3+...+2^462*s23 = s[0]+256*s[1]+...+256^63*s[63]
- long s0 = 2097151 & load3(s, 0);
- long s1 = 2097151 & (load4(s, 2) >> 5);
- long s2 = 2097151 & (load3(s, 5) >> 2);
- long s3 = 2097151 & (load4(s, 7) >> 7);
- long s4 = 2097151 & (load4(s, 10) >> 4);
- long s5 = 2097151 & (load3(s, 13) >> 1);
- long s6 = 2097151 & (load4(s, 15) >> 6);
- long s7 = 2097151 & (load3(s, 18) >> 3);
- long s8 = 2097151 & load3(s, 21);
- long s9 = 2097151 & (load4(s, 23) >> 5);
- long s10 = 2097151 & (load3(s, 26) >> 2);
- long s11 = 2097151 & (load4(s, 28) >> 7);
- long s12 = 2097151 & (load4(s, 31) >> 4);
- long s13 = 2097151 & (load3(s, 34) >> 1);
- long s14 = 2097151 & (load4(s, 36) >> 6);
- long s15 = 2097151 & (load3(s, 39) >> 3);
- long s16 = 2097151 & load3(s, 42);
- long s17 = 2097151 & (load4(s, 44) >> 5);
- long s18 = 2097151 & (load3(s, 47) >> 2);
- long s19 = 2097151 & (load4(s, 49) >> 7);
- long s20 = 2097151 & (load4(s, 52) >> 4);
- long s21 = 2097151 & (load3(s, 55) >> 1);
- long s22 = 2097151 & (load4(s, 57) >> 6);
- long s23 = (load4(s, 60) >> 3);
- long carry0;
- long carry1;
- long carry2;
- long carry3;
- long carry4;
- long carry5;
- long carry6;
- long carry7;
- long carry8;
- long carry9;
- long carry10;
- long carry11;
- long carry12;
- long carry13;
- long carry14;
- long carry15;
- long carry16;
-
- // s23*2^462 = s23*2^210*2^252 is equivalent to s23*2^210*m in mod l
- // As m is a 125 bit number, the result needs to scattered to 6 limbs (125/21 ceil is 6)
- // starting from s11 (s11*2^210)
- // m = [666643, 470296, 654183, -997805, 136657, -683901] in 21-bit limbs
- s11 += s23 * 666643;
- s12 += s23 * 470296;
- s13 += s23 * 654183;
- s14 -= s23 * 997805;
- s15 += s23 * 136657;
- s16 -= s23 * 683901;
- // s23 = 0;
-
- s10 += s22 * 666643;
- s11 += s22 * 470296;
- s12 += s22 * 654183;
- s13 -= s22 * 997805;
- s14 += s22 * 136657;
- s15 -= s22 * 683901;
- // s22 = 0;
-
- s9 += s21 * 666643;
- s10 += s21 * 470296;
- s11 += s21 * 654183;
- s12 -= s21 * 997805;
- s13 += s21 * 136657;
- s14 -= s21 * 683901;
- // s21 = 0;
-
- s8 += s20 * 666643;
- s9 += s20 * 470296;
- s10 += s20 * 654183;
- s11 -= s20 * 997805;
- s12 += s20 * 136657;
- s13 -= s20 * 683901;
- // s20 = 0;
-
- s7 += s19 * 666643;
- s8 += s19 * 470296;
- s9 += s19 * 654183;
- s10 -= s19 * 997805;
- s11 += s19 * 136657;
- s12 -= s19 * 683901;
- // s19 = 0;
-
- s6 += s18 * 666643;
- s7 += s18 * 470296;
- s8 += s18 * 654183;
- s9 -= s18 * 997805;
- s10 += s18 * 136657;
- s11 -= s18 * 683901;
- // s18 = 0;
-
- // Reduce the bit length of limbs from s6 to s15 to 21-bits.
- carry6 = (s6 + (1 << 20)) >> 21;
- s7 += carry6;
- s6 -= carry6 << 21;
- carry8 = (s8 + (1 << 20)) >> 21;
- s9 += carry8;
- s8 -= carry8 << 21;
- carry10 = (s10 + (1 << 20)) >> 21;
- s11 += carry10;
- s10 -= carry10 << 21;
- carry12 = (s12 + (1 << 20)) >> 21;
- s13 += carry12;
- s12 -= carry12 << 21;
- carry14 = (s14 + (1 << 20)) >> 21;
- s15 += carry14;
- s14 -= carry14 << 21;
- carry16 = (s16 + (1 << 20)) >> 21;
- s17 += carry16;
- s16 -= carry16 << 21;
-
- carry7 = (s7 + (1 << 20)) >> 21;
- s8 += carry7;
- s7 -= carry7 << 21;
- carry9 = (s9 + (1 << 20)) >> 21;
- s10 += carry9;
- s9 -= carry9 << 21;
- carry11 = (s11 + (1 << 20)) >> 21;
- s12 += carry11;
- s11 -= carry11 << 21;
- carry13 = (s13 + (1 << 20)) >> 21;
- s14 += carry13;
- s13 -= carry13 << 21;
- carry15 = (s15 + (1 << 20)) >> 21;
- s16 += carry15;
- s15 -= carry15 << 21;
-
- // Resume reduction where we left off.
- s5 += s17 * 666643;
- s6 += s17 * 470296;
- s7 += s17 * 654183;
- s8 -= s17 * 997805;
- s9 += s17 * 136657;
- s10 -= s17 * 683901;
- // s17 = 0;
-
- s4 += s16 * 666643;
- s5 += s16 * 470296;
- s6 += s16 * 654183;
- s7 -= s16 * 997805;
- s8 += s16 * 136657;
- s9 -= s16 * 683901;
- // s16 = 0;
-
- s3 += s15 * 666643;
- s4 += s15 * 470296;
- s5 += s15 * 654183;
- s6 -= s15 * 997805;
- s7 += s15 * 136657;
- s8 -= s15 * 683901;
- // s15 = 0;
-
- s2 += s14 * 666643;
- s3 += s14 * 470296;
- s4 += s14 * 654183;
- s5 -= s14 * 997805;
- s6 += s14 * 136657;
- s7 -= s14 * 683901;
- // s14 = 0;
-
- s1 += s13 * 666643;
- s2 += s13 * 470296;
- s3 += s13 * 654183;
- s4 -= s13 * 997805;
- s5 += s13 * 136657;
- s6 -= s13 * 683901;
- // s13 = 0;
-
- s0 += s12 * 666643;
- s1 += s12 * 470296;
- s2 += s12 * 654183;
- s3 -= s12 * 997805;
- s4 += s12 * 136657;
- s5 -= s12 * 683901;
- s12 = 0;
-
- // Reduce the range of limbs from s0 to s11 to 21-bits.
- carry0 = (s0 + (1 << 20)) >> 21;
- s1 += carry0;
- s0 -= carry0 << 21;
- carry2 = (s2 + (1 << 20)) >> 21;
- s3 += carry2;
- s2 -= carry2 << 21;
- carry4 = (s4 + (1 << 20)) >> 21;
- s5 += carry4;
- s4 -= carry4 << 21;
- carry6 = (s6 + (1 << 20)) >> 21;
- s7 += carry6;
- s6 -= carry6 << 21;
- carry8 = (s8 + (1 << 20)) >> 21;
- s9 += carry8;
- s8 -= carry8 << 21;
- carry10 = (s10 + (1 << 20)) >> 21;
- s11 += carry10;
- s10 -= carry10 << 21;
-
- carry1 = (s1 + (1 << 20)) >> 21;
- s2 += carry1;
- s1 -= carry1 << 21;
- carry3 = (s3 + (1 << 20)) >> 21;
- s4 += carry3;
- s3 -= carry3 << 21;
- carry5 = (s5 + (1 << 20)) >> 21;
- s6 += carry5;
- s5 -= carry5 << 21;
- carry7 = (s7 + (1 << 20)) >> 21;
- s8 += carry7;
- s7 -= carry7 << 21;
- carry9 = (s9 + (1 << 20)) >> 21;
- s10 += carry9;
- s9 -= carry9 << 21;
- carry11 = (s11 + (1 << 20)) >> 21;
- s12 += carry11;
- s11 -= carry11 << 21;
-
- s0 += s12 * 666643;
- s1 += s12 * 470296;
- s2 += s12 * 654183;
- s3 -= s12 * 997805;
- s4 += s12 * 136657;
- s5 -= s12 * 683901;
- s12 = 0;
-
- // Carry chain reduction to propagate excess bits from s0 to s5 to the most significant limbs.
- carry0 = s0 >> 21;
- s1 += carry0;
- s0 -= carry0 << 21;
- carry1 = s1 >> 21;
- s2 += carry1;
- s1 -= carry1 << 21;
- carry2 = s2 >> 21;
- s3 += carry2;
- s2 -= carry2 << 21;
- carry3 = s3 >> 21;
- s4 += carry3;
- s3 -= carry3 << 21;
- carry4 = s4 >> 21;
- s5 += carry4;
- s4 -= carry4 << 21;
- carry5 = s5 >> 21;
- s6 += carry5;
- s5 -= carry5 << 21;
- carry6 = s6 >> 21;
- s7 += carry6;
- s6 -= carry6 << 21;
- carry7 = s7 >> 21;
- s8 += carry7;
- s7 -= carry7 << 21;
- carry8 = s8 >> 21;
- s9 += carry8;
- s8 -= carry8 << 21;
- carry9 = s9 >> 21;
- s10 += carry9;
- s9 -= carry9 << 21;
- carry10 = s10 >> 21;
- s11 += carry10;
- s10 -= carry10 << 21;
- carry11 = s11 >> 21;
- s12 += carry11;
- s11 -= carry11 << 21;
-
- // Do one last reduction as s12 might be 1.
- s0 += s12 * 666643;
- s1 += s12 * 470296;
- s2 += s12 * 654183;
- s3 -= s12 * 997805;
- s4 += s12 * 136657;
- s5 -= s12 * 683901;
- // s12 = 0;
-
- carry0 = s0 >> 21;
- s1 += carry0;
- s0 -= carry0 << 21;
- carry1 = s1 >> 21;
- s2 += carry1;
- s1 -= carry1 << 21;
- carry2 = s2 >> 21;
- s3 += carry2;
- s2 -= carry2 << 21;
- carry3 = s3 >> 21;
- s4 += carry3;
- s3 -= carry3 << 21;
- carry4 = s4 >> 21;
- s5 += carry4;
- s4 -= carry4 << 21;
- carry5 = s5 >> 21;
- s6 += carry5;
- s5 -= carry5 << 21;
- carry6 = s6 >> 21;
- s7 += carry6;
- s6 -= carry6 << 21;
- carry7 = s7 >> 21;
- s8 += carry7;
- s7 -= carry7 << 21;
- carry8 = s8 >> 21;
- s9 += carry8;
- s8 -= carry8 << 21;
- carry9 = s9 >> 21;
- s10 += carry9;
- s9 -= carry9 << 21;
- carry10 = s10 >> 21;
- s11 += carry10;
- s10 -= carry10 << 21;
-
- // Serialize the result into the s.
- s[0] = (byte) s0;
- s[1] = (byte) (s0 >> 8);
- s[2] = (byte) ((s0 >> 16) | (s1 << 5));
- s[3] = (byte) (s1 >> 3);
- s[4] = (byte) (s1 >> 11);
- s[5] = (byte) ((s1 >> 19) | (s2 << 2));
- s[6] = (byte) (s2 >> 6);
- s[7] = (byte) ((s2 >> 14) | (s3 << 7));
- s[8] = (byte) (s3 >> 1);
- s[9] = (byte) (s3 >> 9);
- s[10] = (byte) ((s3 >> 17) | (s4 << 4));
- s[11] = (byte) (s4 >> 4);
- s[12] = (byte) (s4 >> 12);
- s[13] = (byte) ((s4 >> 20) | (s5 << 1));
- s[14] = (byte) (s5 >> 7);
- s[15] = (byte) ((s5 >> 15) | (s6 << 6));
- s[16] = (byte) (s6 >> 2);
- s[17] = (byte) (s6 >> 10);
- s[18] = (byte) ((s6 >> 18) | (s7 << 3));
- s[19] = (byte) (s7 >> 5);
- s[20] = (byte) (s7 >> 13);
- s[21] = (byte) s8;
- s[22] = (byte) (s8 >> 8);
- s[23] = (byte) ((s8 >> 16) | (s9 << 5));
- s[24] = (byte) (s9 >> 3);
- s[25] = (byte) (s9 >> 11);
- s[26] = (byte) ((s9 >> 19) | (s10 << 2));
- s[27] = (byte) (s10 >> 6);
- s[28] = (byte) ((s10 >> 14) | (s11 << 7));
- s[29] = (byte) (s11 >> 1);
- s[30] = (byte) (s11 >> 9);
- s[31] = (byte) (s11 >> 17);
- }
-
- /**
- * Input:
- * a[0]+256*a[1]+...+256^31*a[31] = a
- * b[0]+256*b[1]+...+256^31*b[31] = b
- * c[0]+256*c[1]+...+256^31*c[31] = c
- *
- * Output:
- * s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l
- * where l = 2^252 + 27742317777372353535851937790883648493.
- */
- private static void mulAdd(byte[] s, byte[] a, byte[] b, byte[] c) {
- // This is very similar to Ed25519.reduce, the difference in here is that it computes ab+c
- // See Ed25519.reduce for related comments.
- long a0 = 2097151 & load3(a, 0);
- long a1 = 2097151 & (load4(a, 2) >> 5);
- long a2 = 2097151 & (load3(a, 5) >> 2);
- long a3 = 2097151 & (load4(a, 7) >> 7);
- long a4 = 2097151 & (load4(a, 10) >> 4);
- long a5 = 2097151 & (load3(a, 13) >> 1);
- long a6 = 2097151 & (load4(a, 15) >> 6);
- long a7 = 2097151 & (load3(a, 18) >> 3);
- long a8 = 2097151 & load3(a, 21);
- long a9 = 2097151 & (load4(a, 23) >> 5);
- long a10 = 2097151 & (load3(a, 26) >> 2);
- long a11 = (load4(a, 28) >> 7);
- long b0 = 2097151 & load3(b, 0);
- long b1 = 2097151 & (load4(b, 2) >> 5);
- long b2 = 2097151 & (load3(b, 5) >> 2);
- long b3 = 2097151 & (load4(b, 7) >> 7);
- long b4 = 2097151 & (load4(b, 10) >> 4);
- long b5 = 2097151 & (load3(b, 13) >> 1);
- long b6 = 2097151 & (load4(b, 15) >> 6);
- long b7 = 2097151 & (load3(b, 18) >> 3);
- long b8 = 2097151 & load3(b, 21);
- long b9 = 2097151 & (load4(b, 23) >> 5);
- long b10 = 2097151 & (load3(b, 26) >> 2);
- long b11 = (load4(b, 28) >> 7);
- long c0 = 2097151 & load3(c, 0);
- long c1 = 2097151 & (load4(c, 2) >> 5);
- long c2 = 2097151 & (load3(c, 5) >> 2);
- long c3 = 2097151 & (load4(c, 7) >> 7);
- long c4 = 2097151 & (load4(c, 10) >> 4);
- long c5 = 2097151 & (load3(c, 13) >> 1);
- long c6 = 2097151 & (load4(c, 15) >> 6);
- long c7 = 2097151 & (load3(c, 18) >> 3);
- long c8 = 2097151 & load3(c, 21);
- long c9 = 2097151 & (load4(c, 23) >> 5);
- long c10 = 2097151 & (load3(c, 26) >> 2);
- long c11 = (load4(c, 28) >> 7);
- long s0;
- long s1;
- long s2;
- long s3;
- long s4;
- long s5;
- long s6;
- long s7;
- long s8;
- long s9;
- long s10;
- long s11;
- long s12;
- long s13;
- long s14;
- long s15;
- long s16;
- long s17;
- long s18;
- long s19;
- long s20;
- long s21;
- long s22;
- long s23;
- long carry0;
- long carry1;
- long carry2;
- long carry3;
- long carry4;
- long carry5;
- long carry6;
- long carry7;
- long carry8;
- long carry9;
- long carry10;
- long carry11;
- long carry12;
- long carry13;
- long carry14;
- long carry15;
- long carry16;
- long carry17;
- long carry18;
- long carry19;
- long carry20;
- long carry21;
- long carry22;
-
- s0 = c0 + a0 * b0;
- s1 = c1 + a0 * b1 + a1 * b0;
- s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0;
- s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0;
- s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0;
- s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0;
- s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0;
- s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0;
- s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1
- + a8 * b0;
- s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2
- + a8 * b1 + a9 * b0;
- s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3
- + a8 * b2 + a9 * b1 + a10 * b0;
- s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4
- + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0;
- s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3
- + a10 * b2 + a11 * b1;
- s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3
- + a11 * b2;
- s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4
- + a11 * b3;
- s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4;
- s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5;
- s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6;
- s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7;
- s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8;
- s20 = a9 * b11 + a10 * b10 + a11 * b9;
- s21 = a10 * b11 + a11 * b10;
- s22 = a11 * b11;
- s23 = 0;
-
- carry0 = (s0 + (1 << 20)) >> 21;
- s1 += carry0;
- s0 -= carry0 << 21;
- carry2 = (s2 + (1 << 20)) >> 21;
- s3 += carry2;
- s2 -= carry2 << 21;
- carry4 = (s4 + (1 << 20)) >> 21;
- s5 += carry4;
- s4 -= carry4 << 21;
- carry6 = (s6 + (1 << 20)) >> 21;
- s7 += carry6;
- s6 -= carry6 << 21;
- carry8 = (s8 + (1 << 20)) >> 21;
- s9 += carry8;
- s8 -= carry8 << 21;
- carry10 = (s10 + (1 << 20)) >> 21;
- s11 += carry10;
- s10 -= carry10 << 21;
- carry12 = (s12 + (1 << 20)) >> 21;
- s13 += carry12;
- s12 -= carry12 << 21;
- carry14 = (s14 + (1 << 20)) >> 21;
- s15 += carry14;
- s14 -= carry14 << 21;
- carry16 = (s16 + (1 << 20)) >> 21;
- s17 += carry16;
- s16 -= carry16 << 21;
- carry18 = (s18 + (1 << 20)) >> 21;
- s19 += carry18;
- s18 -= carry18 << 21;
- carry20 = (s20 + (1 << 20)) >> 21;
- s21 += carry20;
- s20 -= carry20 << 21;
- carry22 = (s22 + (1 << 20)) >> 21;
- s23 += carry22;
- s22 -= carry22 << 21;
-
- carry1 = (s1 + (1 << 20)) >> 21;
- s2 += carry1;
- s1 -= carry1 << 21;
- carry3 = (s3 + (1 << 20)) >> 21;
- s4 += carry3;
- s3 -= carry3 << 21;
- carry5 = (s5 + (1 << 20)) >> 21;
- s6 += carry5;
- s5 -= carry5 << 21;
- carry7 = (s7 + (1 << 20)) >> 21;
- s8 += carry7;
- s7 -= carry7 << 21;
- carry9 = (s9 + (1 << 20)) >> 21;
- s10 += carry9;
- s9 -= carry9 << 21;
- carry11 = (s11 + (1 << 20)) >> 21;
- s12 += carry11;
- s11 -= carry11 << 21;
- carry13 = (s13 + (1 << 20)) >> 21;
- s14 += carry13;
- s13 -= carry13 << 21;
- carry15 = (s15 + (1 << 20)) >> 21;
- s16 += carry15;
- s15 -= carry15 << 21;
- carry17 = (s17 + (1 << 20)) >> 21;
- s18 += carry17;
- s17 -= carry17 << 21;
- carry19 = (s19 + (1 << 20)) >> 21;
- s20 += carry19;
- s19 -= carry19 << 21;
- carry21 = (s21 + (1 << 20)) >> 21;
- s22 += carry21;
- s21 -= carry21 << 21;
-
- s11 += s23 * 666643;
- s12 += s23 * 470296;
- s13 += s23 * 654183;
- s14 -= s23 * 997805;
- s15 += s23 * 136657;
- s16 -= s23 * 683901;
- // s23 = 0;
-
- s10 += s22 * 666643;
- s11 += s22 * 470296;
- s12 += s22 * 654183;
- s13 -= s22 * 997805;
- s14 += s22 * 136657;
- s15 -= s22 * 683901;
- // s22 = 0;
-
- s9 += s21 * 666643;
- s10 += s21 * 470296;
- s11 += s21 * 654183;
- s12 -= s21 * 997805;
- s13 += s21 * 136657;
- s14 -= s21 * 683901;
- // s21 = 0;
-
- s8 += s20 * 666643;
- s9 += s20 * 470296;
- s10 += s20 * 654183;
- s11 -= s20 * 997805;
- s12 += s20 * 136657;
- s13 -= s20 * 683901;
- // s20 = 0;
-
- s7 += s19 * 666643;
- s8 += s19 * 470296;
- s9 += s19 * 654183;
- s10 -= s19 * 997805;
- s11 += s19 * 136657;
- s12 -= s19 * 683901;
- // s19 = 0;
-
- s6 += s18 * 666643;
- s7 += s18 * 470296;
- s8 += s18 * 654183;
- s9 -= s18 * 997805;
- s10 += s18 * 136657;
- s11 -= s18 * 683901;
- // s18 = 0;
-
- carry6 = (s6 + (1 << 20)) >> 21;
- s7 += carry6;
- s6 -= carry6 << 21;
- carry8 = (s8 + (1 << 20)) >> 21;
- s9 += carry8;
- s8 -= carry8 << 21;
- carry10 = (s10 + (1 << 20)) >> 21;
- s11 += carry10;
- s10 -= carry10 << 21;
- carry12 = (s12 + (1 << 20)) >> 21;
- s13 += carry12;
- s12 -= carry12 << 21;
- carry14 = (s14 + (1 << 20)) >> 21;
- s15 += carry14;
- s14 -= carry14 << 21;
- carry16 = (s16 + (1 << 20)) >> 21;
- s17 += carry16;
- s16 -= carry16 << 21;
-
- carry7 = (s7 + (1 << 20)) >> 21;
- s8 += carry7;
- s7 -= carry7 << 21;
- carry9 = (s9 + (1 << 20)) >> 21;
- s10 += carry9;
- s9 -= carry9 << 21;
- carry11 = (s11 + (1 << 20)) >> 21;
- s12 += carry11;
- s11 -= carry11 << 21;
- carry13 = (s13 + (1 << 20)) >> 21;
- s14 += carry13;
- s13 -= carry13 << 21;
- carry15 = (s15 + (1 << 20)) >> 21;
- s16 += carry15;
- s15 -= carry15 << 21;
-
- s5 += s17 * 666643;
- s6 += s17 * 470296;
- s7 += s17 * 654183;
- s8 -= s17 * 997805;
- s9 += s17 * 136657;
- s10 -= s17 * 683901;
- // s17 = 0;
-
- s4 += s16 * 666643;
- s5 += s16 * 470296;
- s6 += s16 * 654183;
- s7 -= s16 * 997805;
- s8 += s16 * 136657;
- s9 -= s16 * 683901;
- // s16 = 0;
-
- s3 += s15 * 666643;
- s4 += s15 * 470296;
- s5 += s15 * 654183;
- s6 -= s15 * 997805;
- s7 += s15 * 136657;
- s8 -= s15 * 683901;
- // s15 = 0;
-
- s2 += s14 * 666643;
- s3 += s14 * 470296;
- s4 += s14 * 654183;
- s5 -= s14 * 997805;
- s6 += s14 * 136657;
- s7 -= s14 * 683901;
- // s14 = 0;
-
- s1 += s13 * 666643;
- s2 += s13 * 470296;
- s3 += s13 * 654183;
- s4 -= s13 * 997805;
- s5 += s13 * 136657;
- s6 -= s13 * 683901;
- // s13 = 0;
-
- s0 += s12 * 666643;
- s1 += s12 * 470296;
- s2 += s12 * 654183;
- s3 -= s12 * 997805;
- s4 += s12 * 136657;
- s5 -= s12 * 683901;
- s12 = 0;
-
- carry0 = (s0 + (1 << 20)) >> 21;
- s1 += carry0;
- s0 -= carry0 << 21;
- carry2 = (s2 + (1 << 20)) >> 21;
- s3 += carry2;
- s2 -= carry2 << 21;
- carry4 = (s4 + (1 << 20)) >> 21;
- s5 += carry4;
- s4 -= carry4 << 21;
- carry6 = (s6 + (1 << 20)) >> 21;
- s7 += carry6;
- s6 -= carry6 << 21;
- carry8 = (s8 + (1 << 20)) >> 21;
- s9 += carry8;
- s8 -= carry8 << 21;
- carry10 = (s10 + (1 << 20)) >> 21;
- s11 += carry10;
- s10 -= carry10 << 21;
-
- carry1 = (s1 + (1 << 20)) >> 21;
- s2 += carry1;
- s1 -= carry1 << 21;
- carry3 = (s3 + (1 << 20)) >> 21;
- s4 += carry3;
- s3 -= carry3 << 21;
- carry5 = (s5 + (1 << 20)) >> 21;
- s6 += carry5;
- s5 -= carry5 << 21;
- carry7 = (s7 + (1 << 20)) >> 21;
- s8 += carry7;
- s7 -= carry7 << 21;
- carry9 = (s9 + (1 << 20)) >> 21;
- s10 += carry9;
- s9 -= carry9 << 21;
- carry11 = (s11 + (1 << 20)) >> 21;
- s12 += carry11;
- s11 -= carry11 << 21;
-
- s0 += s12 * 666643;
- s1 += s12 * 470296;
- s2 += s12 * 654183;
- s3 -= s12 * 997805;
- s4 += s12 * 136657;
- s5 -= s12 * 683901;
- s12 = 0;
-
- carry0 = s0 >> 21;
- s1 += carry0;
- s0 -= carry0 << 21;
- carry1 = s1 >> 21;
- s2 += carry1;
- s1 -= carry1 << 21;
- carry2 = s2 >> 21;
- s3 += carry2;
- s2 -= carry2 << 21;
- carry3 = s3 >> 21;
- s4 += carry3;
- s3 -= carry3 << 21;
- carry4 = s4 >> 21;
- s5 += carry4;
- s4 -= carry4 << 21;
- carry5 = s5 >> 21;
- s6 += carry5;
- s5 -= carry5 << 21;
- carry6 = s6 >> 21;
- s7 += carry6;
- s6 -= carry6 << 21;
- carry7 = s7 >> 21;
- s8 += carry7;
- s7 -= carry7 << 21;
- carry8 = s8 >> 21;
- s9 += carry8;
- s8 -= carry8 << 21;
- carry9 = s9 >> 21;
- s10 += carry9;
- s9 -= carry9 << 21;
- carry10 = s10 >> 21;
- s11 += carry10;
- s10 -= carry10 << 21;
- carry11 = s11 >> 21;
- s12 += carry11;
- s11 -= carry11 << 21;
-
- s0 += s12 * 666643;
- s1 += s12 * 470296;
- s2 += s12 * 654183;
- s3 -= s12 * 997805;
- s4 += s12 * 136657;
- s5 -= s12 * 683901;
- // s12 = 0;
-
- carry0 = s0 >> 21;
- s1 += carry0;
- s0 -= carry0 << 21;
- carry1 = s1 >> 21;
- s2 += carry1;
- s1 -= carry1 << 21;
- carry2 = s2 >> 21;
- s3 += carry2;
- s2 -= carry2 << 21;
- carry3 = s3 >> 21;
- s4 += carry3;
- s3 -= carry3 << 21;
- carry4 = s4 >> 21;
- s5 += carry4;
- s4 -= carry4 << 21;
- carry5 = s5 >> 21;
- s6 += carry5;
- s5 -= carry5 << 21;
- carry6 = s6 >> 21;
- s7 += carry6;
- s6 -= carry6 << 21;
- carry7 = s7 >> 21;
- s8 += carry7;
- s7 -= carry7 << 21;
- carry8 = s8 >> 21;
- s9 += carry8;
- s8 -= carry8 << 21;
- carry9 = s9 >> 21;
- s10 += carry9;
- s9 -= carry9 << 21;
- carry10 = s10 >> 21;
- s11 += carry10;
- s10 -= carry10 << 21;
-
- s[0] = (byte) s0;
- s[1] = (byte) (s0 >> 8);
- s[2] = (byte) ((s0 >> 16) | (s1 << 5));
- s[3] = (byte) (s1 >> 3);
- s[4] = (byte) (s1 >> 11);
- s[5] = (byte) ((s1 >> 19) | (s2 << 2));
- s[6] = (byte) (s2 >> 6);
- s[7] = (byte) ((s2 >> 14) | (s3 << 7));
- s[8] = (byte) (s3 >> 1);
- s[9] = (byte) (s3 >> 9);
- s[10] = (byte) ((s3 >> 17) | (s4 << 4));
- s[11] = (byte) (s4 >> 4);
- s[12] = (byte) (s4 >> 12);
- s[13] = (byte) ((s4 >> 20) | (s5 << 1));
- s[14] = (byte) (s5 >> 7);
- s[15] = (byte) ((s5 >> 15) | (s6 << 6));
- s[16] = (byte) (s6 >> 2);
- s[17] = (byte) (s6 >> 10);
- s[18] = (byte) ((s6 >> 18) | (s7 << 3));
- s[19] = (byte) (s7 >> 5);
- s[20] = (byte) (s7 >> 13);
- s[21] = (byte) s8;
- s[22] = (byte) (s8 >> 8);
- s[23] = (byte) ((s8 >> 16) | (s9 << 5));
- s[24] = (byte) (s9 >> 3);
- s[25] = (byte) (s9 >> 11);
- s[26] = (byte) ((s9 >> 19) | (s10 << 2));
- s[27] = (byte) (s10 >> 6);
- s[28] = (byte) ((s10 >> 14) | (s11 << 7));
- s[29] = (byte) (s11 >> 1);
- s[30] = (byte) (s11 >> 9);
- s[31] = (byte) (s11 >> 17);
- }
-
- // The order of the generator as unsigned bytes in little endian order.
- // (2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed, cf. RFC 7748)
- private static final byte[] GROUP_ORDER = {
- (byte) 0xed, (byte) 0xd3, (byte) 0xf5, (byte) 0x5c,
- (byte) 0x1a, (byte) 0x63, (byte) 0x12, (byte) 0x58,
- (byte) 0xd6, (byte) 0x9c, (byte) 0xf7, (byte) 0xa2,
- (byte) 0xde, (byte) 0xf9, (byte) 0xde, (byte) 0x14,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10};
-
- // Checks whether s represents an integer smaller than the order of the group.
- // This is needed to ensure that EdDSA signatures are non-malleable, as failing to check
- // the range of S allows to modify signatures (cf. RFC 8032, Section 5.2.7 and Section 8.4.)
- // @param s an integer in little-endian order.
- private static boolean isSmallerThanGroupOrder(byte[] s) {
- for (int j = Field25519.FIELD_LEN - 1; j >= 0; j--) {
- // compare unsigned bytes
- int a = s[j] & 0xff;
- int b = GROUP_ORDER[j] & 0xff;
- if (a != b) {
- return a < b;
- }
- }
- return false;
- }
-
- /**
- * Returns true if the EdDSA {@code signature} with {@code message}, can be verified with
- * {@code publicKey}.
- */
- public static boolean verify(final byte[] message, final byte[] signature,
- final byte[] publicKey) {
- try {
- if (signature.length != SIGNATURE_LEN) {
- return false;
- }
- if (publicKey.length != PUBLIC_KEY_LEN) {
- return false;
- }
- byte[] s = Arrays.copyOfRange(signature, Field25519.FIELD_LEN, SIGNATURE_LEN);
- if (!isSmallerThanGroupOrder(s)) {
- return false;
- }
- MessageDigest digest = MessageDigest.getInstance("SHA-512");
- digest.update(signature, 0, Field25519.FIELD_LEN);
- digest.update(publicKey);
- digest.update(message);
- byte[] h = digest.digest();
- reduce(h);
-
- XYZT negPublicKey = XYZT.fromBytesNegateVarTime(publicKey);
- XYZ xyz = doubleScalarMultVarTime(h, negPublicKey, s);
- byte[] expectedR = xyz.toBytes();
- for (int i = 0; i < Field25519.FIELD_LEN; i++) {
- if (expectedR[i] != signature[i]) {
- return false;
- }
- }
- return true;
- } catch (final GeneralSecurityException ignored) {
- return false;
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/crypto/Key.java b/app/src/main/java/com/wireguard/crypto/Key.java
deleted file mode 100644
index 9e25e6057..000000000
--- a/app/src/main/java/com/wireguard/crypto/Key.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.crypto;
-
-import com.wireguard.crypto.KeyFormatException.Type;
-
-import java.security.MessageDigest;
-import java.security.SecureRandom;
-import java.util.Arrays;
-
-/**
- * Represents a WireGuard public or private key. This class uses specialized constant-time base64
- * and hexadecimal codec implementations that resist side-channel attacks.
- *
- * Instances of this class are immutable.
- */
-@SuppressWarnings("MagicNumber")
-public final class Key {
- private final byte[] key;
-
- /**
- * Constructs an object encapsulating the supplied key.
- *
- * @param key an array of bytes containing a binary key. Callers of this constructor are
- * responsible for ensuring that the array is of the correct length.
- */
- private Key(final byte[] key) {
- // Defensively copy to ensure immutability.
- this.key = Arrays.copyOf(key, key.length);
- }
-
- /**
- * Decodes a single 4-character base64 chunk to an integer in constant time.
- *
- * @param src an array of at least 4 characters in base64 format
- * @param srcOffset the offset of the beginning of the chunk in {@code src}
- * @return the decoded 3-byte integer, or some arbitrary integer value if the input was not
- * valid base64
- */
- private static int decodeBase64(final char[] src, final int srcOffset) {
- int val = 0;
- for (int i = 0; i < 4; ++i) {
- final char c = src[i + srcOffset];
- val |= (-1
- + ((((('A' - 1) - c) & (c - ('Z' + 1))) >>> 8) & (c - 64))
- + ((((('a' - 1) - c) & (c - ('z' + 1))) >>> 8) & (c - 70))
- + ((((('0' - 1) - c) & (c - ('9' + 1))) >>> 8) & (c + 5))
- + ((((('+' - 1) - c) & (c - ('+' + 1))) >>> 8) & 63)
- + ((((('/' - 1) - c) & (c - ('/' + 1))) >>> 8) & 64)
- ) << (18 - 6 * i);
- }
- return val;
- }
-
- /**
- * Encodes a single 4-character base64 chunk from 3 consecutive bytes in constant time.
- *
- * @param src an array of at least 3 bytes
- * @param srcOffset the offset of the beginning of the chunk in {@code src}
- * @param dest an array of at least 4 characters
- * @param destOffset the offset of the beginning of the chunk in {@code dest}
- */
- private static void encodeBase64(final byte[] src, final int srcOffset,
- final char[] dest, final int destOffset) {
- final byte[] input = {
- (byte) ((src[srcOffset] >>> 2) & 63),
- (byte) ((src[srcOffset] << 4 | ((src[1 + srcOffset] & 0xff) >>> 4)) & 63),
- (byte) ((src[1 + srcOffset] << 2 | ((src[2 + srcOffset] & 0xff) >>> 6)) & 63),
- (byte) ((src[2 + srcOffset]) & 63),
- };
- for (int i = 0; i < 4; ++i) {
- dest[i + destOffset] = (char) (input[i] + 'A'
- + (((25 - input[i]) >>> 8) & 6)
- - (((51 - input[i]) >>> 8) & 75)
- - (((61 - input[i]) >>> 8) & 15)
- + (((62 - input[i]) >>> 8) & 3));
- }
- }
-
- /**
- * Decodes a WireGuard public or private key from its base64 string representation. This
- * function throws a {@link KeyFormatException} if the source string is not well-formed.
- *
- * @param str the base64 string representation of a WireGuard key
- * @return the decoded key encapsulated in an immutable container
- */
- public static Key fromBase64(final String str) throws KeyFormatException {
- final char[] input = str.toCharArray();
- if (input.length != Format.BASE64.length || input[Format.BASE64.length - 1] != '=')
- throw new KeyFormatException(Format.BASE64, Type.LENGTH);
- final byte[] key = new byte[Format.BINARY.length];
- int i;
- int ret = 0;
- for (i = 0; i < key.length / 3; ++i) {
- final int val = decodeBase64(input, i * 4);
- ret |= val >>> 31;
- key[i * 3] = (byte) ((val >>> 16) & 0xff);
- key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff);
- key[i * 3 + 2] = (byte) (val & 0xff);
- }
- final char[] endSegment = {
- input[i * 4],
- input[i * 4 + 1],
- input[i * 4 + 2],
- 'A',
- };
- final int val = decodeBase64(endSegment, 0);
- ret |= (val >>> 31) | (val & 0xff);
- key[i * 3] = (byte) ((val >>> 16) & 0xff);
- key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff);
-
- if (ret != 0)
- throw new KeyFormatException(Format.BASE64, Type.CONTENTS);
- return new Key(key);
- }
-
- /**
- * Wraps a WireGuard public or private key in an immutable container. This function throws a
- * {@link KeyFormatException} if the source data is not the correct length.
- *
- * @param bytes an array of bytes containing a WireGuard key in binary format
- * @return the key encapsulated in an immutable container
- */
- public static Key fromBytes(final byte[] bytes) throws KeyFormatException {
- if (bytes.length != Format.BINARY.length)
- throw new KeyFormatException(Format.BINARY, Type.LENGTH);
- return new Key(bytes);
- }
-
- /**
- * Decodes a WireGuard public or private key from its hexadecimal string representation. This
- * function throws a {@link KeyFormatException} if the source string is not well-formed.
- *
- * @param str the hexadecimal string representation of a WireGuard key
- * @return the decoded key encapsulated in an immutable container
- */
- public static Key fromHex(final String str) throws KeyFormatException {
- final char[] input = str.toCharArray();
- if (input.length != Format.HEX.length)
- throw new KeyFormatException(Format.HEX, Type.LENGTH);
- final byte[] key = new byte[Format.BINARY.length];
- int ret = 0;
- for (int i = 0; i < key.length; ++i) {
- int c;
- int cNum;
- int cNum0;
- int cAlpha;
- int cAlpha0;
- int cVal;
- final int cAcc;
-
- c = input[i * 2];
- cNum = c ^ 48;
- cNum0 = ((cNum - 10) >>> 8) & 0xff;
- cAlpha = (c & ~32) - 55;
- cAlpha0 = (((cAlpha - 10) ^ (cAlpha - 16)) >>> 8) & 0xff;
- ret |= ((cNum0 | cAlpha0) - 1) >>> 8;
- cVal = (cNum0 & cNum) | (cAlpha0 & cAlpha);
- cAcc = cVal * 16;
-
- c = input[i * 2 + 1];
- cNum = c ^ 48;
- cNum0 = ((cNum - 10) >>> 8) & 0xff;
- cAlpha = (c & ~32) - 55;
- cAlpha0 = (((cAlpha - 10) ^ (cAlpha - 16)) >>> 8) & 0xff;
- ret |= ((cNum0 | cAlpha0) - 1) >>> 8;
- cVal = (cNum0 & cNum) | (cAlpha0 & cAlpha);
- key[i] = (byte) (cAcc | cVal);
- }
- if (ret != 0)
- throw new KeyFormatException(Format.HEX, Type.CONTENTS);
- return new Key(key);
- }
-
- /**
- * Generates a private key using the system's {@link SecureRandom} number generator.
- *
- * @return a well-formed random private key
- */
- static Key generatePrivateKey() {
- final SecureRandom secureRandom = new SecureRandom();
- final byte[] privateKey = new byte[Format.BINARY.getLength()];
- secureRandom.nextBytes(privateKey);
- privateKey[0] &= 248;
- privateKey[31] &= 127;
- privateKey[31] |= 64;
- return new Key(privateKey);
- }
-
- /**
- * Generates a public key from an existing private key.
- *
- * @param privateKey a private key
- * @return a well-formed public key that corresponds to the supplied private key
- */
- static Key generatePublicKey(final Key privateKey) {
- final byte[] publicKey = new byte[Format.BINARY.getLength()];
- Curve25519.eval(publicKey, 0, privateKey.getBytes(), null);
- return new Key(publicKey);
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (obj == this)
- return true;
- if (obj == null || obj.getClass() != getClass())
- return false;
- final Key other = (Key) obj;
- return MessageDigest.isEqual(key, other.key);
- }
-
- /**
- * Returns the key as an array of bytes.
- *
- * @return an array of bytes containing the raw binary key
- */
- public byte[] getBytes() {
- // Defensively copy to ensure immutability.
- return Arrays.copyOf(key, key.length);
- }
-
- @Override
- public int hashCode() {
- int ret = 0;
- for (int i = 0; i < key.length / 4; ++i)
- ret ^= (key[i * 4 + 0] >> 0) + (key[i * 4 + 1] >> 8) + (key[i * 4 + 2] >> 16) + (key[i * 4 + 3] >> 24);
- return ret;
- }
-
- /**
- * Encodes the key to base64.
- *
- * @return a string containing the encoded key
- */
- public String toBase64() {
- final char[] output = new char[Format.BASE64.length];
- int i;
- for (i = 0; i < key.length / 3; ++i)
- encodeBase64(key, i * 3, output, i * 4);
- final byte[] endSegment = {
- key[i * 3],
- key[i * 3 + 1],
- 0,
- };
- encodeBase64(endSegment, 0, output, i * 4);
- output[Format.BASE64.length - 1] = '=';
- return new String(output);
- }
-
- /**
- * Encodes the key to hexadecimal ASCII characters.
- *
- * @return a string containing the encoded key
- */
- public String toHex() {
- final char[] output = new char[Format.HEX.length];
- for (int i = 0; i < key.length; ++i) {
- output[i * 2] = (char) (87 + (key[i] >> 4 & 0xf)
- + ((((key[i] >> 4 & 0xf) - 10) >> 8) & ~38));
- output[i * 2 + 1] = (char) (87 + (key[i] & 0xf)
- + ((((key[i] & 0xf) - 10) >> 8) & ~38));
- }
- return new String(output);
- }
-
- /**
- * The supported formats for encoding a WireGuard key.
- */
- public enum Format {
- BASE64(44),
- BINARY(32),
- HEX(64);
-
- private final int length;
-
- Format(final int length) {
- this.length = length;
- }
-
- public int getLength() {
- return length;
- }
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/crypto/KeyFormatException.java b/app/src/main/java/com/wireguard/crypto/KeyFormatException.java
deleted file mode 100644
index 5818b4d45..000000000
--- a/app/src/main/java/com/wireguard/crypto/KeyFormatException.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.crypto;
-
-/**
- * An exception thrown when attempting to parse an invalid key (too short, too long, or byte
- * data inappropriate for the format). The format being parsed can be accessed with the
- * {@link #getFormat} method.
- */
-public final class KeyFormatException extends Exception {
- private final Key.Format format;
- private final Type type;
-
- KeyFormatException(final Key.Format format, final Type type) {
- this.format = format;
- this.type = type;
- }
-
- public Key.Format getFormat() {
- return format;
- }
-
- public Type getType() {
- return type;
- }
-
- public enum Type {
- CONTENTS,
- LENGTH
- }
-}
diff --git a/app/src/main/java/com/wireguard/crypto/KeyPair.java b/app/src/main/java/com/wireguard/crypto/KeyPair.java
deleted file mode 100644
index f8238e91c..000000000
--- a/app/src/main/java/com/wireguard/crypto/KeyPair.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.crypto;
-
-/**
- * Represents a Curve25519 key pair as used by WireGuard.
- *
- * Instances of this class are immutable.
- */
-public class KeyPair {
- private final Key privateKey;
- private final Key publicKey;
-
- /**
- * Creates a key pair using a newly-generated private key.
- */
- public KeyPair() {
- this(Key.generatePrivateKey());
- }
-
- /**
- * Creates a key pair using an existing private key.
- *
- * @param privateKey a private key, used to derive the public key
- */
- public KeyPair(final Key privateKey) {
- this.privateKey = privateKey;
- publicKey = Key.generatePublicKey(privateKey);
- }
-
- /**
- * Returns the private key from the key pair.
- *
- * @return the private key
- */
- public Key getPrivateKey() {
- return privateKey;
- }
-
- /**
- * Returns the public key from the key pair.
- *
- * @return the public key
- */
- public Key getPublicKey() {
- return publicKey;
- }
-}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/BootReceiver.kt b/app/src/main/java/io/nekohasekai/sagernet/BootReceiver.kt
new file mode 100644
index 000000000..fd037ad49
--- /dev/null
+++ b/app/src/main/java/io/nekohasekai/sagernet/BootReceiver.kt
@@ -0,0 +1,42 @@
+package io.nekohasekai.sagernet
+
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import io.nekohasekai.sagernet.bg.SubscriptionUpdater
+import io.nekohasekai.sagernet.database.DataStore
+import io.nekohasekai.sagernet.ktx.app
+import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
+
+class BootReceiver : BroadcastReceiver() {
+ companion object {
+ private val componentName by lazy { ComponentName(app, BootReceiver::class.java) }
+ var enabled: Boolean
+ get() = app.packageManager.getComponentEnabledSetting(componentName) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ set(value) = app.packageManager.setComponentEnabledSetting(
+ componentName, if (value) PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ else PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
+ )
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ runOnDefaultDispatcher {
+ SubscriptionUpdater.reconfigureUpdater()
+ }
+
+ if (!DataStore.persistAcrossReboot) { // sanity check
+ enabled = false
+ return
+ }
+
+ val doStart = when (intent.action) {
+ Intent.ACTION_LOCKED_BOOT_COMPLETED -> false // DataStore.directBootAware
+ else -> Build.VERSION.SDK_INT < 24 || SagerNet.user.isUserUnlocked
+ } && DataStore.selectedProxy > 0
+
+ if (doStart) SagerNet.startService()
+ }
+}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt
index d84be134a..caf363f6f 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt
@@ -7,6 +7,8 @@ object Key {
const val DB_PUBLIC = "configuration.db"
const val DB_PROFILE = "sager_net.db"
+ const val PERSIST_ACROSS_REBOOT = "isAutoConnect"
+
const val APP_EXPERT = "isExpert"
const val APP_THEME = "appTheme"
const val NIGHT_THEME = "nightTheme"
@@ -14,12 +16,12 @@ object Key {
const val MODE_VPN = "vpn"
const val MODE_PROXY = "proxy"
+ const val GLOBAL_CUSTOM_CONFIG = "globalCustomConfig"
+
const val REMOTE_DNS = "remoteDns"
const val DIRECT_DNS = "directDns"
- const val DIRECT_DNS_USE_SYSTEM = "directDnsUseSystem"
const val ENABLE_DNS_ROUTING = "enableDnsRouting"
const val ENABLE_FAKEDNS = "enableFakeDns"
- const val DNS_NETWORK = "dnsNetwork"
const val IPV6_MODE = "ipv6Mode"
@@ -28,7 +30,6 @@ object Key {
const val INDIVIDUAL = "individual"
const val METERED_NETWORK = "meteredNetwork"
- const val DOMAIN_STRATEGY = "domainStrategy"
const val TRAFFIC_SNIFFING = "trafficSniffing"
const val RESOLVE_DESTINATION = "resolveDestination"
@@ -39,16 +40,13 @@ object Key {
const val ALLOW_ACCESS = "allowAccess"
const val SPEED_INTERVAL = "speedInterval"
const val SHOW_DIRECT_SPEED = "showDirectSpeed"
- const val LOCAL_DNS_PORT = "portLocalDns"
const val APPEND_HTTP_PROXY = "appendHttpProxy"
- const val REQUIRE_TRANSPROXY = "requireTransproxy"
- const val TRANSPROXY_MODE = "transproxyMode"
- const val TRANSPROXY_PORT = "transproxyPort"
const val CONNECTION_TEST_URL = "connectionTestURL"
- const val TCP_KEEP_ALIVE_INTERVAL = "tcpKeepAliveInterval"
+ const val NETWORK_CHANGE_RESET_CONNECTIONS = "networkChangeResetConnections"
+ const val WAKE_RESET_CONNECTIONS = "wakeResetConnections"
const val RULES_PROVIDER = "rulesProvider"
const val LOG_LEVEL = "logLevel"
const val LOG_BUF_SIZE = "logBufSize"
@@ -56,12 +54,13 @@ object Key {
const val ALWAYS_SHOW_ADDRESS = "alwaysShowAddress"
// Protocol Settings
- const val MUX_PROTOCOLS = "mux"
- const val MUX_CONCURRENCY = "muxConcurrency"
+ const val GLOBAL_ALLOW_INSECURE = "globalAllowInsecure"
const val ACQUIRE_WAKE_LOCK = "acquireWakeLock"
const val SHOW_BOTTOM_BAR = "showBottomBar"
+ const val ALLOW_INSECURE_ON_REQUEST = "allowInsecureOnRequest"
+
const val TUN_IMPLEMENTATION = "tunImplementation"
const val PROFILE_TRAFFIC_STATISTICS = "profileTrafficStatistics"
@@ -76,14 +75,13 @@ object Key {
const val SERVER_USERNAME = "serverUsername"
const val SERVER_PASSWORD = "serverPassword"
const val SERVER_METHOD = "serverMethod"
- const val SERVER_PLUGIN = "serverPlugin"
- const val SERVER_PLUGIN_CONFIGURE = "serverPluginConfigure"
const val SERVER_PASSWORD1 = "serverPassword1"
+ const val PROTOCOL_VERSION = "protocolVersion"
+
const val SERVER_PROTOCOL = "serverProtocol"
const val SERVER_OBFS = "serverObfs"
- const val SERVER_SECURITY = "serverSecurity"
const val SERVER_NETWORK = "serverNetwork"
const val SERVER_HOST = "serverHost"
const val SERVER_PATH = "serverPath"
@@ -91,6 +89,7 @@ object Key {
const val SERVER_ENCRYPTION = "serverEncryption"
const val SERVER_ALPN = "serverALPN"
const val SERVER_CERTIFICATES = "serverCertificates"
+ const val SERVER_MTU = "serverMTU"
const val SERVER_CONFIG = "serverConfig"
const val SERVER_CUSTOM = "serverCustom"
@@ -98,6 +97,7 @@ object Key {
const val SERVER_SECURITY_CATEGORY = "serverSecurityCategory"
const val SERVER_TLS_CAMOUFLAGE_CATEGORY = "serverTlsCamouflageCategory"
+ const val SERVER_ECH_CATEORY = "serverECHCategory"
const val SERVER_WS_CATEGORY = "serverWsCategory"
const val SERVER_SS_CATEGORY = "serverSsCategory"
const val SERVER_HEADERS = "serverHeaders"
@@ -108,19 +108,16 @@ object Key {
const val SERVER_DOWNLOAD_SPEED = "serverDownloadSpeed"
const val SERVER_STREAM_RECEIVE_WINDOW = "serverStreamReceiveWindow"
const val SERVER_CONNECTION_RECEIVE_WINDOW = "serverConnectionReceiveWindow"
- const val SERVER_MTU = "serverMTU"
const val SERVER_DISABLE_MTU_DISCOVERY = "serverDisableMtuDiscovery"
const val SERVER_HOP_INTERVAL = "hopInterval"
const val SERVER_PRIVATE_KEY = "serverPrivateKey"
- const val SERVER_LOCAL_ADDRESS = "serverLocalAddress"
const val SERVER_INSECURE_CONCURRENCY = "serverInsecureConcurrency"
const val SERVER_UDP_RELAY_MODE = "serverUDPRelayMode"
const val SERVER_CONGESTION_CONTROLLER = "serverCongestionController"
const val SERVER_DISABLE_SNI = "serverDisableSNI"
const val SERVER_REDUCE_RTT = "serverReduceRTT"
- const val SERVER_FAST_CONNECT = "serverFastConnect"
const val ROUTE_NAME = "routeName"
const val ROUTE_DOMAIN = "routeDomain"
@@ -152,7 +149,6 @@ object Key {
//
- const val NEKO_PLUGIN_MANAGED = "nekoPlugins"
const val APP_TLS_VERSION = "appTLSVersion"
const val ENABLE_CLASH_API = "enableClashAPI"
}
@@ -160,6 +156,7 @@ object Key {
object TunImplementation {
const val GVISOR = 0
const val SYSTEM = 1
+ const val MIXED = 2
}
object IPv6Mode {
diff --git a/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt b/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt
index c082ec3db..f38305d46 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt
@@ -6,8 +6,6 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
-import android.content.pm.PackageInfo
-import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.ConnectivityManager
import android.net.Network
@@ -22,22 +20,23 @@ import go.Seq
import io.nekohasekai.sagernet.bg.SagerConnection
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.ktx.Logs
+import io.nekohasekai.sagernet.ktx.isOss
+import io.nekohasekai.sagernet.ktx.isPreview
import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
import io.nekohasekai.sagernet.ui.MainActivity
import io.nekohasekai.sagernet.utils.*
import kotlinx.coroutines.DEBUG_PROPERTY_NAME
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON
-import libcore.BoxPlatformInterface
import libcore.Libcore
-import libcore.NB4AInterface
+import moe.matsuri.nb4a.NativeInterface
+import moe.matsuri.nb4a.net.LocalResolverImpl
import moe.matsuri.nb4a.utils.JavaUtil
import moe.matsuri.nb4a.utils.cleanWebview
-import java.net.InetSocketAddress
+import java.io.File
import androidx.work.Configuration as WorkConfiguration
class SagerNet : Application(),
- BoxPlatformInterface,
- WorkConfiguration.Provider, NB4AInterface {
+ WorkConfiguration.Provider {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
@@ -45,18 +44,31 @@ class SagerNet : Application(),
application = this
}
- val externalAssets by lazy { getExternalFilesDir(null) ?: filesDir }
- val process = JavaUtil.getProcessName()
- val isMainProcess = process == BuildConfig.APPLICATION_ID
+ private val nativeInterface = NativeInterface()
+
+ val externalAssets: File by lazy { getExternalFilesDir(null) ?: filesDir }
+ val process: String = JavaUtil.getProcessName()
+ private val isMainProcess = process == BuildConfig.APPLICATION_ID
val isBgProcess = process.endsWith(":bg")
override fun onCreate() {
super.onCreate()
- System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)
Thread.setDefaultUncaughtExceptionHandler(CrashHandler)
if (isMainProcess || isBgProcess) {
+ externalAssets.mkdirs()
+ Seq.setContext(this)
+ Libcore.initCore(
+ process,
+ cacheDir.absolutePath + "/",
+ filesDir.absolutePath + "/",
+ externalAssets.absolutePath + "/",
+ DataStore.logBufSize,
+ DataStore.logLevel > 0,
+ nativeInterface, nativeInterface, LocalResolverImpl
+ )
+
// fix multi process issue in Android 9+
JavaUtil.handleWebviewDir(this)
@@ -66,24 +78,6 @@ class SagerNet : Application(),
}
}
- Seq.setContext(this)
- updateNotificationChannels()
-
- // nb4a: init core
- externalAssets.mkdirs()
- Libcore.initCore(
- process,
- cacheDir.absolutePath + "/",
- filesDir.absolutePath + "/",
- externalAssets.absolutePath + "/",
- DataStore.logBufSize,
- DataStore.logLevel > 0,
- this
- )
-
- // libbox: platform interface
- Libcore.setBoxPlatformInterface(this)
-
if (isMainProcess) {
Theme.apply(this)
Theme.applyNightTheme()
@@ -91,24 +85,24 @@ class SagerNet : Application(),
DefaultNetworkListener.start(this) {
underlyingNetwork = it
}
+
+ updateNotificationChannels()
}
}
- if (BuildConfig.DEBUG) StrictMode.setVmPolicy(
- StrictMode.VmPolicy.Builder()
- .detectLeakedSqlLiteObjects()
- .detectLeakedClosableObjects()
- .detectLeakedRegistrationObjects()
- .penaltyLog()
- .build()
- )
+ if (BuildConfig.DEBUG) {
+ System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)
+ StrictMode.setVmPolicy(
+ StrictMode.VmPolicy.Builder()
+ .detectLeakedSqlLiteObjects()
+ .detectLeakedClosableObjects()
+ .detectLeakedRegistrationObjects()
+ .penaltyLog()
+ .build()
+ )
+ }
}
- fun getPackageInfo(packageName: String) = packageManager.getPackageInfo(
- packageName, if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES
- else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES
- )!!
-
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateNotificationChannels()
@@ -135,11 +129,6 @@ class SagerNet : Application(),
uiMode.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION
}
- // /data/user_de available when not unlocked
- val deviceStorage by lazy {
- if (Build.VERSION.SDK_INT < 24) application else DeviceStorageApp(application)
- }
-
val configureIntent: (Context) -> PendingIntent by lazy {
{
PendingIntent.getActivity(
@@ -160,8 +149,6 @@ class SagerNet : Application(),
val uiMode by lazy { application.getSystemService()!! }
val power by lazy { application.getSystemService()!! }
- val packageInfo: PackageInfo by lazy { application.getPackageInfo(application.packageName) }
-
fun getClipboardText(): String {
return clipboard.primaryClip?.takeIf { it.itemCount > 0 }
?.getItemAt(0)?.text?.toString() ?: ""
@@ -193,6 +180,10 @@ class SagerNet : Application(),
"service-subscription",
application.getText(R.string.service_subscription),
NotificationManager.IMPORTANCE_DEFAULT
+ ), NotificationChannel(
+ "connection-test",
+ application.getText(R.string.connection_test),
+ NotificationManager.IMPORTANCE_DEFAULT
)
)
)
@@ -211,59 +202,18 @@ class SagerNet : Application(),
var underlyingNetwork: Network? = null
- }
-
-
- // libbox interface
-
- override fun autoDetectInterfaceControl(fd: Int) {
- DataStore.vpnService?.protect(fd)
- }
-
- override fun openTun(singTunOptionsJson: String, tunPlatformOptionsJson: String): Long {
- if (DataStore.vpnService == null) {
- throw Exception("no VpnService")
- }
- return DataStore.vpnService!!.startVpn(singTunOptionsJson, tunPlatformOptionsJson).toLong()
- }
-
- override fun useProcFS(): Boolean {
- return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
- }
-
- @RequiresApi(Build.VERSION_CODES.Q)
- override fun findConnectionOwner(
- ipProto: Int, srcIp: String, srcPort: Int, destIp: String, destPort: Int
- ): Int {
- return connectivity.getConnectionOwnerUid(
- ipProto, InetSocketAddress(srcIp, srcPort), InetSocketAddress(destIp, destPort)
- )
- }
-
- override fun packageNameByUid(uid: Int): String {
- PackageCache.awaitLoadSync()
-
- if (uid <= 1000L) {
- return "android"
- }
-
- val packageNames = PackageCache.uidMap[uid]
- if (!packageNames.isNullOrEmpty()) for (packageName in packageNames) {
- return packageName
- }
-
- error("unknown uid $uid")
- }
-
- override fun uidByPackageName(packageName: String): Int {
- PackageCache.awaitLoadSync()
- return PackageCache[packageName] ?: 0
- }
-
- // nb4a interface
-
- override fun useOfficialAssets(): Boolean {
- return DataStore.rulesProvider == 0
+ var appVersionNameForDisplay = {
+ var n = BuildConfig.VERSION_NAME
+ if (isPreview) {
+ n += " " + BuildConfig.PRE_VERSION_NAME
+ } else if (!isOss) {
+ n += " ${BuildConfig.FLAVOR}"
+ }
+ if (BuildConfig.DEBUG) {
+ n += " DEBUG"
+ }
+ n
+ }()
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt
index d14a3c28d..e760983dc 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt
@@ -7,6 +7,7 @@ import android.content.IntentFilter
import android.os.*
import android.widget.Toast
import io.nekohasekai.sagernet.Action
+import io.nekohasekai.sagernet.BootReceiver
import io.nekohasekai.sagernet.R
import io.nekohasekai.sagernet.SagerNet
import io.nekohasekai.sagernet.aidl.ISagerNetService
@@ -50,6 +51,19 @@ class BaseService {
Intent.ACTION_SHUTDOWN -> service.persistStats()
Action.RELOAD -> service.reload()
// Action.SWITCH_WAKE_LOCK -> runOnDefaultDispatcher { service.switchWakeLock() }
+ PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (SagerNet.power.isDeviceIdleMode) {
+ proxy?.box?.sleep()
+ } else {
+ proxy?.box?.wake()
+ if (DataStore.wakeResetConnections) {
+ Libcore.resetAllConnections(true)
+ }
+ }
+ }
+ }
+
Action.RESET_UPSTREAM_CONNECTIONS -> runOnDefaultDispatcher {
Libcore.resetAllConnections(true)
runOnMainDispatcher {
@@ -58,6 +72,7 @@ class BaseService {
.show()
}
}
+
else -> service.stopRunner()
}
}
@@ -68,8 +83,9 @@ class BaseService {
fun changeState(s: State, msg: String? = null) {
if (state == s && msg == null) return
- binder.stateChanged(s, msg)
state = s
+ DataStore.serviceState = s
+ binder.stateChanged(s, msg)
}
}
@@ -86,9 +102,13 @@ class BaseService {
override val coroutineContext = Dispatchers.Main.immediate + Job()
override fun getState(): Int = (data?.state ?: State.Idle).ordinal
- override fun getProfileName(): String = data?.proxy?.profile?.displayName() ?: "Idle"
+ override fun getProfileName(): String = data?.proxy?.displayProfileName ?: "Idle"
override fun registerCallback(cb: ISagerNetServiceCallback, id: Int) {
+ if (id == SagerConnection.CONNECTION_ID_RESTART_BG) {
+ Runtime.getRuntime().exit(0)
+ return
+ }
if (!callbackIdMap.contains(cb)) {
callbacks.register(cb)
}
@@ -165,12 +185,10 @@ class BaseService {
val ent = SagerDatabase.proxyDao.getById(DataStore.selectedProxy)
val tag = data.proxy!!.config.profileTagMap[ent?.id] ?: ""
if (tag.isNotBlank() && ent != null) {
- val success = data.proxy!!.box.selectOutbound(tag)
- if (success) runOnDefaultDispatcher {
- data.proxy!!.looper?.selectMain(ent.id)
- val title = ServiceNotification.genTitle(ent)
- data.notification?.postNotificationTitle(title)
- }
+ // select from GUI
+ data.proxy!!.box.selectOutbound(tag)
+ // or select from webui
+ // => selector_OnProxySelected
}
return
}
@@ -189,7 +207,6 @@ class BaseService {
tmpBox.buildConfigTmp()
if (tmpBox.lastSelectorGroupId == data.proxy?.lastSelectorGroupId) {
return true
- // TODO if profile changed?
}
return false
}
@@ -248,7 +265,7 @@ class BaseService {
}
}
- open fun persistStats() {
+ fun persistStats() {
// TODO NEW save app stats?
}
@@ -267,7 +284,9 @@ class BaseService {
}
if (oldName != null && upstreamInterfaceName != null && oldName != upstreamInterfaceName) {
Logs.d("Network changed: $oldName -> $upstreamInterfaceName")
- Libcore.resetAllConnections(true)
+ if (DataStore.networkChangeResetConnections) {
+ Libcore.resetAllConnections(true)
+ }
}
}
}
@@ -275,16 +294,6 @@ class BaseService {
var wakeLock: PowerManager.WakeLock?
fun acquireWakeLock()
- suspend fun switchWakeLock() {
- wakeLock?.apply {
- release()
- wakeLock = null
- data.notification?.postNotificationWakeLockStatus(false)
- } ?: apply {
- acquireWakeLock()
- data.notification?.postNotificationWakeLockStatus(true)
- }
- }
suspend fun lateInit() {
wakeLock?.apply {
@@ -315,14 +324,34 @@ class BaseService {
val proxy = ProxyInstance(profile, this)
data.proxy = proxy
+ BootReceiver.enabled = DataStore.persistAcrossReboot
if (!data.closeReceiverRegistered) {
- registerReceiver(data.receiver, IntentFilter().apply {
+ val filter = IntentFilter().apply {
addAction(Action.RELOAD)
addAction(Intent.ACTION_SHUTDOWN)
addAction(Action.CLOSE)
// addAction(Action.SWITCH_WAKE_LOCK)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
+ }
addAction(Action.RESET_UPSTREAM_CONNECTIONS)
- }, "$packageName.SERVICE", null)
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ registerReceiver(
+ data.receiver,
+ filter,
+ "$packageName.SERVICE",
+ null,
+ Context.RECEIVER_EXPORTED
+ )
+ } else {
+ registerReceiver(
+ data.receiver,
+ filter,
+ "$packageName.SERVICE",
+ null
+ )
+ }
data.closeReceiverRegistered = true
}
@@ -344,19 +373,13 @@ class BaseService {
startProcesses()
data.changeState(State.Connected)
- for ((type, routeName) in proxy.config.alerts) {
- data.binder.broadcast {
- it.routeAlert(type, routeName)
- }
- }
-
lateInit()
} catch (_: CancellationException) { // if the job was cancelled, it is canceller's responsibility to call stopRunner
} catch (_: UnknownHostException) {
stopRunner(false, getString(R.string.invalid_server))
} catch (e: PluginManager.PluginNotFoundException) {
Toast.makeText(this@Interface, e.readableMessage, Toast.LENGTH_SHORT).show()
- Logs.d(e.readableMessage)
+ Logs.w(e)
data.binder.missingPlugin(e.plugin)
stopRunner(false, null)
} catch (exc: Throwable) {
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt
index 8e69f0b6a..5b860b35f 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt
@@ -3,24 +3,21 @@ package io.nekohasekai.sagernet.bg
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
-import android.text.TextUtils
import io.nekohasekai.sagernet.ktx.Logs
import java.io.File
import java.io.IOException
+import androidx.core.text.isDigitsOnly
object Executable {
private val EXECUTABLES = setOf(
- "libtrojan.so",
- "libtrojan-go.so",
- "libnaive.so",
- "libhysteria.so",
- "libwg.so"
+ "libtrojan.so", "libtrojan-go.so", "libnaive.so", "libtuic.so", "libhysteria.so"
)
fun killAll(alsoKillBg: Boolean = false) {
- for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }
- ?: return) {
- val exe = File(try {
+ // kill bg may fail
+ for (process in File("/proc").listFiles { _, name -> name.isDigitsOnly() } ?: return) {
+ val exe = File(
+ try {
File(process, "cmdline").inputStream().bufferedReader().use {
it.readText()
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/GuardedProcessPool.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/GuardedProcessPool.kt
index db7000018..cded1cabb 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/GuardedProcessPool.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/GuardedProcessPool.kt
@@ -64,6 +64,7 @@ class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : C
SystemClock.elapsedRealtime() - startTime < 1000 -> throw IOException(
"$cmdName exits too fast (exit code: $exitCode)"
)
+
exitCode == 128 + OsConstants.SIGKILL -> Logs.w("$cmdName was killed")
else -> Logs.w(IOException("$cmdName unexpectedly exits with code $exitCode"))
}
@@ -99,6 +100,7 @@ class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : C
}
override val coroutineContext = Dispatchers.Main.immediate + Job()
+ var processCount = 0
@MainThread
fun start(
@@ -111,6 +113,7 @@ class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : C
start() // if start fails, IOException will be thrown directly
launch { looper(onRestartCallback) }
}
+ processCount += 1
}
@MainThread
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/SagerConnection.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/SagerConnection.kt
index 0ddf8de08..97ff4b8a0 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/SagerConnection.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/SagerConnection.kt
@@ -24,7 +24,7 @@ class SagerConnection(
val serviceClass
get() = when (DataStore.serviceMode) {
Key.MODE_PROXY -> ProxyService::class
- Key.MODE_VPN -> VpnService::class // Key.MODE_TRANS -> TransproxyService::class
+ Key.MODE_VPN -> VpnService::class
else -> throw UnknownError()
}.java
@@ -32,6 +32,9 @@ class SagerConnection(
const val CONNECTION_ID_TILE = 1
const val CONNECTION_ID_MAIN_ACTIVITY_FOREGROUND = 2
const val CONNECTION_ID_MAIN_ACTIVITY_BACKGROUND = 3
+ const val CONNECTION_ID_RESTART_BG = 4
+
+ var restartingApp = false
}
interface Callback {
@@ -39,11 +42,11 @@ class SagerConnection(
fun cbSpeedUpdate(stats: SpeedDisplayData) {}
fun cbTrafficUpdate(data: TrafficData) {}
+ fun cbSelectorUpdate(id: Long) {}
fun stateChanged(state: BaseService.State, profileName: String?, msg: String?)
fun missingPlugin(profileName: String, pluginName: String) {}
- fun routeAlert(type: Int, routeName: String) {}
fun onServiceConnected(service: ISagerNetService)
@@ -83,17 +86,17 @@ class SagerConnection(
}
}
- override fun missingPlugin(profileName: String, pluginName: String) {
+ override fun cbSelectorUpdate(id: Long) {
val callback = callback ?: return
runOnMainDispatcher {
- callback.missingPlugin(profileName, pluginName)
+ callback.cbSelectorUpdate(id)
}
}
- override fun routeAlert(type: Int, routeName: String) {
+ override fun missingPlugin(profileName: String, pluginName: String) {
val callback = callback ?: return
runOnMainDispatcher {
- callback.routeAlert(type, routeName)
+ callback.missingPlugin(profileName, pluginName)
}
}
@@ -105,7 +108,11 @@ class SagerConnection(
fun updateConnectionId(id: Int) {
connectionId = id
- service?.registerCallback(serviceCallback, id)
+ try {
+ service?.registerCallback(serviceCallback, id)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
}
override fun onServiceConnected(name: ComponentName?, binder: IBinder) {
@@ -120,7 +127,7 @@ class SagerConnection(
} catch (e: RemoteException) {
e.printStackTrace()
}
- callback!!.onServiceConnected(service)
+ callback?.onServiceConnected(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
@@ -133,7 +140,9 @@ class SagerConnection(
override fun binderDied() {
service = null
callbackRegistered = false
- callback?.also { runOnMainDispatcher { it.onBinderDied() } }
+ if (!restartingApp) {
+ callback?.also { runOnMainDispatcher { it.onBinderDied() } }
+ }
}
private fun unregisterCallback() {
@@ -145,7 +154,7 @@ class SagerConnection(
callbackRegistered = false
}
- fun connect(context: Context, callback: Callback) {
+ fun connect(context: Context, callback: Callback?) {
if (connectionActive) return
connectionActive = true
check(this.callback == null)
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt
index dce4b2105..0ac84ac6a 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt
@@ -6,8 +6,10 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
import android.os.Build
import android.text.format.Formatter
+import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import io.nekohasekai.sagernet.Action
@@ -19,8 +21,11 @@ import io.nekohasekai.sagernet.database.ProxyEntity
import io.nekohasekai.sagernet.database.SagerDatabase
import io.nekohasekai.sagernet.ktx.app
import io.nekohasekai.sagernet.ktx.getColorAttr
+import io.nekohasekai.sagernet.ktx.runOnMainDispatcher
import io.nekohasekai.sagernet.ui.SwitchActivity
import io.nekohasekai.sagernet.utils.Theme
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
/**
* User can customize visibility of notification since Android 8.
@@ -48,8 +53,10 @@ class ServiceNotification(
}
}
- fun postNotificationSpeedUpdate(stats: SpeedDisplayData) {
- builder.apply {
+ var listenPostSpeed = true
+
+ suspend fun postNotificationSpeedUpdate(stats: SpeedDisplayData) {
+ useBuilder {
if (showDirectSpeed) {
val speedDetail = (service as Context).getString(
R.string.speed_detail, service.getString(
@@ -64,8 +71,8 @@ class ServiceNotification(
Formatter.formatFileSize(service, stats.rxRateDirect)
)
)
- setStyle(NotificationCompat.BigTextStyle().bigText(speedDetail))
- setContentText(speedDetail)
+ it.setStyle(NotificationCompat.BigTextStyle().bigText(speedDetail))
+ it.setContentText(speedDetail)
} else {
val speedSimple = (service as Context).getString(
R.string.traffic, service.getString(
@@ -74,9 +81,9 @@ class ServiceNotification(
R.string.speed, Formatter.formatFileSize(service, stats.rxRateProxy)
)
)
- setContentText(speedSimple)
+ it.setContentText(speedSimple)
}
- setSubText(
+ it.setSubText(
service.getString(
R.string.traffic,
Formatter.formatFileSize(service, stats.txTotal),
@@ -87,15 +94,19 @@ class ServiceNotification(
update()
}
- fun postNotificationTitle(newTitle: String) {
- builder.setContentTitle(newTitle)
+ suspend fun postNotificationTitle(newTitle: String) {
+ useBuilder {
+ it.setContentTitle(newTitle)
+ }
update()
}
- fun postNotificationWakeLockStatus(acquired: Boolean) {
+ suspend fun postNotificationWakeLockStatus(acquired: Boolean) {
updateActions()
- builder.priority =
- if (acquired) NotificationCompat.PRIORITY_HIGH else NotificationCompat.PRIORITY_LOW
+ useBuilder {
+ it.priority =
+ if (acquired) NotificationCompat.PRIORITY_HIGH else NotificationCompat.PRIORITY_LOW
+ }
update()
}
@@ -111,79 +122,100 @@ class ServiceNotification(
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setPriority(if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN)
+ private val buildLock = Mutex()
+
+ private suspend fun useBuilder(f: (NotificationCompat.Builder) -> Unit) {
+ buildLock.withLock {
+ f(builder)
+ }
+ }
+
init {
service as Context
- updateActions()
Theme.apply(app)
Theme.apply(service)
builder.color = service.getColorAttr(R.attr.colorPrimary)
- updateCallback(SagerNet.power.isInteractive)
service.registerReceiver(this, IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_SCREEN_OFF)
})
- show()
+
+ runOnMainDispatcher {
+ updateActions()
+ show()
+ }
}
- private fun updateActions() {
+ private suspend fun updateActions() {
service as Context
- builder.clearActions()
+ useBuilder {
+ it.clearActions()
- val closeAction = NotificationCompat.Action.Builder(
- 0, service.getText(R.string.stop), PendingIntent.getBroadcast(
- service, 0, Intent(Action.CLOSE).setPackage(service.packageName), flags
- )
- ).setShowsUserInterface(false).build()
- builder.addAction(closeAction)
+ val closeAction = NotificationCompat.Action.Builder(
+ 0, service.getText(R.string.stop), PendingIntent.getBroadcast(
+ service, 0, Intent(Action.CLOSE).setPackage(service.packageName), flags
+ )
+ ).setShowsUserInterface(false).build()
+ it.addAction(closeAction)
- val switchAction = NotificationCompat.Action.Builder(
- 0, service.getString(R.string.action_switch), PendingIntent.getActivity(
- service, 0, Intent(service, SwitchActivity::class.java), flags
- )
- ).setShowsUserInterface(false).build()
- builder.addAction(switchAction)
+ val switchAction = NotificationCompat.Action.Builder(
+ 0, service.getString(R.string.action_switch), PendingIntent.getActivity(
+ service, 0, Intent(service, SwitchActivity::class.java), flags
+ )
+ ).setShowsUserInterface(false).build()
+ it.addAction(switchAction)
- val resetUpstreamAction = NotificationCompat.Action.Builder(
- 0, service.getString(R.string.reset_connections),
- PendingIntent.getBroadcast(
- service, 0, Intent(Action.RESET_UPSTREAM_CONNECTIONS), flags
- )
- ).setShowsUserInterface(false).build()
- builder.addAction(resetUpstreamAction)
-
-// val wakeLockAction = NotificationCompat.Action.Builder(
-// 0,
-// service.getText(if (!wakeLockAcquired) R.string.acquire_wake_lock else R.string.release_wake_lock),
-// PendingIntent.getBroadcast(
-// service,
-// 0,
-// Intent(Action.SWITCH_WAKE_LOCK).setPackage(service.packageName),
-// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
-// )
-// ).setShowsUserInterface(false).build()
-// builder.addAction(wakeLockAction)
+ val resetUpstreamAction = NotificationCompat.Action.Builder(
+ 0, service.getString(R.string.reset_connections),
+ PendingIntent.getBroadcast(
+ service, 0, Intent(Action.RESET_UPSTREAM_CONNECTIONS), flags
+ )
+ ).setShowsUserInterface(false).build()
+ it.addAction(resetUpstreamAction)
+ }
}
override fun onReceive(context: Context, intent: Intent) {
- if (service.data.state == BaseService.State.Connected) updateCallback(intent.action == Intent.ACTION_SCREEN_ON)
+ if (service.data.state == BaseService.State.Connected) {
+ listenPostSpeed = intent.action == Intent.ACTION_SCREEN_ON
+ }
}
- var listenPostSpeed = false
- private fun updateCallback(screenOn: Boolean) {
- if (DataStore.speedInterval == 0) return
- listenPostSpeed = screenOn
- }
+ private suspend fun show() =
+ useBuilder {
+ try {
+ if (Build.VERSION.SDK_INT >= 34) {
+ (service as Service).startForeground(
+ notificationId,
+ it.build(),
+ FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
+ )
+ } else {
+ (service as Service).startForeground(notificationId, it.build())
+ }
+ } catch (e: Exception) {
+ Toast.makeText(
+ SagerNet.application,
+ "startForeground: $e",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
- private fun show() = (service as Service).startForeground(notificationId, builder.build())
- private fun update() =
- NotificationManagerCompat.from(service as Service).notify(notificationId, builder.build())
+ private suspend fun update() = useBuilder {
+ NotificationManagerCompat.from(service as Service).notify(notificationId, it.build())
+ }
fun destroy() {
- (service as Service).stopForeground(true)
+ listenPostSpeed = false
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ (service as Service).stopForeground(Service.STOP_FOREGROUND_REMOVE)
+ } else {
+ (service as Service).stopForeground(true)
+ }
service.unregisterReceiver(this)
- updateCallback(false)
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt
index 5a367ee74..a00067283 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt
@@ -4,7 +4,7 @@ import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
-import androidx.work.ExistingPeriodicWorkPolicy
+import androidx.work.ExistingPeriodicWorkPolicy.UPDATE
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkerParameters
import androidx.work.multiprocess.RemoteWorkManager
@@ -39,7 +39,7 @@ object SubscriptionUpdater {
// main process
RemoteWorkManager.getInstance(app).enqueueUniquePeriodicWork(
WORK_NAME,
- ExistingPeriodicWorkPolicy.REPLACE,
+ UPDATE,
PeriodicWorkRequest.Builder(UpdateTask::class.java, minDelay, TimeUnit.MINUTES)
.apply {
if (minInitDelay > 0) setInitialDelay(minInitDelay, TimeUnit.SECONDS)
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/TileService.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/TileService.kt
index 54239e914..dd46ee5cd 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/TileService.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/TileService.kt
@@ -1,23 +1,3 @@
-/*******************************************************************************
- * *
- * Copyright (C) 2017 by Max Lv *
- * Copyright (C) 2017 by Mygod Studio *
- * *
- * 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 . *
- * *
- *******************************************************************************/
-
package io.nekohasekai.sagernet.bg
import android.graphics.drawable.Icon
@@ -26,6 +6,7 @@ import androidx.annotation.RequiresApi
import io.nekohasekai.sagernet.R
import io.nekohasekai.sagernet.SagerNet
import io.nekohasekai.sagernet.aidl.ISagerNetService
+import io.nekohasekai.sagernet.database.SagerDatabase
import android.service.quicksettings.TileService as BaseTileService
@RequiresApi(24)
@@ -39,16 +20,21 @@ class TileService : BaseTileService(), SagerConnection.Callback {
private val connection = SagerConnection(SagerConnection.CONNECTION_ID_TILE)
override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) =
- updateTile(state) { profileName }
+ updateTile(state, profileName)
override fun onServiceConnected(service: ISagerNetService) {
- updateTile(BaseService.State.values()[service.state]) { service.profileName }
+ updateTile(BaseService.State.values()[service.state], service.profileName)
if (tapPending) {
tapPending = false
onClick()
}
}
+ override fun cbSelectorUpdate(id: Long) {
+ val profile = SagerDatabase.proxyDao.getById(id) ?: return
+ updateTile(BaseService.State.Connected, profile.displayName())
+ }
+
override fun onStartListening() {
super.onStartListening()
connection.connect(this, this)
@@ -60,10 +46,10 @@ class TileService : BaseTileService(), SagerConnection.Callback {
}
override fun onClick() {
- toggle()
+ if (isLocked) unlockAndRun(this::toggle) else toggle()
}
- private fun updateTile(serviceState: BaseService.State, profileName: () -> String?) {
+ private fun updateTile(serviceState: BaseService.State, profileName: String?) {
qsTile?.apply {
label = null
when (serviceState) {
@@ -72,15 +58,18 @@ class TileService : BaseTileService(), SagerConnection.Callback {
icon = iconBusy
state = Tile.STATE_ACTIVE
}
+
BaseService.State.Connected -> {
icon = iconConnected
- label = profileName()
+ label = profileName
state = Tile.STATE_ACTIVE
}
+
BaseService.State.Stopping -> {
icon = iconBusy
state = Tile.STATE_UNAVAILABLE
}
+
BaseService.State.Stopped -> {
icon = iconIdle
state = Tile.STATE_INACTIVE
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt
index b6f0b4376..751c14965 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt
@@ -16,9 +16,6 @@ import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean
import io.nekohasekai.sagernet.ktx.*
import io.nekohasekai.sagernet.ui.VpnRequestActivity
import io.nekohasekai.sagernet.utils.Subnet
-import libcore.*
-import moe.matsuri.nb4a.net.LocalResolverImpl
-import moe.matsuri.nb4a.proxy.neko.needBypassRootUid
import android.net.VpnService as BaseVpnService
class VpnService : BaseVpnService(),
@@ -95,9 +92,8 @@ class VpnService : BaseVpnService(),
// val tunOptions = JSONObject(tunOptionsJson)
// address & route & MTU ...... use NB4A GUI config
- val profile = data.proxy!!.profile
val builder = Builder().setConfigureIntent(SagerNet.configureIntent(this))
- .setSession(profile.displayName())
+ .setSession(getString(R.string.app_name))
.setMtu(DataStore.mtu)
val ipv6Mode = DataStore.ipv6Mode
@@ -132,11 +128,11 @@ class VpnService : BaseVpnService(),
// app route
val packageName = packageName
- var proxyApps = DataStore.proxyApps
+ val proxyApps = DataStore.proxyApps
var bypass = DataStore.bypass
- var workaroundSYSTEM = false /* DataStore.tunImplementation == TunImplementation.SYSTEM */
- var needBypassRootUid = workaroundSYSTEM || data.proxy!!.config.trafficMap.values.any {
- it[0].nekoBean?.needBypassRootUid() == true || it[0].hysteriaBean?.protocol == HysteriaBean.PROTOCOL_FAKETCP
+ val workaroundSYSTEM = false /* DataStore.tunImplementation == TunImplementation.SYSTEM */
+ val needBypassRootUid = workaroundSYSTEM || data.proxy!!.config.trafficMap.values.any {
+ it[0].hysteriaBean?.protocol == HysteriaBean.PROTOCOL_FAKETCP
}
if (proxyApps || needBypassRootUid) {
@@ -200,9 +196,6 @@ class VpnService : BaseVpnService(),
if (Build.VERSION.SDK_INT >= 29) builder.setMetered(metered)
conn = builder.establish() ?: throw NullConnectionException()
- // post setup
- Libcore.setLocalResolver(LocalResolverImpl)
-
return conn!!.fd
}
@@ -218,7 +211,6 @@ class VpnService : BaseVpnService(),
override fun onRevoke() = stopRunner()
override fun onDestroy() {
- Libcore.setLocalResolver(null)
DataStore.vpnService = null
super.onDestroy()
data.binder.close()
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt
index 190a99e4f..9f16723de 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt
@@ -1,6 +1,5 @@
package io.nekohasekai.sagernet.bg.proto
-import android.os.Build
import android.os.SystemClock
import io.nekohasekai.sagernet.SagerNet
import io.nekohasekai.sagernet.bg.AbstractInstance
@@ -10,23 +9,19 @@ import io.nekohasekai.sagernet.database.ProxyEntity
import io.nekohasekai.sagernet.fmt.ConfigBuildResult
import io.nekohasekai.sagernet.fmt.buildConfig
import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean
-import io.nekohasekai.sagernet.fmt.hysteria.buildHysteriaConfig
+import io.nekohasekai.sagernet.fmt.hysteria.buildHysteria1Config
+import io.nekohasekai.sagernet.fmt.mieru.MieruBean
+import io.nekohasekai.sagernet.fmt.mieru.buildMieruConfig
import io.nekohasekai.sagernet.fmt.naive.NaiveBean
import io.nekohasekai.sagernet.fmt.naive.buildNaiveConfig
import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean
import io.nekohasekai.sagernet.fmt.trojan_go.buildTrojanGoConfig
-import io.nekohasekai.sagernet.fmt.tuic.TuicBean
-import io.nekohasekai.sagernet.fmt.tuic.buildTuicConfig
import io.nekohasekai.sagernet.ktx.*
import io.nekohasekai.sagernet.plugin.PluginManager
import kotlinx.coroutines.*
import libcore.BoxInstance
import libcore.Libcore
-import moe.matsuri.nb4a.plugin.NekoPluginManager
-import moe.matsuri.nb4a.proxy.neko.NekoBean
-import moe.matsuri.nb4a.proxy.neko.NekoJSInterface
-import moe.matsuri.nb4a.proxy.neko.updateAllConfig
-import org.json.JSONObject
+import moe.matsuri.nb4a.net.LocalResolverImpl
import java.io.File
abstract class BoxInstance(
@@ -54,8 +49,7 @@ abstract class BoxInstance(
}
protected open suspend fun loadConfig() {
- NekoJSInterface.Default.destroyAllJsi()
- box = Libcore.newSingBoxInstance(config.config)
+ box = Libcore.newSingBoxInstance(config.config, LocalResolverImpl)
}
open suspend fun init() {
@@ -67,13 +61,20 @@ abstract class BoxInstance(
initPlugin("trojan-go-plugin")
pluginConfigs[port] = profile.type to bean.buildTrojanGoConfig(port)
}
+
+ is MieruBean -> {
+ initPlugin("mieru-plugin")
+ pluginConfigs[port] = profile.type to bean.buildMieruConfig(port)
+ }
+
is NaiveBean -> {
initPlugin("naive-plugin")
pluginConfigs[port] = profile.type to bean.buildNaiveConfig(port)
}
+
is HysteriaBean -> {
initPlugin("hysteria-plugin")
- pluginConfigs[port] = profile.type to bean.buildHysteriaConfig(port) {
+ pluginConfigs[port] = profile.type to bean.buildHysteria1Config(port) {
File(
app.cacheDir, "hysteria_" + SystemClock.elapsedRealtime() + ".ca"
).apply {
@@ -82,28 +83,6 @@ abstract class BoxInstance(
}
}
}
- is TuicBean -> {
- initPlugin("tuic-plugin")
- pluginConfigs[port] = profile.type to bean.buildTuicConfig(port) {
- File(
- app.noBackupFilesDir,
- "tuic_" + SystemClock.elapsedRealtime() + ".ca"
- ).apply {
- parentFile?.mkdirs()
- cacheFiles.add(this)
- }
- }
- }
- is NekoBean -> {
- // check if plugin binary can be loaded
- initPlugin(bean.plgId)
-
- // build config and check if succeed
- bean.updateAllConfig(port)
- if (bean.allConfig == null) {
- throw NekoPluginManager.PluginInternalException(bean.protocolId)
- }
- }
}
}
}
@@ -112,10 +91,8 @@ abstract class BoxInstance(
override fun launch() {
// TODO move, this is not box
- val context =
- if (Build.VERSION.SDK_INT < 24 || SagerNet.user.isUserUnlocked) SagerNet.application else SagerNet.deviceStorage
- val cache = File(context.cacheDir, "tmpcfg")
- cache.mkdirs()
+ val cacheDir = File(SagerNet.application.cacheDir, "tmpcfg")
+ cacheDir.mkdirs()
for ((chain) in config.externalIndex) {
chain.entries.forEachIndexed { index, (port, profile) ->
@@ -127,9 +104,10 @@ abstract class BoxInstance(
externalInstances.containsKey(port) -> {
externalInstances[port]!!.launch()
}
+
bean is TrojanGoBean -> {
val configFile = File(
- cache, "trojan_go_" + SystemClock.elapsedRealtime() + ".json"
+ cacheDir, "trojan_go_" + SystemClock.elapsedRealtime() + ".json"
)
configFile.parentFile?.mkdirs()
configFile.writeText(config)
@@ -141,9 +119,30 @@ abstract class BoxInstance(
processes.start(commands)
}
+
+ bean is MieruBean -> {
+ val configFile = File(
+ cacheDir, "mieru_" + SystemClock.elapsedRealtime() + ".json"
+ )
+
+ configFile.parentFile?.mkdirs()
+ configFile.writeText(config)
+ cacheFiles.add(configFile)
+
+ val envMap = mutableMapOf()
+ envMap["MIERU_CONFIG_JSON_FILE"] = configFile.absolutePath
+ envMap["MIERU_PROTECT_PATH"] = "protect_path"
+
+ val commands = mutableListOf(
+ initPlugin("mieru-plugin").path, "run",
+ )
+
+ processes.start(commands, envMap)
+ }
+
bean is NaiveBean -> {
val configFile = File(
- cache, "naive_" + SystemClock.elapsedRealtime() + ".json"
+ cacheDir, "naive_" + SystemClock.elapsedRealtime() + ".json"
)
configFile.parentFile?.mkdirs()
@@ -154,7 +153,7 @@ abstract class BoxInstance(
if (bean.certificates.isNotBlank()) {
val certFile = File(
- cache, "naive_" + SystemClock.elapsedRealtime() + ".crt"
+ cacheDir, "naive_" + SystemClock.elapsedRealtime() + ".crt"
)
certFile.parentFile?.mkdirs()
@@ -170,9 +169,10 @@ abstract class BoxInstance(
processes.start(commands, envMap)
}
+
bean is HysteriaBean -> {
val configFile = File(
- cache, "hysteria_" + SystemClock.elapsedRealtime() + ".json"
+ cacheDir, "hysteria_" + SystemClock.elapsedRealtime() + ".json"
)
configFile.parentFile?.mkdirs()
@@ -193,61 +193,6 @@ abstract class BoxInstance(
commands.addAll(0, listOf("su", "-c"))
}
- processes.start(commands)
- }
- bean is NekoBean -> {
- // config built from JS
- val nekoRunConfigs = bean.allConfig.optJSONArray("nekoRunConfigs")
- val configs = mutableMapOf()
-
- nekoRunConfigs?.forEach { _, any ->
- any as JSONObject
-
- val name = any.getString("name")
- val configFile = File(cache, name)
- configFile.parentFile?.mkdirs()
- val content = any.getString("content")
- configFile.writeText(content)
-
- cacheFiles.add(configFile)
- configs[name] = configFile.absolutePath
-
- Logs.d(name + "\n\n" + content)
- }
-
- val nekoCommands = bean.allConfig.getJSONArray("nekoCommands")
- val commands = mutableListOf()
-
- nekoCommands.forEach { _, any ->
- if (any is String) {
- if (configs.containsKey(any)) {
- commands.add(configs[any]!!)
- } else if (any == "%exe%") {
- commands.add(initPlugin(bean.plgId).path)
- } else {
- commands.add(any)
- }
- }
- }
-
- processes.start(commands)
- }
- bean is TuicBean -> {
- val configFile = File(
- context.noBackupFilesDir,
- "tuic_" + SystemClock.elapsedRealtime() + ".json"
- )
-
- configFile.parentFile?.mkdirs()
- configFile.writeText(config)
- cacheFiles.add(configFile)
-
- val commands = mutableListOf(
- initPlugin("tuic-plugin").path,
- "-c",
- configFile.absolutePath,
- )
-
processes.start(commands)
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt
index 81178fdbc..9758a5c40 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt
@@ -2,6 +2,7 @@ package io.nekohasekai.sagernet.bg.proto
import io.nekohasekai.sagernet.BuildConfig
import io.nekohasekai.sagernet.bg.BaseService
+import io.nekohasekai.sagernet.bg.ServiceNotification
import io.nekohasekai.sagernet.database.ProxyEntity
import io.nekohasekai.sagernet.ktx.Logs
import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
@@ -11,9 +12,11 @@ import moe.matsuri.nb4a.utils.JavaUtil
class ProxyInstance(profile: ProxyEntity, var service: BaseService.Interface? = null) :
BoxInstance(profile) {
- var lastSelectorGroupId = -1L
var notTmp = true
+ var lastSelectorGroupId = -1L
+ var displayProfileName = ServiceNotification.genTitle(profile)
+
// for TrafficLooper
var looper: TrafficLooper? = null
@@ -39,9 +42,13 @@ class ProxyInstance(profile: ProxyEntity, var service: BaseService.Interface? =
}
}
+ override suspend fun loadConfig() {
+ super.loadConfig()
+ }
+
override fun launch() {
box.setAsMain()
- super.launch()
+ super.launch() // start box
runOnDefaultDispatcher {
looper = service?.let { TrafficLooper(it.data, this) }
looper?.start()
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TestInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TestInstance.kt
index e4b85df0f..bc6907137 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TestInstance.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TestInstance.kt
@@ -8,10 +8,12 @@ import io.nekohasekai.sagernet.ktx.Logs
import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
import io.nekohasekai.sagernet.ktx.tryResume
import io.nekohasekai.sagernet.ktx.tryResumeWithException
+import kotlinx.coroutines.delay
import libcore.Libcore
+import moe.matsuri.nb4a.net.LocalResolverImpl
import kotlin.coroutines.suspendCoroutine
-class TestInstance(profile: ProxyEntity, val link: String, val timeout: Int) :
+class TestInstance(profile: ProxyEntity, val link: String, private val timeout: Int) :
BoxInstance(profile) {
suspend fun doTest(): Int {
@@ -25,6 +27,10 @@ class TestInstance(profile: ProxyEntity, val link: String, val timeout: Int) :
try {
init()
launch()
+ if (processes.processCount > 0) {
+ // wait for plugin start
+ delay(500)
+ }
c.tryResume(Libcore.urlTest(box, link, timeout))
} catch (e: Exception) {
c.tryResumeWithException(e)
@@ -41,8 +47,7 @@ class TestInstance(profile: ProxyEntity, val link: String, val timeout: Int) :
override suspend fun loadConfig() {
// don't call destroyAllJsi here
if (BuildConfig.DEBUG) Logs.d(config.config)
- box = Libcore.newSingBoxInstance(config.config)
- box.forTest = true
+ box = Libcore.newSingBoxInstance(config.config, LocalResolverImpl)
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficLooper.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficLooper.kt
index 98bfbdda4..2d4bf7323 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficLooper.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficLooper.kt
@@ -9,6 +9,7 @@ import io.nekohasekai.sagernet.database.ProfileManager
import io.nekohasekai.sagernet.fmt.TAG_BYPASS
import io.nekohasekai.sagernet.fmt.TAG_PROXY
import io.nekohasekai.sagernet.ktx.Logs
+import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
import kotlinx.coroutines.*
class TrafficLooper
@@ -17,7 +18,8 @@ class TrafficLooper
) {
private var job: Job? = null
- private val items = mutableMapOf() // associate ent id
+ private val idMap = mutableMapOf() // id to 1 data
+ private val tagMap = mutableMapOf() // tag to 1 data
suspend fun stop() {
job?.cancel()
@@ -26,7 +28,7 @@ class TrafficLooper
val traffic = mutableMapOf()
data.proxy?.config?.trafficMap?.forEach { (_, ents) ->
for (ent in ents) {
- val item = items[ent.id] ?: return@forEach
+ val item = idMap[ent.id] ?: return@forEach
ent.rx = item.rx
ent.tx = item.tx
ProfileManager.updateProfile(ent) // update DB
@@ -54,15 +56,25 @@ class TrafficLooper
fun selectMain(id: Long) {
Logs.d("select traffic count $TAG_PROXY to $id, old id is $selectorNowId")
- val oldData = items[selectorNowId]
- val data = items[id] ?: return
+ val oldData = idMap[selectorNowId]
+ val newData = idMap[id] ?: return
oldData?.apply {
tag = selectorNowFakeTag
ignore = true
+ // post traffic when switch
+ if (DataStore.profileTrafficStatistics) {
+ data.proxy?.config?.trafficMap?.get(tag)?.firstOrNull()?.let {
+ it.rx = rx
+ it.tx = tx
+ runOnDefaultDispatcher {
+ ProfileManager.updateProfile(it) // update DB
+ }
+ }
+ }
}
- selectorNowFakeTag = data.tag
+ selectorNowFakeTag = newData.tag
selectorNowId = id
- data.apply {
+ newData.apply {
tag = TAG_PROXY
ignore = false
}
@@ -78,19 +90,19 @@ class TrafficLooper
var proxy: ProxyInstance?
// for display
- var itemMain: TrafficUpdater.TrafficLooperData? = null
- var itemMainBase: TrafficUpdater.TrafficLooperData? = null
- var itemBypass: TrafficUpdater.TrafficLooperData? = null
+ val itemBypass = TrafficUpdater.TrafficLooperData(tag = TAG_BYPASS)
while (sc.isActive) {
- delay(delayMs)
- proxy = data.proxy ?: continue
+ proxy = data.proxy
+ if (proxy == null) {
+ delay(delayMs)
+ continue
+ }
if (trafficUpdater == null) {
if (!proxy.isInitialized()) continue
- items.clear()
- itemBypass = TrafficUpdater.TrafficLooperData(tag = "bypass")
- items[-1] = itemBypass
+ idMap.clear()
+ idMap[-1] = itemBypass
//
val tags = hashSetOf(TAG_PROXY, TAG_BYPASS)
proxy.config.trafficMap.forEach { (tag, ents) ->
@@ -100,30 +112,21 @@ class TrafficLooper
tag = tag,
rx = ent.rx,
tx = ent.tx,
+ rxBase = ent.rx,
+ txBase = ent.tx,
ignore = proxy.config.selectorGroupId >= 0L,
)
- if (tag == TAG_PROXY && itemMain == null) {
- itemMain = item
- itemMainBase = TrafficUpdater.TrafficLooperData(
- tag = tag,
- rx = ent.rx,
- tx = ent.tx,
- )
- Logs.d("traffic count $tag to main to ${ent.id}")
- }
- items[ent.id] = item
+ idMap[ent.id] = item
+ tagMap[tag] = item
Logs.d("traffic count $tag to ${ent.id}")
}
}
if (proxy.config.selectorGroupId >= 0L) {
- itemMain = TrafficUpdater.TrafficLooperData(tag = TAG_PROXY)
- itemMainBase = TrafficUpdater.TrafficLooperData(tag = TAG_PROXY)
- items[-2] = itemMain!!
selectMain(proxy.config.mainEntId)
}
//
trafficUpdater = TrafficUpdater(
- box = proxy.box, items = items.values.toList()
+ box = proxy.box, items = idMap.values.toList()
)
proxy.box.setV2rayStats(tags.joinToString("\n"))
}
@@ -131,14 +134,28 @@ class TrafficLooper
trafficUpdater.updateAll()
if (!sc.isActive) return
+ // add all non-bypass to "main"
+ var mainTxRate = 0L
+ var mainRxRate = 0L
+ var mainTx = 0L
+ var mainRx = 0L
+ tagMap.forEach { (_, it) ->
+ if (!it.ignore) {
+ mainTxRate += it.txRate
+ mainRxRate += it.rxRate
+ }
+ mainTx += it.tx - it.txBase
+ mainRx += it.rx - it.rxBase
+ }
+
// speed
val speed = SpeedDisplayData(
- itemMain!!.txRate,
- itemMain!!.rxRate,
- if (showDirectSpeed) itemBypass!!.txRate else 0L,
- if (showDirectSpeed) itemBypass!!.rxRate else 0L,
- itemMain!!.tx - itemMainBase!!.tx,
- itemMain!!.rx - itemMainBase!!.rx
+ mainTxRate,
+ mainRxRate,
+ if (showDirectSpeed) itemBypass.txRate else 0L,
+ if (showDirectSpeed) itemBypass.rxRate else 0L,
+ mainTx,
+ mainRx
)
// broadcast (MainActivity)
@@ -149,7 +166,7 @@ class TrafficLooper
if (data.binder.callbackIdMap[b] == SagerConnection.CONNECTION_ID_MAIN_ACTIVITY_FOREGROUND) {
b.cbSpeedUpdate(speed)
if (profileTrafficStatistics) {
- items.forEach { (id, item) ->
+ idMap.forEach { (id, item) ->
b.cbTrafficUpdate(
TrafficData(id = id, rx = item.rx, tx = item.tx) // display
)
@@ -163,6 +180,8 @@ class TrafficLooper
data.notification?.apply {
if (listenPostSpeed) postNotificationSpeedUpdate(speed)
}
+
+ delay(delayMs)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficUpdater.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficUpdater.kt
index 3081e2b0c..c13869c5c 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficUpdater.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficUpdater.kt
@@ -10,6 +10,8 @@ class TrafficUpdater(
var tag: String,
var tx: Long = 0,
var rx: Long = 0,
+ var txBase: Long = 0,
+ var rxBase: Long = 0,
var txRate: Long = 0,
var rxRate: Long = 0,
var lastUpdate: Long = 0,
diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/UrlTest.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/UrlTest.kt
index 3cec7cff4..73b654630 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/UrlTest.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/UrlTest.kt
@@ -6,7 +6,7 @@ import io.nekohasekai.sagernet.database.ProxyEntity
class UrlTest {
val link = DataStore.connectionTestURL
- val timeout = 3000
+ private val timeout = 5000
suspend fun doTest(profile: ProxyEntity): Int {
return TestInstance(profile, link, timeout).doTest()
diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
index c413ee686..06bb50133 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
@@ -2,18 +2,28 @@ package io.nekohasekai.sagernet.database
import android.os.Binder
import androidx.preference.PreferenceDataStore
-import io.nekohasekai.sagernet.*
+import io.nekohasekai.sagernet.CONNECTION_TEST_URL
+import io.nekohasekai.sagernet.GroupType
+import io.nekohasekai.sagernet.IPv6Mode
+import io.nekohasekai.sagernet.Key
+import io.nekohasekai.sagernet.TunImplementation
import io.nekohasekai.sagernet.bg.BaseService
import io.nekohasekai.sagernet.bg.VpnService
import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener
import io.nekohasekai.sagernet.database.preference.PublicDatabase
import io.nekohasekai.sagernet.database.preference.RoomPreferenceDataStore
-import io.nekohasekai.sagernet.ktx.*
+import io.nekohasekai.sagernet.ktx.boolean
+import io.nekohasekai.sagernet.ktx.int
+import io.nekohasekai.sagernet.ktx.long
+import io.nekohasekai.sagernet.ktx.parsePort
+import io.nekohasekai.sagernet.ktx.string
+import io.nekohasekai.sagernet.ktx.stringToInt
+import io.nekohasekai.sagernet.ktx.stringToIntIfExists
import moe.matsuri.nb4a.TempDatabase
object DataStore : OnPreferenceDataStoreChangeListener {
- // share service state in main process
+ // share service state in main & bg process
@Volatile
var serviceState = BaseService.State.Idle
@@ -30,6 +40,10 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var vpnService: VpnService? = null
var baseService: BaseService.Interface? = null
+ // main
+
+ var runningTest = false
+
fun currentGroupId(): Long {
val currentSelected = configurationStore.getLong(Key.PROFILE_GROUP, -1)
if (currentSelected > 0L) return currentSelected
@@ -70,11 +84,14 @@ object DataStore : OnPreferenceDataStoreChangeListener {
return groups.find { it.type == GroupType.BASIC }!!.id
}
- var nekoPlugins by configurationStore.string(Key.NEKO_PLUGIN_MANAGED)
var appTLSVersion by configurationStore.string(Key.APP_TLS_VERSION)
var enableClashAPI by configurationStore.boolean(Key.ENABLE_CLASH_API)
var showBottomBar by configurationStore.boolean(Key.SHOW_BOTTOM_BAR)
+ var allowInsecureOnRequest by configurationStore.boolean(Key.ALLOW_INSECURE_ON_REQUEST)
+ var networkChangeResetConnections by configurationStore.boolean(Key.NETWORK_CHANGE_RESET_CONNECTIONS) { true }
+ var wakeResetConnections by configurationStore.boolean(Key.WAKE_RESET_CONNECTIONS)
+
//
var isExpert by configurationStore.boolean(Key.APP_EXPERT)
@@ -82,11 +99,9 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var nightTheme by configurationStore.stringToInt(Key.NIGHT_THEME)
var serviceMode by configurationStore.string(Key.SERVICE_MODE) { Key.MODE_VPN }
- // var domainStrategy by configurationStore.string(Key.DOMAIN_STRATEGY) { "AsIs" }
- var trafficSniffing by configurationStore.boolean(Key.TRAFFIC_SNIFFING) { true }
+ var trafficSniffing by configurationStore.stringToInt(Key.TRAFFIC_SNIFFING) { 1 }
var resolveDestination by configurationStore.boolean(Key.RESOLVE_DESTINATION)
- // var tcpKeepAliveInterval by configurationStore.stringToInt(Key.TCP_KEEP_ALIVE_INTERVAL) { 15 }
var mtu by configurationStore.stringToInt(Key.MTU) { 9000 }
var bypassLan by configurationStore.boolean(Key.BYPASS_LAN)
@@ -96,12 +111,12 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var speedInterval by configurationStore.stringToInt(Key.SPEED_INTERVAL)
var showGroupInNotification by configurationStore.boolean("showGroupInNotification")
- var remoteDns by configurationStore.string(Key.REMOTE_DNS) { "https://8.8.8.8/dns-query" }
+ var globalCustomConfig by configurationStore.string(Key.GLOBAL_CUSTOM_CONFIG) { "" }
+
+ var remoteDns by configurationStore.string(Key.REMOTE_DNS) { "https://dns.google/dns-query" }
var directDns by configurationStore.string(Key.DIRECT_DNS) { "https://223.5.5.5/dns-query" }
- var directDnsUseSystem by configurationStore.boolean(Key.DIRECT_DNS_USE_SYSTEM)
var enableDnsRouting by configurationStore.boolean(Key.ENABLE_DNS_ROUTING) { true }
- var enableFakeDns by configurationStore.boolean(Key.ENABLE_FAKEDNS)
- var dnsNetwork by configurationStore.stringSet(Key.DNS_NETWORK)
+ var enableFakeDns by configurationStore.boolean(Key.ENABLE_FAKEDNS) { true }
var rulesProvider by configurationStore.stringToInt(Key.RULES_PROVIDER)
var logLevel by configurationStore.stringToInt(Key.LOG_LEVEL)
@@ -113,25 +128,11 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var mixedPort: Int
get() = getLocalPort(Key.MIXED_PORT, 2080)
set(value) = saveLocalPort(Key.MIXED_PORT, value)
- var localDNSPort: Int
- get() = getLocalPort(Key.LOCAL_DNS_PORT, 6450)
- set(value) {
- saveLocalPort(Key.LOCAL_DNS_PORT, value)
- }
- var transproxyPort: Int
- get() = getLocalPort(Key.TRANSPROXY_PORT, 9200)
- set(value) = saveLocalPort(Key.TRANSPROXY_PORT, value)
fun initGlobal() {
if (configurationStore.getString(Key.MIXED_PORT) == null) {
mixedPort = mixedPort
}
- if (configurationStore.getString(Key.LOCAL_DNS_PORT) == null) {
- localDNSPort = localDNSPort
- }
- if (configurationStore.getString(Key.TRANSPROXY_PORT) == null) {
- transproxyPort = transproxyPort
- }
}
@@ -151,22 +152,21 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var individual by configurationStore.string(Key.INDIVIDUAL)
var showDirectSpeed by configurationStore.boolean(Key.SHOW_DIRECT_SPEED) { true }
+ val persistAcrossReboot by configurationStore.boolean(Key.PERSIST_ACROSS_REBOOT) { false }
+
var appendHttpProxy by configurationStore.boolean(Key.APPEND_HTTP_PROXY)
- var requireTransproxy by configurationStore.boolean(Key.REQUIRE_TRANSPROXY)
- var transproxyMode by configurationStore.stringToInt(Key.TRANSPROXY_MODE)
var connectionTestURL by configurationStore.string(Key.CONNECTION_TEST_URL) { CONNECTION_TEST_URL }
var connectionTestConcurrent by configurationStore.int("connectionTestConcurrent") { 5 }
var alwaysShowAddress by configurationStore.boolean(Key.ALWAYS_SHOW_ADDRESS)
- var tunImplementation by configurationStore.stringToInt(Key.TUN_IMPLEMENTATION) { TunImplementation.SYSTEM }
+ var tunImplementation by configurationStore.stringToInt(Key.TUN_IMPLEMENTATION) { TunImplementation.GVISOR }
var profileTrafficStatistics by configurationStore.boolean(Key.PROFILE_TRAFFIC_STATISTICS) { true }
var yacdURL by configurationStore.string("yacdURL") { "http://127.0.0.1:9090/ui" }
// protocol
- var muxProtocols by configurationStore.stringSet(Key.MUX_PROTOCOLS)
- var muxConcurrency by configurationStore.stringToInt(Key.MUX_CONCURRENCY) { 8 }
+ var globalAllowInsecure by configurationStore.boolean(Key.GLOBAL_ALLOW_INSECURE) { false }
// old cache, DO NOT ADD
@@ -176,6 +176,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var profileName by profileCacheStore.string(Key.PROFILE_NAME)
var serverAddress by profileCacheStore.string(Key.SERVER_ADDRESS)
var serverPort by profileCacheStore.stringToInt(Key.SERVER_PORT)
+ var serverPorts by profileCacheStore.string("serverPorts")
var serverUsername by profileCacheStore.string(Key.SERVER_USERNAME)
var serverPassword by profileCacheStore.string(Key.SERVER_PASSWORD)
var serverPassword1 by profileCacheStore.string(Key.SERVER_PASSWORD1)
@@ -193,6 +194,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var serverEncryption by profileCacheStore.string(Key.SERVER_ENCRYPTION)
var serverALPN by profileCacheStore.string(Key.SERVER_ALPN)
var serverCertificates by profileCacheStore.string(Key.SERVER_CERTIFICATES)
+ var serverMTU by profileCacheStore.stringToInt(Key.SERVER_MTU)
var serverHeaders by profileCacheStore.string(Key.SERVER_HEADERS)
var serverAllowInsecure by profileCacheStore.boolean(Key.SERVER_ALLOW_INSECURE)
@@ -201,11 +203,12 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var serverDownloadSpeed by profileCacheStore.stringToInt(Key.SERVER_DOWNLOAD_SPEED)
var serverStreamReceiveWindow by profileCacheStore.stringToIntIfExists(Key.SERVER_STREAM_RECEIVE_WINDOW)
var serverConnectionReceiveWindow by profileCacheStore.stringToIntIfExists(Key.SERVER_CONNECTION_RECEIVE_WINDOW)
- var serverMTU by profileCacheStore.stringToInt(Key.SERVER_MTU) { 1420 }
var serverDisableMtuDiscovery by profileCacheStore.boolean(Key.SERVER_DISABLE_MTU_DISCOVERY)
var serverHopInterval by profileCacheStore.stringToInt(Key.SERVER_HOP_INTERVAL) { 10 }
- var serverProtocolVersion by profileCacheStore.stringToInt(Key.SERVER_PROTOCOL)
+ var protocolVersion by profileCacheStore.stringToInt(Key.PROTOCOL_VERSION) { 2 } // default is SOCKS5
+
+ var serverProtocolInt by profileCacheStore.stringToInt(Key.SERVER_PROTOCOL)
var serverPrivateKey by profileCacheStore.string(Key.SERVER_PRIVATE_KEY)
var serverInsecureConcurrency by profileCacheStore.stringToInt(Key.SERVER_INSECURE_CONCURRENCY)
@@ -213,7 +216,6 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var serverCongestionController by profileCacheStore.string(Key.SERVER_CONGESTION_CONTROLLER)
var serverDisableSNI by profileCacheStore.boolean(Key.SERVER_DISABLE_SNI)
var serverReduceRTT by profileCacheStore.boolean(Key.SERVER_REDUCE_RTT)
- var serverFastConnect by profileCacheStore.boolean(Key.SERVER_FAST_CONNECT)
var routeName by profileCacheStore.string(Key.ROUTE_NAME)
var routeDomain by profileCacheStore.string(Key.ROUTE_DOMAIN)
diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt
index 1bcad16d3..ba21dde44 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt
@@ -17,7 +17,7 @@ object ProfileManager {
interface Listener {
suspend fun onAdd(profile: ProxyEntity)
suspend fun onUpdated(data: TrafficData)
- suspend fun onUpdated(profile: ProxyEntity)
+ suspend fun onUpdated(profile: ProxyEntity, noTraffic: Boolean)
suspend fun onRemoved(groupId: Long, profileId: Long)
}
@@ -87,13 +87,13 @@ object ProfileManager {
suspend fun updateProfile(profile: ProxyEntity) {
SagerDatabase.proxyDao.updateProxy(profile)
- iterator { onUpdated(profile) }
+ iterator { onUpdated(profile, false) }
}
suspend fun updateProfile(profiles: List) {
SagerDatabase.proxyDao.updateProxy(profiles)
profiles.forEach {
- iterator { onUpdated(it) }
+ iterator { onUpdated(it, false) }
}
}
@@ -141,12 +141,12 @@ object ProfileManager {
// postUpdate: post to listeners, don't change the DB
- suspend fun postUpdate(profileId: Long) {
- postUpdate(getProfile(profileId) ?: return)
+ suspend fun postUpdate(profileId: Long, noTraffic: Boolean = false) {
+ postUpdate(getProfile(profileId) ?: return, noTraffic)
}
- suspend fun postUpdate(profile: ProxyEntity) {
- iterator { onUpdated(profile) }
+ suspend fun postUpdate(profile: ProxyEntity, noTraffic: Boolean = false) {
+ iterator { onUpdated(profile, noTraffic) }
}
suspend fun postUpdate(data: TrafficData) {
@@ -200,55 +200,37 @@ object ProfileManager {
outbound = -2
)
)
- createRule(
- RuleEntity(
- name = app.getString(R.string.route_opt_block_analysis),
- domains = app.assets.open("analysis.txt").use {
- it.bufferedReader()
- .readLines()
- .filter { it.isNotBlank() }
- .joinToString("\n")
- },
- outbound = -2,
- )
- )
- var country = Locale.getDefault().country.lowercase()
- var displayCountry = Locale.getDefault().displayCountry
- if (country in arrayOf(
- "ir"
- )
- ) {
- createRule(
+ val fuckedCountry = mutableListOf("cn:中国")
+ if (Locale.getDefault().country != Locale.CHINA.country) {
+ // 非中文用户
+ fuckedCountry += "ir:Iran"
+ fuckedCountry += "ru:Russia"
+ }
+ for (c in fuckedCountry) {
+ val country = c.substringBefore(":")
+ val displayCountry = c.substringAfter(":")
+ //
+ if (country == "cn") createRule(
RuleEntity(
- name = app.getString(R.string.route_bypass_domain, displayCountry),
- domains = "domain:$country",
- outbound = -1
+ name = app.getString(R.string.route_play_store, displayCountry),
+ domains = "googleapis.cn",
), false
)
- } else {
- country = Locale.CHINA.country.lowercase()
- displayCountry = Locale.CHINA.displayCountry
createRule(
RuleEntity(
- name = app.getString(R.string.route_play_store, displayCountry),
- domains = "domain:googleapis.cn",
+ name = app.getString(R.string.route_bypass_domain, displayCountry),
+ domains = "geosite:$country",
+ outbound = -1
), false
)
createRule(
RuleEntity(
- name = app.getString(R.string.route_bypass_domain, displayCountry),
- domains = "geosite:$country",
+ name = app.getString(R.string.route_bypass_ip, displayCountry),
+ ip = "geoip:$country",
outbound = -1
), false
)
}
- createRule(
- RuleEntity(
- name = app.getString(R.string.route_bypass_ip, displayCountry),
- ip = "geoip:$country",
- outbound = -1
- ), false
- )
rules = SagerDatabase.rulesDao.allRules()
}
return rules
diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt
index 551bf0e6d..b975695cb 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt
@@ -11,6 +11,8 @@ import io.nekohasekai.sagernet.fmt.http.HttpBean
import io.nekohasekai.sagernet.fmt.http.toUri
import io.nekohasekai.sagernet.fmt.hysteria.*
import io.nekohasekai.sagernet.fmt.internal.ChainBean
+import io.nekohasekai.sagernet.fmt.mieru.MieruBean
+import io.nekohasekai.sagernet.fmt.mieru.buildMieruConfig
import io.nekohasekai.sagernet.fmt.naive.NaiveBean
import io.nekohasekai.sagernet.fmt.naive.buildNaiveConfig
import io.nekohasekai.sagernet.fmt.naive.toUri
@@ -24,13 +26,15 @@ import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean
import io.nekohasekai.sagernet.fmt.trojan_go.buildTrojanGoConfig
import io.nekohasekai.sagernet.fmt.trojan_go.toUri
import io.nekohasekai.sagernet.fmt.tuic.TuicBean
-import io.nekohasekai.sagernet.fmt.tuic.buildTuicConfig
+import io.nekohasekai.sagernet.fmt.tuic.toUri
import io.nekohasekai.sagernet.fmt.v2ray.*
import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
import io.nekohasekai.sagernet.ktx.app
-import io.nekohasekai.sagernet.ktx.applyDefaultValues
import io.nekohasekai.sagernet.ui.profile.*
-import moe.matsuri.nb4a.Protocols
+import moe.matsuri.nb4a.SingBoxOptions.MultiplexOptions
+import moe.matsuri.nb4a.proxy.anytls.AnyTLSBean
+import moe.matsuri.nb4a.proxy.anytls.AnyTLSSettingsActivity
+import moe.matsuri.nb4a.proxy.anytls.toUri
import moe.matsuri.nb4a.proxy.config.ConfigBean
import moe.matsuri.nb4a.proxy.config.ConfigSettingActivity
import moe.matsuri.nb4a.proxy.neko.*
@@ -56,12 +60,14 @@ data class ProxyEntity(
var vmessBean: VMessBean? = null,
var trojanBean: TrojanBean? = null,
var trojanGoBean: TrojanGoBean? = null,
+ var mieruBean: MieruBean? = null,
var naiveBean: NaiveBean? = null,
var hysteriaBean: HysteriaBean? = null,
var tuicBean: TuicBean? = null,
var sshBean: SSHBean? = null,
var wgBean: WireGuardBean? = null,
var shadowTLSBean: ShadowTLSBean? = null,
+ var anyTLSBean: AnyTLSBean? = null,
var chainBean: ChainBean? = null,
var nekoBean: NekoBean? = null,
var configBean: ConfigBean? = null,
@@ -74,15 +80,16 @@ data class ProxyEntity(
const val TYPE_VMESS = 4
const val TYPE_TROJAN = 6
- const val TYPE_TROJAN_GO = 7
- const val TYPE_NAIVE = 9
- const val TYPE_HYSTERIA = 15
- const val TYPE_TUIC = 20
-
const val TYPE_SSH = 17
const val TYPE_WG = 18
+ const val TYPE_TROJAN_GO = 7
+ const val TYPE_NAIVE = 9
+ const val TYPE_HYSTERIA = 15
const val TYPE_SHADOWTLS = 19
+ const val TYPE_TUIC = 20
+ const val TYPE_MIERU = 21
+ const val TYPE_ANYTLS = 22
const val TYPE_CONFIG = 998
const val TYPE_NEKO = 999
@@ -91,10 +98,8 @@ data class ProxyEntity(
val chainName by lazy { app.getString(R.string.proxy_chain) }
- private val placeHolderBean = SOCKSBean().applyDefaultValues()
-
@JvmField
- val CREATOR = object : Serializable.CREATOR() {
+ val CREATOR = object : CREATOR() {
override fun newInstance(): ProxyEntity {
return ProxyEntity()
@@ -161,31 +166,35 @@ data class ProxyEntity(
TYPE_VMESS -> vmessBean = KryoConverters.vmessDeserialize(byteArray)
TYPE_TROJAN -> trojanBean = KryoConverters.trojanDeserialize(byteArray)
TYPE_TROJAN_GO -> trojanGoBean = KryoConverters.trojanGoDeserialize(byteArray)
+ TYPE_MIERU -> mieruBean = KryoConverters.mieruDeserialize(byteArray)
TYPE_NAIVE -> naiveBean = KryoConverters.naiveDeserialize(byteArray)
TYPE_HYSTERIA -> hysteriaBean = KryoConverters.hysteriaDeserialize(byteArray)
TYPE_SSH -> sshBean = KryoConverters.sshDeserialize(byteArray)
TYPE_WG -> wgBean = KryoConverters.wireguardDeserialize(byteArray)
TYPE_TUIC -> tuicBean = KryoConverters.tuicDeserialize(byteArray)
TYPE_SHADOWTLS -> shadowTLSBean = KryoConverters.shadowTLSDeserialize(byteArray)
+ TYPE_ANYTLS -> anyTLSBean = KryoConverters.anyTLSDeserialize(byteArray)
TYPE_CHAIN -> chainBean = KryoConverters.chainDeserialize(byteArray)
TYPE_NEKO -> nekoBean = KryoConverters.nekoDeserialize(byteArray)
TYPE_CONFIG -> configBean = KryoConverters.configDeserialize(byteArray)
}
}
- fun displayType() = when (type) {
+ fun displayType(): String = when (type) {
TYPE_SOCKS -> socksBean!!.protocolName()
TYPE_HTTP -> if (httpBean!!.isTLS()) "HTTPS" else "HTTP"
TYPE_SS -> "Shadowsocks"
TYPE_VMESS -> if (vmessBean!!.isVLESS) "VLESS" else "VMess"
TYPE_TROJAN -> "Trojan"
TYPE_TROJAN_GO -> "Trojan-Go"
+ TYPE_MIERU -> "Mieru"
TYPE_NAIVE -> "Naïve"
- TYPE_HYSTERIA -> "Hysteria"
+ TYPE_HYSTERIA -> "Hysteria" + hysteriaBean!!.protocolVersion
TYPE_SSH -> "SSH"
TYPE_WG -> "WireGuard"
TYPE_TUIC -> "TUIC"
TYPE_SHADOWTLS -> "ShadowTLS"
+ TYPE_ANYTLS -> "AnyTLS"
TYPE_CHAIN -> chainName
TYPE_NEKO -> nekoBean!!.displayType()
TYPE_CONFIG -> configBean!!.displayType()
@@ -203,12 +212,14 @@ data class ProxyEntity(
TYPE_VMESS -> vmessBean
TYPE_TROJAN -> trojanBean
TYPE_TROJAN_GO -> trojanGoBean
+ TYPE_MIERU -> mieruBean
TYPE_NAIVE -> naiveBean
TYPE_HYSTERIA -> hysteriaBean
TYPE_SSH -> sshBean
TYPE_WG -> wgBean
TYPE_TUIC -> tuicBean
TYPE_SHADOWTLS -> shadowTLSBean
+ TYPE_ANYTLS -> anyTLSBean
TYPE_CHAIN -> chainBean
TYPE_NEKO -> nekoBean
TYPE_CONFIG -> configBean
@@ -225,11 +236,10 @@ data class ProxyEntity(
fun haveStandardLink(): Boolean {
return when (requireBean()) {
- is TuicBean -> false
is SSHBean -> false
is WireGuardBean -> false
is ShadowTLSBean -> false
- is NekoBean -> nekoBean!!.haveStandardLink()
+ is NekoBean -> false
is ConfigBean -> false
else -> true
}
@@ -245,7 +255,9 @@ data class ProxyEntity(
is TrojanGoBean -> toUri()
is NaiveBean -> toUri()
is HysteriaBean -> toUri()
- is NekoBean -> shareLink()
+ is TuicBean -> toUri()
+ is AnyTLSBean -> toUri()
+ is NekoBean -> ""
else -> toUniversalLink()
}
}
@@ -269,17 +281,20 @@ data class ProxyEntity(
append("\n\n")
append(bean.buildTrojanGoConfig(port))
}
+
+ is MieruBean -> {
+ append("\n\n")
+ append(bean.buildMieruConfig(port))
+ }
+
is NaiveBean -> {
append("\n\n")
append(bean.buildNaiveConfig(port))
}
+
is HysteriaBean -> {
append("\n\n")
- append(bean.buildHysteriaConfig(port, null))
- }
- is TuicBean -> {
- append("\n\n")
- append(bean.buildTuicConfig(port, null))
+ append(bean.buildHysteria1Config(port, null))
}
}
}
@@ -291,28 +306,39 @@ data class ProxyEntity(
fun needExternal(): Boolean {
return when (type) {
TYPE_TROJAN_GO -> true
+ TYPE_MIERU -> true
TYPE_NAIVE -> true
TYPE_HYSTERIA -> !hysteriaBean!!.canUseSingBox()
- TYPE_TUIC -> true
TYPE_NEKO -> true
else -> false
}
}
- fun isV2RayNetworkTcp(): Boolean {
- val bean = requireBean() as StandardV2RayBean
- return when (bean.type) {
- "tcp", "ws", "http" -> true
- else -> false
- }
- }
-
- fun needCoreMux(): Boolean {
+ fun singMux(): MultiplexOptions? {
return when (type) {
- TYPE_VMESS -> isV2RayNetworkTcp() && Protocols.shouldEnableMux("vmess") && !vmessBean!!.isVLESS
- TYPE_TROJAN -> isV2RayNetworkTcp() && Protocols.shouldEnableMux("trojan")
- TYPE_SS -> !ssBean!!.sUoT && Protocols.shouldEnableMux("shadowsocks")
- else -> false
+ TYPE_VMESS -> MultiplexOptions().apply {
+ enabled = vmessBean!!.enableMux
+ padding = vmessBean!!.muxPadding
+ max_streams = vmessBean!!.muxConcurrency
+ protocol = when (vmessBean!!.muxType) {
+ 1 -> "smux"
+ 2 -> "yamux"
+ else -> "h2mux"
+ }
+ }
+
+ TYPE_TROJAN -> MultiplexOptions().apply {
+ enabled = trojanBean!!.enableMux
+ padding = trojanBean!!.muxPadding
+ max_streams = trojanBean!!.muxConcurrency
+ protocol = when (trojanBean!!.muxType) {
+ 1 -> "smux"
+ 2 -> "yamux"
+ else -> "h2mux"
+ }
+ }
+
+ else -> null
}
}
@@ -323,12 +349,14 @@ data class ProxyEntity(
vmessBean = null
trojanBean = null
trojanGoBean = null
+ mieruBean = null
naiveBean = null
hysteriaBean = null
sshBean = null
wgBean = null
tuicBean = null
shadowTLSBean = null
+ anyTLSBean = null
chainBean = null
configBean = null
nekoBean = null
@@ -338,62 +366,87 @@ data class ProxyEntity(
type = TYPE_SOCKS
socksBean = bean
}
+
is HttpBean -> {
type = TYPE_HTTP
httpBean = bean
}
+
is ShadowsocksBean -> {
type = TYPE_SS
ssBean = bean
}
+
is VMessBean -> {
type = TYPE_VMESS
vmessBean = bean
}
+
is TrojanBean -> {
type = TYPE_TROJAN
trojanBean = bean
}
+
is TrojanGoBean -> {
type = TYPE_TROJAN_GO
trojanGoBean = bean
}
+
+ is MieruBean -> {
+ type = TYPE_MIERU
+ mieruBean = bean
+ }
+
is NaiveBean -> {
type = TYPE_NAIVE
naiveBean = bean
}
+
is HysteriaBean -> {
type = TYPE_HYSTERIA
hysteriaBean = bean
}
+
is SSHBean -> {
type = TYPE_SSH
sshBean = bean
}
+
is WireGuardBean -> {
type = TYPE_WG
wgBean = bean
}
+
is TuicBean -> {
type = TYPE_TUIC
tuicBean = bean
}
+
is ShadowTLSBean -> {
type = TYPE_SHADOWTLS
shadowTLSBean = bean
}
+
+ is AnyTLSBean -> {
+ type = TYPE_ANYTLS
+ anyTLSBean = bean
+ }
+
is ChainBean -> {
type = TYPE_CHAIN
chainBean = bean
}
+
is NekoBean -> {
type = TYPE_NEKO
nekoBean = bean
}
+
is ConfigBean -> {
type = TYPE_CONFIG
configBean = bean
}
+
else -> error("Undefined type $type")
}
return this
@@ -408,14 +461,15 @@ data class ProxyEntity(
TYPE_VMESS -> VMessSettingsActivity::class.java
TYPE_TROJAN -> TrojanSettingsActivity::class.java
TYPE_TROJAN_GO -> TrojanGoSettingsActivity::class.java
+ TYPE_MIERU -> MieruSettingsActivity::class.java
TYPE_NAIVE -> NaiveSettingsActivity::class.java
TYPE_HYSTERIA -> HysteriaSettingsActivity::class.java
TYPE_SSH -> SSHSettingsActivity::class.java
TYPE_WG -> WireGuardSettingsActivity::class.java
TYPE_TUIC -> TuicSettingsActivity::class.java
TYPE_SHADOWTLS -> ShadowTLSSettingsActivity::class.java
+ TYPE_ANYTLS -> AnyTLSSettingsActivity::class.java
TYPE_CHAIN -> ChainSettingsActivity::class.java
- TYPE_NEKO -> NekoSettingActivity::class.java
TYPE_CONFIG -> ConfigSettingActivity::class.java
else -> throw IllegalArgumentException()
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/RuleEntity.kt b/app/src/main/java/io/nekohasekai/sagernet/database/RuleEntity.kt
index 20cbbc05d..7d610c5e4 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/database/RuleEntity.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/RuleEntity.kt
@@ -8,9 +8,12 @@ import kotlinx.parcelize.Parcelize
@Entity(tableName = "rules")
@Parcelize
+@TypeConverters(StringCollectionConverter::class)
data class RuleEntity(
@PrimaryKey(autoGenerate = true) var id: Long = 0L,
var name: String = "",
+ @ColumnInfo(defaultValue = "")
+ var config: String = "",
var userOrder: Long = 0L,
var enabled: Boolean = false,
var domains: String = "",
@@ -21,7 +24,7 @@ data class RuleEntity(
var source: String = "",
var protocol: String = "",
var outbound: Long = 0,
- var packages: List = listOf(),
+ var packages: Set = emptySet(),
) : Parcelable {
fun displayName(): String {
@@ -30,11 +33,12 @@ data class RuleEntity(
fun mkSummary(): String {
var summary = ""
+ if (config.isNotBlank()) summary += "[config]\n"
if (domains.isNotBlank()) summary += "$domains\n"
if (ip.isNotBlank()) summary += "$ip\n"
- if (source.isNotBlank()) summary += "source: $source\n"
- if (sourcePort.isNotBlank()) summary += "sourcePort: $sourcePort\n"
- if (port.isNotBlank()) summary += "port: $port\n"
+ if (source.isNotBlank()) summary += "src ip: $source\n"
+ if (sourcePort.isNotBlank()) summary += "src port: $sourcePort\n"
+ if (port.isNotBlank()) summary += "dst port: $port\n"
if (network.isNotBlank()) summary += "network: $network\n"
if (protocol.isNotBlank()) summary += "protocol: $protocol\n"
if (packages.isNotEmpty()) summary += app.getString(
@@ -54,7 +58,7 @@ data class RuleEntity(
-1L -> app.getString(R.string.route_bypass)
-2L -> app.getString(R.string.route_block)
else -> ProfileManager.getProfile(outbound)?.displayName()
- ?: app.getString(R.string.route_proxy)
+ ?: app.getString(R.string.error_title)
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt b/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt
index 4d57ed3e4..ececc106e 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt
@@ -1,5 +1,6 @@
package io.nekohasekai.sagernet.database
+import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@@ -15,7 +16,12 @@ import kotlinx.coroutines.launch
@Database(
entities = [ProxyGroup::class, ProxyEntity::class, RuleEntity::class],
- version = 2
+ version = 6,
+ autoMigrations = [
+ AutoMigration(from = 3, to = 4),
+ AutoMigration(from = 4, to = 5),
+ AutoMigration(from = 5, to = 6)
+ ]
)
@TypeConverters(value = [KryoConverters::class, GsonConverters::class])
@GenerateRoomMigrations
@@ -27,7 +33,8 @@ abstract class SagerDatabase : RoomDatabase() {
val instance by lazy {
SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs()
Room.databaseBuilder(SagerNet.application, SagerDatabase::class.java, Key.DB_PROFILE)
- .addMigrations(*SagerDatabase_Migrations.build())
+// .addMigrations(*SagerDatabase_Migrations.build())
+ .setJournalMode(JournalMode.TRUNCATE)
.allowMainThreadQueries()
.enableMultiInstanceInvalidation()
.fallbackToDestructiveMigration()
diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/StringCollectionConverter.kt b/app/src/main/java/io/nekohasekai/sagernet/database/StringCollectionConverter.kt
new file mode 100644
index 000000000..ea45467d8
--- /dev/null
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/StringCollectionConverter.kt
@@ -0,0 +1,44 @@
+package io.nekohasekai.sagernet.database
+
+import androidx.room.TypeConverter
+
+class StringCollectionConverter {
+ companion object {
+ const val SPLIT_FLAG = ","
+
+ /*
+ @TypeConverter
+ @JvmStatic
+ fun fromList(list: List): String = if (list.isEmpty()) {
+ ""
+ } else {
+ list.joinToString(SPLIT_FLAG)
+ }
+
+ @TypeConverter
+ @JvmStatic
+ fun toList(str: String): List = if (str.isBlank()) {
+ emptyList()
+ } else {
+ str.split(SPLIT_FLAG)
+ }
+ */
+
+
+ @TypeConverter
+ @JvmStatic
+ fun fromSet(set: Set): String = if (set.isEmpty()) {
+ ""
+ } else {
+ set.joinToString(SPLIT_FLAG)
+ }
+
+ @TypeConverter
+ @JvmStatic
+ fun toSet(str: String): Set = if (str.isBlank()) {
+ emptySet()
+ } else {
+ str.split(",").toSet()
+ }
+ }
+}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/preference/PublicDatabase.kt b/app/src/main/java/io/nekohasekai/sagernet/database/preference/PublicDatabase.kt
index e9296de55..d4ebf9800 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/database/preference/PublicDatabase.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/preference/PublicDatabase.kt
@@ -16,6 +16,7 @@ abstract class PublicDatabase : RoomDatabase() {
val instance by lazy {
SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs()
Room.databaseBuilder(SagerNet.application, PublicDatabase::class.java, Key.DB_PUBLIC)
+ .setJournalMode(JournalMode.TRUNCATE)
.allowMainThreadQueries()
.enableMultiInstanceInvalidation()
.fallbackToDestructiveMigration()
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
index 9e2635f63..8fe0a2944 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
@@ -1,7 +1,7 @@
package io.nekohasekai.sagernet.fmt
-import io.nekohasekai.sagernet.IPv6Mode
-import io.nekohasekai.sagernet.Key
+import android.widget.Toast
+import io.nekohasekai.sagernet.*
import io.nekohasekai.sagernet.bg.VpnService
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.database.ProxyEntity
@@ -10,7 +10,6 @@ import io.nekohasekai.sagernet.database.SagerDatabase
import io.nekohasekai.sagernet.fmt.ConfigBuildResult.IndexEntity
import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean
import io.nekohasekai.sagernet.fmt.hysteria.buildSingBoxOutboundHysteriaBean
-import io.nekohasekai.sagernet.fmt.hysteria.isMultiPort
import io.nekohasekai.sagernet.fmt.internal.ChainBean
import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean
import io.nekohasekai.sagernet.fmt.shadowsocks.buildSingBoxOutboundShadowsocksBean
@@ -19,6 +18,7 @@ import io.nekohasekai.sagernet.fmt.socks.buildSingBoxOutboundSocksBean
import io.nekohasekai.sagernet.fmt.ssh.SSHBean
import io.nekohasekai.sagernet.fmt.ssh.buildSingBoxOutboundSSHBean
import io.nekohasekai.sagernet.fmt.tuic.TuicBean
+import io.nekohasekai.sagernet.fmt.tuic.buildSingBoxOutboundTuicBean
import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean
import io.nekohasekai.sagernet.fmt.v2ray.buildSingBoxOutboundStandardV2RayBean
import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
@@ -26,30 +26,27 @@ import io.nekohasekai.sagernet.fmt.wireguard.buildSingBoxOutboundWireguardBean
import io.nekohasekai.sagernet.ktx.isIpAddress
import io.nekohasekai.sagernet.ktx.mkPort
import io.nekohasekai.sagernet.utils.PackageCache
+import moe.matsuri.nb4a.*
import moe.matsuri.nb4a.SingBoxOptions.*
-import moe.matsuri.nb4a.applyDNSNetworkSettings
-import moe.matsuri.nb4a.checkEmpty
-import moe.matsuri.nb4a.makeSingBoxRule
import moe.matsuri.nb4a.plugin.Plugins
+import moe.matsuri.nb4a.proxy.anytls.AnyTLSBean
+import moe.matsuri.nb4a.proxy.anytls.buildSingBoxOutboundAnyTLSBean
import moe.matsuri.nb4a.proxy.config.ConfigBean
import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean
import moe.matsuri.nb4a.proxy.shadowtls.buildSingBoxOutboundShadowTLSBean
import moe.matsuri.nb4a.utils.JavaUtil.gson
+import moe.matsuri.nb4a.utils.Util
+import moe.matsuri.nb4a.utils.listByLineOrComma
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
const val TAG_MIXED = "mixed-in"
-const val TAG_TRANS = "trans-in"
const val TAG_PROXY = "proxy"
const val TAG_DIRECT = "direct"
const val TAG_BYPASS = "bypass"
const val TAG_BLOCK = "block"
-const val TAG_DNS_IN = "dns-in"
-const val TAG_DNS_OUT = "dns-out"
-
const val LOCALHOST = "127.0.0.1"
-const val LOCAL_DNS_SERVER = "underlying://0.0.0.0"
class ConfigBuildResult(
var config: String,
@@ -57,26 +54,11 @@ class ConfigBuildResult(
var mainEntId: Long,
var trafficMap: Map>,
var profileTagMap: Map,
- val alerts: List>,
val selectorGroupId: Long,
) {
data class IndexEntity(var chain: LinkedHashMap)
}
-fun mergeJSON(j: String, to: MutableMap) {
- if (j.isBlank()) return
- val m = gson.fromJson(j, to.javaClass)
- m.forEach { (k, v) ->
- if (v is Map<*, *> && to[k] is Map<*, *>) {
- val currentMap = (to[k] as Map<*, *>).toMutableMap()
- currentMap += v
- to[k] = currentMap
- } else {
- to[k] = v
- }
- }
-}
-
fun buildConfig(
proxy: ProxyEntity, forTest: Boolean = false, forExport: Boolean = false
): ConfigBuildResult {
@@ -90,7 +72,6 @@ fun buildConfig(
proxy.id, //
mapOf(TAG_PROXY to listOf(proxy)), //
mapOf(proxy.id to TAG_PROXY), //
- listOf(),
-1L
)
}
@@ -101,7 +82,6 @@ fun buildConfig(
val globalOutbounds = HashMap()
val selectorNames = ArrayList()
val group = SagerDatabase.groupDao.getById(proxy.groupId)
- val optionsToMerge = proxy.requireBean().customConfigJson ?: ""
fun ProxyEntity.resolveChainInternal(): MutableList {
val bean = requireBean()
@@ -130,8 +110,9 @@ fun buildConfig(
}
fun ProxyEntity.resolveChain(): MutableList {
- val frontProxy = group?.frontProxy?.let { SagerDatabase.proxyDao.getById(it) }
- val landingProxy = group?.landingProxy?.let { SagerDatabase.proxyDao.getById(it) }
+ val thisGroup = SagerDatabase.groupDao.getById(groupId)
+ val frontProxy = thisGroup?.frontProxy?.let { SagerDatabase.proxyDao.getById(it) }
+ val landingProxy = thisGroup?.landingProxy?.let { SagerDatabase.proxyDao.getById(it) }
val list = resolveChainInternal()
if (frontProxy != null) {
list.add(frontProxy)
@@ -148,31 +129,25 @@ fun buildConfig(
rule.outbound.takeIf { it > 0 && it != proxy.id }
}.toHashSet().toList()).associateBy { it.id }
val buildSelector = !forTest && group?.isSelector == true && !forExport
- val uidListDNSRemote = mutableListOf()
- val uidListDNSDirect = mutableListOf()
- val domainListDNSRemote = mutableListOf()
- val domainListDNSDirect = mutableListOf()
+ val userDNSRuleList = mutableListOf()
val domainListDNSDirectForce = mutableListOf()
- val domainListDNSBlock = mutableListOf()
val bypassDNSBeans = hashSetOf()
val isVPN = DataStore.serviceMode == Key.MODE_VPN
val bind = if (!forTest && DataStore.allowAccess) "0.0.0.0" else LOCALHOST
val remoteDns = DataStore.remoteDns.split("\n")
.mapNotNull { dns -> dns.trim().takeIf { it.isNotBlank() && !it.startsWith("#") } }
- var directDNS = DataStore.directDns.split("\n")
+ val directDNS = DataStore.directDns.split("\n")
.mapNotNull { dns -> dns.trim().takeIf { it.isNotBlank() && !it.startsWith("#") } }
val enableDnsRouting = DataStore.enableDnsRouting
- val useFakeDns = DataStore.enableFakeDns && !forTest && DataStore.ipv6Mode != IPv6Mode.ONLY
- val needSniff = DataStore.trafficSniffing
+ val useFakeDns = DataStore.enableFakeDns && !forTest
+ val needSniff = DataStore.trafficSniffing > 0
+ val needSniffOverride = DataStore.trafficSniffing == 2
val externalIndexMap = ArrayList()
- val requireTransproxy = if (forTest) false else DataStore.requireTransproxy
val ipv6Mode = if (forTest) IPv6Mode.ENABLE else DataStore.ipv6Mode
- val resolveDestination = DataStore.resolveDestination
- val alerts = mutableListOf>()
fun genDomainStrategy(noAsIs: Boolean): String {
return when {
- !resolveDestination && !noAsIs -> ""
+ !noAsIs -> ""
ipv6Mode == IPv6Mode.DISABLE -> "ipv4_only"
ipv6Mode == IPv6Mode.PREFER -> "prefer_ipv6"
ipv6Mode == IPv6Mode.ONLY -> "ipv6_only"
@@ -185,7 +160,6 @@ fun buildConfig(
clash_api = ClashAPIOptions().apply {
external_controller = "127.0.0.1:9090"
external_ui = "../files/yacd"
- cache_file = "../cache/clash.db"
}
}
@@ -201,22 +175,21 @@ fun buildConfig(
}
dns = DNSOptions().apply {
- // TODO nb4a hosts?
-// hosts = DataStore.hosts.split("\n")
-// .filter { it.isNotBlank() }
-// .associate { it.substringBefore(" ") to it.substringAfter(" ") }
-// .toMutableMap()
-
servers = mutableListOf()
rules = mutableListOf()
+ independent_cache = true
+ }
- when (ipv6Mode) {
- IPv6Mode.DISABLE -> {
- strategy = "ipv4_only"
- }
- IPv6Mode.ONLY -> {
- strategy = "ipv6_only"
- }
+ fun autoDnsDomainStrategy(s: String): String? {
+ if (s.isNotEmpty()) {
+ return s
+ }
+ return when (ipv6Mode) {
+ IPv6Mode.DISABLE -> "ipv4_only"
+ IPv6Mode.ENABLE -> "prefer_ipv4"
+ IPv6Mode.PREFER -> "prefer_ipv6"
+ IPv6Mode.ONLY -> "ipv6_only"
+ else -> null
}
}
@@ -226,17 +199,25 @@ fun buildConfig(
if (isVPN) inbounds.add(Inbound_TunOptions().apply {
type = "tun"
tag = "tun-in"
- stack = if (DataStore.tunImplementation == 1) "system" else "gvisor"
- sniff = needSniff
+ stack = when (DataStore.tunImplementation) {
+ TunImplementation.GVISOR -> "gvisor"
+ TunImplementation.SYSTEM -> "system"
+ else -> "mixed"
+ }
endpoint_independent_nat = true
- domain_strategy = genDomainStrategy(false)
+ mtu = DataStore.mtu
+ domain_strategy = genDomainStrategy(DataStore.resolveDestination)
+ sniff = needSniff
+ sniff_override_destination = needSniffOverride
when (ipv6Mode) {
IPv6Mode.DISABLE -> {
inet4_address = listOf(VpnService.PRIVATE_VLAN4_CLIENT + "/28")
}
+
IPv6Mode.ONLY -> {
inet6_address = listOf(VpnService.PRIVATE_VLAN6_CLIENT + "/126")
}
+
else -> {
inet4_address = listOf(VpnService.PRIVATE_VLAN4_CLIENT + "/28")
inet6_address = listOf(VpnService.PRIVATE_VLAN6_CLIENT + "/126")
@@ -248,48 +229,19 @@ fun buildConfig(
tag = TAG_MIXED
listen = bind
listen_port = DataStore.mixedPort
- domain_strategy = genDomainStrategy(false)
- if (needSniff) {
- sniff = true
-// destOverride = when {
-// useFakeDns && !trafficSniffing -> listOf("fakedns")
-// useFakeDns -> listOf("fakedns", "http", "tls", "quic")
-// else -> listOf("http", "tls", "quic")
-// }
-// metadataOnly = useFakeDns && !trafficSniffing
-// routeOnly = true
- }
+ domain_strategy = genDomainStrategy(DataStore.resolveDestination)
+ sniff = needSniff
+ sniff_override_destination = needSniffOverride
})
}
- if (requireTransproxy) {
- if (DataStore.transproxyMode == 1) {
- inbounds.add(Inbound_TProxyOptions().apply {
- type = "tproxy"
- tag = TAG_TRANS
- listen = bind
- listen_port = DataStore.transproxyPort
- sniff = needSniff
- domain_strategy = genDomainStrategy(false)
- })
- } else {
- inbounds.add(Inbound_RedirectOptions().apply {
- type = "redirect"
- tag = TAG_TRANS
- listen = bind
- listen_port = DataStore.transproxyPort
- sniff = needSniff
- domain_strategy = genDomainStrategy(false)
- })
- }
- }
-
outbounds = mutableListOf()
// init routing object
route = RouteOptions().apply {
auto_detect_interface = true
rules = mutableListOf()
+ rule_set = mutableListOf()
}
// returns outbound tag
@@ -302,20 +254,20 @@ fun buildConfig(
add(entity)
}
- var currentOutbound = mutableMapOf()
- lateinit var pastOutbound: MutableMap
+ var currentOutbound: SingBoxOption
+ lateinit var pastOutbound: SingBoxOption
lateinit var pastInboundTag: String
var pastEntity: ProxyEntity? = null
val externalChainMap = LinkedHashMap()
externalIndexMap.add(IndexEntity(externalChainMap))
- val chainOutbounds = ArrayList>()
+ val chainOutbounds = ArrayList()
// chainTagOut: v2ray outbound tag for this chain
var chainTagOut = ""
val chainTag = "c-$chainId"
var muxApplied = false
- var currentDomainStrategy = genDomainStrategy(false)
+ val defaultServerDomainStrategy = SingBoxOptionsUtil.domainStrategy("server")
profileList.forEachIndexed { index, proxyEntity ->
val bean = proxyEntity.requireBean()
@@ -336,13 +288,6 @@ fun buildConfig(
bypassDNSBeans += proxyEntity.requireBean()
}
- if (needGlobal) {
- globalOutbounds[proxyEntity.id]?.let {
- if (index == 0) chainTagOut = it // single, duplicate chain
- return@forEachIndexed
- }
- }
-
// last profile set as "proxy"
if (chainId == 0L && index == 0) {
tagOut = TAG_PROXY
@@ -353,10 +298,6 @@ fun buildConfig(
tagOut = selectorName(bean.displayName())
}
- // now tagOut is determined
- if (needGlobal) {
- globalOutbounds[proxyEntity.id] = tagOut
- }
// chain rules
if (index > 0) {
@@ -367,93 +308,118 @@ fun buildConfig(
outbound = tagOut
})
} else {
- pastOutbound["detour"] = tagOut
+ pastOutbound._hack_config_map["detour"] = tagOut
}
} else {
// index == 0 means last profile in chain / not chain
chainTagOut = tagOut
}
- // Chain outbound
- if (proxyEntity.needExternal()) {
+ // now tagOut is determined
+ if (needGlobal) {
+ globalOutbounds[proxyEntity.id]?.let {
+ if (index == 0) chainTagOut = it // single, duplicate chain
+ return@forEachIndexed
+ }
+ globalOutbounds[proxyEntity.id] = tagOut
+ }
+
+ if (proxyEntity.needExternal()) { // externel outbound
val localPort = mkPort()
externalChainMap[localPort] = proxyEntity
currentOutbound = Outbound_SocksOptions().apply {
type = "socks"
server = LOCALHOST
server_port = localPort
- }.asMap()
+ }
} else {
// internal outbound
currentOutbound = when (bean) {
- is ConfigBean ->
- gson.fromJson(bean.config, currentOutbound.javaClass)
+ is ConfigBean -> CustomSingBoxOption(bean.config)
+
is ShadowTLSBean -> // before StandardV2RayBean
- buildSingBoxOutboundShadowTLSBean(bean).asMap()
+ buildSingBoxOutboundShadowTLSBean(bean)
+
is StandardV2RayBean -> // http/trojan/vmess/vless
- buildSingBoxOutboundStandardV2RayBean(bean).asMap()
+ buildSingBoxOutboundStandardV2RayBean(bean)
+
is HysteriaBean ->
- buildSingBoxOutboundHysteriaBean(bean).asMap()
+ buildSingBoxOutboundHysteriaBean(bean)
+
+ is TuicBean ->
+ buildSingBoxOutboundTuicBean(bean)
+
is SOCKSBean ->
- buildSingBoxOutboundSocksBean(bean).asMap()
+ buildSingBoxOutboundSocksBean(bean)
+
is ShadowsocksBean ->
- buildSingBoxOutboundShadowsocksBean(bean).asMap()
+ buildSingBoxOutboundShadowsocksBean(bean)
+
is WireGuardBean ->
- buildSingBoxOutboundWireguardBean(bean).asMap()
+ buildSingBoxOutboundWireguardBean(bean)
+
is SSHBean ->
- buildSingBoxOutboundSSHBean(bean).asMap()
+ buildSingBoxOutboundSSHBean(bean)
+
+ is AnyTLSBean ->
+ buildSingBoxOutboundAnyTLSBean(bean)
+
else -> throw IllegalStateException("can't reach")
}
- currentOutbound.apply {
- // TODO nb4a keepAliveInterval?
-// val keepAliveInterval = DataStore.tcpKeepAliveInterval
-// val needKeepAliveInterval = keepAliveInterval !in intArrayOf(0, 15)
-
- if (!muxApplied && proxyEntity.needCoreMux()) {
+ // internal mux
+ if (!muxApplied) {
+ val muxObj = proxyEntity.singMux()
+ if (muxObj != null && muxObj.enabled) {
muxApplied = true
- currentOutbound["multiplex"] = MultiplexOptions().apply {
- enabled = true
- max_streams = DataStore.muxConcurrency
- }
+ currentOutbound._hack_config_map["multiplex"] = muxObj.asMap()
}
}
+ }
- // custom JSON merge
- if (bean.customOutboundJson.isNotBlank()) {
- mergeJSON(bean.customOutboundJson, currentOutbound)
+ // internal & external
+ currentOutbound.apply {
+ // udp over tcp
+ try {
+ val sUoT = bean.javaClass.getField("sUoT").get(bean)
+ if (sUoT is Boolean && sUoT) {
+ _hack_config_map["udp_over_tcp"] = true
+ }
+ } catch (_: Exception) {
}
- }
- pastEntity?.requireBean()?.apply {
- // don't loopback
- if (currentDomainStrategy != "" && !serverAddress.isIpAddress()) {
- domainListDNSDirectForce.add("full:$serverAddress")
+ // domain_strategy
+ pastEntity?.requireBean()?.apply {
+ // don't loopback
+ if (defaultServerDomainStrategy != "" && !serverAddress.isIpAddress()) {
+ domainListDNSDirectForce.add("full:$serverAddress")
+ }
}
- }
- if (forTest) {
- currentDomainStrategy = ""
- }
+ _hack_config_map["domain_strategy"] =
+ if (forTest) "" else defaultServerDomainStrategy
- currentOutbound["tag"] = tagOut
- currentOutbound["domain_strategy"] = currentDomainStrategy
+ _hack_config_map["tag"] = tagOut
+
+ _hack_custom_config = bean.customOutboundJson
+ }
// External proxy need a dokodemo-door inbound to forward the traffic
// For external proxy software, their traffic must goes to v2ray-core to use protected fd.
+ bean.finalAddress = bean.serverAddress
+ bean.finalPort = bean.serverPort
if (bean.canMapping() && proxyEntity.needExternal()) {
// With ss protect, don't use mapping
var needExternal = true
if (index == profileList.lastIndex) {
val pluginId = when (bean) {
- is HysteriaBean -> "hysteria-plugin"
- is TuicBean -> "tuic-plugin"
+ is HysteriaBean -> if (bean.protocolVersion == 1) "hysteria-plugin" else "hysteria2-plugin"
else -> ""
}
if (Plugins.isUsingMatsuriExe(pluginId)) {
needExternal = false
- } else if (bean is HysteriaBean) {
- throw Exception("not supported hysteria-plugin (SagerNet)")
+ } else if (Plugins.getPluginExternal(pluginId) != null) {
+ throw Exception("You are using an unsupported $pluginId, please download the correct plugin.")
}
}
if (needExternal) {
@@ -495,8 +461,8 @@ fun buildConfig(
// build outbounds
if (buildSelector) {
- val list = group?.id?.let { SagerDatabase.proxyDao.getByGroup(it) }
- list?.forEach {
+ val list = group.id.let { SagerDatabase.proxyDao.getByGroup(it) }
+ list.forEach {
tagMap[it.id] = buildChain(it.id, it)
}
outbounds.add(0, Outbound_SelectorOptions().apply {
@@ -504,7 +470,7 @@ fun buildConfig(
tag = TAG_PROXY
default_ = tagMap[proxy.id]
outbounds = tagMap.values.toList()
- }.asMap())
+ })
} else {
buildChain(0, proxy)
}
@@ -518,30 +484,38 @@ fun buildConfig(
if (rule.packages.isNotEmpty()) {
PackageCache.awaitLoadSync()
}
- val uidList2 = rule.packages.map {
+ val uidList = rule.packages.map {
if (!isVPN) {
- alerts.add(0 to rule.displayName())
+ Toast.makeText(
+ SagerNet.application,
+ SagerNet.application.getString(R.string.route_need_vpn, rule.displayName()),
+ Toast.LENGTH_SHORT
+ ).show()
}
PackageCache[it]?.takeIf { uid -> uid >= 1000 }
}.toHashSet().filterNotNull()
+ val ruleSets = mutableListOf()
val ruleObj = Rule_DefaultOptions().apply {
- if (uidList2.isNotEmpty()) {
+ if (uidList.isNotEmpty()) {
PackageCache.awaitLoadSync()
- user_id = uidList2
+ user_id = uidList
}
- var domainList2: List? = null
+ var domainList: List? = null
if (rule.domains.isNotBlank()) {
- domainList2 = rule.domains.split("\n")
- makeSingBoxRule(domainList2, false)
+ domainList = rule.domains.listByLineOrComma()
+ makeSingBoxRule(domainList, false)
}
if (rule.ip.isNotBlank()) {
- makeSingBoxRule(rule.ip.split("\n"), true)
+ makeSingBoxRule(rule.ip.listByLineOrComma(), true)
}
+
+ if (rule_set != null) generateRuleSet(rule_set, ruleSets)
+
if (rule.port.isNotBlank()) {
port = mutableListOf()
port_range = mutableListOf()
- rule.port.split(",").map {
+ rule.port.listByLineOrComma().map {
if (it.contains(":")) {
port_range.add(it)
} else {
@@ -552,7 +526,7 @@ fun buildConfig(
if (rule.sourcePort.isNotBlank()) {
source_port = mutableListOf()
source_port_range = mutableListOf()
- rule.sourcePort.split(",").map {
+ rule.sourcePort.listByLineOrComma().map {
if (it.contains(":")) {
source_port_range.add(it)
} else {
@@ -561,78 +535,88 @@ fun buildConfig(
}
}
if (rule.network.isNotBlank()) {
- network = rule.network
+ network = listOf(rule.network)
}
if (rule.source.isNotBlank()) {
- source_ip_cidr = rule.source.split("\n")
+ source_ip_cidr = rule.source.listByLineOrComma()
}
if (rule.protocol.isNotBlank()) {
- protocol = rule.protocol.split("\n")
+ protocol = rule.protocol.listByLineOrComma()
}
- // also bypass lookup
- // cannot use other outbound profile to lookup...
- if (rule.outbound == -1L) {
- uidListDNSDirect += uidList2
- if (domainList2 != null) domainListDNSDirect += domainList2
- } else if (rule.outbound == 0L) {
- uidListDNSRemote += uidList2
- if (domainList2 != null) domainListDNSRemote += domainList2
- } else if (rule.outbound == -2L) {
- if (domainList2 != null) domainListDNSBlock += domainList2
+ fun makeDnsRuleObj(): DNSRule_DefaultOptions {
+ return DNSRule_DefaultOptions().apply {
+ if (uidList.isNotEmpty()) user_id = uidList
+ domainList?.let { makeSingBoxRule(it) }
+ }
+ }
+
+ when (rule.outbound) {
+ -1L -> {
+ userDNSRuleList += makeDnsRuleObj().apply { server = "dns-direct" }
+ }
+
+ 0L -> {
+ if (useFakeDns) userDNSRuleList += makeDnsRuleObj().apply {
+ server = "dns-fake"
+ inbound = listOf("tun-in")
+ }
+ userDNSRuleList += makeDnsRuleObj().apply {
+ server = "dns-remote"
+ }
+ }
+
+ -2L -> {
+ userDNSRuleList += makeDnsRuleObj().apply {
+ server = "dns-block"
+ disable_cache = true
+ }
+ }
}
outbound = when (val outId = rule.outbound) {
0L -> TAG_PROXY
-1L -> TAG_BYPASS
-2L -> TAG_BLOCK
- else -> if (outId == proxy.id) TAG_PROXY else tagMap[outId]
- ?: throw Exception("invalid rule")
+ else -> if (outId == proxy.id) TAG_PROXY else tagMap[outId] ?: ""
}
+
+ _hack_custom_config = rule.config
}
if (!ruleObj.checkEmpty()) {
- route.rules.add(ruleObj)
+ if (ruleObj.outbound.isNullOrBlank()) {
+ Toast.makeText(
+ SagerNet.application,
+ "Warning: " + rule.displayName() + ": A non-existent outbound was specified.",
+ Toast.LENGTH_LONG
+ ).show()
+ } else {
+ // block 改用新的写法
+ if (ruleObj.outbound == TAG_BLOCK) {
+ ruleObj.outbound = null
+ ruleObj.action = "reject"
+ }
+ route.rules.add(ruleObj)
+ route.rule_set.addAll(ruleSets)
+ }
}
}
+ // 对 rule_set tag 去重
+ if (route.rule_set != null) {
+ route.rule_set = route.rule_set.distinctBy { it.tag }
+ }
+
for (freedom in arrayOf(TAG_DIRECT, TAG_BYPASS)) outbounds.add(Outbound().apply {
tag = freedom
type = "direct"
- }.asMap())
-
- outbounds.add(Outbound().apply {
- tag = TAG_BLOCK
- type = "block"
- }.asMap())
-
- if (!forTest) {
- inbounds.add(0, Inbound_DirectOptions().apply {
- type = "direct"
- tag = TAG_DNS_IN
- listen = bind
- listen_port = DataStore.localDNSPort
- override_address = "8.8.8.8"
- override_port = 53
- })
-
- outbounds.add(Outbound().apply {
- type = "dns"
- tag = TAG_DNS_OUT
- }.asMap())
- }
-
- if (DataStore.directDnsUseSystem) {
- // finally able to use "localDns" now...
- directDNS = listOf(LOCAL_DNS_SERVER)
- }
+ })
// Bypass Lookup for the first profile
bypassDNSBeans.forEach {
var serverAddr = it.serverAddress
- if (it is HysteriaBean && it.isMultiPort()) {
- serverAddr = it.serverAddress.substringBeforeLast(":")
- }
+
if (it is ConfigBean) {
var config = mutableMapOf()
config = gson.fromJson(it.config, config.javaClass)
@@ -658,137 +642,94 @@ fun buildConfig(
}
}
- // remote dns obj
- remoteDns.firstOrNull().let {
- dns.servers.add(DNSServerOptions().apply {
- address = it ?: throw Exception("No remote DNS, check your settings!")
- tag = "dns-remote"
- address_resolver = "dns-direct"
- applyDNSNetworkSettings(false)
- })
- }
+ dns.servers.add(DNSServerOptions().apply {
+ address = "rcode://success"
+ tag = "dns-block"
+ })
+
+ dns.servers.add(DNSServerOptions().apply {
+ address = "local"
+ tag = "dns-local"
+ detour = TAG_DIRECT
+ })
- // add directDNS objects here
directDNS.firstOrNull().let {
dns.servers.add(DNSServerOptions().apply {
address = it ?: throw Exception("No direct DNS, check your settings!")
tag = "dns-direct"
detour = TAG_DIRECT
address_resolver = "dns-local"
- applyDNSNetworkSettings(true)
+ strategy = autoDnsDomainStrategy(SingBoxOptionsUtil.domainStrategy(tag))
})
}
- dns.servers.add(DNSServerOptions().apply {
- address = LOCAL_DNS_SERVER
- tag = "dns-local"
- detour = TAG_DIRECT
- })
- dns.servers.add(DNSServerOptions().apply {
- address = "rcode://success"
- tag = "dns-block"
- })
+
+ remoteDns.firstOrNull().let {
+ // Always use direct DNS for urlTest
+ if (!forTest) dns.servers.add(DNSServerOptions().apply {
+ address = it ?: throw Exception("No remote DNS, check your settings!")
+ tag = "dns-remote"
+ address_resolver = "dns-direct"
+ strategy = autoDnsDomainStrategy(SingBoxOptionsUtil.domainStrategy(tag))
+ })
+ }
+
+ dns.final_ = if (forTest) "dns-direct" else "dns-remote"
// dns object user rules
if (enableDnsRouting) {
- val dnsRuleObj = mutableListOf()
- if (uidListDNSRemote.isNotEmpty()) {
- if (useFakeDns) dnsRuleObj.add(
- DNSRule_DefaultOptions().apply {
- user_id = uidListDNSRemote.toHashSet().toList()
- server = "dns-fake"
- inbound = listOf("tun-in")
- }
- )
- dnsRuleObj.add(
- DNSRule_DefaultOptions().apply {
- user_id = uidListDNSRemote.toHashSet().toList()
- server = "dns-remote"
- }
- )
- }
- if (domainListDNSRemote.isNotEmpty()) {
- if (useFakeDns) dnsRuleObj.add(
- DNSRule_DefaultOptions().apply {
- makeSingBoxRule(domainListDNSRemote.toHashSet().toList())
- server = "dns-fake"
- inbound = listOf("tun-in")
- }
- )
- dnsRuleObj.add(
- DNSRule_DefaultOptions().apply {
- makeSingBoxRule(domainListDNSRemote.toHashSet().toList())
- server = "dns-remote"
- }
- )
- }
- if (uidListDNSDirect.isNotEmpty()) {
- dnsRuleObj.add(
- DNSRule_DefaultOptions().apply {
- user_id = uidListDNSDirect.toHashSet().toList()
- server = "dns-direct"
- }
- )
- }
- if (domainListDNSDirect.isNotEmpty()) {
- dnsRuleObj.add(
- DNSRule_DefaultOptions().apply {
- makeSingBoxRule(domainListDNSDirect.toHashSet().toList())
- server = "dns-direct"
- }
- )
- }
- if (domainListDNSBlock.isNotEmpty()) {
- dnsRuleObj.add(
- DNSRule_DefaultOptions().apply {
- makeSingBoxRule(domainListDNSBlock.toHashSet().toList())
- server = "dns-block"
- disable_cache = true
- }
- )
- }
- dnsRuleObj.forEach {
+ userDNSRuleList.forEach {
if (!it.checkEmpty()) dns.rules.add(it)
}
}
if (forTest) {
- // Disable DNS for test
- dns.servers = listOf(
- DNSServerOptions().apply {
- address = LOCAL_DNS_SERVER
- tag = "dns-local"
- detour = TAG_DIRECT
- }
- ) // Always use system DNS for urlTest
dns.rules = listOf()
} else {
// built-in DNS rules
route.rules.add(0, Rule_DefaultOptions().apply {
- inbound = listOf(TAG_DNS_IN)
- outbound = TAG_DNS_OUT
+ protocol = listOf("dns")
+ action = "hijack-dns"
})
route.rules.add(0, Rule_DefaultOptions().apply {
port = listOf(53)
- outbound = TAG_DNS_OUT
- }) // TODO new mode use system dns?
+ action = "hijack-dns"
+ })
if (DataStore.bypassLanInCore) {
route.rules.add(Rule_DefaultOptions().apply {
outbound = TAG_BYPASS
- geoip = listOf("private")
+ ip_is_private = true
})
}
// block mcast
route.rules.add(Rule_DefaultOptions().apply {
ip_cidr = listOf("224.0.0.0/3", "ff00::/8")
source_ip_cidr = listOf("224.0.0.0/3", "ff00::/8")
- outbound = TAG_BLOCK
+ action = "reject"
})
- dns.rules.add(DNSRule_DefaultOptions().apply {
- domain_suffix = listOf(".arpa.", ".arpa")
- server = "dns-block"
- disable_cache = true
+ // FakeDNS obj
+ if (useFakeDns) {
+ dns.fakeip = DNSFakeIPOptions().apply {
+ enabled = true
+ inet4_range = "198.18.0.0/15"
+ inet6_range = "fc00::/18"
+ }
+ dns.servers.add(DNSServerOptions().apply {
+ address = "fakeip"
+ tag = "dns-fake"
+ strategy = "ipv4_only"
+ })
+ dns.rules.add(DNSRule_DefaultOptions().apply {
+ inbound = listOf("tun-in")
+ server = "dns-fake"
+ disable_cache = true
+ })
+ }
+ // avoid loopback
+ dns.rules.add(0, DNSRule_DefaultOptions().apply {
+ outbound = mutableListOf("any")
+ server = "dns-direct"
})
- // force bypass
+ // force bypass (always top DNS rule)
if (domainListDNSDirectForce.isNotEmpty()) {
dns.rules.add(0, DNSRule_DefaultOptions().apply {
makeSingBoxRule(domainListDNSDirectForce.toHashSet().toList())
@@ -797,34 +738,17 @@ fun buildConfig(
}
}
- // fakedns obj
- if (useFakeDns) {
- dns.servers.add(DNSServerOptions().apply {
- address = "fakedns://" + VpnService.FAKEDNS_VLAN4_CLIENT + "/15"
- tag = "dns-fake"
- strategy = "ipv4_only"
- })
- dns.rules.add(0, DNSRule_DefaultOptions().apply {
- auth_user = listOf("fakedns")
- server = "dns-remote"
- })
- dns.rules.add(DNSRule_DefaultOptions().apply {
- inbound = listOf("tun-in")
- server = "dns-fake"
- disable_cache = true
- })
- }
+ if (!forTest) _hack_custom_config = DataStore.globalCustomConfig
}.let {
+ val configMap = it.asMap()
+ Util.mergeJSON(configMap, proxy.requireBean().customConfigJson)
ConfigBuildResult(
- gson.toJson(it.asMap().apply {
- mergeJSON(optionsToMerge, this)
- }),
+ gson.toJson(configMap),
externalIndexMap,
proxy.id,
trafficMap,
tagMap,
- alerts,
- if (buildSelector) group!!.id else -1L
+ if (buildSelector) group.id else -1L
)
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java
index b9a6338da..8fb9951ba 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java
@@ -13,8 +13,10 @@
import io.nekohasekai.sagernet.fmt.http.HttpBean;
import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean;
import io.nekohasekai.sagernet.fmt.internal.ChainBean;
+import io.nekohasekai.sagernet.fmt.mieru.MieruBean;
import io.nekohasekai.sagernet.fmt.naive.NaiveBean;
import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean;
+import moe.matsuri.nb4a.proxy.anytls.AnyTLSBean;
import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean;
import io.nekohasekai.sagernet.fmt.socks.SOCKSBean;
import io.nekohasekai.sagernet.fmt.ssh.SSHBean;
@@ -99,6 +101,12 @@ public static TrojanGoBean trojanGoDeserialize(byte[] bytes) {
return deserialize(new TrojanGoBean(), bytes);
}
+ @TypeConverter
+ public static MieruBean mieruDeserialize(byte[] bytes) {
+ if (JavaUtil.isEmpty(bytes)) return null;
+ return deserialize(new MieruBean(), bytes);
+ }
+
@TypeConverter
public static NaiveBean naiveDeserialize(byte[] bytes) {
if (JavaUtil.isEmpty(bytes)) return null;
@@ -135,6 +143,13 @@ public static ShadowTLSBean shadowTLSDeserialize(byte[] bytes) {
return deserialize(new ShadowTLSBean(), bytes);
}
+ @TypeConverter
+ public static AnyTLSBean anyTLSDeserialize(byte[] bytes) {
+ if (JavaUtil.isEmpty(bytes)) return null;
+ return deserialize(new AnyTLSBean(), bytes);
+ }
+
+
@TypeConverter
public static ChainBean chainDeserialize(byte[] bytes) {
if (JavaUtil.isEmpty(bytes)) return null;
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt
index 6cd765856..42769e214 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt
@@ -14,35 +14,42 @@ enum class PluginEntry(
SagerNet.application.getString(R.string.action_trojan_go),
"io.nekohasekai.sagernet.plugin.trojan_go"
),
+ MieruProxy(
+ "mieru-plugin",
+ SagerNet.application.getString(R.string.action_mieru),
+ "moe.matsuri.exe.mieru",
+ DownloadSource(
+ playStore = false,
+ fdroid = false,
+ downloadLink = "https://github.com/MatsuriDayo/plugins/releases?q=mieru"
+ )
+ ),
NaiveProxy(
"naive-plugin",
SagerNet.application.getString(R.string.action_naive),
- "io.nekohasekai.sagernet.plugin.naive"
+ "moe.matsuri.exe.naive",
+ DownloadSource(
+ playStore = false,
+ fdroid = false,
+ downloadLink = "https://github.com/MatsuriDayo/plugins/releases?q=naive"
+ )
),
Hysteria(
"hysteria-plugin",
SagerNet.application.getString(R.string.action_hysteria),
- "moe.matsuri.exe.hysteria", DownloadSource(
+ "moe.matsuri.exe.hysteria",
+ DownloadSource(
playStore = false,
fdroid = false,
downloadLink = "https://github.com/MatsuriDayo/plugins/releases?q=Hysteria"
)
),
- TUIC(
- "tuic-plugin",
- SagerNet.application.getString(R.string.action_tuic),
- "moe.matsuri.exe.tuic", DownloadSource(
- playStore = false,
- fdroid = false,
- downloadLink = "https://github.com/MatsuriDayo/plugins/releases?q=tuic"
- )
- ),
;
data class DownloadSource(
val playStore: Boolean = true,
val fdroid: Boolean = true,
- val downloadLink: String = "https://sagernet.org/download/"
+ val downloadLink: String = "https://matsuridayo.github.io/"
)
companion object {
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt
index c55c58b71..1a91eeff8 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt
@@ -10,11 +10,13 @@ object TypeMap : HashMap() {
this["vmess"] = ProxyEntity.TYPE_VMESS
this["trojan"] = ProxyEntity.TYPE_TROJAN
this["trojan-go"] = ProxyEntity.TYPE_TROJAN_GO
+ this["mieru"] = ProxyEntity.TYPE_MIERU
this["naive"] = ProxyEntity.TYPE_NAIVE
this["hysteria"] = ProxyEntity.TYPE_HYSTERIA
this["ssh"] = ProxyEntity.TYPE_SSH
this["wg"] = ProxyEntity.TYPE_WG
this["tuic"] = ProxyEntity.TYPE_TUIC
+ this["anytls"] = ProxyEntity.TYPE_ANYTLS
this["neko"] = ProxyEntity.TYPE_NEKO
this["config"] = ProxyEntity.TYPE_CONFIG
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaBean.java
index 239c9d4ab..6a5f83c03 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaBean.java
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaBean.java
@@ -9,27 +9,21 @@
import io.nekohasekai.sagernet.fmt.AbstractBean;
import io.nekohasekai.sagernet.fmt.KryoConverters;
+import io.nekohasekai.sagernet.ktx.NetsKt;
+import kotlin.text.StringsKt;
public class HysteriaBean extends AbstractBean {
+ public Integer protocolVersion;
- public static final int TYPE_NONE = 0;
- public static final int TYPE_STRING = 1;
- public static final int TYPE_BASE64 = 2;
-
- public Integer authPayloadType;
- public String authPayload;
+ // Use serverPorts instead of serverPort
+ public String serverPorts;
- public static final int PROTOCOL_UDP = 0;
- public static final int PROTOCOL_FAKETCP = 1;
- public static final int PROTOCOL_WECHAT_VIDEO = 2;
-
- public Integer protocol;
+ // HY1 & 2
+ public String authPayload;
public String obfuscation;
public String sni;
- public String alpn;
public String caText;
-
public Integer uploadMbps;
public Integer downloadMbps;
public Boolean allowInsecure;
@@ -38,6 +32,20 @@ public class HysteriaBean extends AbstractBean {
public Boolean disableMtuDiscovery;
public Integer hopInterval;
+ // HY1
+
+ public String alpn;
+
+ public static final int TYPE_NONE = 0;
+ public static final int TYPE_STRING = 1;
+ public static final int TYPE_BASE64 = 2;
+ public Integer authPayloadType;
+
+ public static final int PROTOCOL_UDP = 0;
+ public static final int PROTOCOL_FAKETCP = 1;
+ public static final int PROTOCOL_WECHAT_VIDEO = 2;
+ public Integer protocol;
+
@Override
public boolean canMapping() {
return protocol != PROTOCOL_FAKETCP;
@@ -46,6 +54,8 @@ public boolean canMapping() {
@Override
public void initializeDefaultValues() {
super.initializeDefaultValues();
+ if (protocolVersion == null) protocolVersion = 2;
+
if (authPayloadType == null) authPayloadType = TYPE_NONE;
if (authPayload == null) authPayload = "";
if (protocol == null) protocol = PROTOCOL_UDP;
@@ -53,21 +63,30 @@ public void initializeDefaultValues() {
if (sni == null) sni = "";
if (alpn == null) alpn = "";
if (caText == null) caText = "";
-
- if (uploadMbps == null) uploadMbps = 10;
- if (downloadMbps == null) downloadMbps = 50;
if (allowInsecure == null) allowInsecure = false;
+ if (protocolVersion == 1) {
+ if (uploadMbps == null) uploadMbps = 10;
+ if (downloadMbps == null) downloadMbps = 50;
+ } else {
+ if (uploadMbps == null) uploadMbps = 0;
+ if (downloadMbps == null) downloadMbps = 0;
+ }
+
if (streamReceiveWindow == null) streamReceiveWindow = 0;
if (connectionReceiveWindow == null) connectionReceiveWindow = 0;
if (disableMtuDiscovery == null) disableMtuDiscovery = false;
if (hopInterval == null) hopInterval = 10;
+ if (serverPorts == null) serverPorts = "443";
}
@Override
public void serialize(ByteBufferOutput output) {
- output.writeInt(5);
+ output.writeInt(7);
super.serialize(output);
+
+ output.writeInt(protocolVersion);
+
output.writeInt(authPayloadType);
output.writeString(authPayload);
output.writeInt(protocol);
@@ -84,13 +103,18 @@ public void serialize(ByteBufferOutput output) {
output.writeInt(connectionReceiveWindow);
output.writeBoolean(disableMtuDiscovery);
output.writeInt(hopInterval);
-
+ output.writeString(serverPorts);
}
@Override
public void deserialize(ByteBufferInput input) {
int version = input.readInt();
super.deserialize(input);
+ if (version >= 7) {
+ protocolVersion = input.readInt();
+ } else {
+ protocolVersion = 1;
+ }
authPayloadType = input.readInt();
authPayload = input.readString();
if (version >= 3) {
@@ -113,6 +137,17 @@ public void deserialize(ByteBufferInput input) {
if (version >= 5) {
hopInterval = input.readInt();
}
+ if (version >= 6) {
+ serverPorts = input.readString();
+ } else {
+ // old update to new
+ if (HysteriaFmtKt.isMultiPort(serverAddress)) {
+ serverPorts = StringsKt.substringAfterLast(serverAddress, ":", serverAddress);
+ serverAddress = StringsKt.substringBeforeLast(serverAddress, ":", serverAddress);
+ } else {
+ serverPorts = serverPort.toString();
+ }
+ }
}
@Override
@@ -128,10 +163,7 @@ public void applyFeatureSettings(AbstractBean other) {
@Override
public String displayAddress() {
- if (HysteriaFmtKt.isMultiPort(this)) {
- return serverAddress;
- }
- return super.displayAddress();
+ return NetsKt.wrapIPV6Host(serverAddress) + ":" + serverPorts;
}
@Override
@@ -157,4 +189,4 @@ public HysteriaBean[] newArray(int size) {
return new HysteriaBean[size];
}
};
-}
+}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaFmt.kt
index c0e321c3f..1e78a29f5 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaFmt.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaFmt.kt
@@ -4,24 +4,25 @@ import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.fmt.LOCALHOST
import io.nekohasekai.sagernet.ktx.*
import moe.matsuri.nb4a.SingBoxOptions
+import moe.matsuri.nb4a.utils.listByLineOrComma
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.json.JSONObject
import java.io.File
// hysteria://host:port?auth=123456&peer=sni.domain&insecure=1|0&upmbps=100&downmbps=100&alpn=hysteria&obfs=xplus&obfsParam=123456#remarks
-
-fun parseHysteria(url: String): HysteriaBean {
+fun parseHysteria1(url: String): HysteriaBean {
val link = url.replace("hysteria://", "https://").toHttpUrlOrNull() ?: error(
"invalid hysteria link $url"
)
return HysteriaBean().apply {
+ protocolVersion = 1
serverAddress = link.host
- serverPort = link.port
+ serverPorts = link.port.toString()
name = link.fragment
link.queryParameter("mport")?.also {
- serverAddress = serverAddress.wrapIPV6Host() + ":" + it
+ serverPorts = it
}
link.queryParameter("peer")?.also {
sni = it
@@ -31,7 +32,7 @@ fun parseHysteria(url: String): HysteriaBean {
authPayload = it
}
link.queryParameter("insecure")?.also {
- allowInsecure = it == "1"
+ allowInsecure = it == "1" || it == "true"
}
link.queryParameter("upmbps")?.also {
uploadMbps = it.toIntOrNull() ?: uploadMbps
@@ -50,6 +51,7 @@ fun parseHysteria(url: String): HysteriaBean {
"faketcp" -> {
protocol = HysteriaBean.PROTOCOL_FAKETCP
}
+
"wechat-video" -> {
protocol = HysteriaBean.PROTOCOL_WECHAT_VIDEO
}
@@ -58,53 +60,116 @@ fun parseHysteria(url: String): HysteriaBean {
}
}
-fun HysteriaBean.toUri(): String {
- val builder = linkBuilder().host(serverAddress.substringBeforeLast(":")).port(serverPort)
- if (isMultiPort()) {
- builder.addQueryParameter("mport", serverAddress.substringAfterLast(":"))
- }
- if (allowInsecure) {
- builder.addQueryParameter("insecure", "1")
+// hysteria2://[auth@]hostname[:port]/?[key=value]&[key=value]...
+fun parseHysteria2(url: String): HysteriaBean {
+ val link = url
+ .replace("hysteria2://", "https://")
+ .replace("hy2://", "https://")
+ .toHttpUrlOrNull() ?: error("invalid hysteria link $url")
+ return HysteriaBean().apply {
+ protocolVersion = 2
+ serverAddress = link.host
+ serverPorts = link.port.toString()
+ authPayload = if (link.password.isNotBlank()) {
+ link.username + ":" + link.password
+ } else {
+ link.username
+ }
+ name = link.fragment
+
+ link.queryParameter("mport")?.also {
+ serverPorts = it
+ }
+ link.queryParameter("sni")?.also {
+ sni = it
+ }
+ link.queryParameter("insecure")?.also {
+ allowInsecure = it == "1" || it == "true"
+ }
+// link.queryParameter("upmbps")?.also {
+// uploadMbps = it.toIntOrNull() ?: uploadMbps
+// }
+// link.queryParameter("downmbps")?.also {
+// downloadMbps = it.toIntOrNull() ?: downloadMbps
+// }
+ link.queryParameter("obfs-password")?.also {
+ obfuscation = it
+ }
+// link.queryParameter("pinSHA256")?.also {
+// // TODO your box do not support it
+// }
}
- if (sni.isNotBlank()) {
- builder.addQueryParameter("peer", sni)
+}
+
+fun HysteriaBean.toUri(): String {
+ var un = ""
+ var pw = ""
+ if (protocolVersion == 2) {
+ if (authPayload.contains(":")) {
+ un = authPayload.substringBefore(":")
+ pw = authPayload.substringAfter(":")
+ } else {
+ un = authPayload
+ }
}
- if (authPayload.isNotBlank()) {
- builder.addQueryParameter("auth", authPayload)
+ //
+ val builder = linkBuilder()
+ .host(serverAddress)
+ .port(getFirstPort(serverPorts))
+ .username(un)
+ .password(pw)
+ if (isMultiPort(displayAddress())) {
+ builder.addQueryParameter("mport", serverPorts)
}
- builder.addQueryParameter("upmbps", "$uploadMbps")
- builder.addQueryParameter("downmbps", "$downloadMbps")
- if (alpn.isNotBlank()) {
- builder.addQueryParameter("alpn", alpn)
+ if (name.isNotBlank()) {
+ builder.encodedFragment(name.urlSafe())
}
- if (obfuscation.isNotBlank()) {
- builder.addQueryParameter("obfs", "xplus")
- builder.addQueryParameter("obfsParam", obfuscation)
+ if (allowInsecure) {
+ builder.addQueryParameter("insecure", "1")
}
- when (protocol) {
- HysteriaBean.PROTOCOL_FAKETCP -> {
- builder.addQueryParameter("protocol", "faketcp")
+ if (protocolVersion == 1) {
+ if (sni.isNotBlank()) {
+ builder.addQueryParameter("peer", sni)
}
- HysteriaBean.PROTOCOL_WECHAT_VIDEO -> {
- builder.addQueryParameter("protocol", "wechat-video")
+ if (authPayload.isNotBlank()) {
+ builder.addQueryParameter("auth", authPayload)
+ }
+ builder.addQueryParameter("upmbps", "$uploadMbps")
+ builder.addQueryParameter("downmbps", "$downloadMbps")
+ if (alpn.isNotBlank()) {
+ builder.addQueryParameter("alpn", alpn)
+ }
+ if (obfuscation.isNotBlank()) {
+ builder.addQueryParameter("obfs", "xplus")
+ builder.addQueryParameter("obfsParam", obfuscation)
+ }
+ when (protocol) {
+ HysteriaBean.PROTOCOL_FAKETCP -> {
+ builder.addQueryParameter("protocol", "faketcp")
+ }
+
+ HysteriaBean.PROTOCOL_WECHAT_VIDEO -> {
+ builder.addQueryParameter("protocol", "wechat-video")
+ }
+ }
+ } else {
+ if (sni.isNotBlank()) {
+ builder.addQueryParameter("sni", sni)
+ }
+ if (obfuscation.isNotBlank()) {
+ builder.addQueryParameter("obfs", "salamander")
+ builder.addQueryParameter("obfs-password", obfuscation)
}
}
- if (protocol == HysteriaBean.PROTOCOL_FAKETCP) {
- builder.addQueryParameter("protocol", "faketcp")
- }
- if (name.isNotBlank()) {
- builder.encodedFragment(name.urlSafe())
- }
- return builder.toLink("hysteria")
+ return builder.toLink(if (protocolVersion == 2) "hy2" else "hysteria")
}
-fun JSONObject.parseHysteria(): HysteriaBean {
+fun JSONObject.parseHysteria1Json(): HysteriaBean {
+ // TODO parse HY2 JSON+YAML
return HysteriaBean().apply {
- serverAddress = optString("server")
- if (!isMultiPort()) {
- serverAddress = optString("server").substringBeforeLast(":")
- serverPort = optString("server").substringAfterLast(":").toIntOrNull() ?: 443
- }
+ protocolVersion = 1
+ serverAddress = optString("server").substringBeforeLast(":")
+ serverPorts = optString("server").substringAfterLast(":")
uploadMbps = getIntNya("up_mbps")
downloadMbps = getIntNya("down_mbps")
obfuscation = getStr("obfs")
@@ -121,6 +186,7 @@ fun JSONObject.parseHysteria(): HysteriaBean {
"faketcp" -> {
protocol = HysteriaBean.PROTOCOL_FAKETCP
}
+
"wechat-video" -> {
protocol = HysteriaBean.PROTOCOL_WECHAT_VIDEO
}
@@ -136,13 +202,17 @@ fun JSONObject.parseHysteria(): HysteriaBean {
}
}
-fun HysteriaBean.buildHysteriaConfig(port: Int, cacheFile: (() -> File)?): String {
+fun HysteriaBean.buildHysteria1Config(port: Int, cacheFile: (() -> File)?): String {
+ if (protocolVersion != 1) {
+ throw Exception("error version: $protocolVersion")
+ }
return JSONObject().apply {
- put("server", if (isMultiPort()) serverAddress else wrapUri())
+ put("server", displayAddress())
when (protocol) {
HysteriaBean.PROTOCOL_FAKETCP -> {
put("protocol", "faketcp")
}
+
HysteriaBean.PROTOCOL_WECHAT_VIDEO -> {
put("protocol", "wechat-video")
}
@@ -157,6 +227,8 @@ fun HysteriaBean.buildHysteriaConfig(port: Int, cacheFile: (() -> File)?): Strin
)
)
put("retry", 5)
+ put("fast_open", true)
+ put("lazy_start", true)
put("obfs", obfuscation)
when (authPayloadType) {
HysteriaBean.TYPE_BASE64 -> put("auth", authPayload)
@@ -180,57 +252,117 @@ fun HysteriaBean.buildHysteriaConfig(port: Int, cacheFile: (() -> File)?): Strin
if (connectionReceiveWindow > 0) put("recv_window", connectionReceiveWindow)
if (disableMtuDiscovery) put("disable_mtu_discovery", true)
- // hy 1.2.0 (不兼容)
- put("resolver", "udp://127.0.0.1:" + DataStore.localDNSPort)
-
put("hop_interval", hopInterval)
}.toStringPretty()
}
-fun HysteriaBean.isMultiPort(): Boolean {
- if (!serverAddress.contains(":")) return false
- val p = serverAddress.substringAfterLast(":")
+fun isMultiPort(hyAddr: String): Boolean {
+ if (!hyAddr.contains(":")) return false
+ val p = hyAddr.substringAfterLast(":")
if (p.contains("-") || p.contains(",")) return true
return false
}
+fun getFirstPort(portStr: String): Int {
+ return portStr.substringBefore(":").substringBefore(",").toIntOrNull() ?: 443
+}
+
fun HysteriaBean.canUseSingBox(): Boolean {
- if (isMultiPort() || protocol != HysteriaBean.PROTOCOL_UDP) return false
+ if (protocol != HysteriaBean.PROTOCOL_UDP) return false
return true
}
-fun buildSingBoxOutboundHysteriaBean(bean: HysteriaBean): SingBoxOptions.Outbound_HysteriaOptions {
- // No multi-port
- return SingBoxOptions.Outbound_HysteriaOptions().apply {
- type = "hysteria"
- server = bean.serverAddress
- server_port = bean.serverPort
- up_mbps = bean.uploadMbps
- down_mbps = bean.downloadMbps
- obfs = bean.obfuscation
- disable_mtu_discovery = bean.disableMtuDiscovery
- when (bean.authPayloadType) {
- HysteriaBean.TYPE_BASE64 -> auth = bean.authPayload
- HysteriaBean.TYPE_STRING -> auth_str = bean.authPayload
- }
- if (bean.streamReceiveWindow > 0) {
- recv_window_conn = bean.streamReceiveWindow.toLong()
- }
- if (bean.connectionReceiveWindow > 0) {
- recv_window_conn = bean.connectionReceiveWindow.toLong()
- }
- tls = SingBoxOptions.OutboundTLSOptions().apply {
- if (bean.sni.isNotBlank()) {
- server_name = bean.sni
+fun buildSingBoxOutboundHysteriaBean(bean: HysteriaBean): SingBoxOptions.SingBoxOption {
+ return when (bean.protocolVersion) {
+ 1 -> SingBoxOptions.Outbound_HysteriaOptions().apply {
+ type = "hysteria"
+ server = bean.serverAddress
+ val port = bean.serverPorts.toIntOrNull()
+ if (port != null) {
+ server_port = port
+ } else {
+ server_ports = hopPortsToSingboxList(bean.serverPorts)
+ }
+ hop_interval = "${bean.hopInterval}s"
+ up_mbps = bean.uploadMbps
+ down_mbps = bean.downloadMbps
+ obfs = bean.obfuscation
+ disable_mtu_discovery = bean.disableMtuDiscovery
+ when (bean.authPayloadType) {
+ HysteriaBean.TYPE_BASE64 -> auth = bean.authPayload
+ HysteriaBean.TYPE_STRING -> auth_str = bean.authPayload
+ }
+ if (bean.streamReceiveWindow > 0) {
+ recv_window_conn = bean.streamReceiveWindow.toLong()
+ }
+ if (bean.connectionReceiveWindow > 0) {
+ recv_window_conn = bean.connectionReceiveWindow.toLong()
+ }
+ tls = SingBoxOptions.OutboundTLSOptions().apply {
+ if (bean.sni.isNotBlank()) {
+ server_name = bean.sni
+ }
+ if (bean.alpn.isNotBlank()) {
+ alpn = bean.alpn.listByLineOrComma()
+ }
+ if (bean.caText.isNotBlank()) {
+ certificate = bean.caText
+ }
+ insecure = bean.allowInsecure || DataStore.globalAllowInsecure
+ enabled = true
+ }
+ }
+
+ 2 -> SingBoxOptions.Outbound_Hysteria2Options().apply {
+ type = "hysteria2"
+ server = bean.serverAddress
+ val port = bean.serverPorts.toIntOrNull()
+ if (port != null) {
+ server_port = port
+ } else {
+ server_ports = hopPortsToSingboxList(bean.serverPorts)
}
- if (bean.alpn.isNotBlank()) {
- alpn = bean.alpn.split("\n")
+ hop_interval = "${bean.hopInterval}s"
+ up_mbps = bean.uploadMbps
+ down_mbps = bean.downloadMbps
+ if (bean.obfuscation.isNotBlank()) {
+ obfs = SingBoxOptions.Hysteria2Obfs().apply {
+ type = "salamander"
+ password = bean.obfuscation
+ }
}
- if (bean.caText.isNotBlank()) {
- certificate = bean.caText
+// disable_mtu_discovery = bean.disableMtuDiscovery
+ password = bean.authPayload
+// if (bean.streamReceiveWindow > 0) {
+// recv_window_conn = bean.streamReceiveWindow.toLong()
+// }
+// if (bean.connectionReceiveWindow > 0) {
+// recv_window_conn = bean.connectionReceiveWindow.toLong()
+// }
+ tls = SingBoxOptions.OutboundTLSOptions().apply {
+ if (bean.sni.isNotBlank()) {
+ server_name = bean.sni
+ }
+ alpn = listOf("h3")
+ if (bean.caText.isNotBlank()) {
+ certificate = bean.caText
+ }
+ insecure = bean.allowInsecure || DataStore.globalAllowInsecure
+ enabled = true
}
- insecure = bean.allowInsecure
- enabled = true
+ }
+
+ else -> error("error_version $bean.protocolVersion")
+ }
+}
+
+fun hopPortsToSingboxList(s: String): List {
+ return s.split(",").mapNotNull {
+ val pRange = it.replace("-", ":")
+ if (pRange.split(":").size == 2) {
+ pRange
+ } else {
+ null
}
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruBean.java
new file mode 100644
index 000000000..0a7c21fab
--- /dev/null
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruBean.java
@@ -0,0 +1,89 @@
+/******************************************************************************
+ * Copyright (C) 2022 by nekohasekai *
+ * *
+ * 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 . *
+ * *
+ ******************************************************************************/
+
+package io.nekohasekai.sagernet.fmt.mieru;
+
+import androidx.annotation.NonNull;
+
+import com.esotericsoftware.kryo.io.ByteBufferInput;
+import com.esotericsoftware.kryo.io.ByteBufferOutput;
+
+import org.jetbrains.annotations.NotNull;
+
+import io.nekohasekai.sagernet.fmt.AbstractBean;
+import io.nekohasekai.sagernet.fmt.KryoConverters;
+
+public class MieruBean extends AbstractBean {
+
+ public String protocol;
+ public String username;
+ public String password;
+ public Integer mtu;
+
+ @Override
+ public void initializeDefaultValues() {
+ super.initializeDefaultValues();
+ if (protocol == null) protocol = "TCP";
+ if (username == null) username = "";
+ if (password == null) password = "";
+ if (mtu == null) mtu = 1400;
+ }
+
+ @Override
+ public void serialize(ByteBufferOutput output) {
+ output.writeInt(0);
+ super.serialize(output);
+ output.writeString(protocol);
+ output.writeString(username);
+ output.writeString(password);
+ if (protocol.equals("UDP")) {
+ output.writeInt(mtu);
+ }
+ }
+
+ @Override
+ public void deserialize(ByteBufferInput input) {
+ int version = input.readInt();
+ super.deserialize(input);
+ protocol = input.readString();
+ username = input.readString();
+ password = input.readString();
+ if (protocol.equals("UDP")) {
+ mtu = input.readInt();
+ }
+ }
+
+ @NotNull
+ @Override
+ public MieruBean clone() {
+ return KryoConverters.deserialize(new MieruBean(), KryoConverters.serialize(this));
+ }
+
+ public static final Creator CREATOR = new CREATOR() {
+ @NonNull
+ @Override
+ public MieruBean newInstance() {
+ return new MieruBean();
+ }
+
+ @Override
+ public MieruBean[] newArray(int size) {
+ return new MieruBean[size];
+ }
+ };
+}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruFmt.kt
new file mode 100644
index 000000000..476999a0b
--- /dev/null
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruFmt.kt
@@ -0,0 +1,54 @@
+/******************************************************************************
+ * Copyright (C) 2022 by nekohasekai *
+ * *
+ * 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 . *
+ * *
+ ******************************************************************************/
+
+package io.nekohasekai.sagernet.fmt.mieru
+
+import io.nekohasekai.sagernet.ktx.toStringPretty
+import org.json.JSONArray
+import org.json.JSONObject
+
+fun MieruBean.buildMieruConfig(port: Int): String {
+ val serverInfo = JSONArray().apply {
+ put(JSONObject().apply {
+ put("ipAddress", finalAddress)
+ put("portBindings", JSONArray().apply {
+ put(JSONObject().apply {
+ put("port", finalPort)
+ put("protocol", protocol)
+ })
+ })
+ })
+ }
+ return JSONObject().apply {
+ put("activeProfile", "default")
+ put("socks5Port", port)
+ // TODO: follow NekoBox logging level.
+ put("loggingLevel", "INFO")
+ put("profiles", JSONArray().apply {
+ put(JSONObject().apply {
+ put("profileName", "default")
+ put("user", JSONObject().apply {
+ put("name", username)
+ put("password", password)
+ })
+ put("servers", serverInfo)
+ put("mtu", mtu)
+ })
+ })
+ }.toStringPretty()
+}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveBean.java
index 841ae59a8..6e504d4a5 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveBean.java
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveBean.java
@@ -23,6 +23,9 @@ public class NaiveBean extends AbstractBean {
public String certificates;
public Integer insecureConcurrency;
+ // sing-box socks
+ public Boolean sUoT;
+
@Override
public void initializeDefaultValues() {
if (serverPort == null) serverPort = 443;
@@ -34,11 +37,12 @@ public void initializeDefaultValues() {
if (certificates == null) certificates = "";
if (sni == null) sni = "";
if (insecureConcurrency == null) insecureConcurrency = 0;
+ if (sUoT == null) sUoT = false;
}
@Override
public void serialize(ByteBufferOutput output) {
- output.writeInt(2);
+ output.writeInt(3);
super.serialize(output);
output.writeString(proto);
output.writeString(username);
@@ -48,6 +52,7 @@ public void serialize(ByteBufferOutput output) {
output.writeString(certificates);
output.writeString(sni);
output.writeInt(insecureConcurrency);
+ output.writeBoolean(sUoT);
}
@Override
@@ -65,6 +70,9 @@ public void deserialize(ByteBufferInput input) {
if (version >= 1) {
insecureConcurrency = input.readInt();
}
+ if (version >= 3) {
+ sUoT = input.readBoolean();
+ }
}
@NotNull
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksBean.java
index 10c394f59..326662ffb 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksBean.java
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksBean.java
@@ -50,6 +50,13 @@ public void deserialize(ByteBufferInput input) {
sUoT = input.readBoolean();
}
+ @Override
+ public void applyFeatureSettings(AbstractBean other) {
+ if (!(other instanceof ShadowsocksBean)) return;
+ ShadowsocksBean bean = ((ShadowsocksBean) other);
+ bean.sUoT = sUoT;
+ }
+
@NotNull
@Override
public ShadowsocksBean clone() {
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksFmt.kt
index 9b0c61639..b1d0bb9c1 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksFmt.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksFmt.kt
@@ -114,14 +114,13 @@ fun buildSingBoxOutboundShadowsocksBean(bean: ShadowsocksBean): SingBoxOptions.O
server_port = bean.serverPort
password = bean.password
method = bean.method
- if (bean.sUoT) {
- udp_over_tcp = SingBoxOptions.UDPOverTCPOptions().apply {
- enabled = true
- }
- }
if (bean.plugin.isNotBlank()) {
plugin = bean.plugin.substringBefore(";")
plugin_opts = bean.plugin.substringAfter(";")
+ if (plugin == "none") {
+ plugin = null
+ plugin_opts = null
+ }
}
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSBean.java
index fb07ac109..5780480d7 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSBean.java
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSBean.java
@@ -14,6 +14,8 @@ public class SOCKSBean extends AbstractBean {
public Integer protocol;
+ public Boolean sUoT;
+
public int protocolVersion() {
switch (protocol) {
case 0:
@@ -66,15 +68,17 @@ public void initializeDefaultValues() {
if (protocol == null) protocol = PROTOCOL_SOCKS5;
if (username == null) username = "";
if (password == null) password = "";
+ if (sUoT == null) sUoT = false;
}
@Override
public void serialize(ByteBufferOutput output) {
- output.writeInt(1);
+ output.writeInt(2);
super.serialize(output);
output.writeInt(protocol);
output.writeString(username);
output.writeString(password);
+ output.writeBoolean(sUoT);
}
@Override
@@ -86,6 +90,9 @@ public void deserialize(ByteBufferInput input) {
}
username = input.readString();
password = input.readString();
+ if (version >= 2) {
+ sUoT = input.readBoolean();
+ }
}
@NotNull
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSFmt.kt
index 8eee29106..b3aff52d7 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSFmt.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSFmt.kt
@@ -6,42 +6,33 @@ import io.nekohasekai.sagernet.ktx.unUrlSafe
import io.nekohasekai.sagernet.ktx.urlSafe
import moe.matsuri.nb4a.SingBoxOptions
import moe.matsuri.nb4a.utils.NGUtil
+import moe.matsuri.nb4a.utils.Util
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
fun parseSOCKS(link: String): SOCKSBean {
- if (!link.substringAfter("://").contains(":")) {
- // v2rayN shit format
- var url = link.substringAfter("://")
- if (url.contains("#")) {
- url = url.substringBeforeLast("#")
- }
- url = url.decodeBase64UrlSafe()
- val httpUrl = "http://$url".toHttpUrlOrNull() ?: error("Invalid v2rayN link content: $url")
- return SOCKSBean().apply {
- serverAddress = httpUrl.host
- serverPort = httpUrl.port
- username = httpUrl.username.takeIf { it != "null" } ?: ""
- password = httpUrl.password.takeIf { it != "null" } ?: ""
- if (link.contains("#")) {
- name = link.substringAfter("#").unUrlSafe()
- }
- }
- } else {
- val url = ("http://" + link.substringAfter("://")).toHttpUrlOrNull()
- ?: error("Not supported: $link")
+ val url = ("http://" + link.substringAfter("://")).toHttpUrlOrNull()
+ ?: error("Not supported: $link")
- return SOCKSBean().apply {
- protocol = when {
- link.startsWith("socks4://") -> SOCKSBean.PROTOCOL_SOCKS4
- link.startsWith("socks4a://") -> SOCKSBean.PROTOCOL_SOCKS4A
- else -> SOCKSBean.PROTOCOL_SOCKS5
+ return SOCKSBean().apply {
+ protocol = when {
+ link.startsWith("socks4://") -> SOCKSBean.PROTOCOL_SOCKS4
+ link.startsWith("socks4a://") -> SOCKSBean.PROTOCOL_SOCKS4A
+ else -> SOCKSBean.PROTOCOL_SOCKS5
+ }
+ name = url.fragment
+ serverAddress = url.host
+ serverPort = url.port
+ username = url.username
+ password = url.password
+ // v2rayN fmt
+ if (password.isNullOrBlank() && !username.isNullOrBlank()) {
+ try {
+ val n = username.decodeBase64UrlSafe()
+ username = n.substringBefore(":")
+ password = n.substringAfter(":")
+ } catch (_: Exception) {
}
- serverAddress = url.host
- serverPort = url.port
- username = url.username
- password = url.password
- name = url.fragment
}
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHFmt.kt
index 4ee032a24..170e640f4 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHFmt.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHFmt.kt
@@ -1,6 +1,7 @@
package io.nekohasekai.sagernet.fmt.ssh
import moe.matsuri.nb4a.SingBoxOptions
+import moe.matsuri.nb4a.utils.listByLineOrComma
fun buildSingBoxOutboundSSHBean(bean: SSHBean): SingBoxOptions.Outbound_SSHOptions {
return SingBoxOptions.Outbound_SSHOptions().apply {
@@ -9,7 +10,7 @@ fun buildSingBoxOutboundSSHBean(bean: SSHBean): SingBoxOptions.Outbound_SSHOptio
server_port = bean.serverPort
user = bean.username
if (bean.publicKey.isNotBlank()) {
- host_key = bean.publicKey.split("\n")
+ host_key = bean.publicKey.listByLineOrComma()
}
when (bean.authType) {
SSHBean.AUTH_TYPE_PRIVATE_KEY -> {
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoFmt.kt
index cb0235754..4e6e91f45 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoFmt.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoFmt.kt
@@ -92,10 +92,10 @@ fun TrojanGoBean.buildTrojanGoConfig(port: Int): String {
put(password)
})
put("log_level", if (DataStore.logLevel > 0) 0 else 2)
- if (Protocols.shouldEnableMux("trojan-go")) put("mux", JSONObject().apply {
- put("enabled", true)
- put("concurrency", DataStore.muxConcurrency)
- })
+// if (Protocols.shouldEnableMux("trojan-go")) put("mux", JSONObject().apply {
+// put("enabled", true)
+// put("concurrency", DataStore.muxConcurrency)
+// })
put("tcp", JSONObject().apply {
put("prefer_ipv4", DataStore.ipv6Mode <= IPv6Mode.ENABLE)
})
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicBean.java
index 4ce148f46..f1b76d369 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicBean.java
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicBean.java
@@ -21,9 +21,18 @@ public class TuicBean extends AbstractBean {
public Boolean reduceRTT;
public Integer mtu;
public String sni;
+
+ // TUIC zep
+
public Boolean fastConnect;
public Boolean allowInsecure;
+ // TUIC v5
+
+ public String customJSON;
+ public Integer protocolVersion;
+ public String uuid;
+
@Override
public void initializeDefaultValues() {
super.initializeDefaultValues();
@@ -38,11 +47,14 @@ public void initializeDefaultValues() {
if (sni == null) sni = "";
if (fastConnect == null) fastConnect = false;
if (allowInsecure == null) allowInsecure = false;
+ if (customJSON == null) customJSON = "";
+ if (protocolVersion == null) protocolVersion = 5;
+ if (uuid == null) uuid = "";
}
@Override
public void serialize(ByteBufferOutput output) {
- output.writeInt(1);
+ output.writeInt(2);
super.serialize(output);
output.writeString(token);
output.writeString(caText);
@@ -55,6 +67,9 @@ public void serialize(ByteBufferOutput output) {
output.writeString(sni);
output.writeBoolean(fastConnect);
output.writeBoolean(allowInsecure);
+ output.writeString(customJSON);
+ output.writeInt(protocolVersion);
+ output.writeString(uuid);
}
@Override
@@ -74,6 +89,13 @@ public void deserialize(ByteBufferInput input) {
fastConnect = input.readBoolean();
allowInsecure = input.readBoolean();
}
+ if (version >= 2) {
+ customJSON = input.readString();
+ protocolVersion = input.readInt();
+ uuid = input.readString();
+ } else {
+ protocolVersion = 4;
+ }
}
@Override
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt
index ce3136378..cad2147e6 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt
@@ -1,64 +1,89 @@
package io.nekohasekai.sagernet.fmt.tuic
import io.nekohasekai.sagernet.database.DataStore
-import io.nekohasekai.sagernet.fmt.LOCALHOST
-import io.nekohasekai.sagernet.ktx.isIpAddress
-import io.nekohasekai.sagernet.ktx.toStringPretty
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-import moe.matsuri.nb4a.plugin.Plugins
-import org.json.JSONArray
-import org.json.JSONObject
-import java.io.File
-import java.net.InetAddress
+import io.nekohasekai.sagernet.ktx.linkBuilder
+import io.nekohasekai.sagernet.ktx.toLink
+import io.nekohasekai.sagernet.ktx.urlSafe
+import moe.matsuri.nb4a.SingBoxOptions
+import moe.matsuri.nb4a.utils.listByLineOrComma
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
-fun TuicBean.buildTuicConfig(port: Int, cacheFile: (() -> File)?): String {
- if (Plugins.isUsingMatsuriExe("tuic-plugin")) {
- if (!serverAddress.isIpAddress()) {
- runBlocking {
- finalAddress = withContext(Dispatchers.IO) {
- InetAddress.getAllByName(serverAddress)
- }?.firstOrNull()?.hostAddress ?: "127.0.0.1"
- // TODO network on main thread, tuic don't support "sni"
- }
+fun parseTuic(url: String): TuicBean {
+ // https://github.com/daeuniverse/dae/discussions/182
+ val link = url.replace("tuic://", "https://").toHttpUrlOrNull() ?: error(
+ "invalid tuic link $url"
+ )
+ return TuicBean().apply {
+ protocolVersion = 5
+
+ name = link.fragment
+ uuid = link.username
+ token = link.password
+ serverAddress = link.host
+ serverPort = link.port
+
+ link.queryParameter("sni")?.let {
+ sni = it
+ }
+ link.queryParameter("congestion_control")?.let {
+ congestionController = it
+ }
+ link.queryParameter("udp_relay_mode")?.let {
+ udpRelayMode = it
+ }
+ link.queryParameter("alpn")?.let {
+ alpn = it
+ }
+ link.queryParameter("allow_insecure")?.let {
+ if (it == "1") allowInsecure = true
+ }
+ link.queryParameter("disable_sni")?.let {
+ if (it == "1") disableSNI = true
}
}
- return JSONObject().apply {
- put("relay", JSONObject().apply {
- if (sni.isNotBlank()) {
- put("server", sni)
- put("ip", finalAddress)
- } else if (serverAddress.isIpAddress()) {
- put("server", finalAddress)
- } else {
- put("server", serverAddress)
- put("ip", finalAddress)
- }
- put("port", finalPort)
- put("token", token)
+}
- if (caText.isNotBlank() && cacheFile != null) {
- val caFile = cacheFile()
- caFile.writeText(caText)
- put("certificates", JSONArray(listOf(caFile.absolutePath)))
- }
+fun TuicBean.toUri(): String {
+ val builder = linkBuilder().username(uuid).password(token).host(serverAddress).port(serverPort)
+
+ builder.addQueryParameter("congestion_control", congestionController)
+ builder.addQueryParameter("udp_relay_mode", udpRelayMode)
+
+ if (sni.isNotBlank()) builder.addQueryParameter("sni", sni)
+ if (alpn.isNotBlank()) builder.addQueryParameter("alpn", alpn)
+ if (allowInsecure) builder.addQueryParameter("allow_insecure", "1")
+ if (disableSNI) builder.addQueryParameter("disable_sni", "1")
+ if (name.isNotBlank()) builder.encodedFragment(name.urlSafe())
+
+ return builder.toLink("tuic")
+}
- put("udp_relay_mode", udpRelayMode)
- if (alpn.isNotBlank()) {
- put("alpn", JSONArray(alpn.split("\n")))
+fun buildSingBoxOutboundTuicBean(bean: TuicBean): SingBoxOptions.Outbound_TUICOptions {
+ if (bean.protocolVersion == 4) throw Exception("TUIC v4 is no longer supported")
+ return SingBoxOptions.Outbound_TUICOptions().apply {
+ type = "tuic"
+ server = bean.serverAddress
+ server_port = bean.serverPort
+ uuid = bean.uuid
+ password = bean.token
+ congestion_control = bean.congestionController
+ when (bean.udpRelayMode) {
+ "quic" -> udp_relay_mode = "quic"
+ }
+ zero_rtt_handshake = bean.reduceRTT
+ tls = SingBoxOptions.OutboundTLSOptions().apply {
+ if (bean.sni.isNotBlank()) {
+ server_name = bean.sni
+ }
+ if (bean.alpn.isNotBlank()) {
+ alpn = bean.alpn.listByLineOrComma()
+ }
+ if (bean.caText.isNotBlank()) {
+ certificate = bean.caText
}
- put("congestion_controller", congestionController)
- put("disable_sni", disableSNI)
- put("reduce_rtt", reduceRTT)
- put("max_udp_relay_packet_size", mtu)
- if (fastConnect) put("fast_connect", true)
- if (allowInsecure) put("insecure", true)
- })
- put("local", JSONObject().apply {
- put("ip", LOCALHOST)
- put("port", port)
- })
- put("log_level", if (DataStore.logLevel > 0) "debug" else "info")
- }.toStringPretty()
+ disable_sni = bean.disableSNI
+ insecure = bean.allowInsecure || DataStore.globalAllowInsecure
+ enabled = true
+ }
+ }
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java
index 808c90e5d..2f32c50f4 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java
@@ -14,7 +14,7 @@ public abstract class StandardV2RayBean extends AbstractBean {
//////// End of VMess & VLESS ////////
- // "V2Ray Transport" tcp/http/ws/quic/grpc
+ // "V2Ray Transport" tcp/http/ws/quic/grpc/httpupgrade
public String type;
public String host;
@@ -48,6 +48,20 @@ public abstract class StandardV2RayBean extends AbstractBean {
public String certificates;
+ // --------------------------------------- ech
+
+ public Boolean enableECH;
+
+ public String echConfig;
+
+ // --------------------------------------- Mux
+
+ public Boolean enableMux;
+ public Boolean muxPadding;
+ public Integer muxType;
+ public Integer muxConcurrency;
+
+
// --------------------------------------- //
public Integer packetEncoding; // 1:packet 2:xudp
@@ -61,11 +75,13 @@ public void initializeDefaultValues() {
if (JavaUtil.isNullOrBlank(type)) type = "tcp";
else if ("h2".equals(type)) type = "http";
+ type = type.toLowerCase();
+
if (JavaUtil.isNullOrBlank(host)) host = "";
if (JavaUtil.isNullOrBlank(path)) path = "";
if (JavaUtil.isNullOrBlank(security)) {
- if (this instanceof TrojanBean || isVLESS()) {
+ if (this instanceof TrojanBean) {
security = "tls";
} else {
security = "none";
@@ -84,13 +100,20 @@ public void initializeDefaultValues() {
if (realityPubKey == null) realityPubKey = "";
if (realityShortId == null) realityShortId = "";
+
+ if (enableECH == null) enableECH = false;
+ if (JavaUtil.isNullOrBlank(echConfig)) echConfig = "";
+
+ if (enableMux == null) enableMux = false;
+ if (muxPadding == null) muxPadding = false;
+ if (muxType == null) muxType = 0;
+ if (muxConcurrency == null) muxConcurrency = 1;
}
@Override
public void serialize(ByteBufferOutput output) {
- output.writeInt(0);
+ output.writeInt(4);
super.serialize(output);
-
output.writeString(uuid);
output.writeString(encryption);
if (this instanceof VMessBean) {
@@ -110,13 +133,15 @@ public void serialize(ByteBufferOutput output) {
output.writeString(earlyDataHeaderName);
break;
}
- case "http": {
+ case "http":
+ case "httpupgrade": {
output.writeString(host);
output.writeString(path);
break;
}
case "grpc": {
output.writeString(path);
+ break;
}
}
@@ -131,7 +156,15 @@ public void serialize(ByteBufferOutput output) {
output.writeString(realityShortId);
}
+ output.writeBoolean(enableECH);
+ output.writeString(echConfig);
+
output.writeInt(packetEncoding);
+
+ output.writeBoolean(enableMux);
+ output.writeBoolean(muxPadding);
+ output.writeInt(muxType);
+ output.writeInt(muxConcurrency);
}
@Override
@@ -157,13 +190,20 @@ public void deserialize(ByteBufferInput input) {
earlyDataHeaderName = input.readString();
break;
}
- case "http": {
+ case "http":
+ case "httpupgrade": {
host = input.readString();
path = input.readString();
break;
}
case "grpc": {
path = input.readString();
+ if (version < 4) {
+ // 解决老版本数据的读取问题
+ input.readString();
+ input.readString();
+ }
+ break;
}
}
@@ -178,7 +218,44 @@ public void deserialize(ByteBufferInput input) {
realityShortId = input.readString();
}
+ if (version >= 1) {
+ enableECH = input.readBoolean();
+ if (version >= 3) {
+ echConfig = input.readString();
+ } else {
+ if (enableECH) {
+ input.readBoolean();
+ input.readBoolean();
+ echConfig = input.readString();
+ }
+ }
+ } else if (version == 0) {
+ // 从老版本升级上来但是 version == 0, 可能有 enableECH 也可能没有,需要做判断
+ int position = input.getByteBuffer().position(); // 当前位置
+
+ boolean tmpEnableECH = input.readBoolean();
+ int tmpPacketEncoding = input.readInt();
+
+ input.setPosition(position); // 读后归位
+
+ if (tmpPacketEncoding != 1 && tmpPacketEncoding != 2) {
+ enableECH = tmpEnableECH;
+ if (enableECH) {
+ input.readBoolean();
+ input.readBoolean();
+ echConfig = input.readString();
+ }
+ } // 否则后一位就是 packetEncoding
+ }
+
packetEncoding = input.readInt();
+
+ if (version >= 2) {
+ enableMux = input.readBoolean();
+ muxPadding = input.readBoolean();
+ muxType = input.readInt();
+ muxConcurrency = input.readInt();
+ }
}
@Override
@@ -187,8 +264,13 @@ public void applyFeatureSettings(AbstractBean other) {
StandardV2RayBean bean = ((StandardV2RayBean) other);
bean.allowInsecure = allowInsecure;
bean.utlsFingerprint = utlsFingerprint;
- bean.realityPubKey = realityPubKey;
- bean.realityShortId = realityShortId;
+ bean.packetEncoding = packetEncoding;
+ bean.enableECH = enableECH;
+ bean.echConfig = echConfig;
+ bean.enableMux = enableMux;
+ bean.muxPadding = muxPadding;
+ bean.muxType = muxType;
+ bean.muxConcurrency = muxConcurrency;
}
public boolean isVLESS() {
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt
index 20e63202b..93684a5b3 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt
@@ -2,11 +2,13 @@ package io.nekohasekai.sagernet.fmt.v2ray
import android.text.TextUtils
import com.google.gson.Gson
+import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.fmt.http.HttpBean
import io.nekohasekai.sagernet.fmt.trojan.TrojanBean
import io.nekohasekai.sagernet.ktx.*
import moe.matsuri.nb4a.SingBoxOptions.*
import moe.matsuri.nb4a.utils.NGUtil
+import moe.matsuri.nb4a.utils.listByLineOrComma
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject
@@ -100,6 +102,7 @@ fun parseV2Ray(link: String): StandardV2RayBean {
bean.host = it.split("|").joinToString(",")
}
}
+
"ws" -> {
url.queryParameter("path")?.let {
bean.path = it
@@ -108,11 +111,21 @@ fun parseV2Ray(link: String): StandardV2RayBean {
bean.host = it
}
}
+
"grpc" -> {
url.queryParameter("serviceName")?.let {
bean.path = it
}
}
+
+ "httpupgrade" -> {
+ url.queryParameter("path")?.let {
+ bean.path = it
+ }
+ url.queryParameter("host")?.let {
+ bean.host = it
+ }
+ }
}
} else {
// also vless format
@@ -140,21 +153,27 @@ fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) {
}
type = url.queryParameter("type") ?: "tcp"
- if (type == "h2") type = "http"
+ if (type == "h2" || url.queryParameter("headerType") == "http") type = "http"
security = url.queryParameter("security")
- if (security == null) {
+ if (security.isNullOrBlank()) {
security = if (this is TrojanBean) "tls" else "none"
}
when (security) {
"tls", "reality" -> {
security = "tls"
+ url.queryParameter("allowInsecure")?.let {
+ allowInsecure = it == "1" || it == "true"
+ }
url.queryParameter("sni")?.let {
sni = it
}
+ url.queryParameter("host")?.let {
+ if (sni.isNullOrBlank()) sni = it
+ }
url.queryParameter("alpn")?.let {
- alpn = it.replace(",", "\n")
+ alpn = it
}
url.queryParameter("cert")?.let {
certificates = it
@@ -167,16 +186,8 @@ fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) {
}
}
}
+
when (type) {
- "tcp" -> {
- // v2rayNG
- if (url.queryParameter("headerType") == "http") {
- url.queryParameter("host")?.let {
- type = "http"
- host = it
- }
- }
- }
"http" -> {
url.queryParameter("host")?.let {
host = it
@@ -185,6 +196,7 @@ fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) {
path = it
}
}
+
"ws" -> {
url.queryParameter("host")?.let {
host = it
@@ -200,11 +212,21 @@ fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) {
}
}
}
+
"grpc" -> {
url.queryParameter("serviceName")?.let {
path = it
}
}
+
+ "httpupgrade" -> {
+ url.queryParameter("host")?.let {
+ host = it
+ }
+ url.queryParameter("path")?.let {
+ path = it
+ }
+ }
}
// maybe from matsuri vmess exoprt
@@ -222,8 +244,8 @@ fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) {
}
url.queryParameter("flow")?.let {
- if (isVLESS && it.contains("vision")) {
- encryption = it
+ if (isVLESS) {
+ encryption = it.removeSuffix("-udp443")
}
}
@@ -302,6 +324,7 @@ fun parseV2RayN(link: String): VMessBean {
throw Exception("invalid VmessQRCode")
}
+ bean.name = vmessQRCode.ps
bean.serverAddress = vmessQRCode.add
bean.serverPort = vmessQRCode.port.toIntOrNull()
bean.encryption = vmessQRCode.scy
@@ -320,14 +343,15 @@ fun parseV2RayN(link: String): VMessBean {
}
}
when (vmessQRCode.tls) {
- "tls", "reality" -> bean.security = "tls"
+ "tls", "reality" -> {
+ bean.security = "tls"
+ bean.sni = vmessQRCode.sni
+ if (bean.sni.isNullOrBlank()) bean.sni = bean.host
+ bean.alpn = vmessQRCode.alpn
+ bean.utlsFingerprint = vmessQRCode.fp
+ }
}
- bean.name = vmessQRCode.ps
- bean.sni = vmessQRCode.sni
- bean.alpn = vmessQRCode.alpn.replace(",", "\n")
- bean.utlsFingerprint = vmessQRCode.fp
-
return bean
}
@@ -424,7 +448,7 @@ fun StandardV2RayBean.toUriVMessVLESSTrojan(isTrojan: Boolean): String {
when (type) {
"tcp" -> {}
- "ws", "http" -> {
+ "ws", "http", "httpupgrade" -> {
if (host.isNotBlank()) {
builder.addQueryParameter("host", host)
}
@@ -443,6 +467,7 @@ fun StandardV2RayBean.toUriVMessVLESSTrojan(isTrojan: Boolean): String {
builder.addQueryParameter("headerType", "http")
}
}
+
"grpc" -> {
if (path.isNotBlank()) {
builder.setQueryParameter("serviceName", path)
@@ -480,8 +505,9 @@ fun StandardV2RayBean.toUriVMessVLESSTrojan(isTrojan: Boolean): String {
when (packetEncoding) {
1 -> {
- builder.addQueryParameter("packetEncoding", "packet")
+ builder.addQueryParameter("packetEncoding", "packetaddr")
}
+
2 -> {
builder.addQueryParameter("packetEncoding", "xudp")
}
@@ -499,6 +525,7 @@ fun buildSingBoxOutboundStreamSettings(bean: StandardV2RayBean): V2RayTransportO
"tcp" -> {
return null
}
+
"ws" -> {
return V2RayTransportOptions_WebsocketOptions().apply {
type = "ws"
@@ -525,33 +552,39 @@ fun buildSingBoxOutboundStreamSettings(bean: StandardV2RayBean): V2RayTransportO
}
}
}
+
"http" -> {
return V2RayTransportOptions_HTTPOptions().apply {
type = "http"
+ if (!bean.isTLS()) method = "GET" // v2ray tcp header
if (bean.host.isNotBlank()) {
host = bean.host.split(",")
}
path = bean.path.takeIf { it.isNotBlank() } ?: "/"
}
}
+
"quic" -> {
return V2RayTransportOptions().apply {
type = "quic"
}
}
+
"grpc" -> {
return V2RayTransportOptions_GRPCOptions().apply {
type = "grpc"
service_name = bean.path
}
}
- }
-// if (needKeepAliveInterval) {
-// sockopt = StreamSettingsObject.SockoptObject().apply {
-// tcpKeepAliveInterval = keepAliveInterval
-// }
-// }
+ "httpupgrade" -> {
+ return V2RayTransportOptions_HTTPUpgradeOptions().apply {
+ type = "httpupgrade"
+ host = bean.host
+ path = bean.path
+ }
+ }
+ }
return null
}
@@ -560,22 +593,32 @@ fun buildSingBoxOutboundTLS(bean: StandardV2RayBean): OutboundTLSOptions? {
if (bean.security != "tls") return null
return OutboundTLSOptions().apply {
enabled = true
- insecure = bean.allowInsecure
+ insecure = bean.allowInsecure || DataStore.globalAllowInsecure
if (bean.sni.isNotBlank()) server_name = bean.sni
- if (bean.alpn.isNotBlank()) alpn = bean.alpn.split("\n")
+ if (bean.alpn.isNotBlank()) alpn = bean.alpn.listByLineOrComma()
if (bean.certificates.isNotBlank()) certificate = bean.certificates
- if (bean.utlsFingerprint.isNotBlank()) {
- utls = OutboundUTLSOptions().apply {
- enabled = true
- fingerprint = bean.utlsFingerprint
- }
- }
+ var fp = bean.utlsFingerprint
if (bean.realityPubKey.isNotBlank()) {
reality = OutboundRealityOptions().apply {
enabled = true
public_key = bean.realityPubKey
short_id = bean.realityShortId
}
+ if (fp.isNullOrBlank()) fp = "chrome"
+ }
+ if (fp.isNotBlank()) {
+ utls = OutboundUTLSOptions().apply {
+ enabled = true
+ fingerprint = fp
+ }
+ }
+ if (bean.enableECH) {
+ ech = OutboundECHOptions().apply {
+ enabled = true
+ if (bean.echConfig.isNotBlank()) {
+ config = bean.echConfig.lines()
+ }
+ }
}
}
}
@@ -592,6 +635,7 @@ fun buildSingBoxOutboundStandardV2RayBean(bean: StandardV2RayBean): Outbound {
tls = buildSingBoxOutboundTLS(bean)
}
}
+
is VMessBean -> {
if (bean.isVLESS) return Outbound_VLESSOptions().apply {
type = "vless"
@@ -625,6 +669,7 @@ fun buildSingBoxOutboundStandardV2RayBean(bean: StandardV2RayBean): Outbound {
transport = buildSingBoxOutboundStreamSettings(bean)
}
}
+
is TrojanBean -> {
return Outbound_TrojanOptions().apply {
type = "trojan"
@@ -635,6 +680,7 @@ fun buildSingBoxOutboundStandardV2RayBean(bean: StandardV2RayBean): Outbound {
transport = buildSingBoxOutboundStreamSettings(bean)
}
}
+
else -> throw IllegalStateException("can't reach")
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java
index 84044da1b..2b7e8ae9e 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java
@@ -16,7 +16,12 @@ public void initializeDefaultValues() {
super.initializeDefaultValues();
alterId = alterId != null ? alterId : 0;
- encryption = JavaUtil.isNotBlank(encryption) ? encryption : "auto";
+
+ if (alterId == -1) {
+ encryption = JavaUtil.isNotBlank(encryption) ? encryption : "";
+ } else {
+ encryption = JavaUtil.isNotBlank(encryption) ? encryption : "auto";
+ }
}
@NotNull
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt
index 8e8388649..5fa7397b0 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt
@@ -1,17 +1,41 @@
package io.nekohasekai.sagernet.fmt.wireguard
import moe.matsuri.nb4a.SingBoxOptions
+import moe.matsuri.nb4a.utils.Util
+import moe.matsuri.nb4a.utils.listByLineOrComma
+
+fun genReserved(anyStr: String): String {
+ try {
+ val list = anyStr.listByLineOrComma()
+ val ba = ByteArray(3)
+ if (list.size == 3) {
+ list.forEachIndexed { index, s ->
+ val i = s
+ .replace("[", "")
+ .replace("]", "")
+ .replace(" ", "")
+ .toIntOrNull() ?: return anyStr
+ ba[index] = i.toByte()
+ }
+ return Util.b64EncodeOneLine(ba)
+ } else {
+ return anyStr
+ }
+ } catch (e: Exception) {
+ return anyStr
+ }
+}
fun buildSingBoxOutboundWireguardBean(bean: WireGuardBean): SingBoxOptions.Outbound_WireGuardOptions {
return SingBoxOptions.Outbound_WireGuardOptions().apply {
type = "wireguard"
server = bean.serverAddress
server_port = bean.serverPort
- local_address = bean.localAddress.split("\n")
+ local_address = bean.localAddress.listByLineOrComma()
private_key = bean.privateKey
peer_public_key = bean.peerPublicKey
pre_shared_key = bean.peerPreSharedKey
mtu = bean.mtu
- if (bean.reserved.isNotBlank()) reserved = bean.reserved
+ if (bean.reserved.isNotBlank()) reserved = genReserved(bean.reserved)
}
}
diff --git a/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt b/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt
index af97d1e69..28213489a 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt
@@ -1,18 +1,19 @@
package io.nekohasekai.sagernet.group
import android.annotation.SuppressLint
-import android.net.Uri
import io.nekohasekai.sagernet.R
import io.nekohasekai.sagernet.database.*
import io.nekohasekai.sagernet.fmt.AbstractBean
import io.nekohasekai.sagernet.fmt.http.HttpBean
import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean
-import io.nekohasekai.sagernet.fmt.hysteria.parseHysteria
+import io.nekohasekai.sagernet.fmt.hysteria.parseHysteria1Json
import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean
import io.nekohasekai.sagernet.fmt.shadowsocks.parseShadowsocks
import io.nekohasekai.sagernet.fmt.socks.SOCKSBean
import io.nekohasekai.sagernet.fmt.trojan.TrojanBean
import io.nekohasekai.sagernet.fmt.trojan_go.parseTrojanGo
+import io.nekohasekai.sagernet.fmt.tuic.TuicBean
+import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean
import io.nekohasekai.sagernet.fmt.v2ray.VMessBean
import io.nekohasekai.sagernet.fmt.v2ray.isTLS
import io.nekohasekai.sagernet.fmt.v2ray.setTLS
@@ -20,7 +21,9 @@ import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
import io.nekohasekai.sagernet.ktx.*
import libcore.Libcore
import moe.matsuri.nb4a.Protocols
+import moe.matsuri.nb4a.proxy.anytls.AnyTLSBean
import moe.matsuri.nb4a.proxy.config.ConfigBean
+import moe.matsuri.nb4a.utils.Util
import org.ini4j.Ini
import org.json.JSONArray
import org.json.JSONObject
@@ -29,6 +32,7 @@ import org.yaml.snakeyaml.TypeDescription
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.error.YAMLException
import java.io.StringReader
+import androidx.core.net.toUri
@Suppress("EXPERIMENTAL_API_USAGE")
object RawUpdater : GroupUpdater() {
@@ -44,7 +48,7 @@ object RawUpdater : GroupUpdater() {
val link = subscription.link
var proxies: List
if (link.startsWith("content://")) {
- val contentText = app.contentResolver.openInputStream(Uri.parse(link))
+ val contentText = app.contentResolver.openInputStream(link.toUri())
?.bufferedReader()
?.readText()
@@ -54,18 +58,33 @@ object RawUpdater : GroupUpdater() {
val response = Libcore.newHttpClient().apply {
trySocks5(DataStore.mixedPort)
+ tryH3Direct()
when (DataStore.appTLSVersion) {
"1.3" -> restrictedTLS()
}
}.newRequest().apply {
+ if (DataStore.allowInsecureOnRequest) {
+ allowInsecure()
+ }
setURL(subscription.link)
setUserAgent(subscription.customUserAgent.takeIf { it.isNotBlank() } ?: USER_AGENT)
}.execute()
-
- proxies = parseRaw(response.contentString)
+ proxies = parseRaw(Util.getStringBox(response.contentString))
?: error(app.getString(R.string.no_proxies_found))
- subscription.subscriptionUserinfo = response.getHeader("Subscription-Userinfo")
+ subscription.subscriptionUserinfo =
+ Util.getStringBox(response.getHeader("Subscription-Userinfo"))
+
+ // 修改默认名字
+ if (proxyGroup.name?.startsWith("Subscription #") == true) {
+ var remoteName = Util.getStringBox(response.getHeader("content-disposition"))
+ if (remoteName.isNotBlank()) {
+ remoteName = Util.decodeFilename(remoteName)
+ if (remoteName.isNotBlank()) {
+ proxyGroup.name = remoteName
+ }
+ }
+ }
}
val proxiesMap = LinkedHashMap()
@@ -167,11 +186,12 @@ object RawUpdater : GroupUpdater() {
}
} else {
changed++
- SagerDatabase.proxyDao.addProxy(ProxyEntity(
- groupId = proxyGroup.id, userOrder = userOrder
- ).apply {
- putBean(bean)
- })
+ SagerDatabase.proxyDao.addProxy(
+ ProxyEntity(
+ groupId = proxyGroup.id, userOrder = userOrder
+ ).apply {
+ putBean(bean)
+ })
added.add(name)
Logs.d("Inserted profile: $name")
}
@@ -208,12 +228,17 @@ object RawUpdater : GroupUpdater() {
if (text.contains("proxies:")) {
+ // clash & meta
+
try {
- // clash
- for (proxy in (Yaml().apply {
+ val yaml = Yaml().apply {
addTypeDescription(TypeDescription(String::class.java, "str"))
- }.loadAs(text, Map::class.java)["proxies"] as? (List