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