DnsSdTxtRecord.java revision 26d4452a08813cdbb7280c475fe5527cdc9673a3
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
23package	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
31public 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