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