Source code: com/strangeberry/rendezvous/ServiceInfo.java
1 // Copyright (C) 2002 Strangeberry Inc.
2 // @(#)ServiceInfo.java, 1.21, 11/29/2002
3 //
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 //
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 //
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18 package com.strangeberry.rendezvous;
19
20 import java.io.*;
21 import java.net.*;
22 import java.util.*;
23
24 /**
25 * Rendezvous service information.
26 *
27 * @author Arthur van Hoff
28 * @version 1.21, 11/29/2002
29 */
30 public class ServiceInfo extends Rendezvous.Listener
31 {
32 public final static byte[] NO_VALUE = new byte[0];
33
34 String type;
35 String name;
36 String server;
37 int port;
38 int weight;
39 int priority;
40 byte text[];
41 Hashtable props;
42 InetAddress addr;
43
44 /**
45 * Construct a service description for registrating with Rendezvous.
46 * @param type fully qualified service type name
47 * @param name fully qualified service name
48 * @param addr the address to which the service is bound
49 * @param port the local port on which the service runs
50 * @param weight weight of the service
51 * @param priority priority of the service
52 * @param text string describing the service
53 */
54 public ServiceInfo(String type, String name, InetAddress addr, int port, int weight, int priority, String text)
55 {
56 this(type, name, addr, port, weight, priority, (byte[])null);
57 try {
58 ByteArrayOutputStream out = new ByteArrayOutputStream(text.length());
59 writeUTF(out, text);
60 this.text = out.toByteArray();
61 } catch (IOException e) {
62 throw new RuntimeException("unexpected exception: " + e);
63 }
64 }
65
66 /**
67 * Construct a service description for registrating with Rendezvous. The properties hashtable must
68 * map property names to either Strings or byte arrays describing the property values.
69 * @param type fully qualified service type name
70 * @param name fully qualified service name
71 * @param addr the address to which the service is bound
72 * @param port the local port on which the service runs
73 * @param weight weight of the service
74 * @param priority priority of the service
75 * @param props properties describing the service
76 */
77 public ServiceInfo(String type, String name, InetAddress addr, int port, int weight, int priority, Hashtable props)
78 {
79 this(type, name, addr, port, weight, priority, (byte [])null);
80 try {
81 ByteArrayOutputStream out = new ByteArrayOutputStream(256);
82 for (Enumeration e = props.keys() ; e.hasMoreElements() ;) {
83 String key = (String)e.nextElement();
84 Object val = props.get(key);
85 ByteArrayOutputStream out2 = new ByteArrayOutputStream(100);
86 writeUTF(out2, key);
87 if (val instanceof String) {
88 out2.write('=');
89 writeUTF(out2, (String)val);
90 } else if (val instanceof byte[]) {
91 out2.write('=');
92 byte[] bval = (byte[])val;
93 out2.write(bval, 0, bval.length);
94 } else if (val != NO_VALUE) {
95 throw new IllegalArgumentException("invalid property value: " + val);
96 }
97 byte data[] = out2.toByteArray();
98 out.write(data.length);
99 out.write(data, 0, data.length);
100 }
101 this.text = out.toByteArray();
102 } catch (IOException e) {
103 throw new RuntimeException("unexpected exception: " + e);
104 }
105 }
106
107 /**
108 * Construct a service description for registrating with Rendezvous.
109 * @param type fully qualified service type name
110 * @param name fully qualified service name
111 * @param addr the address to which the service is bound
112 * @param port the local port on which the service runs
113 * @param weight weight of the service
114 * @param priority priority of the service
115 * @param text bytes describing the service
116 */
117 public ServiceInfo(String type, String name, InetAddress addr, int port, int weight, int priority, byte text[])
118 {
119 this.type = type;
120 this.name = name;
121 this.port = port;
122 this.weight = weight;
123 this.priority = priority;
124 this.server = name;
125 this.text = text;
126 this.addr = addr;
127 }
128
129 /**
130 * Construct a serive record during service discovery.
131 */
132 ServiceInfo(String type, String name)
133 {
134 this.type = type;
135 this.name = name;
136 }
137
138 /**
139 * Fully qualified service type name, such as <code>_http._tcp.local.</code>.
140 */
141 public String getType()
142 {
143 return type;
144 }
145 /**
146 * Service name, such as <code>foobar</code>.
147 */
148 public String getName()
149 {
150 if ((type != null) && name.endsWith("." + type)) {
151 return name.substring(0, name.length() - (type.length() + 1));
152 }
153 return name;
154 }
155 /**
156 * Get the host address of the service (ie X.X.X.X).
157 */
158 public String getAddress()
159 {
160 byte data[] = addr.getAddress();
161 return (data[0] & 0xFF) + "." + (data[1] & 0xFF) + "." + (data[2] & 0xFF) + "." + (data[3] & 0xFF);
162 }
163 /**
164 * Get the port for the service.
165 */
166 public int getPort()
167 {
168 return port;
169 }
170 /**
171 * Get the priority of the service.
172 */
173 public int getPriority()
174 {
175 return priority;
176 }
177 /**
178 * Get the weight of the service.
179 */
180 public int getWeight()
181 {
182 return weight;
183 }
184
185 /**
186 * Get the text for the serivce as raw bytes.
187 */
188 public byte[] getTextBytes()
189 {
190 return text;
191 }
192
193 /**
194 * Get the text for the service. This will interpret the text bytes
195 * as a UTF8 encoded string. Will return null if the bytes are not
196 * a valid UTF8 encoded string.
197 */
198 public String getTextString()
199 {
200 if ((text == null) || (text.length == 0) || ((text.length == 1) && (text[0] == 0))) {
201 return null;
202 }
203 return readUTF(text, 0, text.length);
204 }
205
206 /**
207 * Get a property of the service. This involves decoding the
208 * text bytes into a property list. Returns null if the property
209 * is not found or the text data could not be decoded correctly.
210 */
211 public synchronized byte[] getPropertyBytes(String name)
212 {
213 return (byte [])getProperties().get(name);
214 }
215
216 /**
217 * Get a property of the service. This involves decoding the
218 * text bytes into a property list. Returns null if the property
219 * is not found, the text data could not be decoded correctly, or
220 * the resulting bytes are not a valid UTF8 string.
221 */
222 public synchronized String getPropertyString(String name)
223 {
224 byte data[] = (byte [])getProperties().get(name);
225 if (data == null) {
226 return null;
227 }
228 if (data == NO_VALUE) {
229 return "true";
230 }
231 return readUTF(data, 0, data.length);
232 }
233
234 /**
235 * Enumeration of the property names.
236 */
237 public Enumeration getPropertyNames()
238 {
239 Hashtable props = getProperties();
240 return (props != null) ? props.keys() : new Vector().elements();
241 }
242
243 /**
244 * Write a UTF string with a length to a stream.
245 */
246 void writeUTF(OutputStream out, String str) throws IOException
247 {
248 for (int i = 0, len = str.length() ; i < len ; i++) {
249 int c = str.charAt(i);
250 if ((c >= 0x0001) && (c <= 0x007F)) {
251 out.write(c);
252 } else if (c > 0x07FF) {
253 out.write(0xE0 | ((c >> 12) & 0x0F));
254 out.write(0x80 | ((c >> 6) & 0x3F));
255 out.write(0x80 | ((c >> 0) & 0x3F));
256 } else {
257 out.write(0xC0 | ((c >> 6) & 0x1F));
258 out.write(0x80 | ((c >> 0) & 0x3F));
259 }
260 }
261 }
262
263 /**
264 * Read data bytes as a UTF stream.
265 */
266 String readUTF(byte data[], int off, int len)
267 {
268 StringBuffer buf = new StringBuffer();
269 for (int end = off + len ; off < end ; ) {
270 int ch = data[off++] & 0xFF;
271 switch (ch >> 4) {
272 case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
273 // 0xxxxxxx
274 break;
275 case 12: case 13:
276 if (off >= len) {
277 return null;
278 }
279 // 110x xxxx 10xx xxxx
280 ch = ((ch & 0x1F) << 6) | (data[off++] & 0x3F);
281 break;
282 case 14:
283 if (off + 2 >= len) {
284 return null;
285 }
286 // 1110 xxxx 10xx xxxx 10xx xxxx
287 ch = ((ch & 0x0f) << 12) | ((data[off++] & 0x3F) << 6) | (data[off++] & 0x3F);
288 break;
289 default:
290 if (off + 1 >= len) {
291 return null;
292 }
293 // 10xx xxxx, 1111 xxxx
294 ch = ((ch & 0x3F) << 4) | (data[off++] & 0x0f);
295 break;
296 }
297 buf.append((char)ch);
298 }
299 return buf.toString();
300 }
301
302 synchronized Hashtable getProperties()
303 {
304 if ((props == null) && (text != null)) {
305 Hashtable props = new Hashtable();
306 int off = 0;
307 while (off < text.length) {
308 // length of the next key value pair
309 int len = text[off++] & 0xFF;
310 if ((len == 0) || (off + len > text.length)) {
311 props.clear();
312 break;
313 }
314 // look for the '='
315 int i = 0;
316 for (; (i < len) && (text[off + i] != '=') ; i++);
317
318 // get the property name
319 String name = readUTF(text, off, i);
320 if (name == null) {
321 props.clear();
322 break;
323 }
324 if (i == len) {
325 props.put(name, NO_VALUE);
326 } else {
327 byte value[] = new byte[len - i];
328 System.arraycopy(text, off + i, value, 0, len - i);
329 props.put(name, value);
330 off += len;
331 }
332 }
333 }
334 return props;
335 }
336
337 /**
338 * Get the ip address of the service.
339 */
340 int getIPAddress()
341 {
342 byte data[] = addr.getAddress();
343 return ((data[0] & 0xFF) << 24) | ((data[1] & 0xFF) << 16) | ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
344 }
345
346 /**
347 * Rendezvous callback to update a DNS record.
348 */
349 void updateRecord(Rendezvous rendezvous, long now, DNSRecord rec)
350 {
351 if ((rec != null) && !rec.isExpired(now)) {
352 switch (rec.type) {
353 case TYPE_A:
354 if (rec.name.equals(server)) {
355 addr = ((DNSRecord.Address)rec).getAddress();
356 }
357 break;
358 case TYPE_SRV:
359 if (rec.name.equals(name)) {
360 DNSRecord.Service srv = (DNSRecord.Service)rec;
361 server = srv.server;
362 port = srv.port;
363 weight = srv.weight;
364 priority = srv.priority;
365 addr = null;
366 updateRecord(rendezvous, now, (DNSRecord)rendezvous.cache.get(server, TYPE_A, CLASS_IN));
367 }
368 break;
369 case TYPE_TXT:
370 if (rec.name.equals(name)) {
371 DNSRecord.Text txt = (DNSRecord.Text)rec;
372 text = txt.text;
373 }
374 break;
375 }
376 }
377 }
378
379 /**
380 * Update the server information from the cache, send out
381 * repeated DNS queries for updated information.
382 */
383 boolean request(Rendezvous rendezvous, long timeout)
384 {
385 long now = System.currentTimeMillis();
386 int delay = 200;
387 long next = now + delay;
388 long last = now + timeout;
389 try {
390 rendezvous.addListener(this, new DNSQuestion(name, TYPE_ANY, CLASS_IN));
391 while (server == null || addr == null || text == null) {
392 // check if timeout was reached
393 if (last <= now) {
394 return false;
395 }
396 // check if we need to send out another request
397 if (next <= now) {
398 DNSOutgoing out = new DNSOutgoing(FLAGS_QR_QUERY);
399 out.addQuestion(new DNSQuestion(name, TYPE_SRV, CLASS_IN));
400 out.addQuestion(new DNSQuestion(name, TYPE_TXT, CLASS_IN));
401 if (server != null) {
402 out.addQuestion(new DNSQuestion(server, TYPE_A, CLASS_IN));
403 }
404 out.addAnswer((DNSRecord)rendezvous.cache.get(name, TYPE_SRV, CLASS_IN), now);
405 out.addAnswer((DNSRecord)rendezvous.cache.get(name, TYPE_TXT, CLASS_IN), now);
406 if (server != null) {
407 out.addAnswer((DNSRecord)rendezvous.cache.get(server, TYPE_A, CLASS_IN), now);
408 }
409 rendezvous.send(out);
410
411 next = now + delay;
412 delay = delay * 2;
413 }
414 // wait for an update or a timeout
415 synchronized (rendezvous) {
416 rendezvous.wait(Math.min(next, last) - now);
417 }
418 now = System.currentTimeMillis();
419 }
420 return true;
421 } catch (IOException e) {
422 return false;
423 } catch (InterruptedException e) {
424 return false;
425 } finally {
426 rendezvous.removeListener(this);
427 }
428 }
429
430 public int hashCode()
431 {
432 return name.hashCode();
433 }
434
435 public boolean equals(Object obj)
436 {
437 return (obj instanceof ServiceInfo) && name.equals(((ServiceInfo)obj).name);
438 }
439
440 public String toString()
441 {
442 StringBuffer buf = new StringBuffer();
443 buf.append("service[");
444 buf.append(name);
445 buf.append(',');
446 buf.append(getAddress());
447 buf.append(':');
448 buf.append(port);
449 buf.append(',');
450 buf.append((text.length < 20) ? new String(text) : new String(text, 0, 17) + "...");
451 buf.append(']');
452 return buf.toString();
453 }
454 }