diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java b/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java index 0f1354a93..b9f1fb5ed 100644 --- a/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java +++ b/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java @@ -152,6 +152,14 @@ public abstract SSLEngineResult wrap( @SuppressWarnings("MissingOverride") // For compiling pre Java 9. public abstract String getHandshakeApplicationProtocol(); + public abstract void setEchParameters(EchParameters parameters); + + public abstract EchParameters getEchParameters(); + + public abstract String getEchNameOverride(); + + public abstract boolean echAccepted(); + /** * Sets an application-provided ALPN protocol selector. If provided, this will override * the list of protocols set by {@link #setApplicationProtocols(String[])}. diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java index b177e9d61..6f396b3d4 100644 --- a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java +++ b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java @@ -738,4 +738,12 @@ private boolean isDelegating() { */ abstract byte[] exportKeyingMaterial(String label, byte[] context, int length) throws SSLException; + + public abstract void setEchParameters(EchParameters parameters); + + public abstract EchParameters getEchParameters(); + + public abstract String getEchNameOverride(); + + public abstract boolean echAccepted(); } diff --git a/common/src/main/java/org/conscrypt/Conscrypt.java b/common/src/main/java/org/conscrypt/Conscrypt.java index 0450874bc..abfddaaa7 100644 --- a/common/src/main/java/org/conscrypt/Conscrypt.java +++ b/common/src/main/java/org/conscrypt/Conscrypt.java @@ -15,8 +15,10 @@ */ package org.conscrypt; +import org.conscrypt.com.android.net.module.util.DnsPacket; import org.conscrypt.io.IoUtils; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; @@ -533,6 +535,47 @@ public static byte[] exportKeyingMaterial( return toConscrypt(socket).exportKeyingMaterial(label, context, length); } + /** + * Casts a socket to a Conscrypt socket if possible, and sets the parameters + * required to configure ECH for that socket. + * Throws an IllegalArgumentException if the socket is not a ConscryptSocket + * @param socket the socket (instance of ConscryptSocket) + * @param parameters parameters required to configure ECH + */ + public static void setEchParameters(SSLSocket socket, EchParameters parameters) { + toConscrypt(socket).setEchParameters(parameters); + } + + /** + * Casts a socket to a Conscrypt socket if possible, and returns the parameters + * used to configure ECH for that socket. + * Throws an IllegalArgumentException if the socket is not a ConscryptSocket + * @param socket the socket (instance of ConscryptSocket) + */ + public static EchParameters getEchParameters(SSLSocket socket) { + return toConscrypt(socket).getEchParameters(); + } + + /** + * Casts a socket to a Conscrypt socket if possible, and returns the string used + * to replace the hostname as the public name. + * Throws an IllegalArgumentException if the socket is not a ConscryptSocket + * @param socket the socket (instance of ConscryptSocket) + */ + public static String getEchNameOverride(SSLSocket socket) { + return toConscrypt(socket).getEchNameOverride(); + } + + /** + * Casts a socket to a Conscrypt socket if possible, and returns whether the native + * SSL/crypto implementation detects that the connection supports ECH. + * Throws an IllegalArgumentException if the socket is not a ConscryptSocket + * @param socket the socket (instance of ConscryptSocket) + */ + public static boolean echAccepted(SSLSocket socket) { + return toConscrypt(socket).echAccepted(); + } + /** * Indicates whether the given {@link SSLEngine} was created by this distribution of Conscrypt. */ @@ -791,6 +834,148 @@ public static byte[] exportKeyingMaterial( return toConscrypt(engine).exportKeyingMaterial(label, context, length); } + /** + * This method enables or disables Encrypted Client Hello (ECH) GREASE. + * + * @param engine the engine + * @param enabled Whether to enable TLSv1.3 ECH GREASE + * + * @see TLS Encrypted Client Hello 6.2. GREASE ECH + */ + + public static void setEchParameters(SSLEngine engine, EchParameters parameters) { + toConscrypt(engine).setEchParameters(parameters); + } + + public static EchParameters getEchParameters(SSLEngine engine) { + return toConscrypt(engine).getEchParameters(); + } + + public static String getEchNameOverride(SSLEngine engine) { + return toConscrypt(engine).getEchNameOverride(); + } + + public static boolean echAccepted(SSLEngine engine) { + return toConscrypt(engine).echAccepted(); + } + + + /** + * < Max RR value size, as given to API + */ + private static int ECH_MAX_RRVALUE_LEN = 2000; + /** + * < Max for an ECHConfig extension + */ + private static int ECH_MAX_ECHCONFIGEXT_LEN = 100; + /** + * < just for a sanity check + */ + private static int ECH_MIN_ECHCONFIG_LEN = 32; + /** + * < for a sanity check + */ + private static int ECH_MAX_ECHCONFIG_LEN = ECH_MAX_RRVALUE_LEN; + + /** + * One or more catenated binary ECHConfigs + */ + public static int ECH_FMT_BIN = 1; + /** + * < presentation form of HTTPSSVC + */ + public static int ECH_FMT_HTTPSSVC = 4; + /** + * the wire-format code for ECH within an SVCB or HTTPS RData + */ + private static int ECH_PCODE_ECH = 0x0005; + + /** + * Decode SVCB/HTTPS RR value provided as binary or ascii-hex. + *
+ * The rrval may be the catenation of multiple encoded ECHConfigs. + * We internally try decode and handle those and (later) + * use whichever is relevant/best. + *
+ * Note that we "succeed" even if there is no ECHConfigs in the input - some
+ * callers might download the RR from DNS and pass it here without looking
+ * inside, and there are valid uses of such RRs. The caller can check though
+ * using the num_echs output.
+ *
+ * @param rrval is the binary encoded RData
+ * @return is 1 for success, error otherwise
+ */
+ public static byte[] getEchConfigListFromDnsRR(byte[] rrval) {
+ int rv = 0;
+ int binlen = 0; /* the RData */
+ byte[] binbuf = null;
+ int pos = 0;
+ int remaining = rrval.length;;
+ String dnsname = null;
+ int plen = 0;
+ boolean done = false;
+
+ /*
+ * skip 2 octet priority and TargetName as those are the
+ * application's responsibility, not the library's
+ */
+ if (remaining <= 2) {
+ return null;
+ }
+ pos += 2;
+ remaining -= 2;
+ pos++;
+ int clen = DnsPacket.byteToUnsignedInt(rrval[pos]);
+ ByteArrayOutputStream thename = new ByteArrayOutputStream();
+ if (clen == 0) {
+ // special case - return "." as name
+ thename.write('.');
+ rv = 1;
+ }
+ while (clen != 0) {
+ if (clen > remaining) {
+ rv = 1;
+ break;
+ }
+ for (int i =pos; i < clen; i++) {
+ thename.write(DnsPacket.byteToUnsignedInt(rrval[pos + i]));
+ }
+ thename.write('.');
+ pos += clen;
+ remaining -= clen + 1;
+ clen = DnsPacket.byteToUnsignedInt(rrval[pos]);
+ }
+ if (rv != 1) {
+ return null;
+ }
+
+ int echStart = 0;
+ while (!done && remaining >= 4) {
+ int pcode = (rrval[pos] << 8) + rrval[pos + 1];
+ pos += 2;
+ plen = (rrval[pos] << 8) + rrval[pos + 1];
+ pos += 2;
+ remaining -= 4;
+ if (pcode == ECH_PCODE_ECH) {
+ echStart = pos;
+ done = true;
+ }
+ if (plen != 0 && plen <= remaining) {
+ pos += plen;
+ remaining -= plen;
+ }
+ }
+ if (!done) {
+ return null;
+ }
+ if (plen <=0) {
+ return null;
+ }
+ byte[] ret = new byte[plen];
+ System.arraycopy(rrval, echStart, ret, 0, plen);
+ return ret;
+ }
+
/**
* Indicates whether the given {@link TrustManager} was created by this distribution of
* Conscrypt.
diff --git a/common/src/main/java/org/conscrypt/ConscryptEngine.java b/common/src/main/java/org/conscrypt/ConscryptEngine.java
index 818fa93cc..ec06943e5 100644
--- a/common/src/main/java/org/conscrypt/ConscryptEngine.java
+++ b/common/src/main/java/org/conscrypt/ConscryptEngine.java
@@ -398,6 +398,24 @@ public int getPeerPort() {
return peerInfoProvider.getPort();
}
+ public void setEchParameters(EchParameters parameters) {
+ sslParameters.setEchParameters(parameters);
+ }
+
+ public EchParameters getEchParameters() {
+ return sslParameters.getEchParameters();
+ }
+
+ @Override
+ public String getEchNameOverride() {
+ return ssl.getEchNameOverride();
+ }
+
+ @Override
+ public boolean echAccepted() {
+ return ssl.echAccepted();
+ }
+
@Override
public void beginHandshake() throws SSLException {
synchronized (ssl) {
diff --git a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
index c2db73e59..458505b49 100644
--- a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
+++ b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
@@ -473,6 +473,26 @@ byte[] exportKeyingMaterial(String label, byte[] context, int length) throws SSL
return engine.exportKeyingMaterial(label, context, length);
}
+ @Override
+ public void setEchParameters(EchParameters parameters) {
+ engine.setEchParameters(parameters);
+ }
+
+ @Override
+ public EchParameters getEchParameters() {
+ return engine.getEchParameters();
+ }
+
+ @Override
+ public String getEchNameOverride() {
+ return engine.getEchNameOverride();
+ }
+
+ @Override
+ public boolean echAccepted() {
+ return engine.echAccepted();
+ }
+
@Override
public final boolean getUseClientMode() {
return engine.getUseClientMode();
diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
index 1f7940ecf..fc3b58062 100644
--- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
+++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
@@ -895,6 +895,27 @@ byte[] exportKeyingMaterial(String label, byte[] context, int length) throws SSL
return ssl.exportKeyingMaterial(label, context, length);
}
+
+ @Override
+ public void setEchParameters(EchParameters parameters) {
+ sslParameters.setEchParameters(parameters);
+ }
+
+ @Override
+ public EchParameters getEchParameters() {
+ return sslParameters.getEchParameters();
+ }
+
+ @Override
+ public String getEchNameOverride() {
+ return ssl.getEchNameOverride();
+ }
+
+ @Override
+ public boolean echAccepted() {
+ return ssl.echAccepted();
+ }
+
@Override
public final boolean getUseClientMode() {
return sslParameters.getUseClientMode();
diff --git a/common/src/main/java/org/conscrypt/EchParameters.java b/common/src/main/java/org/conscrypt/EchParameters.java
new file mode 100644
index 000000000..9c581569d
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/EchParameters.java
@@ -0,0 +1,29 @@
+package org.conscrypt;
+
+public class EchParameters {
+
+ public boolean useEchGrease;
+
+ public byte[] configList;
+
+ public EchParameters() {
+ this.useEchGrease = false;
+ this.configList = null;
+ }
+
+ public EchParameters(boolean useEchGrease) {
+ this.useEchGrease = useEchGrease;
+ this.configList = null;
+ }
+
+ public EchParameters(byte[] configList) {
+ this.useEchGrease = false;
+ this.configList = configList;
+ }
+
+ public EchParameters(boolean useEchGrease, byte[] configList) {
+ this.useEchGrease = useEchGrease;
+ this.configList = configList;
+ }
+
+}
diff --git a/common/src/main/java/org/conscrypt/EchRejectedException.java b/common/src/main/java/org/conscrypt/EchRejectedException.java
new file mode 100644
index 000000000..af91ce859
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/EchRejectedException.java
@@ -0,0 +1,17 @@
+package org.conscrypt;
+
+import javax.net.ssl.SSLHandshakeException;
+
+/**
+ * The server rejected the ECH Config List, and might have supplied an ECH
+ * Retry Config.
+ *
+ * @see NativeCrypto#SSL_get0_ech_retry_configs(long, NativeSsl)
+ */
+public class EchRejectedException extends SSLHandshakeException {
+ private static final long serialVersionUID = 98723498273473923L;
+
+ EchRejectedException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/org/conscrypt/Java8EngineWrapper.java b/common/src/main/java/org/conscrypt/Java8EngineWrapper.java
index 5cf135d4f..1abd1ea04 100644
--- a/common/src/main/java/org/conscrypt/Java8EngineWrapper.java
+++ b/common/src/main/java/org/conscrypt/Java8EngineWrapper.java
@@ -116,6 +116,24 @@ public int getPeerPort() {
return delegate.getPeerPort();
}
+ public void setEchParameters(EchParameters parameters) {
+ delegate.setEchParameters(parameters);
+ }
+
+ public EchParameters getEchParameters() {
+ return delegate.getEchParameters();
+ }
+
+ @Override
+ public String getEchNameOverride() {
+ return delegate.getEchNameOverride();
+ }
+
+ @Override
+ public boolean echAccepted() {
+ return delegate.echAccepted();
+ }
+
@Override
public void beginHandshake() throws SSLException {
delegate.beginHandshake();
diff --git a/common/src/main/java/org/conscrypt/NativeSsl.java b/common/src/main/java/org/conscrypt/NativeSsl.java
index 406c68f2c..ea2d56611 100644
--- a/common/src/main/java/org/conscrypt/NativeSsl.java
+++ b/common/src/main/java/org/conscrypt/NativeSsl.java
@@ -274,6 +274,14 @@ byte[] getTlsChannelId() throws SSLException {
return NativeCrypto.SSL_get_tls_channel_id(ssl, this);
}
+ String getEchNameOverride() {
+ return NativeCrypto.SSL_get0_ech_name_override(ssl, this);
+ }
+
+ boolean echAccepted() {
+ return NativeCrypto.SSL_ech_accepted(ssl, this);
+ }
+
void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOException {
boolean enableSessionCreation = parameters.getEnableSessionCreation();
if (!enableSessionCreation) {
@@ -293,6 +301,11 @@ void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOExcept
if (parameters.isCTVerificationEnabled(hostname)) {
NativeCrypto.SSL_enable_signed_cert_timestamps(ssl, this);
}
+ NativeCrypto.SSL_set_enable_ech_grease(ssl, this, parameters.getEchParameters().useEchGrease);
+ if (parameters.getEchParameters().configList != null
+ && !NativeCrypto.SSL_set1_ech_config_list(ssl, this, parameters.getEchParameters().configList)) {
+ throw new SSLHandshakeException("Error setting ECHConfigList");
+ }
} else {
NativeCrypto.SSL_set_accept_state(ssl, this);
diff --git a/common/src/main/java/org/conscrypt/SSLParametersImpl.java b/common/src/main/java/org/conscrypt/SSLParametersImpl.java
index f2056f2bd..f51e6b174 100644
--- a/common/src/main/java/org/conscrypt/SSLParametersImpl.java
+++ b/common/src/main/java/org/conscrypt/SSLParametersImpl.java
@@ -109,6 +109,8 @@ final class SSLParametersImpl implements Cloneable {
boolean useSessionTickets;
private Boolean useSni;
+ private EchParameters echParameters;
+
/**
* Whether the TLS Channel ID extension is enabled. This field is
* server-side only.
@@ -235,6 +237,7 @@ private SSLParametersImpl(ClientSessionContext clientSessionContext,
this.useSessionTickets = sslParams.useSessionTickets;
this.useSni = sslParams.useSni;
this.channelIdEnabled = sslParams.channelIdEnabled;
+ this.echParameters = sslParams.echParameters;
}
/**
@@ -474,6 +477,17 @@ boolean getUseSni() {
return useSni != null ? useSni : isSniEnabledByDefault();
}
+ /*
+ * Includes parameters for supporting ECH with this SSL connection
+ */
+ void setEchParameters(EchParameters parameters) {
+ this.echParameters = parameters;
+ }
+
+ EchParameters getEchParameters() {
+ return echParameters;
+ }
+
/*
* For testing only.
*/
diff --git a/common/src/main/java/org/conscrypt/SSLUtils.java b/common/src/main/java/org/conscrypt/SSLUtils.java
index 39eb05a42..c29c5d42e 100644
--- a/common/src/main/java/org/conscrypt/SSLUtils.java
+++ b/common/src/main/java/org/conscrypt/SSLUtils.java
@@ -352,6 +352,11 @@ static SSLHandshakeException toSSLHandshakeException(Throwable e) {
return (SSLHandshakeException) e;
}
+ if (e.getMessage().contains(":ECH_REJECTED ")) {
+ // TODO should this be implemented in boringssl?
+ return (SSLHandshakeException) new EchRejectedException(e.getMessage()).initCause(e);
+ }
+
return (SSLHandshakeException) new SSLHandshakeException(e.getMessage()).initCause(e);
}
diff --git a/common/src/main/java/org/conscrypt/com/android/net/module/util/DnsPacket.java b/common/src/main/java/org/conscrypt/com/android/net/module/util/DnsPacket.java
new file mode 100644
index 000000000..0052fa382
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/com/android/net/module/util/DnsPacket.java
@@ -0,0 +1,253 @@
+package org.conscrypt.com.android.net.module.util;
+
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines basic data for DNS protocol based on RFC 1035.
+ * Subclasses create the specific format used in DNS packet.
+ *
+ * @hide
+ */
+
+/**
+ * @see original source
+ *
+ * Some local changes have been made to provide additional utility
+ */
+
+public abstract class DnsPacket {
+ /**
+ * Thrown when parsing packet failed.
+ */
+ public static class ParseException extends RuntimeException {
+ public String reason;
+ public ParseException(String reason) {
+ super(reason);
+ this.reason = reason;
+ }
+ public ParseException(String reason, Throwable cause) {
+ super(reason, cause);
+ this.reason = reason;
+ }
+ }
+ /**
+ * DNS header for DNS protocol based on RFC 1035.
+ */
+ public class DnsHeader {
+ private static final String TAG = "DnsHeader";
+ public final int id;
+ public final int flags;
+ public final int rcode;
+ private final int[] mRecordCount;
+ /**
+ * Create a new DnsHeader from a positioned ByteBuffer.
+ *
+ * The ByteBuffer must be in network byte order (which is the default).
+ * Reads the passed ByteBuffer from its current position and decodes a DNS header.
+ * When this constructor returns, the reading position of the ByteBuffer has been
+ * advanced to the end of the DNS header record.
+ * This is meant to chain with other methods reading a DNS response in sequence.
+ */
+ DnsHeader(ByteBuffer buf) throws BufferUnderflowException {
+ id = Short.toUnsignedInt(buf.getShort());
+ flags = Short.toUnsignedInt(buf.getShort());
+ rcode = flags & 0xF;
+ mRecordCount = new int[NUM_SECTIONS];
+ for (int i = 0; i < NUM_SECTIONS; ++i) {
+ mRecordCount[i] = Short.toUnsignedInt(buf.getShort());
+ }
+ }
+ /**
+ * Get record count by type.
+ */
+ public int getRecordCount(int type) {
+ return mRecordCount[type];
+ }
+ }
+ /**
+ * Superclass for DNS questions and DNS resource records.
+ *
+ * DNS questions (No TTL/RDATA)
+ * DNS resource records (With TTL/RDATA)
+ */
+ public class DnsRecord {
+ private static final int MAXNAMESIZE = 255;
+ private static final int MAXLABELSIZE = 63;
+ private static final int MAXLABELCOUNT = 128;
+ public static final int NAME_NORMAL = 0;
+ public static final int NAME_COMPRESSION = 0xC0;
+ private final DecimalFormat mByteFormat = new DecimalFormat();
+ private final FieldPosition mPos = new FieldPosition(0);
+ private static final String TAG = "DnsRecord";
+ public final String dName;
+ public final int nsType;
+ public final int nsClass;
+ public final long ttl;
+ private final byte[] mRdata;
+ /**
+ * Create a new DnsRecord from a positioned ByteBuffer.
+ *
+ * Reads the passed ByteBuffer from its current position and decodes a DNS record.
+ * When this constructor returns, the reading position of the ByteBuffer has been
+ * advanced to the end of the DNS header record.
+ * This is meant to chain with other methods reading a DNS response in sequence.
+ *
+ * @param ByteBuffer input of record, must be in network byte order
+ * (which is the default).
+ */
+ DnsRecord(int recordType, ByteBuffer buf)
+ throws BufferUnderflowException, ParseException {
+ dName = parseName(buf, 0 /* Parse depth */);
+ if (dName.length() > MAXNAMESIZE) {
+ throw new ParseException(
+ "Parse name fail, name size is too long: " + dName.length());
+ }
+ nsType = Short.toUnsignedInt(buf.getShort());
+ nsClass = Short.toUnsignedInt(buf.getShort());
+ if (recordType != QDSECTION) {
+ ttl = Integer.toUnsignedLong(buf.getInt());
+ final int length = Short.toUnsignedInt(buf.getShort());
+ mRdata = new byte[length];
+ buf.get(mRdata);
+ } else {
+ ttl = 0;
+ mRdata = null;
+ }
+ }
+ /**
+ * Get a copy of rdata.
+ */
+ public byte[] getRR() {
+ return (mRdata == null) ? null : mRdata.clone();
+ }
+ /**
+ * Convert label from {@code byte[]} to {@code String}
+ *
+ * Follows the same conversion rules of the native code (ns_name.c in libc)
+ */
+ private String labelToString(byte[] label) {
+ final StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < label.length; ++i) {
+ int b = Byte.toUnsignedInt(label[i]);
+ // Control characters and non-ASCII characters.
+ if (b <= 0x20 || b >= 0x7f) {
+ // Append the byte as an escaped decimal number, e.g., "\19" for 0x13.
+ sb.append('\\');
+ mByteFormat.format(b, sb, mPos);
+ } else if (b == '"' || b == '.' || b == ';' || b == '\\'
+ || b == '(' || b == ')' || b == '@' || b == '$') {
+ // Append the byte as an escaped character, e.g., "\:" for 0x3a.
+ sb.append('\\');
+ sb.append((char) b);
+ } else {
+ // Append the byte as a character, e.g., "a" for 0x61.
+ sb.append((char) b);
+ }
+ }
+ return sb.toString();
+ }
+ private String parseName(ByteBuffer buf, int depth) throws
+ BufferUnderflowException, ParseException {
+ if (depth > MAXLABELCOUNT) {
+ throw new ParseException("Failed to parse name, too many labels");
+ }
+ final int len = Byte.toUnsignedInt(buf.get());
+ final int mask = len & NAME_COMPRESSION;
+ if (0 == len) {
+ return "";
+ } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
+ throw new ParseException("Parse name fail, bad label type");
+ } else if (mask == NAME_COMPRESSION) {
+ // Name compression based on RFC 1035 - 4.1.4 Message compression
+ final int offset = ((len & ~NAME_COMPRESSION) << 8) + Byte.toUnsignedInt(buf.get());
+ final int oldPos = buf.position();
+ if (offset >= oldPos - 2) {
+ throw new ParseException("Parse compression name fail, invalid compression");
+ }
+ buf.position(offset);
+ final String pointed = parseName(buf, depth + 1);
+ buf.position(oldPos);
+ return pointed;
+ } else {
+ final byte[] label = new byte[len];
+ buf.get(label);
+ final String head = labelToString(label);
+ if (head.length() > MAXLABELSIZE) {
+ throw new ParseException("Parse name fail, invalid label length");
+ }
+ final String tail = parseName(buf, depth + 1);
+ // return TextUtils.isEmpty(tail) ? head : head + "." + tail;
+ return (tail == null || tail.isEmpty()) ? head : head + "." + tail;
+ }
+ }
+ }
+
+ /** {@link Byte#toUnsignedInt(byte)} was added to Android in API 26. */
+ public static int byteToUnsignedInt(byte b) {
+ return b & 255;
+ }
+
+ /** {@link Short#toUnsignedInt(short)} was added to Android in API 26. */
+ public static int shortToUnsignedInt(short s) {
+ return s & '\uffff';
+ }
+
+ /** {@link Integer#toUnsignedLong(int)} was added to Android in API 26. */
+ public static long integerToUnsignedLong(int i) {
+ return (long) i & 4294967295L;
+ }
+
+ public static final int QDSECTION = 0;
+ public static final int ANSECTION = 1;
+ public static final int NSSECTION = 2;
+ public static final int ARSECTION = 3;
+ private static final int NUM_SECTIONS = ARSECTION + 1;
+ private static final String TAG = DnsPacket.class.getSimpleName();
+ protected final DnsHeader mHeader;
+ protected final List