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 com.android.net.module.util;
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 mSeparator = '=';
44 
45     private byte[] mData;
46 
47     /** Constructs a new, empty TXT record. */
DnsSdTxtRecord()48     public DnsSdTxtRecord()  {
49         mData = new byte[0];
50     }
51 
52     /** Constructs a new TXT record from a byte array in the standard format. */
DnsSdTxtRecord(byte[] data)53     public DnsSdTxtRecord(byte[] data) {
54         mData = (byte[]) data.clone();
55     }
56 
57     /** Copy constructor */
DnsSdTxtRecord(DnsSdTxtRecord src)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      */
set(String key, String value)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      */
get(String key)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 */
remove(String key)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] == mSeparator)) {
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 */
keyCount()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. */
contains(String key)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 */
size()159     public int size() {
160         return mData.length;
161     }
162 
163     /* Gets the raw data in bytes */
getRawData()164     public byte[] getRawData() {
165         return (byte[]) mData.clone();
166     }
167 
insert(byte[] keyBytes, byte[] value, int index)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] = mSeparator;
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. */
getKey(int index)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] == mSeparator) 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      */
getValue(int index)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] == mSeparator) {
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 
getValueAsString(int index)241     private String getValueAsString(int index) {
242         byte[] value = this.getValue(index);
243         return value != null ? new String(value) : null;
244     }
245 
getValue(String forKey)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      */
toString()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
equals(Object o)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
hashCode()298     public int hashCode() {
299         return Arrays.hashCode(mData);
300     }
301 
302     /** Implement the Parcelable interface */
describeContents()303     public int describeContents() {
304         return 0;
305     }
306 
307     /** Implement the Parcelable interface */
writeToParcel(Parcel dest, int flags)308     public void writeToParcel(Parcel dest, int flags) {
309         dest.writeByteArray(mData);
310     }
311 
312     /** Implement the Parcelable interface */
313     public static final @android.annotation.NonNull 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