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