xref: /illumos-gate/usr/src/lib/libslp/javalib/com/sun/slp/Transact.java (revision 55fea89dcaa64928bed4327112404dcb3e07b79f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * ident	"%Z%%M%	%I%	%E% SMI"
24  *
25  * Copyright 1999-2002 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  *
28  */
29 
30 //  Transact.java:    Low level details of performing an SLP
31 //                    network transaction.
32 
33 package com.sun.slp;
34 
35 import java.util.*;
36 import java.net.*;
37 import java.io.*;
38 
39 /**
40  * Transact performs the low level details for transacting an SLP network
41  * query. Note that, in the future, this class may spin separate threads
42  * for DA requests as well.
43  */
44 
45 class Transact extends Object implements Runnable {
46 
47     // Cache of open TCP sockets.
48 
49     private static final Hashtable TCPSocketCache = new Hashtable();
50 
51     // SLP config object.
52 
53     protected static SLPConfig config = null;
54 
55     // Message to send.
56 
57     protected SrvLocMsg msgOut = null;
58 
59     // Vector of return values.
60 
61     protected Vector returns = null;
62 
63     // Timeout for multicast convergence. Varies if it's DA discovery or
64     //  request multicast.
65 
66     protected int[] MSTimeouts;
67 
68     // Maximum results desired for multicast.
69 
70     protected int maxResults = 0;
71 
72     // Exception to throw.
73 
74     protected ServiceLocationException exErr = null;
75 
76     // Multicast address to use.
77 
78     protected InetAddress address = null;
79 
80     // If this is true, continue multicast after the first set of stuff
81     //  is found. Exit when three tries have happened without finding
82     //  anything.
83 
84     boolean continueAfterFound = false;
85 
86     /**
87      * Perform a query to the SLP network. The multicast query is performed
88      * in a separate thread for performance reasons. DAs having the
89      * same scope set are queried until one answers. These DAs form
90      * an equivalence class.
91      *
92      * @param daEquivClasses Vector of DATable.DARecord objects in the
93      *			  same equivalence clase w.r.t. scopes.
94      * @param uniMsg A unicast message to send.
95      * @param multiMsg A multicast message to send.
96      * @param address Multicast address to use.
97      * @return Vector of SrvLocMsg objects with results.
98      */
99 
100     static Vector
transactUA(Vector daEquivClasses, SrvLocMsg uniMsg, SrvLocMsg multiMsg, InetAddress address)101 	transactUA(Vector daEquivClasses,
102 		   SrvLocMsg uniMsg,
103 		   SrvLocMsg multiMsg,
104 		   InetAddress address)
105 	throws ServiceLocationException {
106 
107 	// If we need to multicast, then start the multicast thread.
108 
109 	Vector ret = new Vector();
110 	Thread multiThread = null;
111 	Transact tracon = null;
112 
113 	if (multiMsg != null) {
114 
115 	    // Create a new Transact multicast thread.
116 
117             // The final argument to the constructor of Transact determines
118             // whether to return after the first result or to continue to
119             // gather more than one result.  The value to this field
120             // continueAfterFound MUST be set to 'true' or else multicast
121             // based discovery will find the first result, not all results,
122             // as it should.
123 	    tracon =
124 		new Transact(multiMsg,
125 			     ret,
126 			     config.getMulticastTimeouts(),
127 			     config.getMaximumResults(),
128 			     address,
129 			     true);  // continueAfterFound
130 
131 	    multiThread = new Thread(tracon);
132 
133 	    // Run it.
134 
135 	    multiThread.start();
136 
137 	}
138 
139 	// Go through the msgTable doing all the DAs.
140 
141 	ServiceLocationException exx = null;
142 
143 	if (daEquivClasses != null) {
144 	    exx =
145 		transactUnicastMsg(daEquivClasses,
146 				   uniMsg,
147 				   ret,
148 				   config.getMaximumResults());
149 
150 	}
151 
152 	// Wait until the TransactConverge thread is done, if necessary.
153 
154 	if (multiThread != null) {
155 
156 	    try {
157 		multiThread.join();
158 
159 	    } catch (InterruptedException ex) {
160 
161 	    }
162 
163 	}
164 
165 	// If there was a problem in either the multicast thread or in
166 	//  the unicast call, throw an exception, but *only* if no
167 	//  results came back.
168 
169 	if (ret.size() <= 0) {
170 
171 	    if (exx != null) {
172 		short err = exx.getErrorCode();
173 
174 		if (err != ServiceLocationException.VERSION_NOT_SUPPORTED &&
175 		    err != ServiceLocationException.INTERNAL_ERROR &&
176 		    err != ServiceLocationException.OPTION_NOT_SUPPORTED &&
177 		    err != ServiceLocationException.REQUEST_NOT_SUPPORTED) {
178 		    throw exx;
179 
180 		}
181 
182 	    }
183 
184 	    if (tracon != null && tracon.exErr != null) {
185 		short err = tracon.exErr.getErrorCode();
186 
187 		if (err != ServiceLocationException.VERSION_NOT_SUPPORTED &&
188 		    err != ServiceLocationException.INTERNAL_ERROR &&
189 		    err != ServiceLocationException.OPTION_NOT_SUPPORTED &&
190 		    err != ServiceLocationException.REQUEST_NOT_SUPPORTED) {
191 		    throw tracon.exErr;
192 
193 		}
194 	    }
195 	}
196 
197 
198 	// Return the result to the client.
199 
200 	return ret;
201 
202     }
203 
204     /**
205      * Transact a message with DAs. Put the returned SrvLocMsg
206      * object into the Vector ret.
207      *
208      * @param daEquivClasses Vector of DATable.DARecord objects in the
209      *			   same equivalence clase w.r.t. scopes.
210      * @param msg SrvLocMsg Message to send.
211      * @param ret Vector for returns.
212      * @param maxResults Maximum results expected.
213      * @return A ServiceLocationException object if an exception occured.
214      * @exception ServiceLocationException
215      *            If results cannot be obtained in the timeout interval
216      *            specified in the 'config.' or
217      *            If networking resources cannot be obtained or used
218      *            effectively.
219      */
220     static ServiceLocationException
transactUnicastMsg(Vector daEquivClasses, SrvLocMsg msg, Vector ret, int maxResults)221 	transactUnicastMsg(Vector daEquivClasses,
222 			   SrvLocMsg msg,
223 			   Vector ret,
224 			   int maxResults) {
225 
226 	// Get the config object if we need it.
227 
228 	if (config == null) {
229 	    config = SLPConfig.getSLPConfig();
230 
231 	}
232 
233 	DatagramSocket ds = null;
234 	int i, n = daEquivClasses.size();
235 	ServiceLocationException exx = null;
236 	InetAddress addr = null;
237 	int numReplies = 0;
238 	DATable daTable = DATable.getDATable();
239 
240 	try {
241 
242 	    // Go through the DA address equivalence classes we need
243 	    //  to query.
244 
245 	    for (i = 0; i < n && numReplies < maxResults; i++) {
246 
247 		DATable.DARecord rec =
248 		    (DATable.DARecord)daEquivClasses.elementAt(i);
249 		Vector daAddresses = (Vector)rec.daAddresses.clone();
250 
251 		// Get a new outgoing socket.
252 
253 		if (ds == null) {
254 		    ds = new DatagramSocket();
255 
256 		}
257 
258 		// Go through the DA addresses until we get a reply from one.
259 
260 		Enumeration en = daAddresses.elements();
261 		SrvLocHeader mhdr = msg.getHeader();
262 
263 		while (en.hasMoreElements()) {
264 
265 		    try {
266 
267 			addr = (InetAddress)en.nextElement();
268 
269 			if (config.traceDATraffic()) {
270 			    config.writeLog("sending_da_trace",
271 					    new Object[] {
272 				Integer.toHexString(mhdr.xid),
273 				    addr});
274 
275 			}
276 
277 			// Get the reply message if any.
278 
279 			SrvLocMsg rply = transactDatagramMsg(ds, addr, msg);
280 
281 			if (!filterRply(msg, rply, addr)) {
282 			    continue;
283 
284 			}
285 
286 			SrvLocHeader rhdr = rply.getHeader();
287 
288 			if (config.traceDATraffic()) {
289 			    config.writeLog("reply_da_trace",
290 					    new Object[] {
291 				Integer.toHexString(rhdr.xid),
292 				    addr});
293 
294 			}
295 
296 			// If overflow, try TCP.
297 
298 			if (rhdr.overflow) {
299 			    if (config.traceDATraffic()) {
300 				config.writeLog("tcp_send_da_trace",
301 						new Object[] {
302 				    Integer.toHexString(mhdr.xid),
303 					addr});
304 
305 			    }
306 
307 			    rply = transactTCPMsg(addr, msg, false);
308 
309 			    if (config.traceDATraffic()) {
310 				config.writeLog("tcp_reply_da_trace",
311 						new Object[] {
312 				    (msg == null ? "<null>":
313 				     Integer.toHexString(mhdr.xid)),
314 					addr});
315 
316 			    }
317 
318 			    if (rply == null) {
319 				continue;
320 
321 			    }
322 
323 			}
324 
325 			// Increment number of replies we received.
326 
327 			SrvLocHeader hdr = rply.getHeader();
328 
329 			numReplies += hdr.iNumReplies;
330 
331 			// Add to return vector.
332 
333 			ret.addElement(rply);
334 
335 			// Break out of the loop, since we only need one in
336 			//  this equivalence class.
337 
338 			break;
339 
340 		    } catch (ServiceLocationException ex) {
341 
342 			config.writeLog("da_exception_trace",
343 					new Object[] {
344 			    new Short(ex.getErrorCode()),
345 				addr,
346 				ex.getMessage()});
347 
348 			// In case we are querying more than one DA, we
349 			// save th exception, returning it to the caller to
350 			// decide if it should be thrown. We ignore DA_BUSY,
351 			// though, since the DA may free up later.
352 
353 			short errCode = ex.getErrorCode();
354 
355 			if (errCode != ServiceLocationException.DA_BUSY) {
356 			    exx = ex;
357 
358 			}
359 
360 			// If the error code is NETWORK_TIMED_OUT, then remove
361 			//  this DA from the DA table. If it's just down
362 			//  temporarily, we'll get it next time we go to
363 			//  the server to get the DA addresses.
364 
365 			if (errCode ==
366 			    ServiceLocationException.NETWORK_TIMED_OUT) {
367 
368 			    if (config.traceDATraffic()) {
369 				config.writeLog("da_drop",
370 						new Object[] {
371 				    addr, rec.scopes});
372 
373 			    }
374 
375 			    daTable.removeDA(addr, rec.scopes);
376 
377 			}
378 		    }
379 		}
380 	    }
381 
382 	} catch (SocketException ex) {
383 	    exx =
384 		new ServiceLocationException(
385 				ServiceLocationException.NETWORK_ERROR,
386 				"socket_creation_failure",
387 				new Object[] {addr, ex.getMessage()});
388 
389 	} finally {
390 
391 	    // Clean up socket.
392 
393 	    if (ds != null) {
394 		ds.close();
395 	    }
396 	}
397 
398 	return exx;
399     }
400 
401     /**
402      * Transact a message via. UDP. Try a maximum of three times if
403      * a timeout.
404      *
405      * @param ds The datagram socket to use.
406      * @param addr The DA to contact.
407      * @param msg The message to send.
408      * @return The SrvLocMsg returned or null if none.
409      * @exception ServiceLocationException Due to errors in parsing message.
410      */
411 
412     static private SrvLocMsg
transactDatagramMsg(DatagramSocket ds, InetAddress addr, SrvLocMsg msg)413 	transactDatagramMsg(DatagramSocket ds, InetAddress addr, SrvLocMsg msg)
414 	throws ServiceLocationException {
415 
416 	SrvLocMsg rply = null;
417 	byte[] outbuf = getBytes(msg, false, false);
418 	byte[] inbuf = new byte[Defaults.iReadMaxMTU];
419 
420 	// Construct the datagram packet to send.
421 
422 	DatagramPacket dpReply =
423 	    new DatagramPacket(inbuf, inbuf.length);
424 	DatagramPacket dpRequest =
425 	    new DatagramPacket(outbuf, outbuf.length, addr, Defaults.iSLPPort);
426 	int[] timeouts = config.getDatagramTimeouts();
427 
428 	// Resend for number of timeouts in timeout interval.
429 
430 	int i;
431 
432 	for (i = 0; i < timeouts.length; i++) {
433 
434 	    // Catch timeout and IO errors.
435 
436 	    try {
437 
438 		ds.setSoTimeout(timeouts[i]);
439 		ds.send(dpRequest);
440 		ds.receive(dpReply);
441 
442 		// Process result into a reply object.
443 
444 		DataInputStream dis =
445 		    new DataInputStream(
446 			new ByteArrayInputStream(dpReply.getData()));
447 
448 		rply = internalize(dis, addr);
449 		break;
450 
451 	    } catch (InterruptedIOException ex) {
452 
453 		// Did not get it on the first timeout, try again.
454 
455 		if (config.traceDrop()|| config.traceDATraffic()) {
456 		    config.writeLog("udp_timeout",
457 				    new Object[] {addr});
458 
459 		}
460 
461 		continue;
462 
463 	    } catch (IOException ex) {
464 		Object[] message = {addr, ex.getMessage()};
465 
466 		if (config.traceDrop() || config.traceDATraffic()) {
467 		    config.writeLog("datagram_io_error",
468 				    message);
469 
470 		}
471 
472 		throw
473 		    new ServiceLocationException(
474 				ServiceLocationException.NETWORK_ERROR,
475 				"datagram_io_error",
476 				message);
477 
478 	    }
479 	}
480 
481 	// If nothing, then we've timed out. DAs with no matching
482 	//  info should at least return a reply.
483 
484 	if (rply == null) {
485 	    throw
486 		new ServiceLocationException(
487 				ServiceLocationException.NETWORK_TIMED_OUT,
488 				"udp_timeout",
489 				new Object[] {addr});
490 
491 	}
492 
493 	return rply;
494     }
495 
496     /**
497      * Transact a message using TCP, since the reply was too big.
498      * @parameter addr Address of the DA to contact.
499      * @parameter msg  The message object to use.
500      * @parameter cacheIt Cache socket, if new.
501      * @return  The SrvLocMsg returned if any.
502      * @exception ServiceLocationException
503      *            If results cannot be obtained in the timeout interval
504      *            specified in the 'config.'
505      *            If networking resources cannot be obtained or used
506      *            effectively.
507      */
508 
509     static SrvLocMsg
transactTCPMsg(InetAddress addr, SrvLocMsg msg, boolean cacheIt)510 	transactTCPMsg(InetAddress addr, SrvLocMsg msg, boolean cacheIt)
511 	throws ServiceLocationException {
512 
513 	// Get the config object if we need it.
514 
515 	if (config == null) {
516 	    config = SLPConfig.getSLPConfig();
517 
518 	}
519 
520 	SrvLocMsg rply = null;
521 
522 	try {
523 
524 	    // Transact the message, taking care of socket caching.
525 
526 	    rply = transactMsg(addr, msg, cacheIt, true);
527 
528 	} catch (InterruptedIOException ex) {
529 	    Object[] message = {addr};
530 
531 	    if (config.traceDrop()|| config.traceDATraffic()) {
532 		config.writeLog("tcp_timeout",
533 				message);
534 
535 	    }
536 
537 	    throw
538 		new ServiceLocationException(
539 				ServiceLocationException.NETWORK_TIMED_OUT,
540 				"tcp_timeout",
541 				message);
542 
543 	} catch (IOException ex) {
544 	    Object[] message = {addr, ex.getMessage()};
545 
546 	    if (config.traceDrop() || config.traceDATraffic()) {
547 		config.writeLog("tcp_io_error",
548 				message);
549 
550 	    }
551 
552 	    throw
553 		new ServiceLocationException(
554 				ServiceLocationException.NETWORK_ERROR,
555 				"tcp_io_error",
556 				message);
557 
558 	}
559 
560 	// Filter reply for nulls, invalid xid.
561 
562 	if (!filterRply(msg, rply, addr)) {
563 	    return null;
564 
565 	}
566 
567 	return rply;
568     }
569 
570     // Uncache a socket.
571 
uncacheSocket(InetAddress addr, Socket s)572     static private void uncacheSocket(InetAddress addr, Socket s) {
573 
574 	try {
575 
576 	    s.close();
577 
578 	} catch (IOException ex) {
579 
580 	}
581 
582 	TCPSocketCache.remove(addr);
583 
584     }
585 
586     // Get a (possibly cached) TCP socket, cache it if cache is on.
587 
getTCPSocket(InetAddress addr, boolean cacheIt)588     static private Socket getTCPSocket(InetAddress addr, boolean cacheIt)
589 	throws IOException {
590 
591 	Socket s = null;
592 
593 	// We use the cached socket if we've got it.
594 
595 	s = (Socket)TCPSocketCache.get(addr);
596 
597 	if (s == null) {
598 	    s = new Socket(addr, Defaults.iSLPPort);
599 
600 	    // Set it so the socket will block for fixed timeout.
601 
602 	    s.setSoTimeout(config.getTCPTimeout());
603 
604 	}
605 
606 	// We cache it if we're supposed to.
607 
608 	if (cacheIt) {
609 	    TCPSocketCache.put(addr, s);
610 
611 	}
612 
613 	return s;
614     }
615 
616     // Transact the message, using cached socket if necessary. Retry if
617     //  flag is true.
618 
619     static private SrvLocMsg
transactMsg(InetAddress addr, SrvLocMsg msg, boolean cacheIt, boolean retry)620 	transactMsg(InetAddress addr,
621 		    SrvLocMsg msg,
622 		    boolean cacheIt,
623 		    boolean retry)
624 	throws InterruptedIOException, IOException, ServiceLocationException {
625 
626 	Socket s = null;
627 	byte outbuf[] = getBytes(msg, false, true);
628 
629 	try {
630 
631 	    s = getTCPSocket(addr, cacheIt);
632 
633 	    DataOutputStream dos = new DataOutputStream(s.getOutputStream());
634 	    DataInputStream dis = new DataInputStream(s.getInputStream());
635 
636 	    // In case the server cuts us off...
637 
638 	    try {
639 
640 		// Only one thread at a time gets to use this socket, in case
641 		//  it was cached. Otherwise, we *may* get interleaved i/o.
642 
643 		synchronized (s) {
644 
645 		    // Send the request.
646 
647 		    dos.write(outbuf, 0, outbuf.length);
648 
649 		    // Read reply.
650 
651 		    return internalize(dis, addr);
652 
653 		}
654 
655 	    } catch (IOException ex) {
656 
657 		// Uncache it, get a new one. If that one doesn't work, we're
658 		//  hosed.
659 
660 		uncacheSocket(addr, s);
661 
662 		s = null;
663 
664 		if (!retry) {
665 		    throw ex;
666 
667 		}
668 
669 		// Recursively call ourselves to take care of this, but
670 		//  don't retry it.
671 
672 		return transactMsg(addr, msg, cacheIt, false);
673 
674 	    }
675 
676 	} finally {
677 
678 	    if (s != null && !cacheIt) {
679 		uncacheSocket(addr, s);
680 
681 	    }
682 	}
683     }
684 
685     // Externalize the message into bytes.
686 
getBytes(SrvLocMsg slm, boolean isMulti, boolean isTCP)687     static protected byte[] getBytes(SrvLocMsg slm,
688 				     boolean isMulti,
689 				     boolean isTCP)
690 	throws ServiceLocationException {
691 
692 	ByteArrayOutputStream baos = new ByteArrayOutputStream();
693 	SrvLocHeader hdr = slm.getHeader();
694 
695 	hdr.externalize(baos, isMulti, isTCP);
696 
697 	byte[] outbuf = baos.toByteArray();
698 
699 	// Check if it excceds the output buffer length.
700 
701 	if (hdr.overflow) {
702 	    throw
703 		new ServiceLocationException(
704 				ServiceLocationException.BUFFER_OVERFLOW,
705 				"buffer_overflow",
706 				new Object[] {
707 		    new Integer(outbuf.length),
708 			new Integer(config.getMTU())});
709 	}
710 
711 	return outbuf;
712     }
713 
714     // Filter the reply to make sure the xid matches and that it's not null.
715 
716     static protected boolean
filterRply(SrvLocMsg msg, SrvLocMsg rply, InetAddress addr)717 	filterRply(SrvLocMsg msg, SrvLocMsg rply, InetAddress addr) {
718 
719 	SrvLocHeader mhdr = msg.getHeader();
720 	SrvLocHeader rhdr = rply.getHeader();
721 
722 	if (rply == null) {
723 	    if (config.traceDrop()) {
724 		config.writeLog("reply_unparsable",
725 				new Object[] {addr});
726 
727 	    }
728 
729 	    return false;
730 
731 	}
732 
733 	// Check for invalid xid.
734 
735 	if (mhdr.xid != rhdr.xid) {
736 	    if (config.traceDrop()) {
737 		config.writeLog("wrong_xid",
738 				new Object[] {addr});
739 
740 	    }
741 	    return false;
742 
743 	}
744 	return true;
745 
746     }
747 
748     /**
749      * Internalize the byte array in the input stream into a SrvLocMsg
750      * subclass. It will be an appropriate subclass for the client agent.
751      * If an exception comes out of this method, it is converted into
752      * a SrvLocMsg with error code.
753      *
754      *
755      * @param dis The input stream containing the packet.
756      * @param addr The address of the replying agent (for error reporting).
757      * @return The right SrvLocMsg subclass appropriate for the Client Agent.
758      *		If null is returned, the function code wasn't recognized,
759      *		and so it may be appropriate for another agent.
760      * @exception ServiceLocationException If the character set was not valid
761      *		or an error occured during parsing.
762      * @exception IOException If DataInputStream throws it.
763      */
764 
internalize(DataInputStream dis, InetAddress addr)765     static protected SrvLocMsg internalize(DataInputStream dis,
766 					   InetAddress addr)
767 	throws ServiceLocationException {
768 
769 	int ver = 0, fun = 0;
770 	SrvLocMsg msg = null;
771 	SrvLocHeader hdr = null;
772 	byte[] b = new byte[2];
773 
774 	try {
775 
776 	    dis.readFully(b, 0, 2);
777 
778 	    ver = (int) ((char)b[0] & 0XFF);
779 	    fun = (int) ((char)b[1] & 0XFF);
780 
781 	    // Unrecognized version number if header not returned.
782 
783 	    if (ver != Defaults.version) {
784 		throw
785 		    new ServiceLocationException(
786 				ServiceLocationException.VERSION_NOT_SUPPORTED,
787 				"version_number_error",
788 				new Object[] {new Integer(ver)});
789 
790 	    }
791 
792 	    // Create the header. Note that we only need to create a
793 	    //  client side header here, because that is all that
794 	    //  will be expected by the client side code. Note that we
795 	    //  *can't* use the SrvLocHeader.newInstance() mechanism
796 	    //  because Transact lives in the server as well, and
797 	    //  SrvLocHeader can only handle one header class per
798 	    //  version.
799 
800 	    hdr = new SLPHeaderV2();
801 
802 	    // Parse header.
803 
804 	    hdr.parseHeader(fun, dis);
805 
806 	    // Parse body.
807 
808 	    if ((msg = hdr.parseMsg(dis)) != null) {
809 
810 		// Parse options, if any.
811 
812 		hdr.parseOptions(dis);
813 
814 	    }
815 
816 	} catch (IllegalArgumentException ex) {
817 
818 	    // During parsing, this can be thrown if syntax errors occur.
819 
820 	    throw
821 		new ServiceLocationException(
822 				ServiceLocationException.PARSE_ERROR,
823 				"passthrough_addr",
824 				new Object[] {ex.getMessage(), addr});
825 
826 	} catch (IOException ex) {
827 
828 	    // If version code is zero, then we suspect a network error,
829 	    //  otherwise, it is probably a parse error.
830 
831 	    String fcode = (fun == 0 ? "???":Integer.toString(fun));
832 	    short exCode =
833 		(ver == 0 ? ServiceLocationException.NETWORK_ERROR:
834 		 ServiceLocationException.PARSE_ERROR);
835 
836 	    // During parsing, this can be thrown if the message stream
837 	    //  is improperly formatted.
838 
839 	    throw
840 		new ServiceLocationException(exCode,
841 					     "ioexception_parsing",
842 					     new Object[] {
843 		    ex, fcode, addr, ex.getMessage()});
844 
845 	} catch (ServiceLocationException ex) {
846 
847 	    // Add the address of the replying agent.
848 
849 	    throw
850 		new ServiceLocationException(ex.getErrorCode(),
851 					     "passthrough_addr",
852 					     new Object[] {
853 		    ex.getMessage(), addr});
854 
855 	}
856 
857 	return msg;
858     }
859 
860     // Send out the message.
861 
862     static protected void
send(DatagramSocket ds, SrvLocMsg msg, InetAddress addr)863 	send(DatagramSocket ds, SrvLocMsg msg, InetAddress addr)
864 	throws ServiceLocationException, IOException {
865 
866 	byte[] outbuf = getBytes(msg, true, false);
867 	DatagramPacket dpsend =
868 	    new DatagramPacket(outbuf, outbuf.length, addr, Defaults.iSLPPort);
869 	ds.send(dpsend);
870 
871     }
872 
873     // Check the response and add the previous responder if it is OK.
874 
875     static protected boolean
addPreviousResponder(SrvLocMsg msg, InetAddress addr)876 	addPreviousResponder(SrvLocMsg msg, InetAddress addr) {
877 
878 	// Add incoming result to the vector.
879 
880 	SrvLocHeader hdr = msg.getHeader();
881 	Vector v = hdr.previousResponders;
882 	String srcAddr = addr.getHostAddress();
883 
884 	if (v.contains(srcAddr)) {	// the SRC ignored its PR list
885 	    if (config.traceDrop()) {
886 		config.writeLog("drop_pr",
887 				new Object[] {
888 		    srcAddr,
889 			Integer.toHexString(hdr.xid)});
890 
891 	    }
892 	    return false;
893 
894 	} else {
895 	    hdr.addPreviousResponder(addr);
896 	    return true;
897 
898 	}
899     }
900 
901     // Transact an active request for DA or SA adverts.
902 
transactActiveAdvertRequest(ServiceType type, SrvLocMsg rqst, ServerDATable daTable)903     static Vector transactActiveAdvertRequest(ServiceType type,
904 					      SrvLocMsg rqst,
905 					      ServerDATable daTable)
906 	throws ServiceLocationException {
907 
908 	// Perform active advertisement.
909 
910 	Vector ret = new Vector();
911 	Vector results = new Vector();
912 
913 	// Create Transact object and start.
914 
915 	Transact tran = new Transact(rqst,
916 				     results,
917 				     config.getMulticastTimeouts(),
918 				     Integer.MAX_VALUE, // config doesn't apply
919 				     config.getMulticastAddress(),
920 				     true);
921 
922 	Thread multiThread = new Thread(tran);
923 
924 	multiThread.start();
925 
926 	// Wait until the TransactConverge thread is done, if necessary.
927 
928 	try {
929 	    multiThread.join();
930 
931 	} catch (InterruptedException ex) {
932 
933 	}
934 
935 	ServiceLocationException ex = tran.exErr;
936 
937 	// Report error.
938 
939 	if (ex != null && config.traceDATraffic()) {
940 	    config.writeLog("sdat_active_err",
941 			    new Object[] {new Integer(ex.getErrorCode()),
942 					      ex.getMessage()});
943 
944 	    throw ex;
945 
946 	}
947 
948 	// Process the results.
949 
950 	int i, n = results.size();
951 
952 	for (i = 0; i < n; i++) {
953 	    Object msg = results.elementAt(i);
954 
955 	    if ((type.equals(Defaults.DA_SERVICE_TYPE) &&
956 		!(msg instanceof CDAAdvert)) ||
957 		(type.equals(Defaults.SA_SERVICE_TYPE) &&
958 		!(msg instanceof CSAAdvert))) {
959 
960 		if (config.traceDrop()) {
961 		    config.writeLog("sdat_nonadvert_err",
962 				    new Object[] {
963 			msg});
964 
965 		}
966 
967 		continue;
968 	    }
969 
970 	    // Let DA table handle it if it`s a DAAdvert.
971 
972 	    if (type.equals(Defaults.DA_SERVICE_TYPE)) {
973 		CDAAdvert advert = (CDAAdvert)msg;
974 
975 		daTable.handleAdvertIn(advert);
976 
977 	    } else {
978 
979 		// Add scopes from the SAAdvert if not already there.
980 
981 		SrvLocHeader hdr = ((SrvLocMsg)msg).getHeader();
982 
983 		int j, m = hdr.scopes.size();
984 
985 		for (j = 0; j < m; j++) {
986 		    Object o = hdr.scopes.elementAt(j);
987 
988 		    if (!ret.contains(o)) {
989 			ret.addElement(o);
990 
991 		    }
992 		}
993 	    }
994 	}
995 
996 	return ret;
997     }
998 
999     // Construct a Transact object to run a convergence transaction in
1000     //  a separate thread.
1001 
Transact(SrvLocMsg msg, Vector ret, int[] msT, int mResults, InetAddress address, boolean continueAfterFound)1002     Transact(SrvLocMsg msg,
1003 	     Vector ret,
1004 	     int[] msT,
1005 	     int mResults,
1006 	     InetAddress address,
1007 	     boolean continueAfterFound) {
1008 
1009 	msgOut = msg;
1010 	returns = ret;
1011 	MSTimeouts = msT;
1012 	maxResults = mResults;
1013 	this.address = address;
1014 	this.continueAfterFound = continueAfterFound;
1015     }
1016 
1017     // Run the multicast convergence algorithm.
1018 
run()1019     public void run() {
1020 
1021 	Exception xes = null;
1022 	DatagramSocket ds = null;
1023 
1024 	// Get the config object if we need it.
1025 
1026 	if (config == null) {
1027 	    config = SLPConfig.getSLPConfig();
1028 
1029 	}
1030 
1031 	// Set thread name.
1032 
1033 	if (config.isBroadcastOnly()) {
1034 	    Thread.currentThread().setName("SLP Broadcast Transact");
1035 	    address = config.getBroadcastAddress();
1036 
1037 	} else {
1038 	    Thread.currentThread().setName("SLP Multicast Transact");
1039 
1040 	}
1041 
1042 	try {
1043 
1044 	    // Multicast out on the default interface only.
1045 
1046 	    ds = config.getMulticastSocketOnInterface(config.getLocalHost(),
1047 						      true);
1048 
1049 	    // Perform convergence.
1050 
1051 	    transactConvergeMsg(address,
1052 				ds,
1053 				msgOut,
1054 				returns,
1055 				MSTimeouts,
1056 				maxResults,
1057 				continueAfterFound);
1058 
1059 	    ds.close();
1060 
1061 	    ds = null;
1062 
1063 	} catch (ServiceLocationException ex) {
1064 
1065 	    // Ignore DA_BUSY, the DA may free up later.
1066 
1067 	    if (ex.getErrorCode() != ServiceLocationException.DA_BUSY) {
1068 		exErr = ex;
1069 		xes = ex;
1070 
1071 	    }
1072 
1073 	} catch (Exception ex) {
1074 
1075 	    // Create new exception to be thrown.
1076 
1077 	    xes = ex;
1078 	    exErr = new ServiceLocationException(
1079 				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
1080 				"passthrough",
1081 				new Object[] {ex.getMessage()});
1082 
1083 	} finally {
1084 
1085 	    // Close the socket if it's been opened.
1086 
1087 	    if (ds != null) {
1088 		ds.close();
1089 
1090 	    }
1091 	}
1092 
1093 	// Log any errors.
1094 
1095 	if (xes != null) {
1096 	    StringWriter sw = new StringWriter();
1097 	    PrintWriter pw = new PrintWriter(sw);
1098 
1099 	    xes.printStackTrace(pw);
1100 	    pw.flush();
1101 
1102 	    config.writeLog("multicast_error",
1103 			    new Object[] {xes.getMessage(),
1104 					      sw.toString()});
1105 
1106 	}
1107     }
1108 
1109     /**
1110      * Send the message using multicast and use convergence to gather the
1111      * results. Note that this routine must be synchronized because
1112      * only one multicast can be active at a time; othewise, the client
1113      * may get back an unexpected result. However, there can be many unicast
1114      * requests active along with a multicast request, hence the separate
1115      * thread for multicast.
1116      *
1117      * The subtlety of the timing routine is that it will only resend the
1118      * message when one of the multicast convergence timeout intervals
1119      * elapses.  Further, for efficiency, it will give up after a complete
1120      * interval has gone by without receiving any results.  This may mean
1121      * that the intervals have to be extended in larger networks.  In the
1122      * common case, multicast convergence will complete under 3 seconds
1123      * as all results will arrive during the first interval (1 second long)
1124      * and none will arrive during the second interval.
1125      *
1126      * @param     addr  The multicast/broadcast address to send the request to.
1127      * @param     ds  The datagram socket to send on.
1128      * @param     msg The message to send.
1129      * @param     vResult A vector in which to put the returns.
1130      * @param	msTimeouts Array of timeout values for multicast convergence.
1131      * @param     maxResults Maximum replies desired.
1132      * @param	continueAfterFound If true, continue after something is
1133      *				   found. Try three times if nothing was
1134      *				   found. If false, exit at the first
1135      *				   timeout. DA discovery should set this
1136      *				   to true so as many DAs as possible are
1137      *				   found, otherwise, it should be false.
1138      * @exception ServiceLocationException
1139      *            If results cannot be obtained in the timeout interval
1140      *            specified in the 'config.' or
1141      *            if networking resources cannot be obtained or used
1142      *            effectively.
1143      */
1144 
1145     static public void
transactConvergeMsg(InetAddress addr, DatagramSocket ds, SrvLocMsg msg, Vector vResult, int[] msTimeouts, int maxResults, boolean continueAfterFound)1146 	transactConvergeMsg(InetAddress addr,
1147 			    DatagramSocket ds,
1148 			    SrvLocMsg   msg,
1149 			    Vector vResult,
1150 			    int[] msTimeouts,
1151 			    int maxResults,
1152 			    boolean continueAfterFound)
1153 	throws ServiceLocationException {
1154 
1155 	// Get the config object if we need it.
1156 
1157 	if (config == null) {
1158 	    config = SLPConfig.getSLPConfig();
1159 
1160 	}
1161 
1162 	int numReplies = 0;
1163 	int tries = 0;
1164 	SrvLocMsg rply = null;
1165 	ByteArrayOutputStream baos = null;
1166 	int multiMax = config.getMulticastMaximumWait();
1167 	long lStartTime = System.currentTimeMillis();
1168 	int mtu = config.getMTU();
1169 
1170 	try {
1171 
1172 	    // Send the request for the 1st iteration.  It will be sent again
1173 	    //  only when the timeout intervals elapse.
1174 
1175 	    send(ds, msg, addr);
1176 	    tries++;
1177 
1178 	    long lTimeSent = System.currentTimeMillis();
1179 
1180 	    // Continue collecting results only as long as we need more for
1181 	    //   the 'max results' configuration.
1182 
1183 	    while (numReplies < maxResults) {
1184 
1185 		// Set up the reply buffer.
1186 
1187 		byte [] incoming = new byte[mtu];
1188 		DatagramPacket dprecv =
1189 		    new DatagramPacket(incoming, incoming.length);
1190 
1191 		// Block on receive (no longer than max timeout - time spent).
1192 
1193 		int iTimeout =
1194 		    getTimeout(lStartTime, lTimeSent, multiMax, msTimeouts);
1195 
1196 		if (iTimeout < 0) {
1197 		    break; // we have no time left!
1198 		}
1199 
1200 		ds.setSoTimeout(iTimeout);
1201 
1202 		try {
1203 		    ds.receive(dprecv);
1204 
1205 		} catch (InterruptedIOException ex) {
1206 
1207 		    // We try sending at least three times, unless there was
1208 		    // a timeout. If continueAfterFound is false, we exit
1209 		    // after the first timeout if something was found.
1210 
1211 		    if ((!continueAfterFound && numReplies > 0) ||
1212 		(int)(System.currentTimeMillis() - lStartTime) > multiMax ||
1213 			tries >= 3) {
1214 			break;
1215 
1216 		    }
1217 
1218 		    // Now resend the request...
1219 
1220 		    send(ds, msg, addr);
1221 		    tries++;
1222 
1223 		    lTimeSent = System.currentTimeMillis();
1224 		    continue; // since we did not receive anything, continue...
1225 
1226 		}
1227 
1228 		// Data was received without timeout or fail.
1229 
1230 		DataInputStream dis =
1231 		    new DataInputStream(
1232 			new ByteArrayInputStream(dprecv.getData()));
1233 
1234 		InetAddress raddr = dprecv.getAddress();
1235 		rply = internalize(dis, raddr);
1236 
1237 		if (!filterRply(msg, rply, raddr)) {
1238 		    continue;
1239 
1240 		}
1241 
1242 		// Add this responder to previous responders. If the message
1243 		//  was already received but the SA resent because it isn't
1244 		//  doing multicast convergence correctly, then ignore it.
1245 
1246 		if (!addPreviousResponder(msg, raddr)) {
1247 		    continue;
1248 
1249 		}
1250 
1251 		// Handle any overflow thru TCP.
1252 
1253 		SrvLocHeader rhdr = rply.getHeader();
1254 
1255 		if (rhdr.overflow) {
1256 
1257 		    rply = transactTCPMsg(raddr, msg, false);
1258 
1259 		    if (rply == null) {
1260 			continue;
1261 
1262 		    }
1263 
1264 		    rhdr = rply.getHeader();
1265 		}
1266 
1267 		// Add response to list.
1268 
1269 		if (vResult.size() < maxResults) {
1270 		    vResult.addElement(rply);
1271 
1272 		}
1273 
1274 		// Increment the number of results returned.
1275 
1276 		numReplies += rhdr.iNumReplies;
1277 
1278 		// Exit if we should not continue.
1279 
1280 		if (!continueAfterFound) {
1281 		    break;
1282 
1283 		}
1284 	    }
1285 	} catch (ServiceLocationException ex) {
1286 
1287 	    // If we broke off because the previous responder's list is too
1288 	    // long, then return, otherwise throw the exception again.
1289 
1290 	    if (ex.getErrorCode() ==
1291 		ServiceLocationException.PREVIOUS_RESPONDER_OVERFLOW) {
1292 		return;
1293 
1294 	    }
1295 
1296 	    throw ex;
1297 
1298 	} catch (IOException ex) {
1299 
1300 	    throw
1301 		new ServiceLocationException(
1302 				ServiceLocationException.NETWORK_ERROR,
1303 				"ioexception_conv",
1304 				new Object[] {ex, ex.getMessage()});
1305 
1306 	}
1307     }
1308 
1309     // Calculate the multicast timeout depending on where we are in the loop.
1310 
1311     static private int
getTimeout(long lStart, long lSent, int iTimeout, int[] a_iTOs)1312 	getTimeout(long lStart, long lSent, int iTimeout, int[] a_iTOs) {
1313 	int iTotal = (int)(lSent - lStart);
1314 
1315 	if (iTimeout < iTotal) {
1316 	    return -1;
1317 
1318 	}
1319 
1320 	int iWaitTotal = 0;
1321 	int i;
1322 
1323 	for (i = 0; i < a_iTOs.length; i++) {
1324 	    iWaitTotal += a_iTOs[i];
1325 
1326 	    int iTillNext = (iWaitTotal - iTotal);
1327 
1328 	    if (iTotal < iWaitTotal) {
1329 		if (iTimeout < (iTotal + iTillNext)) {
1330 		    return (iTimeout - iTotal);  // max to wait is iTimeout
1331 
1332 		} else {
1333 		    return iTillNext; // otherwise wait till next interval
1334 		}
1335 	    }
1336 	}
1337 
1338 	return -1; // if we get here we have waited past all of the timeouts
1339     }
1340 
1341     static {
1342 
1343 	config = SLPConfig.getSLPConfig();
1344 
1345     }
1346 }
1347