/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sun.jini.test.share;
import com.sun.jini.qa.harness.QAConfig;
import com.sun.jini.qa.harness.AdminManager;
import com.sun.jini.qa.harness.TestException;
import com.sun.jini.start.HTTPDStatus;
import com.sun.jini.start.ServiceStarter;
import com.sun.jini.thread.TaskManager;
import com.sun.jini.discovery.ClientSubjectChecker;
import com.sun.jini.discovery.Discovery;
import com.sun.jini.discovery.DiscoveryConstraints;
import com.sun.jini.discovery.DiscoveryProtocolException;
import com.sun.jini.discovery.EncodeIterator;
import com.sun.jini.discovery.MulticastAnnouncement;
import com.sun.jini.discovery.MulticastRequest;
import com.sun.jini.discovery.UnicastResponse;
import net.jini.discovery.Constants;
import net.jini.discovery.IncomingMulticastRequest;
import net.jini.discovery.IncomingUnicastRequest;
import net.jini.discovery.OutgoingMulticastAnnouncement;
import net.jini.discovery.OutgoingUnicastResponse;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceMatches;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lookup.ServiceTemplate;
import com.sun.jini.test.services.lookupsimulator.LookupSimulatorProxyInterface;
import net.jini.constraint.BasicMethodConstraints;
import net.jini.io.UnsupportedConstraintException;
import net.jini.config.Configuration;
import net.jini.config.NoSuchEntryException;
import net.jini.config.ConfigurationException;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import com.sun.jini.config.Config;
import net.jini.security.Security;
import net.jini.security.AuthenticationPermission;
import java.lang.reflect.Array;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.nio.BufferUnderflowException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.rmi.activation.ActivationGroupDesc;
import java.rmi.activation.ActivationGroupDesc.CommandEnvironment;
import java.rmi.activation.ActivationGroup;
import java.rmi.activation.ActivationGroupID;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationException;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.security.Permission;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import java.util.logging.Level;
import javax.security.auth.login.LoginContext;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
/**
* Instances of this class provide general-purpose functions related to the
* discovery protocol. This utility class is intended to be useful to all
* categories of tests that wish to simulate the multicast and unicast
* message exchange between a client or service and a lookup service --
* from the point of view of the lookup service. (For provide the
* analogous functionality from the point of view of the client,
* use the <code>net.jini.discovery.LookupDiscovery</code> and the
* <code>net.jini.discovery.LookupLocatorDiscovery</code> utilities.
*
* @see net.jini.core.discovery.LookupLocator
*
* @see net.jini.discovery.IncomingMulticastAnnouncement
* @see net.jini.discovery.IncomingMulticastRequest
* @see net.jini.discovery.IncomingUnicastRequest
* @see net.jini.discovery.IncomingUnicastResponse
*
* @see net.jini.discovery.OutgoingMulticastAnnouncement
* @see net.jini.discovery.OutgoingMulticastRequest
* @see net.jini.discovery.OutgoingUnicastRequest
* @see net.jini.discovery.OutgoingUnicastResponse
*/
public class DiscoveryProtocolSimulator {
/** Maximum minMax lease duration for both services and events */
private static final long MAX_LEASE = 1000L * 60 * 60 * 24 * 365 * 1000;
/** Maximum minimum renewal interval */
private static final long MAX_RENEW = 1000L * 60 * 60 * 24 * 365;
/** Default maximum size of multicast packets to send and receive */
private static final int DEFAULT_MAX_PACKET_SIZE = 512;
/** Default time to live value to use for sending multicast packets */
private static int DEFAULT_MULTICAST_TTL;
/** Default timeout to set on sockets used for unicast discovery */
private static final int DEFAULT_SOCKET_TIMEOUT = 1*60*1000;
private static Logger logger = Logger.getLogger("com.sun.jini.qa.harness");
/** Socket timeout for multicast request receive() */
private static final int SOCKET_TIMEOUT = 5*60*1000;
/** Only allow connections from this address */
private InetAddress thisInetAddress = null;
/** Current number of multicast announcements sent by this class */
private int nAnnouncements = 0;
/** Internet Protocol (IP) addresses of the network interfaces (NICs)
* through which multicast packets will be sent.
*/
private InetAddress[] nicAddresses;
/** Port for unicast discovery */
private int unicastPort = 0;
/** The locator to send */
private LookupLocator lookupLocator = null;
/** The member groups to send */
private String[] memberGroups = {};
/** Thread to receive/process multicast requests from client or service */
MulticastThread multicastRequestThread;
/** Thread to receive/process unicast requests from client or service */
UnicastThread unicastRequestThread;
/** Thread to send multicast announcements to from client or service */
AnnounceThread multicastAnnouncementThread;
/** Task manager for sending events and discovery responses */
private final TaskManager taskMgr = new TaskManager(10, 1000 * 15, 1.0f);
/** Proxy for the "fake" lookup service that is sent */
private LookupSimulatorProxyInterface lookupProxy;
/** The service ID to assign to the lookup service that is sent */
private ServiceID lookupServiceID = null;
/** Socket timeout for unicast discovery request processing */
private int unicastTimeout =
Integer.getInteger("com.sun.jini.reggie.unicastTimeout",
1000 * 60).intValue();
/* For synchronization, instead of ReadersWriter locks used by reggie */
private Object lockNAnnouncements = new Object();
private Object lockLookupProxy = new Object();
private Object lockLookupLocator = new Object();
private Object lockMemberGroups = new Object();
/* new fields taken from the davis reggie */
/** Network interfaces to use for multicast discovery */
private NetworkInterface[] multicastInterfaces;
/** Flag indicating whether network interfaces were explicitly specified */
private boolean multicastInterfacesSpecified;
private Discovery protocol2;
/** Constraints specified for incoming multicast requests */
private DiscoveryConstraints multicastRequestConstraints;
/** Constraints specified for outgoing multicast announcements */
private DiscoveryConstraints multicastAnnouncementConstraints;
/** Constraints specified for handling unicast discovery */
private DiscoveryConstraints unicastDiscoveryConstraints;
/** Client subject checker to apply to incoming multicast requests */
private ClientSubjectChecker multicastRequestSubjectChecker;
/** Client subject checker to apply to unicast discovery attempts */
private ClientSubjectChecker unicastDiscoverySubjectChecker;
/** Interval to wait in between sending multicast announcements */
private long multicastAnnouncementInterval = 1000 * 60 * 2;
public DiscoveryProtocolSimulator(QAConfig config,
String[] memberGroups,
AdminManager manager)
throws ActivationException, IOException, TestException
{
this(config,memberGroups,manager,0);
}//end constructor
public DiscoveryProtocolSimulator(QAConfig config,
String[] memberGroups,
AdminManager manager,
int unicastPort)
throws ActivationException, IOException, TestException
{
this.memberGroups = memberGroups;
this.unicastPort = unicastPort;
// start LUS before switching identity to reggie
lookupProxy = (LookupSimulatorProxyInterface) manager.startLookupService();
LoginContext context = null;
Configuration c = config.getConfiguration();
try {
context = (LoginContext) c.getEntry("test",
"reggieLoginContext",
LoginContext.class,
null);
} catch (ConfigurationException e) {
throw new RuntimeException("Bad configuration", e);
}
if (context == null) {
init(config);
} else {
try {
Principal reggie =
(Principal) c.getEntry("principal", "reggie", Principal.class);
// allow the simulator, running as reggie, to authenticate as reggie
// XXX Should the permission be obtained from the configuration???
Security.grant(lookupProxy.getClass(),
new Principal[]{reggie},
new Permission[] {
new AuthenticationPermission(
Collections.singleton(reggie),
Collections.singleton(reggie),
"connect")});
context.login();
} catch (LoginException e) {
throw new RuntimeException("LoginFailed", e);
} catch (ConfigurationException e) {
throw new RuntimeException("Configuration error", e);
}
try {
final QAConfig finalConfig = config;
Subject.doAsPrivileged(context.getSubject(),
new PrivilegedExceptionAction() {
public Object run() throws ActivationException, IOException {
init(finalConfig);
return null;
}
},
null);
} catch (PrivilegedActionException e) {
Throwable t = e.getException();
if (t instanceof ActivationException) {
throw (ActivationException) t;
}
if (t instanceof IOException) {
throw (IOException) t;
}
}
}
}//end constructor
public void stopAnnouncements() {
stopAnnouncements(null);
}//end stopAnnouncements
public void stopAnnouncements(String testname) {
logger.log(Level.FINE, " stopAnnouncements entered");
/* terminate all daemons */
unicastRequestThread.interrupt();
multicastRequestThread.interrupt();
multicastAnnouncementThread.interrupt();
logger.log(Level.FINE, " interrupted all threads");
taskMgr.terminate();
logger.log(Level.FINE, " terminated task manager");
try {
logger.log(Level.FINE, " unicastRequestThread.join()");
unicastRequestThread.join();
logger.log(Level.FINE, " multicastRequestThread.join()");
multicastRequestThread.join();
logger.log(Level.FINE,
" multicastAnnouncementThread.join()");
multicastAnnouncementThread.join();
} catch (InterruptedException e) { }
logger.log(Level.FINE, " close all request sockets");
closeRequestSockets(taskMgr.getPending());
logger.log(Level.FINE, " stopAnnouncements exited");
}//end stopAnnouncements
// public void destroyLookup() throws RemoteException {
// lookupProxy.destroy();
// }//end destroyLookup
public int getNAnnouncementsSent() {
synchronized(lockNAnnouncements) {
return nAnnouncements;
}//end sync
}//end getNAnnouncementsSent
public ServiceRegistrar getLookupProxy() {
synchronized(lockLookupProxy) {
return lookupProxy;
}//end sync
}//end getLookupProxy
public LookupLocator getLookupLocator() {
synchronized(lockLookupLocator) {
return lookupLocator;
}//end sync
}//end getLookupLocator
public String[] getMemberGroups() {
synchronized(lockMemberGroups) {
return memberGroups; // don't clone, never modified once created
}//end sync
}//end getMemberGroups
public void addMemberGroups(String[] groups) throws RemoteException {
String[] tmpArray = null;
/* Change the member groups locally, then replace them remotely */
synchronized(lockMemberGroups) {
for (int i=0;i<groups.length;i++) {
if (indexOf(memberGroups, groups[i]) < 0)
memberGroups = (String[])arrayAdd(memberGroups,groups[i]);
}
tmpArray = new String[memberGroups.length];
System.arraycopy(memberGroups,0,tmpArray,0,memberGroups.length);
}//end sync
/* Replace the groups remotely - no remote calls in sync block*/
lookupProxy.setMemberGroups(tmpArray);
synchronized (multicastAnnouncementThread) {
multicastAnnouncementThread.notify();
}//end sync
}//end addMemberGroups
public void removeMemberGroups(String[] groups) throws RemoteException {
String[] tmpArray = null;
/* Change the member groups locally, then replace them remotely */
synchronized(lockMemberGroups) {
for (int i=0;i<groups.length;i++) {
int j = indexOf(memberGroups, groups[i]);
if (j >= 0) memberGroups = (String[])arrayDel(memberGroups,j);
}
tmpArray = new String[memberGroups.length];
System.arraycopy(memberGroups,0,tmpArray,0,memberGroups.length);
}//end sync
/* Replace the groups remotely - no remote calls in sync block*/
lookupProxy.setMemberGroups(tmpArray);
synchronized (multicastAnnouncementThread) {
multicastAnnouncementThread.notify();
}//end sync
}//end removeMemberGroups
public void setMemberGroups(String[] groups) throws RemoteException {
String[] tmpArray = null;
/* Change the member groups locally, then replace them remotely */
synchronized(lockMemberGroups) {
memberGroups = (String[])removeDups(groups);
tmpArray = new String[memberGroups.length];
System.arraycopy(memberGroups,0,tmpArray,0,memberGroups.length);
}//end sync
/* Replace the groups remotely - no remote calls in sync block*/
lookupProxy.setMemberGroups(memberGroups);
synchronized (multicastAnnouncementThread) {
multicastAnnouncementThread.notify();
}//end sync
}//end setMemberGroups
public int getUnicastPort() {
synchronized(lockLookupLocator) {
return unicastPort;
}//end sync
}//end getUnicastPort
public void setUnicastPort(int port) throws IOException {
if (port == unicastPort) return;
LookupLocator tmpLocator = null;
synchronized(lockLookupLocator) {
if( ( (port == 0)
&& (unicastRequestThread.port == Constants.discoveryPort))
|| (port == unicastRequestThread.port) )
{
unicastPort = port;
return;
}
/* create a UnicastThread that listens on the new port */
UnicastThread newUnicastRequestThread = new UnicastThread(port);
/* terminate the current UnicastThread listening on the old port */
unicastRequestThread.interrupt();
try {
unicastRequestThread.join();
} catch (InterruptedException e) { }
/* start the UnicastThread listening on the new port */
unicastRequestThread = newUnicastRequestThread;
unicastRequestThread.start();
unicastPort = port;
lookupLocator = QAConfig.getConstrainedLocator(lookupLocator.getHost(),
unicastRequestThread.port);
/* Equality (same port&host ==> lookupLocator.equals(tmpLocator) */
tmpLocator = QAConfig.getConstrainedLocator(lookupLocator.getHost(),
unicastRequestThread.port);
}//end sync
/* Set unicast port remotely - no remote calls in sync block*/
lookupProxy.setLocator(tmpLocator); // also sets unicastPort
synchronized (multicastAnnouncementThread) {
multicastAnnouncementThread.notify();
}//end sync
}//end setUnicastPort
/** Multicast discovery request thread code. */
private class MulticastThread extends Thread {
/** Multicast socket to receive packets */
private final MulticastSocket socket;
/**
* Create a high priority daemon thread. Set up the socket now
* rather than in run, so that we get any exception up front.
*/
public MulticastThread() throws IOException {
super("multicast request");
setDaemon(true);
if (multicastInterfaces != null && multicastInterfaces.length == 0)
{
socket = null;
return;
}
InetAddress requestAddr = Constants.getRequestAddress();
socket = new MulticastSocket(Constants.discoveryPort);
socket.setTimeToLive(
multicastAnnouncementConstraints.getMulticastTimeToLive(
DEFAULT_MULTICAST_TTL));
if (multicastInterfaces != null) {
Level failureLogLevel =
multicastInterfacesSpecified ? Level.WARNING : Level.FINE;
for (int i = 0; i < multicastInterfaces.length; i++) {
try {
socket.setNetworkInterface(multicastInterfaces[i]);
socket.joinGroup(requestAddr);
} catch (IOException e) {
logger.log(
failureLogLevel,
"exception configuring multicast interface", e);
}
}
} else {
// REMIND: tolerate failure here?
socket.joinGroup(requestAddr);
}
}
/** True if thread has been interrupted */
private boolean interrupted = false;
/* This is a workaround for Thread.interrupt not working on
* MulticastSocket.receive on all platforms.
*/
public synchronized void interrupt() {
interrupted = true;
socket.close();
}
public synchronized boolean isInterrupted() {
return interrupted;
}
public void run() {
if (multicastInterfaces != null && multicastInterfaces.length == 0)
{
return;
}
byte[] buf = new byte[
multicastRequestConstraints.getMulticastMaxPacketSize(
DEFAULT_MAX_PACKET_SIZE)];
DatagramPacket dgram = new DatagramPacket(buf, buf.length);
while (!isInterrupted()) {
try {
dgram.setLength(buf.length);
try {
socket.receive(dgram);
} catch (NullPointerException e) {
break; // workaround for bug 4190513
}
int pv;
try {
pv = ByteBuffer.wrap(dgram.getData(),
dgram.getOffset(),
dgram.getLength()).getInt();
} catch (BufferUnderflowException e) {
throw new DiscoveryProtocolException(null, e);
}
multicastRequestConstraints.checkProtocolVersion(pv);
MulticastRequest req =
getDiscovery(pv).decodeMulticastRequest(
dgram,
multicastRequestConstraints.
getUnfulfilledConstraints(),
multicastRequestSubjectChecker);
if ((req.getGroups().length != 0 &&
!overlap(memberGroups, req.getGroups())) ||
indexOf(req.getServiceIDs(), lookupServiceID) >= 0)
continue;
logger.log(Level.FINE, "Received valid multicast for " + lookupLocator);
taskMgr.addIfNew(
new AddressTask(InetAddress.getByName(req.getHost()),
req.getPort()));
} catch (InterruptedIOException ignore) {
break;
} catch (DiscoveryProtocolException ignore) {
break;
} catch (Exception e) {
if (isInterrupted()) {
break;
}
logger.log(Level.FINE,
"exception receiving multicast request", e);
}
}
socket.close();
}
}
/** Unicast discovery request thread code. */
private class UnicastThread extends Thread {
/** Server socket to accepts connections on. */
private ServerSocket listen;
/** Listen port */
public int port;
/**
* Create a daemon thread. Set up the socket now rather than in run,
* so that we get any exception up front.
*/
public UnicastThread(int port) throws IOException {
super("unicast request");
setDaemon(true);
if (port == 0) {
try {
listen = new ServerSocket(Constants.discoveryPort);
} catch (IOException e) {
logger.log(Level.FINE,
"failed to bind to default port", e);
}
}
if (listen == null) {
listen = new ServerSocket(port);
}
this.port = listen.getLocalPort();
}
/** True if thread has been interrupted */
private boolean interrupted = false;
/* This is a workaround for Thread.interrupt not working on
* ServerSocket.accept on all platforms. ServerSocket.close
* can't be used as a workaround, because it also doesn't work
* on all platforms.
*/
public synchronized void interrupt() {
interrupted = true;
try {
(new Socket(InetAddress.getLocalHost(), port)).close();
} catch (IOException e) {
}
}
public synchronized boolean isInterrupted() {
return interrupted;
}
public void run() {
while (!isInterrupted()) {
try {
Socket socket = listen.accept();
if (isInterrupted()) {
try {
socket.close();
} catch (IOException e) {
logger.log(Level.FINE,
"exception closing socket", e);
}
break;
}
logger.log(Level.FINE, "Adding socket task for " + lookupLocator);
taskMgr.add(new SocketTask(socket));
} catch (InterruptedIOException e) {
break;
} catch (Exception e) {
logger.log(Level.FINE, "exception listening on socket", e);
}
/* if we fail in any way, just forget about it */
}
try {
listen.close();
} catch (IOException e) {
}
}
}
/** Multicast discovery announcement thread code. */
private class AnnounceThread extends Thread {
/** Multicast socket to send packets on */
private final MulticastSocket socket;
private boolean interrupted = false;
/* This is a workaround for Thread.interrupt not working due
* to the logging system sometimes throwing away InterruptedIOException
*/
public synchronized void interrupt() {
interrupted = true;
super.interrupt();
}
public synchronized boolean isInterrupted() {
return interrupted;
}
/**
* Create a daemon thread. Set up the socket now rather than in run,
* so that we get any exception up front.
*/
public AnnounceThread() throws IOException {
super("discovery announcement");
setDaemon(true);
if (multicastInterfaces == null || multicastInterfaces.length > 0)
{
socket = new MulticastSocket();
socket.setTimeToLive(
multicastAnnouncementConstraints.getMulticastTimeToLive(
DEFAULT_MULTICAST_TTL));
} else {
socket = null;
}
}
public synchronized void run() {
if (multicastInterfaces != null && multicastInterfaces.length == 0)
{
return;
}
try {
while (!isInterrupted() && announce(memberGroups)) {
wait(multicastAnnouncementInterval);
}
} catch (InterruptedException e) {
}
// disable this to allow simulation of disappearance of multicast announcements
// if (memberGroups.length > 0)
// announce(new String[0]);//send NO_GROUPS just before shutdown
socket.close();
}
/**
* Announce membership in the specified groups, and return false if
* interrupted, otherwise return true.
*/
private boolean announce(String[] groups) {
// REMIND: cache latest announcement to skip re-encoding
List packets = new ArrayList();
Discovery disco;
try {
disco = getDiscovery(
multicastAnnouncementConstraints.chooseProtocolVersion());
} catch (DiscoveryProtocolException e) {
throw new AssertionError(e);
}
EncodeIterator ei = disco.encodeMulticastAnnouncement(
new MulticastAnnouncement(System.currentTimeMillis(),
lookupLocator.getHost(),
lookupLocator.getPort(),
groups,
lookupServiceID),
multicastAnnouncementConstraints.getMulticastMaxPacketSize(
DEFAULT_MAX_PACKET_SIZE),
multicastAnnouncementConstraints.getUnfulfilledConstraints());
while (ei.hasNext()) {
try {
packets.addAll(Arrays.asList(ei.next()));
} catch (Exception e) {
// UnsupportedConstraintException is expected and normal
if (! (e instanceof UnsupportedConstraintException)) {
logger.log(Level.INFO,
"exception encoding multicast announcement", e);
}
}
}
try {
logger.log(Level.FINE, "Sending packets from " + lookupLocator);
send((DatagramPacket[])
packets.toArray(new DatagramPacket[packets.size()]));
synchronized(lockNAnnouncements) {
nAnnouncements++;
}
} catch (InterruptedIOException e) {
return false;
} catch (IOException e) {
logger.log(Level.INFO,
"exception sending multicast announcement", e);
}
return true;
}
/**
* Attempts to multicast the given packets on each of the configured
* network interfaces.
*/
private void send(DatagramPacket[] packets)
throws InterruptedIOException
{
if (multicastInterfaces != null) {
Level failureLogLevel =
multicastInterfacesSpecified ? Level.WARNING : Level.FINE;
for (int i = 0; i < multicastInterfaces.length; i++) {
try {
socket.setNetworkInterface(multicastInterfaces[i]);
send(packets, failureLogLevel);
} catch (SocketException e) {
logger.log(failureLogLevel,
"exception setting interface", e);
}
}
} else {
send(packets, Level.WARNING);
}
}
/**
* Attempts to multicast the given packets on the currently set network
* interface, logging failures at the specified logging level.
*/
private void send(DatagramPacket[] packets, Level failureLogLevel)
throws InterruptedIOException
{
for (int i = 0; i < packets.length; i++) {
try {
socket.send(packets[i]);
} catch (InterruptedIOException e) {
throw e;
} catch (IOException e) {
logger.log(failureLogLevel, "exception sending packet", e);
}
}
}
}
/** Returns Discovery instance implementing the given protocol version */
private Discovery getDiscovery(int version)
throws DiscoveryProtocolException
{
switch (version) {
case Discovery.PROTOCOL_VERSION_1:
return Discovery.getProtocol1();
case Discovery.PROTOCOL_VERSION_2:
return protocol2;
default:
throw new DiscoveryProtocolException(
"unsupported protocol version: " + version);
}
}
private final class AddressTask implements TaskManager.Task {
/** The address */
public final InetAddress addr;
/** The port */
public final int port;
/** Simple constructor */
public AddressTask(InetAddress addr, int port) {
this.addr = addr;
this.port = port;
}
public int hashCode() {
return addr.hashCode();
}
/** Two tasks are equal if they have the same address and port */
public boolean equals(Object obj) {
if (!(obj instanceof AddressTask))
return false;
AddressTask ua = (AddressTask)obj;
return addr.equals(ua.addr) && port == ua.port;
}
/** Connect and then process a unicast discovery request */
public void run() {
try {
logger.log(Level.FINE, "Responding to multicast with unicast from " + lookupLocator);
respond(new Socket(addr, port));
} catch (IOException e) {
} catch (SecurityException e) {
}
}
/** No ordering */
public boolean runAfter(List tasks, int size) {
return false;
}
}//end class AddressTask
/** Socket for unicast discovery response. */
private final class SocketTask implements TaskManager.Task {
/** The socket */
public final Socket socket;
/** Simple constructor */
public SocketTask(Socket socket) {
this.socket = socket;
}
/** Process a unicast discovery request */
public void run() {
respond(socket);
}
/** No ordering */
public boolean runAfter(List tasks, int size) {
return false;
}
}//end class SocketTask
/** Process a unicast discovery request, and respond. */
private void respond(Socket socket) {
try {
socket.setSoTimeout(
unicastDiscoveryConstraints.getUnicastSocketTimeout(
DEFAULT_SOCKET_TIMEOUT));
int pv = new DataInputStream(socket.getInputStream()).readInt();
unicastDiscoveryConstraints.checkProtocolVersion(pv);
getDiscovery(pv).handleUnicastDiscovery(
new UnicastResponse(lookupLocator.getHost(),
lookupLocator.getPort(),
memberGroups,
lookupProxy),
socket,
unicastDiscoveryConstraints.getUnfulfilledConstraints(),
unicastDiscoverySubjectChecker,
Collections.EMPTY_LIST);
logger.log(Level.FINE, "Responded to unicast request for " + lookupLocator);
} catch (Exception e) {
try {
if (InetAddress.getLocalHost().equals(socket.getInetAddress())) {
logger.log(Level.FINE,
"exception handling unicast discovery from "
+ socket.getInetAddress() + ":"
+ socket.getPort(),
e);
} else {
logger.log(Level.FINE,
"Ignoring spurious request packet from "
+ socket.getInetAddress());
}
} catch (UnknownHostException ue) {
logger.log(Level.SEVERE, "Unknown host!", ue);
}
} finally {
try {
socket.close();
} catch (IOException e) {
logger.log(Level.FINE, "exception closing socket", e);
}
}
}
/** Close any sockets that were sitting in the task queue. */
private void closeRequestSockets(ArrayList tasks) {
for (int i = tasks.size(); --i >= 0; ) {
Object obj = tasks.get(i);
if (obj instanceof SocketTask) {
try {
((SocketTask)obj).socket.close();
} catch (IOException e) {
}
}
}
}//end closeRequestSockets
/** Return a new array containing the elements of the given array
* plus the given element added to the end.
*/
private static Object[] arrayAdd(Object[] array, Object elt) {
int len = array.length;
Object[] narray =
(Object[])Array.newInstance(array.getClass().getComponentType(),
len + 1);
System.arraycopy(array, 0, narray, 0, len);
narray[len] = elt;
return narray;
}//end arrayAdd
/** Return a new array containing all the elements of the given array
* except the one at the specified index.
*/
private static Object[] arrayDel(Object[] array, int i) {
int len = array.length - 1;
Object[] narray =
(Object[])Array.newInstance(array.getClass().getComponentType(),
len);
System.arraycopy(array, 0, narray, 0, i);
System.arraycopy(array, i + 1, narray, i, len - i);
return narray;
}//end ArrayDel
/** Returns the first index of elt in the array, else -1. */
private static int indexOf(Object[] array, Object elt) {
return indexOf(array, array.length, elt);
}//end indexOf
/** Returns the first index of elt in the array if < len, else -1. */
private static int indexOf(Object[] array, int len, Object elt) {
for (int i = 0; i < len; i++) {
if (elt.equals(array[i]))
return i;
}
return -1;
}//end indexOf
/** Return true if some object is an element of both arrays */
private static boolean overlap(Object[] arr1, Object[] arr2) {
for (int i = arr1.length; --i >= 0; ) {
if (indexOf(arr2, arr1[i]) >= 0)
return true;
}
return false;
}//end overlap
/** Weed out duplicates. */
private static Object[] removeDups(Object[] arr) {
for (int i = arr.length; --i >= 0; ) {
if (indexOf(arr, i, arr[i]) >= 0)
arr = arrayDel(arr, i);
}
return arr;
}//end removeDups
/**
* Sends the given packet data on the given <code>MulticastSocket</code>
* through each of the network interfaces corresponding to elements of
* the given array of IP addresses. If the given array of IP addresses
* is <code>null</code> or empty, then the data will be sent through only
* the default network interface.
*
* @param mcSocket the <code>MulticastSocket</code> on which the data
* will be sent
* @param packet <code>DatagramPacket</code> array whose elements are
* the data to send
* @param addresses <code>InetAddress</code> array whose elements
* represent the Internet Protocol (IP) addresses
* corresponding to the network interfaces (NICs)
* through which the multicast data should be sent
*
* @throws java.io.InterruptedIOException
*/
private static void sendPacketByNIC(MulticastSocket mcSocket,
DatagramPacket[] packet,
InetAddress[] addresses)
throws InterruptedIOException
{
if( (addresses != null) && (addresses.length > 0) ) {
for(int i=0;i<addresses.length;i++) {
try {
mcSocket.setInterface(addresses[i]);
} catch(SocketException e) {
continue;//skip to next interface address
}
sendPacket(mcSocket,packet);
}//end loop
} else {//use default interface
sendPacket(mcSocket,packet);
}//endif
}//end sendPacketByNIC
/**
* Sends the given packet data on the given <code>MulticastSocket</code>
* through the network interface that is currently set.
*
* @param mcSocket the <code>MulticastSocket</code> on which the data
* will be sent
* @param packet <code>DatagramPacket</code> array whose elements are
* the data to send
*
* @throws java.io.InterruptedIOException
*/
private static void sendPacket(MulticastSocket mcSocket,
DatagramPacket[] packet)
throws InterruptedIOException
{
for(int i=0;i<packet.length;i++) {
try {
mcSocket.send(packet[i]);
} catch(InterruptedIOException e) {
throw e;
} catch(IOException e) {
}
}//end loop
}//end sendPacket
/**
* Retrieves and parses the <code>-Dnet.jini.discovery.interface</code>
* system property, converting each parsed value to an instance of
* <code>InetAddress</code>, and returning the results of each conversion
* in an array.
*
* @return <code>InetAddress</code> array in which each element represents
* the Internet Protocol (IP) address corresponding to a network
* interface.
*
* @throws java.net.UnknownHostException
*/
private static InetAddress[] getNICAddresses() throws UnknownHostException
{
String str = null;
try {
str = System.getProperty("net.jini.discovery.interface");
} catch (SecurityException e) { /* ignore */ }
if (str == null) return null;
InetAddress[] addrs = null;
String delimiter = ",";
StringTokenizer st = null;
st = new StringTokenizer(str,delimiter);
int n = st.countTokens();
if (n > 0) {
addrs = new InetAddress[n];
for(int i=0;((st.hasMoreTokens())&&(i<n));i++) {
addrs[i] = InetAddress.getByName(st.nextToken());
}
return addrs;
} else {
return addrs;
}
}//end getNICAddresses
/* Note that the QAConfig is named qaConfig here to avoid cut/paste
* screwups pulling davis configuration entries into the code, which
* use the name 'config' for the Configuration object.
*/
private void init(QAConfig qaConfig)
throws ActivationException, IOException
{
String host = System.getProperty("java.rmi.server.hostname");
if (host == null) {
host = InetAddress.getLocalHost().getHostName();
}
thisInetAddress = InetAddress.getByName(host);
unicastRequestThread = new UnicastThread(unicastPort);
lookupLocator = QAConfig.getConstrainedLocator(host, unicastRequestThread.port);
/* start an activatable lookup service simulation */
if (lookupServiceID == null) {
lookupServiceID = lookupProxy.getServiceID();
}
if( (lookupProxy == null) || (lookupServiceID == null) ) {
throw new ActivationException("failure creating lookup service");
}
// the code block was a noop for unicastPort > 0, because
// setUnicastPort does nothing if the argument is unicastPort
// if(unicastPort > 0) {
// /* Change the locator port for this lookup service. */
// setUnicastPort(unicastPort);
// } else {
// /* Port is already set (randomly). need to set only the locator. */
// lookupProxy.setLocator(lookupLocator);
// }//endif
// change to set unconditionally
lookupProxy.setLocator(lookupLocator);
/* add new configration entries from the davis reggie implementation */
Configuration config = qaConfig.getConfiguration();
MethodConstraints discoveryConstraints = null;
try {
try {
multicastInterfaces = (NetworkInterface[]) config.getEntry(
"test", "multicastInterfaces", NetworkInterface[].class);
multicastInterfacesSpecified = true;
} catch (NoSuchEntryException e) {
List l = Collections.list(NetworkInterface.getNetworkInterfaces());
multicastInterfaces = (NetworkInterface[])
l.toArray(new NetworkInterface[l.size()]);
multicastInterfacesSpecified = false;
}
// multicastAnnouncementInterval = Config.getLongEntry(
// config, "test", "multicastAnnouncementInterval",
// multicastAnnouncementInterval, 1, Long.MAX_VALUE);
multicastAnnouncementInterval =
qaConfig.getLongConfigVal("net.jini.discovery.announce", 120000);
discoveryConstraints =
(MethodConstraints) config.getEntry(
"test", "discoveryConstraints",
MethodConstraints.class, null);
if (discoveryConstraints == null) {
discoveryConstraints =
new BasicMethodConstraints(InvocationConstraints.EMPTY);
}
try {
multicastRequestSubjectChecker =
(ClientSubjectChecker) Config.getNonNullEntry(
config, "test", "multicastRequestSubjectChecker",
ClientSubjectChecker.class);
} catch (NoSuchEntryException e) {
// leave null
}
try {
unicastDiscoverySubjectChecker =
(ClientSubjectChecker) Config.getNonNullEntry(
config, "test", "unicastDiscoverySubjectChecker",
ClientSubjectChecker.class);
} catch (NoSuchEntryException e) {
// leave null
}
} catch (ConfigurationException ce) {
throw new RuntimeException("Configuration error", ce);
}
protocol2 = Discovery.getProtocol2(null);
multicastRequestConstraints = DiscoveryConstraints.process(
discoveryConstraints.getConstraints(
DiscoveryConstraints.multicastRequestMethod));
multicastAnnouncementConstraints = DiscoveryConstraints.process(
discoveryConstraints.getConstraints(
DiscoveryConstraints.multicastAnnouncementMethod));
unicastDiscoveryConstraints = DiscoveryConstraints.process(
discoveryConstraints.getConstraints(
DiscoveryConstraints.unicastDiscoveryMethod));
try {
DEFAULT_MULTICAST_TTL = Config.getIntEntry(
config, "multicast", "ttl", 1, 0, 15);
} catch (ConfigurationException ce) {
DEFAULT_MULTICAST_TTL = 1;
}
/* start the discovery-related threads */
multicastRequestThread = new MulticastThread();
multicastAnnouncementThread = new AnnounceThread();
/* start the threads */
unicastRequestThread.start();
multicastRequestThread.start();
multicastAnnouncementThread.start();
}//end init
}//end class DiscoveryProtocolSimulator