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 23 package com.apple.dnssd; 24 25 26 /** 27 Object used to construct and parse DNS-SD format TXT records. 28 For more info see <a href="http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt">DNS-Based Service Discovery</a>, section 6. 29 */ 30 31 public class TXTRecord 32 { 33 /* 34 DNS-SD specifies that a TXT record corresponding to an SRV record consist of 35 a packed array of bytes, each preceded by a length byte. Each string 36 is an attribute-value pair. 37 38 The TXTRecord object stores the entire TXT data as a single byte array, traversing it 39 as need be to implement its various methods. 40 */ 41 42 static final protected byte kAttrSep = '='; 43 44 protected byte[] fBytes; 45 46 /** Constructs a new, empty TXT record. */ 47 public TXTRecord() 48 { fBytes = new byte[0]; } 49 50 /** Constructs a new TXT record from a byte array in the standard format. */ 51 public TXTRecord( byte[] initBytes) 52 { fBytes = (byte[]) initBytes.clone(); } 53 54 /** Set a key/value pair in the TXT record. Setting an existing key will replace its value.<P> 55 @param key 56 The key name. Must be ASCII, with no '=' characters. 57 <P> 58 @param value 59 Value to be encoded into bytes using the default platform character set. 60 */ 61 public void set( String key, String value) 62 { 63 byte[] valBytes = (value != null) ? value.getBytes() : null; 64 this.set( key, valBytes); 65 } 66 67 /** Set a key/value pair in the TXT record. Setting an existing key will replace its value.<P> 68 @param key 69 The key name. Must be ASCII, with no '=' characters. 70 <P> 71 @param value 72 Binary representation of the value. 73 */ 74 public void set( String key, byte[] value) 75 { 76 byte[] keyBytes; 77 int valLen = (value != null) ? value.length : 0; 78 79 try { 80 keyBytes = key.getBytes( "US-ASCII"); 81 } 82 catch ( java.io.UnsupportedEncodingException uee) { 83 throw new IllegalArgumentException(); 84 } 85 86 for ( int i=0; i < keyBytes.length; i++) 87 if ( keyBytes[i] == '=') 88 throw new IllegalArgumentException(); 89 90 if ( keyBytes.length + valLen >= 255) 91 throw new ArrayIndexOutOfBoundsException(); 92 93 int prevLoc = this.remove( key); 94 if ( prevLoc == -1) 95 prevLoc = this.size(); 96 97 this.insert( keyBytes, value, prevLoc); 98 } 99 100 protected void insert( byte[] keyBytes, byte[] value, int index) 101 // Insert a key-value pair at index 102 { 103 byte[] oldBytes = fBytes; 104 int valLen = (value != null) ? value.length : 0; 105 int insertion = 0; 106 int newLen, avLen; 107 108 // locate the insertion point 109 for ( int i=0; i < index && insertion < fBytes.length; i++) 110 insertion += (0xFF & (fBytes[ insertion] + 1)); 111 112 avLen = keyBytes.length + valLen + (value != null ? 1 : 0); 113 newLen = avLen + oldBytes.length + 1; 114 115 fBytes = new byte[ newLen]; 116 System.arraycopy( oldBytes, 0, fBytes, 0, insertion); 117 int secondHalfLen = oldBytes.length - insertion; 118 System.arraycopy( oldBytes, insertion, fBytes, newLen - secondHalfLen, secondHalfLen); 119 fBytes[ insertion] = ( byte) avLen; 120 System.arraycopy( keyBytes, 0, fBytes, insertion + 1, keyBytes.length); 121 if ( value != null) 122 { 123 fBytes[ insertion + 1 + keyBytes.length] = kAttrSep; 124 System.arraycopy( value, 0, fBytes, insertion + keyBytes.length + 2, valLen); 125 } 126 } 127 128 /** Remove a key/value pair from the TXT record. Returns index it was at, or -1 if not found. */ 129 public int remove( String key) 130 { 131 int avStart = 0; 132 133 for ( int i=0; avStart < fBytes.length; i++) 134 { 135 int avLen = fBytes[ avStart]; 136 if ( key.length() <= avLen && 137 ( key.length() == avLen || fBytes[ avStart + key.length() + 1] == kAttrSep)) 138 { 139 String s = new String( fBytes, avStart + 1, key.length()); 140 if ( 0 == key.compareToIgnoreCase( s)) 141 { 142 byte[] oldBytes = fBytes; 143 fBytes = new byte[ oldBytes.length - avLen - 1]; 144 System.arraycopy( oldBytes, 0, fBytes, 0, avStart); 145 System.arraycopy( oldBytes, avStart + avLen + 1, fBytes, avStart, oldBytes.length - avStart - avLen - 1); 146 return i; 147 } 148 } 149 avStart += (0xFF & (avLen + 1)); 150 } 151 return -1; 152 } 153 154 /** Return the number of keys in the TXT record. */ 155 public int size() 156 { 157 int i, avStart; 158 159 for ( i=0, avStart=0; avStart < fBytes.length; i++) 160 avStart += (0xFF & (fBytes[ avStart] + 1)); 161 return i; 162 } 163 164 /** Return true if key is present in the TXT record, false if not. */ 165 public boolean contains( String key) 166 { 167 String s = null; 168 169 for ( int i=0; null != ( s = this.getKey( i)); i++) 170 if ( 0 == key.compareToIgnoreCase( s)) 171 return true; 172 return false; 173 } 174 175 /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */ 176 public String getKey( int index) 177 { 178 int avStart = 0; 179 180 for ( int i=0; i < index && avStart < fBytes.length; i++) 181 avStart += fBytes[ avStart] + 1; 182 183 if ( avStart < fBytes.length) 184 { 185 int avLen = fBytes[ avStart]; 186 int aLen = 0; 187 188 for ( aLen=0; aLen < avLen; aLen++) 189 if ( fBytes[ avStart + aLen + 1] == kAttrSep) 190 break; 191 return new String( fBytes, avStart + 1, aLen); 192 } 193 return null; 194 } 195 196 /** 197 Look up a key in the TXT record by zero-based index and return its value. <P> 198 Returns null if index exceeds the total number of keys. 199 Returns null if the key is present with no value. 200 */ 201 public byte[] getValue( int index) 202 { 203 int avStart = 0; 204 byte[] value = null; 205 206 for ( int i=0; i < index && avStart < fBytes.length; i++) 207 avStart += fBytes[ avStart] + 1; 208 209 if ( avStart < fBytes.length) 210 { 211 int avLen = fBytes[ avStart]; 212 int aLen = 0; 213 214 for ( aLen=0; aLen < avLen; aLen++) 215 { 216 if ( fBytes[ avStart + aLen + 1] == kAttrSep) 217 { 218 value = new byte[ avLen - aLen - 1]; 219 System.arraycopy( fBytes, avStart + aLen + 2, value, 0, avLen - aLen - 1); 220 break; 221 } 222 } 223 } 224 return value; 225 } 226 227 /** Converts the result of getValue() to a string in the platform default character set. */ 228 public String getValueAsString( int index) 229 { 230 byte[] value = this.getValue( index); 231 return value != null ? new String( value) : null; 232 } 233 234 /** Get the value associated with a key. Will be null if the key is not defined. 235 Array will have length 0 if the key is defined with an = but no value.<P> 236 237 @param forKey 238 The left-hand side of the key-value pair. 239 <P> 240 @return The binary representation of the value. 241 */ 242 public byte[] getValue( String forKey) 243 { 244 String s = null; 245 int i; 246 247 for ( i=0; null != ( s = this.getKey( i)); i++) 248 if ( 0 == forKey.compareToIgnoreCase( s)) 249 return this.getValue( i); 250 return null; 251 } 252 253 /** Converts the result of getValue() to a string in the platform default character set.<P> 254 255 @param forKey 256 The left-hand side of the key-value pair. 257 <P> 258 @return The value represented in the default platform character set. 259 */ 260 public String getValueAsString( String forKey) 261 { 262 byte[] val = this.getValue( forKey); 263 return val != null ? new String( val) : null; 264 } 265 266 /** Return the contents of the TXT record as raw bytes. */ 267 public byte[] getRawBytes() { return (byte[]) fBytes.clone(); } 268 269 /** Return a string representation of the object. */ 270 public String toString() 271 { 272 String a, result = null; 273 274 for ( int i=0; null != ( a = this.getKey( i)); i++) 275 { 276 String av = String.valueOf( i) + "={" + a; 277 String val = this.getValueAsString( i); 278 if ( val != null) 279 av += "=" + val + "}"; 280 else 281 av += "}"; 282 if ( result == null) 283 result = av; 284 else 285 result = result + ", " + av; 286 } 287 return result != null ? result : ""; 288 } 289 } 290 291