content: /* -*- Mode: Java; tab-width: 4 -*-
   *
   * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
   *
   * 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.
  
   To do:
   - implement remove()
   - fix set() to replace existing values
   */
  
  package android.net.nsd;
  
  import android.os.Parcelable;
  import android.os.Parcel;
  
  import java.util.Arrays;
  
  /**
   * This class handles TXT record data for DNS based service discovery as specified at
   * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
   *
   * DNS-SD specifies that a TXT record corresponding to an SRV record consist of
   * a packed array of bytes, each preceded by a length byte. Each string
   * is an attribute-value pair.
   *
   * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
   * as need be to implement its various methods.
   * @hide
   *
   */
  public class DnsSdTxtRecord implements Parcelable {
      private static final byte mSeperator = '=';
  
      private byte[] mData;
  
      /** Constructs a new, empty TXT record. */
      public DnsSdTxtRecord()  {
          mData = new byte[0];
      }
  
      /** Constructs a new TXT record from a byte array in the standard format. */
      public DnsSdTxtRecord(byte[] data) {
          mData = (byte[]) data.clone();
      }
  
      /** Copy constructor */
      public DnsSdTxtRecord(DnsSdTxtRecord src) {
          if (src != null && src.mData != null) {
              mData = (byte[]) src.mData.clone();
          }
      }
  
      /**
       * Set a key/value pair. Setting an existing key will replace its value.
       * @param key Must be ascii with no '='
       * @param value matching value to key
       */
      public void set(String key, String value) {
          byte[] keyBytes;
          byte[] valBytes;
          int valLen;
  
          if (value != null) {
              valBytes = value.getBytes();
              valLen = valBytes.length;
          } else {
              valBytes = null;
              valLen = 0;
          }
  
          try {
              keyBytes = key.getBytes("US-ASCII");
          }
          catch (java.io.UnsupportedEncodingException e) {
              throw new IllegalArgumentException("key should be US-ASCII");
          }
  
          for (int i = 0; i < keyBytes.length; i++) {
              if (keyBytes[i] == '=') {
                  throw new IllegalArgumentException("= is not a valid character in key");
              }
          }
  
          if (keyBytes.length + valLen >= 255) {
              throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
          }
  
          int currentLoc = remove(key);
          if (currentLoc == -1)
              currentLoc = keyCount();
  
          insert(keyBytes, valBytes, currentLoc);
      }
  
      /**
       * Get a value for a key
       *
       * @param key
       * @return The value associated with the key
       */
      public String get(String key) {
          byte[] val = this.getValue(key);
          return val != null ? new String(val) : null;
      }
  
      /** Remove a key/value pair. If found, returns the index or -1 if not found */
      public int remove(String key) {
          int avStart = 0;
  
          for (int i=0; avStart < mData.length; i++) {
              int avLen = mData[avStart];
              if (key.length() <= avLen &&
                      (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) {
                  String s = new String(mData, avStart + 1, key.length());
                  if (0 == key.compareToIgnoreCase(s)) {
                      byte[] oldBytes = mData;
                      mData = new byte[oldBytes.length - avLen - 1];
                      System.arraycopy(oldBytes, 0, mData, 0, avStart);
                      System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart,
                              oldBytes.length - avStart - avLen - 1);
                      return i;
                  }
              }
              avStart += (0xFF & (avLen + 1));
          }
          return -1;
      }
  
      /** Return the count of keys */
      public int keyCount() {
          int count = 0, nextKey;
          for (nextKey = 0; nextKey < mData.length; count++) {
              nextKey += (0xFF & (mData[nextKey] + 1));
          }
          return count;
      }
  
      /** Return true if key is present, false if not. */
      public boolean contains(String key) {
          String s = null;
          for (int i = 0; null != (s = this.getKey(i)); i++) {
              if (0 == key.compareToIgnoreCase(s)) return true;
          }
          return false;
      }
  
      /* Gets the size in bytes */
      public int size() {
          return mData.length;
      }
  
      /* Gets the raw data in bytes */
      public byte[] getRawData() {
          return (byte[]) mData.clone();
      }
  
      private void insert(byte[] keyBytes, byte[] value, int index) {
          byte[] oldBytes = mData;
          int valLen = (value != null) ? value.length : 0;
          int insertion = 0;
          int newLen, avLen;
  
          for (int i = 0; i < index && insertion < mData.length; i++) {
              insertion += (0xFF & (mData[insertion] + 1));
          }
  
          avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
          newLen = avLen + oldBytes.length + 1;
  
          mData = new byte[newLen];
          System.arraycopy(oldBytes, 0, mData, 0, insertion);
          int secondHalfLen = oldBytes.length - insertion;
          System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen);
          mData[insertion] = (byte) avLen;
          System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length);
          if (value != null) {
              mData[insertion + 1 + keyBytes.length] = mSeperator;
              System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen);
          }
      }
  
      /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */
      private String getKey(int index) {
          int avStart = 0;
  
          for (int i=0; i < index && avStart < mData.length; i++) {
              avStart += mData[avStart] + 1;
          }
  
          if (avStart < mData.length) {
              int avLen = mData[avStart];
              int aLen = 0;
  
              for (aLen=0; aLen < avLen; aLen++) {
                  if (mData[avStart + aLen + 1] == mSeperator) break;
              }
              return new String(mData, avStart + 1, aLen);
          }
          return null;
      }
  
      /**
       * Look up a key in the TXT record by zero-based index and return its value.
       * Returns null if index exceeds the total number of keys.
       * Returns null if the key is present with no value.
       */
      private byte[] getValue(int index) {
          int avStart = 0;
          byte[] value = null;
  
          for (int i=0; i < index && avStart < mData.length; i++) {
              avStart += mData[avStart] + 1;
          }
  
          if (avStart < mData.length) {
              int avLen = mData[avStart];
              int aLen = 0;
  
              for (aLen=0; aLen < avLen; aLen++) {
                  if (mData[avStart + aLen + 1] == mSeperator) {
                      value = new byte[avLen - aLen - 1];
                      System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1);
                      break;
                  }
              }
          }
          return value;
      }
  
      private String getValueAsString(int index) {
          byte[] value = this.getValue(index);
          return value != null ? new String(value) : null;
      }
  
      private byte[] getValue(String forKey) {
          String s = null;
          int i;
  
          for (i = 0; null != (s = this.getKey(i)); i++) {
              if (0 == forKey.compareToIgnoreCase(s)) {
                  return this.getValue(i);
              }
          }
  
          return null;
      }
  
      /**
       * Return a string representation.
       * Example : {key1=value1},{key2=value2}..
       *
       * For a key say like "key3" with null value
       * {key1=value1},{key2=value2}{key3}
       */
      public String toString() {
          String a, result = null;
  
          for (int i = 0; null != (a = this.getKey(i)); i++) {
              String av =  "{" + a;
              String val = this.getValueAsString(i);
              if (val != null)
                  av += "=" + val + "}";
              else
                  av += "}";
              if (result == null)
                  result = av;
              else
                  result = result + ", " + av;
          }
          return result != null ? result : "";
      }
  
      @Override
      public boolean equals(Object o) {
          if (o == this) {
              return true;
          }
          if (!(o instanceof DnsSdTxtRecord)) {
              return false;
          }
  
          DnsSdTxtRecord record = (DnsSdTxtRecord)o;
          return  Arrays.equals(record.mData, mData);
      }
  
      @Override
      public int hashCode() {
          return Arrays.hashCode(mData);
      }
  
      /** Implement the Parcelable interface */
      public int describeContents() {
          return 0;
      }
  
      /** Implement the Parcelable interface */
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeByteArray(mData);
      }
  
      /** Implement the Parcelable interface */
      public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR =
          new Creator<DnsSdTxtRecord>() {
              public DnsSdTxtRecord createFromParcel(Parcel in) {
                  DnsSdTxtRecord info = new DnsSdTxtRecord();
                  in.readByteArray(info.mData);
                  return info;
              }
  
              public DnsSdTxtRecord[] newArray(int size) {
                  return new DnsSdTxtRecord[size];
              }
          };
  }
  