/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* ident "%Z%%M% %I% %E% SMI"
*
* Copyright 1999-2002 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
*/
// Transact.java: Low level details of performing an SLP
// network transaction.
package com.sun.slp;
import java.util.*;
import java.net.*;
import java.io.*;
/**
* Transact performs the low level details for transacting an SLP network
* query. Note that, in the future, this class may spin separate threads
* for DA requests as well.
*/
class Transact extends Object implements Runnable {
// Cache of open TCP sockets.
private static final Hashtable TCPSocketCache = new Hashtable();
// SLP config object.
protected static SLPConfig config = null;
// Message to send.
protected SrvLocMsg msgOut = null;
// Vector of return values.
protected Vector returns = null;
// Timeout for multicast convergence. Varies if it's DA discovery or
// request multicast.
protected int[] MSTimeouts;
// Maximum results desired for multicast.
protected int maxResults = 0;
// Exception to throw.
protected ServiceLocationException exErr = null;
// Multicast address to use.
protected InetAddress address = null;
// If this is true, continue multicast after the first set of stuff
// is found. Exit when three tries have happened without finding
// anything.
boolean continueAfterFound = false;
/**
* Perform a query to the SLP network. The multicast query is performed
* in a separate thread for performance reasons. DAs having the
* same scope set are queried until one answers. These DAs form
* an equivalence class.
*
* @param daEquivClasses Vector of DATable.DARecord objects in the
* same equivalence clase w.r.t. scopes.
* @param uniMsg A unicast message to send.
* @param multiMsg A multicast message to send.
* @param address Multicast address to use.
* @return Vector of SrvLocMsg objects with results.
*/
static Vector
transactUA(Vector daEquivClasses,
SrvLocMsg uniMsg,
SrvLocMsg multiMsg,
InetAddress address)
throws ServiceLocationException {
// If we need to multicast, then start the multicast thread.
Vector ret = new Vector();
Thread multiThread = null;
Transact tracon = null;
if (multiMsg != null) {
// Create a new Transact multicast thread.
// The final argument to the constructor of Transact determines
// whether to return after the first result or to continue to
// gather more than one result. The value to this field
// continueAfterFound MUST be set to 'true' or else multicast
// based discovery will find the first result, not all results,
// as it should.
tracon =
new Transact(multiMsg,
ret,
config.getMulticastTimeouts(),
config.getMaximumResults(),
address,
true); // continueAfterFound
multiThread = new Thread(tracon);
// Run it.
multiThread.start();
}
// Go through the msgTable doing all the DAs.
ServiceLocationException exx = null;
if (daEquivClasses != null) {
exx =
transactUnicastMsg(daEquivClasses,
uniMsg,
ret,
config.getMaximumResults());
}
// Wait until the TransactConverge thread is done, if necessary.
if (multiThread != null) {
try {
multiThread.join();
} catch (InterruptedException ex) {
}
}
// If there was a problem in either the multicast thread or in
// the unicast call, throw an exception, but *only* if no
// results came back.
if (ret.size() <= 0) {
if (exx != null) {
short err = exx.getErrorCode();
if (err != ServiceLocationException.VERSION_NOT_SUPPORTED &&
err != ServiceLocationException.INTERNAL_ERROR &&
err != ServiceLocationException.OPTION_NOT_SUPPORTED &&
err != ServiceLocationException.REQUEST_NOT_SUPPORTED) {
throw exx;
}
}
if (tracon != null && tracon.exErr != null) {
short err = tracon.exErr.getErrorCode();
if (err != ServiceLocationException.VERSION_NOT_SUPPORTED &&
err != ServiceLocationException.INTERNAL_ERROR &&
err != ServiceLocationException.OPTION_NOT_SUPPORTED &&
err != ServiceLocationException.REQUEST_NOT_SUPPORTED) {
throw tracon.exErr;
}
}
}
// Return the result to the client.
return ret;
}
/**
* Transact a message with DAs. Put the returned SrvLocMsg
* object into the Vector ret.
*
* @param daEquivClasses Vector of DATable.DARecord objects in the
* same equivalence clase w.r.t. scopes.
* @param msg SrvLocMsg Message to send.
* @param ret Vector for returns.
* @param maxResults Maximum results expected.
* @return A ServiceLocationException object if an exception occured.
* @exception ServiceLocationException
* If results cannot be obtained in the timeout interval
* specified in the 'config.' or
* If networking resources cannot be obtained or used
* effectively.
*/
static ServiceLocationException
transactUnicastMsg(Vector daEquivClasses,
SrvLocMsg msg,
Vector ret,
int maxResults) {
// Get the config object if we need it.
if (config == null) {
config = SLPConfig.getSLPConfig();
}
DatagramSocket ds = null;
int i, n = daEquivClasses.size();
ServiceLocationException exx = null;
InetAddress addr = null;
int numReplies = 0;
DATable daTable = DATable.getDATable();
try {
// Go through the DA address equivalence classes we need
// to query.
for (i = 0; i < n && numReplies < maxResults; i++) {
DATable.DARecord rec =
(DATable.DARecord)daEquivClasses.elementAt(i);
Vector daAddresses = (Vector)rec.daAddresses.clone();
// Get a new outgoing socket.
if (ds == null) {
ds = new DatagramSocket();
}
// Go through the DA addresses until we get a reply from one.
Enumeration en = daAddresses.elements();
SrvLocHeader mhdr = msg.getHeader();
while (en.hasMoreElements()) {
try {
addr = (InetAddress)en.nextElement();
if (config.traceDATraffic()) {
config.writeLog("sending_da_trace",
new Object[] {
Integer.toHexString(mhdr.xid),
addr});
}
// Get the reply message if any.
SrvLocMsg rply = transactDatagramMsg(ds, addr, msg);
if (!filterRply(msg, rply, addr)) {
continue;
}
SrvLocHeader rhdr = rply.getHeader();
if (config.traceDATraffic()) {
config.writeLog("reply_da_trace",
new Object[] {
Integer.toHexString(rhdr.xid),
addr});
}
// If overflow, try TCP.
if (rhdr.overflow) {
if (config.traceDATraffic()) {
config.writeLog("tcp_send_da_trace",
new Object[] {
Integer.toHexString(mhdr.xid),
addr});
}
rply = transactTCPMsg(addr, msg, false);
if (config.traceDATraffic()) {
config.writeLog("tcp_reply_da_trace",
new Object[] {
(msg == null ? "<null>":
Integer.toHexString(mhdr.xid)),
addr});
}
if (rply == null) {
continue;
}
}
// Increment number of replies we received.
SrvLocHeader hdr = rply.getHeader();
numReplies += hdr.iNumReplies;
// Add to return vector.
ret.addElement(rply);
// Break out of the loop, since we only need one in
// this equivalence class.
break;
} catch (ServiceLocationException ex) {
config.writeLog("da_exception_trace",
new Object[] {
new Short(ex.getErrorCode()),
addr,
ex.getMessage()});
// In case we are querying more than one DA, we
// save th exception, returning it to the caller to
// decide if it should be thrown. We ignore DA_BUSY,
// though, since the DA may free up later.
short errCode = ex.getErrorCode();
if (errCode != ServiceLocationException.DA_BUSY) {
exx = ex;
}
// If the error code is NETWORK_TIMED_OUT, then remove
// this DA from the DA table. If it's just down
// temporarily, we'll get it next time we go to
// the server to get the DA addresses.
if (errCode ==
ServiceLocationException.NETWORK_TIMED_OUT) {
if (config.traceDATraffic()) {
config.writeLog("da_drop",
new Object[] {
addr, rec.scopes});
}
daTable.removeDA(addr, rec.scopes);
}
}
}
}
} catch (SocketException ex) {
exx =
new ServiceLocationException(
ServiceLocationException.NETWORK_ERROR,
"socket_creation_failure",
new Object[] {addr, ex.getMessage()});
} finally {
// Clean up socket.
if (ds != null) {
ds.close();
}
}
return exx;
}
/**
* Transact a message via. UDP. Try a maximum of three times if
* a timeout.
*
* @param ds The datagram socket to use.
* @param addr The DA to contact.
* @param msg The message to send.
* @return The SrvLocMsg returned or null if none.
* @exception ServiceLocationException Due to errors in parsing message.
*/
static private SrvLocMsg
transactDatagramMsg(DatagramSocket ds, InetAddress addr, SrvLocMsg msg)
throws ServiceLocationException {
SrvLocMsg rply = null;
byte[] outbuf = getBytes(msg, false, false);
byte[] inbuf = new byte[Defaults.iReadMaxMTU];
// Construct the datagram packet to send.
DatagramPacket dpReply =
new DatagramPacket(inbuf, inbuf.length);
DatagramPacket dpRequest =
new DatagramPacket(outbuf, outbuf.length, addr, Defaults.iSLPPort);
int[] timeouts = config.getDatagramTimeouts();
// Resend for number of timeouts in timeout interval.
int i;
for (i = 0; i < timeouts.length; i++) {
// Catch timeout and IO errors.
try {
ds.setSoTimeout(timeouts[i]);
ds.send(dpRequest);
ds.receive(dpReply);
// Process result into a reply object.
DataInputStream dis =
new DataInputStream(
new ByteArrayInputStream(dpReply.getData()));
rply = internalize(dis, addr);
break;
} catch (InterruptedIOException ex) {
// Did not get it on the first timeout, try again.
if (config.traceDrop()|| config.traceDATraffic()) {
config.writeLog("udp_timeout",
new Object[] {addr});
}
continue;
} catch (IOException ex) {
Object[] message = {addr, ex.getMessage()};
if (config.traceDrop() || config.traceDATraffic()) {
config.writeLog("datagram_io_error",
message);
}
throw
new ServiceLocationException(
ServiceLocationException.NETWORK_ERROR,
"datagram_io_error",
message);
}
}
// If nothing, then we've timed out. DAs with no matching
// info should at least return a reply.
if (rply == null) {
throw
new ServiceLocationException(
ServiceLocationException.NETWORK_TIMED_OUT,
"udp_timeout",
new Object[] {addr});
}
return rply;
}
/**
* Transact a message using TCP, since the reply was too big.
* @parameter addr Address of the DA to contact.
* @parameter msg The message object to use.
* @parameter cacheIt Cache socket, if new.
* @return The SrvLocMsg returned if any.
* @exception ServiceLocationException
* If results cannot be obtained in the timeout interval
* specified in the 'config.'
* If networking resources cannot be obtained or used
* effectively.
*/
static SrvLocMsg
transactTCPMsg(InetAddress addr, SrvLocMsg msg, boolean cacheIt)
throws ServiceLocationException {
// Get the config object if we need it.
if (config == null) {
config = SLPConfig.getSLPConfig();
}
SrvLocMsg rply = null;
try {
// Transact the message, taking care of socket caching.
rply = transactMsg(addr, msg, cacheIt, true);
} catch (InterruptedIOException ex) {
Object[] message = {addr};
if (config.traceDrop()|| config.traceDATraffic()) {
config.writeLog("tcp_timeout",
message);
}
throw
new ServiceLocationException(
ServiceLocationException.NETWORK_TIMED_OUT,
"tcp_timeout",
message);
} catch (IOException ex) {
Object[] message = {addr, ex.getMessage()};
if (config.traceDrop() || config.traceDATraffic()) {
config.writeLog("tcp_io_error",
message);
}
throw
new ServiceLocationException(
ServiceLocationException.NETWORK_ERROR,
"tcp_io_error",
message);
}
// Filter reply for nulls, invalid xid.
if (!filterRply(msg, rply, addr)) {
return null;
}
return rply;
}
// Uncache a socket.
static private void uncacheSocket(InetAddress addr, Socket s) {
try {
s.close();
} catch (IOException ex) {
}
TCPSocketCache.remove(addr);
}
// Get a (possibly cached) TCP socket, cache it if cache is on.
static private Socket getTCPSocket(InetAddress addr, boolean cacheIt)
throws IOException {
Socket s = null;
// We use the cached socket if we've got it.
s = (Socket)TCPSocketCache.get(addr);
if (s == null) {
s = new Socket(addr, Defaults.iSLPPort);
// Set it so the socket will block for fixed timeout.
s.setSoTimeout(config.getTCPTimeout());
}
// We cache it if we're supposed to.
if (cacheIt) {
TCPSocketCache.put(addr, s);
}
return s;
}
// Transact the message, using cached socket if necessary. Retry if
// flag is true.
static private SrvLocMsg
transactMsg(InetAddress addr,
SrvLocMsg msg,
boolean cacheIt,
boolean retry)
throws InterruptedIOException, IOException, ServiceLocationException {
Socket s = null;
byte outbuf[] = getBytes(msg, false, true);
try {
s = getTCPSocket(addr, cacheIt);
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
DataInputStream dis = new DataInputStream(s.getInputStream());
// In case the server cuts us off...
try {
// Only one thread at a time gets to use this socket, in case
// it was cached. Otherwise, we *may* get interleaved i/o.
synchronized (s) {
// Send the request.
dos.write(outbuf, 0, outbuf.length);
// Read reply.
return internalize(dis, addr);
}
} catch (IOException ex) {
// Uncache it, get a new one. If that one doesn't work, we're
// hosed.
uncacheSocket(addr, s);
s = null;
if (!retry) {
throw ex;
}
// Recursively call ourselves to take care of this, but
// don't retry it.
return transactMsg(addr, msg, cacheIt, false);
}
} finally {
if (s != null && !cacheIt) {
uncacheSocket(addr, s);
}
}
}
// Externalize the message into bytes.
static protected byte[] getBytes(SrvLocMsg slm,
boolean isMulti,
boolean isTCP)
throws ServiceLocationException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
SrvLocHeader hdr = slm.getHeader();
hdr.externalize(baos, isMulti, isTCP);
byte[] outbuf = baos.toByteArray();
// Check if it excceds the output buffer length.
if (hdr.overflow) {
throw
new ServiceLocationException(
ServiceLocationException.BUFFER_OVERFLOW,
"buffer_overflow",
new Object[] {
new Integer(outbuf.length),
new Integer(config.getMTU())});
}
return outbuf;
}
// Filter the reply to make sure the xid matches and that it's not null.
static protected boolean
filterRply(SrvLocMsg msg, SrvLocMsg rply, InetAddress addr) {
SrvLocHeader mhdr = msg.getHeader();
SrvLocHeader rhdr = rply.getHeader();
if (rply == null) {
if (config.traceDrop()) {
config.writeLog("reply_unparsable",
new Object[] {addr});
}
return false;
}
// Check for invalid xid.
if (mhdr.xid != rhdr.xid) {
if (config.traceDrop()) {
config.writeLog("wrong_xid",
new Object[] {addr});
}
return false;
}
return true;
}
/**
* Internalize the byte array in the input stream into a SrvLocMsg
* subclass. It will be an appropriate subclass for the client agent.
* If an exception comes out of this method, it is converted into
* a SrvLocMsg with error code.
*
*
* @param dis The input stream containing the packet.
* @param addr The address of the replying agent (for error reporting).
* @return The right SrvLocMsg subclass appropriate for the Client Agent.
* If null is returned, the function code wasn't recognized,
* and so it may be appropriate for another agent.
* @exception ServiceLocationException If the character set was not valid
* or an error occured during parsing.
* @exception IOException If DataInputStream throws it.
*/
static protected SrvLocMsg internalize(DataInputStream dis,
InetAddress addr)
throws ServiceLocationException {
int ver = 0, fun = 0;
SrvLocMsg msg = null;
SrvLocHeader hdr = null;
byte[] b = new byte[2];
try {
dis.readFully(b, 0, 2);
ver = (int) ((char)b[0] & 0XFF);
fun = (int) ((char)b[1] & 0XFF);
// Unrecognized version number if header not returned.
if (ver != Defaults.version) {
throw
new ServiceLocationException(
ServiceLocationException.VERSION_NOT_SUPPORTED,
"version_number_error",
new Object[] {new Integer(ver)});
}
// Create the header. Note that we only need to create a
// client side header here, because that is all that
// will be expected by the client side code. Note that we
// *can't* use the SrvLocHeader.newInstance() mechanism
// because Transact lives in the server as well, and
// SrvLocHeader can only handle one header class per
// version.
hdr = new SLPHeaderV2();
// Parse header.
hdr.parseHeader(fun, dis);
// Parse body.
if ((msg = hdr.parseMsg(dis)) != null) {
// Parse options, if any.
hdr.parseOptions(dis);
}
} catch (IllegalArgumentException ex) {
// During parsing, this can be thrown if syntax errors occur.
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"passthrough_addr",
new Object[] {ex.getMessage(), addr});
} catch (IOException ex) {
// If version code is zero, then we suspect a network error,
// otherwise, it is probably a parse error.
String fcode = (fun == 0 ? "???":Integer.toString(fun));
short exCode =
(ver == 0 ? ServiceLocationException.NETWORK_ERROR:
ServiceLocationException.PARSE_ERROR);
// During parsing, this can be thrown if the message stream
// is improperly formatted.
throw
new ServiceLocationException(exCode,
"ioexception_parsing",
new Object[] {
ex, fcode, addr, ex.getMessage()});
} catch (ServiceLocationException ex) {
// Add the address of the replying agent.
throw
new ServiceLocationException(ex.getErrorCode(),
"passthrough_addr",
new Object[] {
ex.getMessage(), addr});
}
return msg;
}
// Send out the message.
static protected void
send(DatagramSocket ds, SrvLocMsg msg, InetAddress addr)
throws ServiceLocationException, IOException {
byte[] outbuf = getBytes(msg, true, false);
DatagramPacket dpsend =
new DatagramPacket(outbuf, outbuf.length, addr, Defaults.iSLPPort);
ds.send(dpsend);
}
// Check the response and add the previous responder if it is OK.
static protected boolean
addPreviousResponder(SrvLocMsg msg, InetAddress addr) {
// Add incoming result to the vector.
SrvLocHeader hdr = msg.getHeader();
Vector v = hdr.previousResponders;
String srcAddr = addr.getHostAddress();
if (v.contains(srcAddr)) { // the SRC ignored its PR list
if (config.traceDrop()) {
config.writeLog("drop_pr",
new Object[] {
srcAddr,
Integer.toHexString(hdr.xid)});
}
return false;
} else {
hdr.addPreviousResponder(addr);
return true;
}
}
// Transact an active request for DA or SA adverts.
static Vector transactActiveAdvertRequest(ServiceType type,
SrvLocMsg rqst,
ServerDATable daTable)
throws ServiceLocationException {
// Perform active advertisement.
Vector ret = new Vector();
Vector results = new Vector();
// Create Transact object and start.
Transact tran = new Transact(rqst,
results,
config.getMulticastTimeouts(),
Integer.MAX_VALUE, // config doesn't apply
config.getMulticastAddress(),
true);
Thread multiThread = new Thread(tran);
multiThread.start();
// Wait until the TransactConverge thread is done, if necessary.
try {
multiThread.join();
} catch (InterruptedException ex) {
}
ServiceLocationException ex = tran.exErr;
// Report error.
if (ex != null && config.traceDATraffic()) {
config.writeLog("sdat_active_err",
new Object[] {new Integer(ex.getErrorCode()),
ex.getMessage()});
throw ex;
}
// Process the results.
int i, n = results.size();
for (i = 0; i < n; i++) {
Object msg = results.elementAt(i);
if ((type.equals(Defaults.DA_SERVICE_TYPE) &&
!(msg instanceof CDAAdvert)) ||
(type.equals(Defaults.SA_SERVICE_TYPE) &&
!(msg instanceof CSAAdvert))) {
if (config.traceDrop()) {
config.writeLog("sdat_nonadvert_err",
new Object[] {
msg});
}
continue;
}
// Let DA table handle it if it`s a DAAdvert.
if (type.equals(Defaults.DA_SERVICE_TYPE)) {
CDAAdvert advert = (CDAAdvert)msg;
daTable.handleAdvertIn(advert);
} else {
// Add scopes from the SAAdvert if not already there.
SrvLocHeader hdr = ((SrvLocMsg)msg).getHeader();
int j, m = hdr.scopes.size();
for (j = 0; j < m; j++) {
Object o = hdr.scopes.elementAt(j);
if (!ret.contains(o)) {
ret.addElement(o);
}
}
}
}
return ret;
}
// Construct a Transact object to run a convergence transaction in
// a separate thread.
Transact(SrvLocMsg msg,
Vector ret,
int[] msT,
int mResults,
InetAddress address,
boolean continueAfterFound) {
msgOut = msg;
returns = ret;
MSTimeouts = msT;
maxResults = mResults;
this.address = address;
this.continueAfterFound = continueAfterFound;
}
// Run the multicast convergence algorithm.
public void run() {
Exception xes = null;
DatagramSocket ds = null;
// Get the config object if we need it.
if (config == null) {
config = SLPConfig.getSLPConfig();
}
// Set thread name.
if (config.isBroadcastOnly()) {
Thread.currentThread().setName("SLP Broadcast Transact");
address = config.getBroadcastAddress();
} else {
Thread.currentThread().setName("SLP Multicast Transact");
}
try {
// Multicast out on the default interface only.
ds = config.getMulticastSocketOnInterface(config.getLocalHost(),
true);
// Perform convergence.
transactConvergeMsg(address,
ds,
msgOut,
returns,
MSTimeouts,
maxResults,
continueAfterFound);
ds.close();
ds = null;
} catch (ServiceLocationException ex) {
// Ignore DA_BUSY, the DA may free up later.
if (ex.getErrorCode() != ServiceLocationException.DA_BUSY) {
exErr = ex;
xes = ex;
}
} catch (Exception ex) {
// Create new exception to be thrown.
xes = ex;
exErr = new ServiceLocationException(
ServiceLocationException.INTERNAL_SYSTEM_ERROR,
"passthrough",
new Object[] {ex.getMessage()});
} finally {
// Close the socket if it's been opened.
if (ds != null) {
ds.close();
}
}
// Log any errors.
if (xes != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
xes.printStackTrace(pw);
pw.flush();
config.writeLog("multicast_error",
new Object[] {xes.getMessage(),
sw.toString()});
}
}
/**
* Send the message using multicast and use convergence to gather the
* results. Note that this routine must be synchronized because
* only one multicast can be active at a time; othewise, the client
* may get back an unexpected result. However, there can be many unicast
* requests active along with a multicast request, hence the separate
* thread for multicast.
*
* The subtlety of the timing routine is that it will only resend the
* message when one of the multicast convergence timeout intervals
* elapses. Further, for efficiency, it will give up after a complete
* interval has gone by without receiving any results. This may mean
* that the intervals have to be extended in larger networks. In the
* common case, multicast convergence will complete under 3 seconds
* as all results will arrive during the first interval (1 second long)
* and none will arrive during the second interval.
*
* @param addr The multicast/broadcast address to send the request to.
* @param ds The datagram socket to send on.
* @param msg The message to send.
* @param vResult A vector in which to put the returns.
* @param msTimeouts Array of timeout values for multicast convergence.
* @param maxResults Maximum replies desired.
* @param continueAfterFound If true, continue after something is
* found. Try three times if nothing was
* found. If false, exit at the first
* timeout. DA discovery should set this
* to true so as many DAs as possible are
* found, otherwise, it should be false.
* @exception ServiceLocationException
* If results cannot be obtained in the timeout interval
* specified in the 'config.' or
* if networking resources cannot be obtained or used
* effectively.
*/
static public void
transactConvergeMsg(InetAddress addr,
DatagramSocket ds,
SrvLocMsg msg,
Vector vResult,
int[] msTimeouts,
int maxResults,
boolean continueAfterFound)
throws ServiceLocationException {
// Get the config object if we need it.
if (config == null) {
config = SLPConfig.getSLPConfig();
}
int numReplies = 0;
int tries = 0;
SrvLocMsg rply = null;
ByteArrayOutputStream baos = null;
int multiMax = config.getMulticastMaximumWait();
long lStartTime = System.currentTimeMillis();
int mtu = config.getMTU();
try {
// Send the request for the 1st iteration. It will be sent again
// only when the timeout intervals elapse.
send(ds, msg, addr);
tries++;
long lTimeSent = System.currentTimeMillis();
// Continue collecting results only as long as we need more for
// the 'max results' configuration.
while (numReplies < maxResults) {
// Set up the reply buffer.
byte [] incoming = new byte[mtu];
DatagramPacket dprecv =
new DatagramPacket(incoming, incoming.length);
// Block on receive (no longer than max timeout - time spent).
int iTimeout =
getTimeout(lStartTime, lTimeSent, multiMax, msTimeouts);
if (iTimeout < 0) {
break; // we have no time left!
}
ds.setSoTimeout(iTimeout);
try {
ds.receive(dprecv);
} catch (InterruptedIOException ex) {
// We try sending at least three times, unless there was
// a timeout. If continueAfterFound is false, we exit
// after the first timeout if something was found.
if ((!continueAfterFound && numReplies > 0) ||
(int)(System.currentTimeMillis() - lStartTime) > multiMax ||
tries >= 3) {
break;
}
// Now resend the request...
send(ds, msg, addr);
tries++;
lTimeSent = System.currentTimeMillis();
continue; // since we did not receive anything, continue...
}
// Data was received without timeout or fail.
DataInputStream dis =
new DataInputStream(
new ByteArrayInputStream(dprecv.getData()));
InetAddress raddr = dprecv.getAddress();
rply = internalize(dis, raddr);
if (!filterRply(msg, rply, raddr)) {
continue;
}
// Add this responder to previous responders. If the message
// was already received but the SA resent because it isn't
// doing multicast convergence correctly, then ignore it.
if (!addPreviousResponder(msg, raddr)) {
continue;
}
// Handle any overflow thru TCP.
SrvLocHeader rhdr = rply.getHeader();
if (rhdr.overflow) {
rply = transactTCPMsg(raddr, msg, false);
if (rply == null) {
continue;
}
rhdr = rply.getHeader();
}
// Add response to list.
if (vResult.size() < maxResults) {
vResult.addElement(rply);
}
// Increment the number of results returned.
numReplies += rhdr.iNumReplies;
// Exit if we should not continue.
if (!continueAfterFound) {
break;
}
}
} catch (ServiceLocationException ex) {
// If we broke off because the previous responder's list is too
// long, then return, otherwise throw the exception again.
if (ex.getErrorCode() ==
ServiceLocationException.PREVIOUS_RESPONDER_OVERFLOW) {
return;
}
throw ex;
} catch (IOException ex) {
throw
new ServiceLocationException(
ServiceLocationException.NETWORK_ERROR,
"ioexception_conv",
new Object[] {ex, ex.getMessage()});
}
}
// Calculate the multicast timeout depending on where we are in the loop.
static private int
getTimeout(long lStart, long lSent, int iTimeout, int[] a_iTOs) {
int iTotal = (int)(lSent - lStart);
if (iTimeout < iTotal) {
return -1;
}
int iWaitTotal = 0;
int i;
for (i = 0; i < a_iTOs.length; i++) {
iWaitTotal += a_iTOs[i];
int iTillNext = (iWaitTotal - iTotal);
if (iTotal < iWaitTotal) {
if (iTimeout < (iTotal + iTillNext)) {
return (iTimeout - iTotal); // max to wait is iTimeout
} else {
return iTillNext; // otherwise wait till next interval
}
}
}
return -1; // if we get here we have waited past all of the timeouts
}
static {
config = SLPConfig.getSLPConfig();
}
}