____ _ _______ __
/ __ \ | /| / / __/ |/ /
/ /_/ / |/ |/ / _// /
\____/|__/|__/___/_||_/
owenclave / app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java
/******************************************************************************
* *
* Copyright (C) 2021 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.v2ray;
import com.esotericsoftware.kryo.io.ByteBufferInput;
import com.esotericsoftware.kryo.io.ByteBufferOutput;
import io.nekohasekai.sagernet.fmt.AbstractBean;
import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean;
import io.nekohasekai.sagernet.fmt.socks.SOCKSBean;
import io.nekohasekai.sagernet.fmt.trojan.TrojanBean;
import libexclavecore.Libexclavecore;
public abstract class StandardV2RayBean extends AbstractBean {
public String uuid;
public String encryption;
public String type;
public String host;
public String path;
public String headerType;
public String mKcpSeed;
public String quicSecurity;
public String quicKey;
public String security;
public String sni;
public String alpn;
public String grpcServiceName;
public Boolean grpcServiceNameCompat;
public Boolean grpcMultiMode;
public Integer maxEarlyData;
public String earlyDataHeaderName;
public String meekUrl;
public String splithttpMode;
public String splithttpExtra;
public String certificates;
public String pinnedPeerCertificateChainSha256;
public String pinnedPeerCertificatePublicKeySha256;
public String pinnedPeerCertificateSha256;
public String mtlsCertificate;
public String mtlsCertificatePrivateKey;
public String utlsFingerprint;
public Boolean echEnabled;
public String echConfig;
public String serverNameToVerify;
public Boolean wsUseBrowserForwarder;
public Boolean shUseBrowserForwarder;
public Boolean allowInsecure;
public String packetEncoding;
public String realityPublicKey;
public String realityShortId;
public String realityMldsa65Verify;
public String realityFingerprint;
public Boolean realityDisableX25519Mlkem768;
public Long hy2DownMbps;
public Long hy2UpMbps;
public String hy2Password;
public String mekyaKcpSeed;
public String mekyaKcpHeaderType;
public String mekyaUrl;
public Boolean mux;
public Integer muxConcurrency;
public String muxPacketEncoding;
public Boolean singMux;
public String singMuxProtocol;
public Integer singMuxMaxConnections;
public Integer singMuxMinStreams;
public Integer singMuxMaxStreams;
public Boolean singMuxPadding;
@Override
public boolean canMapping() {
return switch (type) {
case "ws" -> !wsUseBrowserForwarder;
case "splithttp" -> !shUseBrowserForwarder;
default -> true;
};
}
@Override
public void initializeDefaultValues() {
super.initializeDefaultValues();
if (uuid == null) uuid = "";
if (encryption == null) encryption = ""; // overridden in VMessBean and VLESSBean
if (type == null) type = "tcp";
if (host == null) host = "";
if (path == null) path = "";
if (headerType == null) headerType = "none";
if (mKcpSeed == null) mKcpSeed = "";
if (quicSecurity == null) quicSecurity = "none";
if (quicKey == null) quicKey = "";
if (meekUrl == null) meekUrl = "";
if (splithttpMode == null) splithttpMode = "auto";
if (splithttpExtra == null) splithttpExtra = "";
if (security == null) security = "none";
if (sni == null) sni = "";
if (alpn == null) alpn = "";
if (grpcServiceName == null) grpcServiceName = "";
if (grpcServiceNameCompat == null) grpcServiceNameCompat = false;
if (grpcMultiMode == null) grpcMultiMode = false;
if (maxEarlyData == null) maxEarlyData = 0;
if (wsUseBrowserForwarder == null) wsUseBrowserForwarder = false;
if (shUseBrowserForwarder == null) shUseBrowserForwarder = false;
if (certificates == null) certificates = "";
if (pinnedPeerCertificateChainSha256 == null) pinnedPeerCertificateChainSha256 = "";
if (pinnedPeerCertificatePublicKeySha256 == null) pinnedPeerCertificatePublicKeySha256 = "";
if (pinnedPeerCertificateSha256 == null) pinnedPeerCertificateSha256 = "";
if (mtlsCertificate == null) mtlsCertificate = "";
if (mtlsCertificatePrivateKey == null) mtlsCertificatePrivateKey = "";
if (earlyDataHeaderName == null) earlyDataHeaderName = "";
if (allowInsecure == null) allowInsecure = false;
if (packetEncoding == null) packetEncoding = "none";
if (utlsFingerprint == null) utlsFingerprint = "";
if (echEnabled == null) echEnabled = false;
if (echConfig == null) echConfig = "";
if (serverNameToVerify == null) serverNameToVerify = "";
if (realityPublicKey == null) realityPublicKey = "";
if (realityShortId == null) realityShortId = "";
if (realityMldsa65Verify == null) realityMldsa65Verify = "";
if (realityFingerprint == null) realityFingerprint = "chrome";
if (realityDisableX25519Mlkem768 == null) realityDisableX25519Mlkem768 = false;
if (hy2DownMbps == null) hy2DownMbps = 0L;
if (hy2UpMbps == null) hy2UpMbps = 0L;
if (hy2Password == null) hy2Password = "";
if (mekyaKcpSeed == null) mekyaKcpSeed = "";
if (mekyaKcpHeaderType == null) mekyaKcpHeaderType = "none";
if (mekyaUrl == null) mekyaUrl = "";
if (mux == null) mux = false;
if (muxConcurrency == null) muxConcurrency = 8;
if (muxPacketEncoding == null) muxPacketEncoding = "none";
if (singMux == null) singMux = false;
if (singMuxProtocol == null) singMuxProtocol = "h2mux";
if (singMuxMaxConnections == null) singMuxMaxConnections = 0;
if (singMuxMinStreams == null) singMuxMinStreams = 0;
if (singMuxMaxStreams == null) singMuxMaxStreams = 0;
if (singMuxPadding == null) singMuxPadding = false;
}
@Override
public void serialize(ByteBufferOutput output) {
output.writeInt(37);
super.serialize(output);
output.writeString(uuid);
output.writeString(encryption);
output.writeString(type);
switch (type) {
case "tcp": {
output.writeString(headerType);
output.writeString(host);
output.writeString(path);
break;
}
case "kcp": {
output.writeString(headerType);
output.writeString(mKcpSeed);
break;
}
case "ws": {
output.writeString(host);
output.writeString(path);
output.writeInt(maxEarlyData);
output.writeBoolean(wsUseBrowserForwarder);
output.writeString(earlyDataHeaderName);
break;
}
case "http": {
output.writeString(host);
output.writeString(path);
break;
}
case "httpupgrade": {
output.writeString(host);
output.writeString(path);
output.writeInt(maxEarlyData);
output.writeString(earlyDataHeaderName);
break;
}
case "splithttp": {
output.writeString(host);
output.writeString(path);
output.writeBoolean(shUseBrowserForwarder);
output.writeString(splithttpMode);
output.writeString(splithttpExtra);
break;
}
case "quic": {
output.writeString(headerType);
output.writeString(quicSecurity);
output.writeString(quicKey);
break;
}
case "grpc": {
output.writeString(grpcServiceName);
output.writeBoolean(grpcServiceNameCompat);
output.writeBoolean(grpcMultiMode);
break;
}
case "meek": {
output.writeString(meekUrl);
break;
}
case "hysteria2": {
output.writeLong(hy2DownMbps);
output.writeLong(hy2UpMbps);
output.writeString(hy2Password);
break;
}
case "mekya": {
output.writeString(mekyaKcpHeaderType);
output.writeString(mekyaKcpSeed);
output.writeString(mekyaUrl);
break;
}
}
output.writeString(security);
switch (security) {
case "tls": {
output.writeString(sni);
output.writeString(alpn);
output.writeString(certificates);
output.writeString(pinnedPeerCertificateChainSha256);
output.writeString(pinnedPeerCertificatePublicKeySha256);
output.writeString(pinnedPeerCertificateSha256);
output.writeBoolean(allowInsecure);
output.writeString(utlsFingerprint);
output.writeString(echConfig);
output.writeString(mtlsCertificate);
output.writeString(mtlsCertificatePrivateKey);
break;
}
case "reality": {
output.writeString(sni);
output.writeString(realityPublicKey);
output.writeString(realityShortId);
output.writeString(realityFingerprint);
output.writeBoolean(realityDisableX25519Mlkem768);
break;
}
}
if (this instanceof VMessBean) {
output.writeInt(((VMessBean) this).alterId);
output.writeBoolean(((VMessBean) this).experimentalAuthenticatedLength);
output.writeBoolean(((VMessBean) this).experimentalNoTerminationSignal);
}
if (this instanceof VLESSBean) {
output.writeString(((VLESSBean) this).flow);
}
output.writeString(packetEncoding);
output.writeBoolean(mux);
output.writeInt(muxConcurrency);
output.writeString(muxPacketEncoding);
if (this instanceof ShadowsocksBean) {
output.writeBoolean(((ShadowsocksBean) this).singUoT);
}
if (this instanceof SOCKSBean) {
output.writeBoolean(((SOCKSBean) this).singUoT);
}
output.writeBoolean(singMux);
output.writeString(singMuxProtocol);
output.writeInt(singMuxMaxConnections);
output.writeInt(singMuxMinStreams);
output.writeInt(singMuxMaxStreams);
output.writeBoolean(singMuxPadding);
output.writeBoolean(echEnabled);
output.writeString(realityMldsa65Verify);
output.writeString(serverNameToVerify);
}
@Override
public void deserialize(ByteBufferInput input) {
int version = input.readInt();
super.deserialize(input);
uuid = input.readString();
encryption = input.readString();
type = input.readString();
switch (type) {
case "tcp": {
headerType = input.readString();
host = input.readString();
path = input.readString();
break;
}
case "kcp": {
headerType = input.readString();
mKcpSeed = input.readString();
break;
}
case "ws": {
host = input.readString();
path = input.readString();
maxEarlyData = input.readInt();
wsUseBrowserForwarder = input.readBoolean();
if (version >= 2) {
earlyDataHeaderName = input.readString();
}
break;
}
case "http": {
host = input.readString();
path = input.readString();
break;
}
case "quic": {
headerType = input.readString();
quicSecurity = input.readString();
quicKey = input.readString();
if (version >= 16) {
break;
}
}
case "grpc": {
grpcServiceName = input.readString();
if (version >= 8 && version <= 12) {
input.readString(); // grpcMode, removed
}
if (version >= 33) {
grpcServiceNameCompat = input.readBoolean();
grpcMultiMode = input.readBoolean();
if (version < 34) {
input.readString();
}
}
if (version >= 16) {
break;
}
}
case "meek": {
if (version >= 10) {
meekUrl = input.readString();
}
if (version >= 16) {
break;
}
}
case "httpupgrade": {
if (version >= 12) {
host = input.readString();
path = input.readString();
}
if (version >= 25) {
maxEarlyData = input.readInt();
earlyDataHeaderName = input.readString();
}
if (version >= 16) {
break;
}
}
case "hysteria2": {
if (version >= 14) {
if (version <= 28) {
hy2DownMbps = (long) input.readInt();
} else {
hy2DownMbps = input.readLong();
}
if (version <= 28) {
hy2UpMbps = (long) input.readInt();
} else {
hy2UpMbps = input.readLong();
}
if (version < 26) {
input.readString(); // hy2ObfsPassword, removed
}
}
if (version >= 15) {
hy2Password = input.readString();
}
break;
}
case "splithttp": {
if (version >= 18) {
host = input.readString();
path = input.readString();
}
if (version >= 20) {
shUseBrowserForwarder = input.readBoolean();
}
if (version >= 23) {
splithttpMode = input.readString();
}
if (version >= 24) {
splithttpExtra = input.readString();
}
break;
}
case "mekya": {
if (version >= 22) {
mekyaKcpHeaderType = input.readString();
mekyaKcpSeed = input.readString();
mekyaUrl = input.readString();
}
break;
}
}
security = input.readString();
switch (security) {
case "tls": {
sni = input.readString();
alpn = input.readString();
if (version >= 1) {
certificates = input.readString();
pinnedPeerCertificateChainSha256 = input.readString();
}
if (version >= 31) {
pinnedPeerCertificatePublicKeySha256 = input.readString();
pinnedPeerCertificateSha256 = input.readString();
}
if (version >= 3) {
allowInsecure = input.readBoolean();
}
if (version >= 9) {
utlsFingerprint = input.readString();
}
if (version >= 21) {
echConfig = input.readString();
if (version <= 34 && !echConfig.isEmpty()) {
echEnabled = true;
}
if (version <= 28) {
input.readString(); // echDohServer, removed
}
}
if (version >= 31) {
mtlsCertificate = input.readString();
mtlsCertificatePrivateKey = input.readString();
}
break;
}
case "xtls": { // removed, for compatibility
if (version <= 8) {
security = "tls";
sni = input.readString();
alpn = input.readString();
input.readString(); // flow, removed
}
if (version >= 16) {
break;
}
}
case "reality": {
if (version >= 11) {
sni = input.readString();
realityPublicKey = input.readString();
realityShortId = input.readString();
if (version <= 25) {
input.readString(); // realitySpiderX, removed
}
realityFingerprint = input.readString();
if (version == 28 || version == 29) {
input.readString(); // echConfig, removed
}
if (version == 28) {
input.readString(); // echDohServer, removed
}
if (version >= 27) {
realityDisableX25519Mlkem768 = input.readBoolean();
}
if (version >= 27 && version <= 29) {
input.readBoolean(); // realityReenableChacha20Poly1305, removed
}
}
break;
}
}
if (this instanceof VMessBean && version != 4 && version < 6) {
((VMessBean) this).alterId = input.readInt();
}
if (this instanceof VMessBean && version >= 4) {
if (version >= 17) {
((VMessBean) this).alterId = input.readInt();
}
((VMessBean) this).experimentalAuthenticatedLength = input.readBoolean();
((VMessBean) this).experimentalNoTerminationSignal = input.readBoolean();
}
if (this instanceof VLESSBean && version >= 11) {
((VLESSBean) this).flow = input.readString();
}
if (version >= 7 && version <= 15) {
switch (input.readInt()) {
case 0:
packetEncoding = "none";
break;
case 1:
packetEncoding = "packet";
break;
case 2:
packetEncoding = "xudp";
break;
}
}
if (version >= 16) {
packetEncoding = input.readString();
}
if (version >= 19) {
mux = input.readBoolean();
muxConcurrency = input.readInt();
muxPacketEncoding = input.readString();
}
if (version >= 32) {
if (this instanceof ShadowsocksBean) {
((ShadowsocksBean) this).singUoT = input.readBoolean();
}
if (this instanceof SOCKSBean) {
((SOCKSBean) this).singUoT = input.readBoolean();
}
singMux = input.readBoolean();
singMuxProtocol = input.readString();
singMuxMaxConnections = input.readInt();
singMuxMinStreams = input.readInt();
singMuxMaxStreams = input.readInt();
singMuxPadding = input.readBoolean();
}
if (version >= 35) {
echEnabled = input.readBoolean();
}
if (version >= 36) {
realityMldsa65Verify = input.readString();
}
if (version >= 37) {
serverNameToVerify = input.readString();
}
}
@Override
public void applyFeatureSettings(AbstractBean other) {
if (!(other instanceof StandardV2RayBean bean)) return;
if (allowInsecure) {
bean.allowInsecure = true;
}
if (bean.maxEarlyData == null || bean.maxEarlyData == 0 && maxEarlyData != 0) {
bean.maxEarlyData = maxEarlyData;
}
if (bean.earlyDataHeaderName == null || bean.earlyDataHeaderName.isEmpty() && !earlyDataHeaderName.isEmpty()) {
bean.earlyDataHeaderName = earlyDataHeaderName;
}
bean.wsUseBrowserForwarder = wsUseBrowserForwarder;
bean.shUseBrowserForwarder = shUseBrowserForwarder;
if (bean.certificates == null || bean.certificates.isEmpty() && !certificates.isEmpty()) {
bean.certificates = certificates;
}
if (bean.pinnedPeerCertificateChainSha256 == null || bean.pinnedPeerCertificateChainSha256.isEmpty() &&
!pinnedPeerCertificateChainSha256.isEmpty()) {
bean.pinnedPeerCertificateChainSha256 = pinnedPeerCertificateChainSha256;
}
if (bean.pinnedPeerCertificatePublicKeySha256 == null || bean.pinnedPeerCertificatePublicKeySha256.isEmpty() &&
!pinnedPeerCertificatePublicKeySha256.isEmpty()) {
bean.pinnedPeerCertificatePublicKeySha256 = pinnedPeerCertificatePublicKeySha256;
}
if (bean.pinnedPeerCertificateSha256 == null || bean.pinnedPeerCertificateSha256.isEmpty() &&
!pinnedPeerCertificateSha256.isEmpty()) {
bean.pinnedPeerCertificateSha256 = pinnedPeerCertificateSha256;
}
if (bean instanceof VLESSBean || bean instanceof VMessBean || bean instanceof TrojanBean) {
if (bean.echEnabled == null || !bean.echEnabled && !echEnabled) {
bean.echEnabled = echEnabled;
}
if (bean.echConfig == null || bean.echConfig.isEmpty() && !echConfig.isEmpty()) {
bean.echConfig = echConfig;
}
} else {
bean.echEnabled = echEnabled;
bean.echConfig = echConfig;
}
if (bean.packetEncoding == null) {
bean.packetEncoding = packetEncoding;
}
bean.utlsFingerprint = utlsFingerprint;
bean.realityFingerprint = realityFingerprint;
bean.realityDisableX25519Mlkem768 = realityDisableX25519Mlkem768;
bean.hy2DownMbps = hy2DownMbps;
bean.hy2UpMbps = hy2UpMbps;
bean.grpcServiceNameCompat = grpcServiceNameCompat;
bean.mux = mux;
bean.muxConcurrency = muxConcurrency;
bean.muxPacketEncoding = muxPacketEncoding;
bean.singMux = singMux;
bean.singMuxProtocol = singMuxProtocol;
bean.singMuxMaxConnections = singMuxMaxConnections;
bean.singMuxMinStreams = singMuxMinStreams;
bean.singMuxMaxStreams = singMuxMaxStreams;
bean.singMuxPadding = singMuxPadding;
}
@Override
public boolean isInsecure() {
if (Libexclavecore.isLoopbackIP(serverAddress) || serverAddress.equals("localhost")) {
return false;
}
switch (security) {
case "tls":
if (echEnabled) {
// do not care if DNS server is reliable or not
return false;
}
if (!allowInsecure) {
return false;
}
if (!pinnedPeerCertificateChainSha256.isEmpty()) {
return false;
}
if (!pinnedPeerCertificatePublicKeySha256.isEmpty()) {
return false;
}
if (!pinnedPeerCertificateSha256.isEmpty()) {
return false;
}
if (!serverNameToVerify.isEmpty()) {
return false;
}
break;
case "reality":
return false;
}
switch (type) {
case "kcp":
if (!mKcpSeed.isEmpty()) {
return false;
}
break;
case "quic":
if (!quicSecurity.equals("none")) {
return false;
}
break;
}
return true;
}
}