Home | History | Annotate | Download | only in nsd
      1 /* -*- Mode: Java; tab-width: 4 -*-
      2  *
      3  * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16 
     17  To do:
     18  - implement remove()
     19  - fix set() to replace existing values
     20  */
     21 
     22 package android.net.nsd;
     23 
     24 import android.os.Parcelable;
     25 import android.os.Parcel;
     26 
     27 import java.util.Arrays;
     28 
     29 /**
     30  * This class handles TXT record data for DNS based service discovery as specified at
     31  * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
     32  *
     33  * DNS-SD specifies that a TXT record corresponding to an SRV record consist of
     34  * a packed array of bytes, each preceded by a length byte. Each string
     35  * is an attribute-value pair.
     36  *
     37  * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
     38  * as need be to implement its various methods.
     39  * @hide
     40  *
     41  */
     42 public class DnsSdTxtRecord implements Parcelable {
     43     private static final byte mSeperator = '=';
     44 
     45     private byte[] mData;
     46 
     47     /** Constructs a new, empty TXT record. */
     48     public DnsSdTxtRecord()  {
     49         mData = new byte[0];
     50     }
     51 
     52     /** Constructs a new TXT record from a byte array in the standard format. */
     53     public DnsSdTxtRecord(byte[] data) {
     54         mData = (byte[]) data.clone();
     55     }
     56 
     57     /** Copy constructor */
     58     public DnsSdTxtRecord(DnsSdTxtRecord src) {
     59         if (src != null && src.mData != null) {
     60             mData = (byte[]) src.mData.clone();
     61         }
     62     }
     63 
     64     /**
     65      * Set a key/value pair. Setting an existing key will replace its value.
     66      * @param key Must be ascii with no '='
     67      * @param value matching value to key
     68      */
     69     public void set(String key, String value) {
     70         byte[] keyBytes;
     71         byte[] valBytes;
     72         int valLen;
     73 
     74         if (value != null) {
     75             valBytes = value.getBytes();
     76             valLen = valBytes.length;
     77         } else {
     78             valBytes = null;
     79             valLen = 0;
     80         }
     81 
     82         try {
     83             keyBytes = key.getBytes("US-ASCII");
     84         }
     85         catch (java.io.UnsupportedEncodingException e) {
     86             throw new IllegalArgumentException("key should be US-ASCII");
     87         }
     88 
     89         for (int i = 0; i < keyBytes.length; i++) {
     90             if (keyBytes[i] == '=') {
     91                 throw new IllegalArgumentException("= is not a valid character in key");
     92             }
     93         }
     94 
     95         if (keyBytes.length + valLen >= 255) {
     96             throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
     97         }
     98 
     99         int currentLoc = remove(key);
    100         if (currentLoc == -1)
    101             currentLoc = keyCount();
    102 
    103         insert(keyBytes, valBytes, currentLoc);
    104     }
    105 
    106     /**
    107      * Get a value for a key
    108      *
    109      * @param key
    110      * @return The value associated with the key
    111      */
    112     public String get(String key) {
    113         byte[] val = this.getValue(key);
    114         return val != null ? new String(val) : null;
    115     }
    116 
    117     /** Remove a key/value pair. If found, returns the index or -1 if not found */
    118     public int remove(String key) {
    119         int avStart = 0;
    120 
    121         for (int i=0; avStart < mData.length; i++) {
    122             int avLen = mData[avStart];
    123             if (key.length() <= avLen &&
    124                     (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) {
    125                 String s = new String(mData, avStart + 1, key.length());
    126                 if (0 == key.compareToIgnoreCase(s)) {
    127                     byte[] oldBytes = mData;
    128                     mData = new byte[oldBytes.length - avLen - 1];
    129                     System.arraycopy(oldBytes, 0, mData, 0, avStart);
    130                     System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart,
    131                             oldBytes.length - avStart - avLen - 1);
    132                     return i;
    133                 }
    134             }
    135             avStart += (0xFF & (avLen + 1));
    136         }
    137         return -1;
    138     }
    139 
    140     /** Return the count of keys */
    141     public int keyCount() {
    142         int count = 0, nextKey;
    143         for (nextKey = 0; nextKey < mData.length; count++) {
    144             nextKey += (0xFF & (mData[nextKey] + 1));
    145         }
    146         return count;
    147     }
    148 
    149     /** Return true if key is present, false if not. */
    150     public boolean contains(String key) {
    151         String s = null;
    152         for (int i = 0; null != (s = this.getKey(i)); i++) {
    153             if (0 == key.compareToIgnoreCase(s)) return true;
    154         }
    155         return false;
    156     }
    157 
    158     /* Gets the size in bytes */
    159     public int size() {
    160         return mData.length;
    161     }
    162 
    163     /* Gets the raw data in bytes */
    164     public byte[] getRawData() {
    165         return (byte[]) mData.clone();
    166     }
    167 
    168     private void insert(byte[] keyBytes, byte[] value, int index) {
    169         byte[] oldBytes = mData;
    170         int valLen = (value != null) ? value.length : 0;
    171         int insertion = 0;
    172         int newLen, avLen;
    173 
    174         for (int i = 0; i < index && insertion < mData.length; i++) {
    175             insertion += (0xFF & (mData[insertion] + 1));
    176         }
    177 
    178         avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
    179         newLen = avLen + oldBytes.length + 1;
    180 
    181         mData = new byte[newLen];
    182         System.arraycopy(oldBytes, 0, mData, 0, insertion);
    183         int secondHalfLen = oldBytes.length - insertion;
    184         System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen);
    185         mData[insertion] = (byte) avLen;
    186         System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length);
    187         if (value != null) {
    188             mData[insertion + 1 + keyBytes.length] = mSeperator;
    189             System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen);
    190         }
    191     }
    192 
    193     /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */
    194     private String getKey(int index) {
    195         int avStart = 0;
    196 
    197         for (int i=0; i < index && avStart < mData.length; i++) {
    198             avStart += mData[avStart] + 1;
    199         }
    200 
    201         if (avStart < mData.length) {
    202             int avLen = mData[avStart];
    203             int aLen = 0;
    204 
    205             for (aLen=0; aLen < avLen; aLen++) {
    206                 if (mData[avStart + aLen + 1] == mSeperator) break;
    207             }
    208             return new String(mData, avStart + 1, aLen);
    209         }
    210         return null;
    211     }
    212 
    213     /**
    214      * Look up a key in the TXT record by zero-based index and return its value.
    215      * Returns null if index exceeds the total number of keys.
    216      * Returns null if the key is present with no value.
    217      */
    218     private byte[] getValue(int index) {
    219         int avStart = 0;
    220         byte[] value = null;
    221 
    222         for (int i=0; i < index && avStart < mData.length; i++) {
    223             avStart += mData[avStart] + 1;
    224         }
    225 
    226         if (avStart < mData.length) {
    227             int avLen = mData[avStart];
    228             int aLen = 0;
    229 
    230             for (aLen=0; aLen < avLen; aLen++) {
    231                 if (mData[avStart + aLen + 1] == mSeperator) {
    232                     value = new byte[avLen - aLen - 1];
    233                     System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1);
    234                     break;
    235                 }
    236             }
    237         }
    238         return value;
    239     }
    240 
    241     private String getValueAsString(int index) {
    242         byte[] value = this.getValue(index);
    243         return value != null ? new String(value) : null;
    244     }
    245 
    246     private byte[] getValue(String forKey) {
    247         String s = null;
    248         int i;
    249 
    250         for (i = 0; null != (s = this.getKey(i)); i++) {
    251             if (0 == forKey.compareToIgnoreCase(s)) {
    252                 return this.getValue(i);
    253             }
    254         }
    255 
    256         return null;
    257     }
    258 
    259     /**
    260      * Return a string representation.
    261      * Example : {key1=value1},{key2=value2}..
    262      *
    263      * For a key say like "key3" with null value
    264      * {key1=value1},{key2=value2}{key3}
    265      */
    266     public String toString() {
    267         String a, result = null;
    268 
    269         for (int i = 0; null != (a = this.getKey(i)); i++) {
    270             String av =  "{" + a;
    271             String val = this.getValueAsString(i);
    272             if (val != null)
    273                 av += "=" + val + "}";
    274             else
    275                 av += "}";
    276             if (result == null)
    277                 result = av;
    278             else
    279                 result = result + ", " + av;
    280         }
    281         return result != null ? result : "";
    282     }
    283 
    284     @Override
    285     public boolean equals(Object o) {
    286         if (o == this) {
    287             return true;
    288         }
    289         if (!(o instanceof DnsSdTxtRecord)) {
    290             return false;
    291         }
    292 
    293         DnsSdTxtRecord record = (DnsSdTxtRecord)o;
    294         return  Arrays.equals(record.mData, mData);
    295     }
    296 
    297     @Override
    298     public int hashCode() {
    299         return Arrays.hashCode(mData);
    300     }
    301 
    302     /** Implement the Parcelable interface */
    303     public int describeContents() {
    304         return 0;
    305     }
    306 
    307     /** Implement the Parcelable interface */
    308     public void writeToParcel(Parcel dest, int flags) {
    309         dest.writeByteArray(mData);
    310     }
    311 
    312     /** Implement the Parcelable interface */
    313     public static final Creator<DnsSdTxtRecord> CREATOR =
    314         new Creator<DnsSdTxtRecord>() {
    315             public DnsSdTxtRecord createFromParcel(Parcel in) {
    316                 DnsSdTxtRecord info = new DnsSdTxtRecord();
    317                 in.readByteArray(info.mData);
    318                 return info;
    319             }
    320 
    321             public DnsSdTxtRecord[] newArray(int size) {
    322                 return new DnsSdTxtRecord[size];
    323             }
    324         };
    325 }
    326