content: /*
   * 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 android.net;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.text.TextUtils;
  
  import com.android.internal.util.BitUtils;
  
  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
   */
  public abstract class DnsPacket {
      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(@NonNull ByteBuffer buf) throws BufferUnderflowException {
              id = BitUtils.uint16(buf.getShort());
              flags = BitUtils.uint16(buf.getShort());
              rcode = flags & 0xF;
              mRecordCount = new int[NUM_SECTIONS];
              for (int i = 0; i < NUM_SECTIONS; ++i) {
                  mRecordCount[i] = BitUtils.uint16(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;
          private static final int NAME_NORMAL = 0;
          private static final int NAME_COMPRESSION = 0xC0;
          private final DecimalFormat byteFormat = new DecimalFormat();
          private final FieldPosition pos = 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, @NonNull 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 = BitUtils.uint16(buf.getShort());
              nsClass = BitUtils.uint16(buf.getShort());
  
              if (recordType != QDSECTION) {
                  ttl = BitUtils.uint32(buf.getInt());
                  final int length = BitUtils.uint16(buf.getShort());
                  mRdata = new byte[length];
                  buf.get(mRdata);
              } else {
                  ttl = 0;
                  mRdata = null;
              }
          }
  
          /**
           * Get a copy of rdata.
           */
          @Nullable
          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(@NonNull byte[] label) {
              final StringBuffer sb = new StringBuffer();
              for (int i = 0; i < label.length; ++i) {
                  int b = BitUtils.uint8(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('\\');
                      byteFormat.format(b, sb, pos);
                  } 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(@NonNull ByteBuffer buf, int depth) throws
                  BufferUnderflowException, ParseException {
              if (depth > MAXLABELCOUNT) {
                  throw new ParseException("Failed to parse name, too many labels");
              }
              final int len = BitUtils.uint8(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) + BitUtils.uint8(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;
              }
          }
      }
  
      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<DnsRecord>[] mRecords;
  
      protected DnsPacket(@NonNull byte[] data) throws ParseException {
          if (null == data) throw new ParseException("Parse header failed, null input data");
          final ByteBuffer buffer;
          try {
              buffer = ByteBuffer.wrap(data);
              mHeader = new DnsHeader(buffer);
          } catch (BufferUnderflowException e) {
              throw new ParseException("Parse Header fail, bad input data", e);
          }
  
          mRecords = new ArrayList[NUM_SECTIONS];
  
          for (int i = 0; i < NUM_SECTIONS; ++i) {
              final int count = mHeader.getRecordCount(i);
              if (count > 0) {
                  mRecords[i] = new ArrayList(count);
              }
              for (int j = 0; j < count; ++j) {
                  try {
                      mRecords[i].add(new DnsRecord(i, buffer));
                  } catch (BufferUnderflowException e) {
                      throw new ParseException("Parse record fail", e);
                  }
              }
          }
      }
  }
  