[DnsPacketUtils] Knob for name compression support

- Allows clients of DnsRecordParser to explicitly mention
whether name compression is supported.

Test: atest NetworkStaticLibTests
Bug: b/207710965
Change-Id: Iac92f062a16f8f28f58cb180ab27e7626a9bd701
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacket.java b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
index 4c88000..080781c 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacket.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
@@ -131,7 +131,8 @@
          */
         DnsRecord(int recordType, @NonNull ByteBuffer buf)
                 throws BufferUnderflowException, ParseException {
-            dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */);
+            dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */,
+                    /* isNameCompressionSupported= */ true);
             if (dName.length() > MAXNAMESIZE) {
                 throw new ParseException(
                         "Parse name fail, name size is too long: " + dName.length());
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
index 677474c..3448cad 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
@@ -73,8 +73,14 @@
 
         /**
          * Parses the domain / target name of a DNS record.
+         *
+         * As described in RFC 1035 Section 4.1.3, the NAME field of a DNS Resource Record always
+         * supports Name Compression, whereas domain names contained in the RDATA payload of a DNS
+         * record may or may not support Name Compression, depending on the record TYPE. Moreover,
+         * even if Name Compression is supported, its usage is left to the implementation.
          */
-        public static String parseName(ByteBuffer buf, int depth) throws
+        public static String parseName(ByteBuffer buf, int depth,
+                boolean isNameCompressionSupported) throws
                 BufferUnderflowException, DnsPacket.ParseException {
             if (depth > MAXLABELCOUNT) {
                 throw new DnsPacket.ParseException("Failed to parse name, too many labels");
@@ -83,7 +89,8 @@
             final int mask = len & NAME_COMPRESSION;
             if (0 == len) {
                 return "";
-            } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
+            } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION
+                    || (!isNameCompressionSupported && mask == NAME_COMPRESSION)) {
                 throw new DnsPacket.ParseException("Parse name fail, bad label type: " + mask);
             } else if (mask == NAME_COMPRESSION) {
                 // Name compression based on RFC 1035 - 4.1.4 Message compression
@@ -94,7 +101,7 @@
                             "Parse compression name fail, invalid compression");
                 }
                 buf.position(offset);
-                final String pointed = parseName(buf, depth + 1);
+                final String pointed = parseName(buf, depth + 1, isNameCompressionSupported);
                 buf.position(oldPos);
                 return pointed;
             } else {
@@ -104,7 +111,7 @@
                 if (head.length() > MAXLABELSIZE) {
                     throw new DnsPacket.ParseException("Parse name fail, invalid label length");
                 }
-                final String tail = parseName(buf, depth + 1);
+                final String tail = parseName(buf, depth + 1, isNameCompressionSupported);
                 return TextUtils.isEmpty(tail) ? head : head + "." + tail;
             }
         }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java
new file mode 100644
index 0000000..48777ac
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+package com.android.net.module.util;
+
+import static com.android.net.module.util.DnsPacketUtils.DnsRecordParser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsPacketUtilsTest {
+
+    /**
+     * Verifies that the compressed NAME field in the answer section of the DNS message is parsed
+     * successfully when name compression is permitted. Additionally, verifies that a
+     * {@link DnsPacket.ParseException} is thrown in a hypothetical scenario where name compression
+     * is not expected.
+     */
+    @Test
+    public void testParsingAnswerSectionNameCompressed() throws Exception {
+        final byte[] v4blobNameCompressedAnswer = new byte[] {
+                /* Header */
+                0x55, 0x66, /* Transaction ID */
+                (byte) 0x81, (byte) 0x80, /* Flags */
+                0x00, 0x01, /* Questions */
+                0x00, 0x01, /* Answer RRs */
+                0x00, 0x00, /* Authority RRs */
+                0x00, 0x00, /* Additional RRs */
+                /* Queries */
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                /* Answers */
+                (byte) 0xc0, 0x0c, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                0x00, 0x00, 0x01, 0x2b, /* TTL */
+                0x00, 0x04, /* Data length */
+                (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+        };
+        final int answerOffsetBytePosition = 32;
+        final ByteBuffer nameCompressedBuf = ByteBuffer.wrap(v4blobNameCompressedAnswer);
+
+        nameCompressedBuf.position(answerOffsetBytePosition);
+        assertThrows(DnsPacket.ParseException.class, () -> DnsRecordParser.parseName(
+                nameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */false));
+
+        nameCompressedBuf.position(answerOffsetBytePosition);
+        String domainName = DnsRecordParser.parseName(
+                nameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */true);
+        assertEquals(domainName, "www.google.com");
+    }
+
+    /**
+     * Verifies that an uncompressed NAME field in the answer section of the DNS message is parsed
+     * successfully irrespective of whether name compression is permitted.
+     */
+    @Test
+    public void testParsingAnswerSectionNoNameCompression() throws Exception {
+        final byte[] v4blobNoNameCompression = new byte[] {
+                /* Header */
+                0x55, 0x66, /* Transaction ID */
+                (byte) 0x81, (byte) 0x80, /* Flags */
+                0x00, 0x01, /* Questions */
+                0x00, 0x01, /* Answer RRs */
+                0x00, 0x00, /* Authority RRs */
+                0x00, 0x00, /* Additional RRs */
+                /* Queries */
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                /* Answers */
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                0x00, 0x00, 0x01, 0x2b, /* TTL */
+                0x00, 0x04, /* Data length */
+                (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+        };
+        final int answerOffsetBytePosition = 32;
+        final ByteBuffer notNameCompressedBuf = ByteBuffer.wrap(v4blobNoNameCompression);
+
+        notNameCompressedBuf.position(answerOffsetBytePosition);
+        String domainName = DnsRecordParser.parseName(
+                notNameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */ true);
+        assertEquals(domainName, "www.google.com");
+
+        notNameCompressedBuf.position(answerOffsetBytePosition);
+        domainName = DnsRecordParser.parseName(
+                notNameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */ false);
+        assertEquals(domainName, "www.google.com");
+    }
+}