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