blob: e4a91c5798f17f1f3c5e41d7c3d1d129a6014d6e [file] [log] [blame]
Irfan Sheriff26d44522012-04-03 12:13:27 -07001/* -*- 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
Irfan Sheriff7d024d32012-03-22 17:01:39 -070017 To do:
18 - implement remove()
19 - fix set() to replace existing values
Irfan Sheriff26d44522012-04-03 12:13:27 -070020 */
21
Irfan Sheriff7d024d32012-03-22 17:01:39 -070022package android.net.nsd;
Irfan Sheriff26d44522012-04-03 12:13:27 -070023
Irfan Sheriff7d024d32012-03-22 17:01:39 -070024import android.os.Parcelable;
25import android.os.Parcel;
Irfan Sheriff26d44522012-04-03 12:13:27 -070026
Irfan Sheriff21ba8152012-04-04 16:22:21 -070027import java.util.Arrays;
28
Irfan Sheriff7d024d32012-03-22 17:01:39 -070029/**
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.
Irfan Sheriff527ba072012-05-09 14:15:04 -070039 * @hide
Irfan Sheriff7d024d32012-03-22 17:01:39 -070040 *
Irfan Sheriff7d024d32012-03-22 17:01:39 -070041 */
42public class DnsSdTxtRecord implements Parcelable {
43 private static final byte mSeperator = '=';
Irfan Sheriff26d44522012-04-03 12:13:27 -070044
Irfan Sheriff7d024d32012-03-22 17:01:39 -070045 private byte[] mData;
Irfan Sheriff26d44522012-04-03 12:13:27 -070046
Irfan Sheriff7d024d32012-03-22 17:01:39 -070047 /** Constructs a new, empty TXT record. */
48 public DnsSdTxtRecord() {
49 mData = new byte[0];
50 }
Irfan Sheriff26d44522012-04-03 12:13:27 -070051
Irfan Sheriff7d024d32012-03-22 17:01:39 -070052 /** Constructs a new TXT record from a byte array in the standard format. */
53 public DnsSdTxtRecord(byte[] data) {
54 mData = (byte[]) data.clone();
55 }
Irfan Sheriff26d44522012-04-03 12:13:27 -070056
Irfan Sheriff7d024d32012-03-22 17:01:39 -070057 /** Copy constructor */
58 public DnsSdTxtRecord(DnsSdTxtRecord src) {
59 if (src != null && src.mData != null) {
60 mData = (byte[]) src.mData.clone();
61 }
62 }
Irfan Sheriff26d44522012-04-03 12:13:27 -070063
Irfan Sheriff7d024d32012-03-22 17:01:39 -070064 /**
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;
Irfan Sheriff26d44522012-04-03 12:13:27 -070073
Irfan Sheriff7d024d32012-03-22 17:01:39 -070074 if (value != null) {
75 valBytes = value.getBytes();
76 valLen = valBytes.length;
77 } else {
78 valBytes = null;
79 valLen = 0;
80 }
Irfan Sheriff26d44522012-04-03 12:13:27 -070081
Irfan Sheriff7d024d32012-03-22 17:01:39 -070082 try {
83 keyBytes = key.getBytes("US-ASCII");
84 }
85 catch (java.io.UnsupportedEncodingException e) {
86 throw new IllegalArgumentException("key should be US-ASCII");
87 }
Irfan Sheriff26d44522012-04-03 12:13:27 -070088
Irfan Sheriff7d024d32012-03-22 17:01:39 -070089 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 }
Irfan Sheriff26d44522012-04-03 12:13:27 -070094
Irfan Sheriff7d024d32012-03-22 17:01:39 -070095 if (keyBytes.length + valLen >= 255) {
96 throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
97 }
Irfan Sheriff26d44522012-04-03 12:13:27 -070098
Irfan Sheriff7d024d32012-03-22 17:01:39 -070099 int currentLoc = remove(key);
100 if (currentLoc == -1)
101 currentLoc = keyCount();
Irfan Sheriff26d44522012-04-03 12:13:27 -0700102
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700103 insert(keyBytes, valBytes, currentLoc);
104 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700105
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700106 /**
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 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700116
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700117 /** 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;
Irfan Sheriff26d44522012-04-03 12:13:27 -0700120
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700121 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 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700139
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700140 /** 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 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700148
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700149 /** 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 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700157
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700158 /* Gets the size in bytes */
159 public int size() {
160 return mData.length;
161 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700162
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700163 /* Gets the raw data in bytes */
164 public byte[] getRawData() {
Irfan Sheriff21ba8152012-04-04 16:22:21 -0700165 return (byte[]) mData.clone();
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700166 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700167
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700168 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;
Irfan Sheriff26d44522012-04-03 12:13:27 -0700173
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700174 for (int i = 0; i < index && insertion < mData.length; i++) {
175 insertion += (0xFF & (mData[insertion] + 1));
176 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700177
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700178 avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
179 newLen = avLen + oldBytes.length + 1;
Irfan Sheriff26d44522012-04-03 12:13:27 -0700180
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700181 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 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700192
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700193 /** 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;
Irfan Sheriff26d44522012-04-03 12:13:27 -0700196
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700197 for (int i=0; i < index && avStart < mData.length; i++) {
198 avStart += mData[avStart] + 1;
199 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700200
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700201 if (avStart < mData.length) {
202 int avLen = mData[avStart];
203 int aLen = 0;
Irfan Sheriff26d44522012-04-03 12:13:27 -0700204
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700205 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 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700212
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700213 /**
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;
Irfan Sheriff26d44522012-04-03 12:13:27 -0700221
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700222 for (int i=0; i < index && avStart < mData.length; i++) {
223 avStart += mData[avStart] + 1;
224 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700225
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700226 if (avStart < mData.length) {
227 int avLen = mData[avStart];
228 int aLen = 0;
Irfan Sheriff26d44522012-04-03 12:13:27 -0700229
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700230 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 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700240
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700241 private String getValueAsString(int index) {
242 byte[] value = this.getValue(index);
243 return value != null ? new String(value) : null;
244 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700245
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700246 private byte[] getValue(String forKey) {
247 String s = null;
248 int i;
Irfan Sheriff26d44522012-04-03 12:13:27 -0700249
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700250 for (i = 0; null != (s = this.getKey(i)); i++) {
251 if (0 == forKey.compareToIgnoreCase(s)) {
252 return this.getValue(i);
253 }
254 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700255
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700256 return null;
257 }
Irfan Sheriff26d44522012-04-03 12:13:27 -0700258
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700259 /**
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
Irfan Sheriff21ba8152012-04-04 16:22:21 -0700284 @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
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700302 /** 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 */
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700313 public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR =
Irfan Sheriff7d024d32012-03-22 17:01:39 -0700314 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 };
Irfan Sheriff26d44522012-04-03 12:13:27 -0700325}