xref: /titanic_44/usr/src/lib/libdns_sd/java/com/apple/dnssd/TXTRecord.java (revision 4b22b9337f359bfd063322244f5336cc7c6ffcfa)
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