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