All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.ethz.iks.slp.impl.SLPDaemonImpl Maven / Gradle / Ivy

The newest version!
/****************************************************************************
 * Copyright (c) 2005, 2010 Jan S. Rellermeyer, Systems Group,
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *    Jan S. Rellermeyer - initial API and implementation
 *    Markus Alexander Kuppe - enhancements and bug fixes
 * 
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/
package ch.ethz.iks.slp.impl;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import ch.ethz.iks.slp.ServiceLocationException;
import ch.ethz.iks.slp.ServiceType;
import ch.ethz.iks.slp.ServiceURL;

/**
 * the jSLP daemon class. This class is only required, if the peer is configured
 * as a SA and no other SLP daemon is running on the machine. UA-only
 * configurations or distributions that are intended to run on a machine with
 * OpenSLP slpd can be packaged without this class.
 * 
 * @author Jan S. Rellermeyer, ETH Zurich
 * @since 0.6
 */
public final class SLPDaemonImpl implements SLPDaemon {

	/**
	 * thread loop variable.
	 */
	private boolean running = true;

	/**
	 * Map of registered services:
	 * 
	 * String scope -> List of ServiceURLs services.
	 */
	private Map registeredServices = new HashMap();

	/**
	 * Sorted set for disposal of services which lifetimes have expired:
	 * 
	 * Long expirationTimestamp -> ServiceURL service.
	 */
	private SortedMap serviceDisposalQueue = new TreeMap();

	/**
	 * create a new SLPDaemon instance.
	 * 
	 * @param tcpSocket
	 *            the server socket.
	 * @throws Exception
	 *             if something goes wrong.
	 */
	public SLPDaemonImpl() throws Exception {
		new TcpServerThread();
		new ServiceDisposalThread();
		SLPCore.platform.logDebug("jSLP daemon starting ...");
	}

	/**
	 * register a service with the SLP framework. For the scopes, where DAs are
	 * known, the service will be registered with all DAs.
	 * 
	 * @param reg
	 *            the ServiceRegistration.
	 */
	private void registerService(final ServiceRegistration reg) {

		Service service = new Service(reg);

		for (Iterator scopeIter = reg.scopeList.iterator(); scopeIter.hasNext();) {
			String scope = (String) scopeIter.next();
			scope = scope.toLowerCase();
			synchronized (registeredServices) {
				SLPUtils.addValue(registeredServices, scope, service);
			}
			if (reg.url.getLifetime() > ServiceURL.LIFETIME_PERMANENT) {
				synchronized (serviceDisposalQueue) {
					long next = System.currentTimeMillis()
							+ (reg.url.getLifetime() * 1000);
					ArrayList keys = new ArrayList(serviceDisposalQueue
							.keySet());
					for (Iterator iter = keys.iterator(); iter.hasNext();) {
						Object key = iter.next();
						if (serviceDisposalQueue.get(key).equals(reg.url)) {
							serviceDisposalQueue.remove(key);
						}
					}
					serviceDisposalQueue.put(new Long(next), reg.url);
					serviceDisposalQueue.notifyAll();
				}
			}

			SLPCore.platform.logTraceReg("REGISTERED " + reg.url);

			// register the service with all known DAs in the scopes
			List daList = (List) SLPCore.dAs.get(scope);

			// no DA for the scope known ?
			// try to find one
			if ((daList == null || daList.isEmpty()) && !SLPCore.noDiscovery) {
				try {
					SLPCore.daLookup(Arrays
							.asList(new String[] { (String) scope }));

					// wait a short time for incoming replies
					synchronized (SLPCore.dAs) {
						try {
							SLPCore.dAs.wait(SLPCore.CONFIG.getWaitTime());
						} catch (InterruptedException e) {
						}
					}
					daList = (List) SLPCore.dAs.get(scope);
				} catch (ServiceLocationException sle) {
					SLPCore.platform.logError(sle.getMessage(), sle
								.fillInStackTrace());
				}
			}

			if (daList != null && !daList.isEmpty()) {
				final String[] dAs = (String[]) daList
						.toArray(new String[daList.size()]);
				final ServiceRegistration announcement = new ServiceRegistration(
						reg.url, reg.serviceType, reg.scopeList, reg.attList,
						reg.locale);
				announcement.authBlocks = reg.authBlocks;
				for (int i = 0; i < dAs.length; i++) {
					try {
						announceService(dAs[i], announcement);
						SLPCore.platform.logTraceReg("ANNOUNCED "
									+ announcement.url + " to " + dAs[i]);
					} catch (ServiceLocationException e) {
						// remove DA from list
						SLPUtils.removeValueFromAll(SLPCore.dAs, dAs[i]);
						SLPCore.dASPIs.remove(dAs[i]);
						SLPCore.platform.logError(e.getMessage(), e
									.fillInStackTrace());
					}
				}
			}
		}

	}

	/**
	 * deregister a service from the SLP framework. Deregisters from all DAs
	 * within the scopes and from the local service cache.
	 * 
	 * @param dereg
	 *            the service deregistration.
	 * @throws ServiceLocationException
	 */
	private void deregisterService(final ServiceDeregistration dereg)
			throws ServiceLocationException {

		final String[] scopes = (String[]) registeredServices.keySet().toArray(
				new String[registeredServices.size()]);
		for (int i = 0; i < scopes.length; i++) {
			final List tmp = (List) registeredServices.get(scopes[i]);
			final Service[] services = (Service[]) tmp.toArray(new Service[tmp
					.size()]);

			for (int j = 0; j < services.length; j++) {
				if (dereg.url.matches(services[j].url)) {
					List daList = (List) SLPCore.dAs.get(scopes[i].toLowerCase());
					if (daList != null) {
						for (Iterator daIter = daList.iterator(); daIter
								.hasNext();) {
							try {
								String dA = (String) daIter.next();
								dereg.address = InetAddress.getByName(dA);
								dereg.port = SLPCore.SLP_RESERVED_PORT;
								dereg.xid = SLPCore.nextXid();
								if (SLPCore.CONFIG.getSecurityEnabled()) {
									List spiList = (List) SLPCore.dASPIs
											.get(dA);
									dereg.sign(spiList);
								}
								ReplyMessage reply = SLPCore.sendMessage(dereg,
										true);
								if (reply.errorCode != 0) {
									throw new ServiceLocationException(
											(short) reply.errorCode,
											"Error during deregistration: "
													+ reply.errorCode);
								}
							} catch (UnknownHostException uhe) {
								throw new ServiceLocationException(
										ServiceLocationException.NETWORK_ERROR,
										uhe.getMessage());
							}
						}
					}
					synchronized (registeredServices) {
						SLPUtils.removeValue(registeredServices, scopes[i],
								services[j]);
					}
					break;
				}
			}
		}
	}

	/**
	 * all incoming messages are handled here.
	 * 
	 * @param msg
	 *            the message to be processed.
	 * @return the reply if the handled message came in via TCP. Otherwise null
	 *         will be returned.
	 * @throws ServiceLocationException
	 *             for various reasons like authentication failures etc.
	 */
	public ReplyMessage handleMessage(final SLPMessage msg)
			throws ServiceLocationException {
		if (msg == null) {
			return null;
		}

		String via = msg.tcp ? " (tcp)" : " (udp)";

		SLPCore.platform.logTraceMessage("RECEIVED (" + msg.address + ":"
					+ msg.port + ") " + msg.toString() + via);

		ReplyMessage reply = null;

		switch (msg.funcID) {
		case SLPMessage.SRVRQST:
			ServiceRequest req = (ServiceRequest) msg;

			List results = new ArrayList();
			for (Iterator scopes = req.scopeList.iterator(); scopes.hasNext();) {
				String scope = (String) scopes.next();
				List services = (List) registeredServices.get(scope.toLowerCase());
				if (services == null) {
					continue;
				}

				for (Iterator srvs = services.iterator(); srvs.hasNext();) {
					Service service = (Service) srvs.next();
					if (service.url.getServiceType().matches(req.serviceType)) {
						if (req.predicate == null) {
							results.add(service.url);
							continue;
						}
						if (req.predicate.match(service.attributes)) {
							results.add(service.url);
						}
					}
				}
			}

			/*
			 * if there is no result, don't send a reply. This causes the SA to
			 * get the same message at least two more times but the RFC strictly
			 * demands this for multicast requests
			 */
			if (results.size() == 0 && req.multicast) {
				return null;
			}

			reply = new ServiceReply(req, results);

			if (SLPCore.CONFIG.getSecurityEnabled()) {
				((ServiceReply) reply).sign(req.spi);
			}

			return reply;

		case SLPMessage.ATTRRQST:
			AttributeRequest attreq = (AttributeRequest) msg;

			List attResult = new ArrayList();
			for (Iterator scopes = attreq.scopeList.iterator(); scopes
					.hasNext();) {
				String scope = (String) scopes.next();
				List services = (List) registeredServices.get(scope.toLowerCase());
				if (services == null) {
					continue;
				}
				// the request can either be for a ServiceURL or a ServiceType
				Object reqService;
				boolean fullurl = false;
				if (attreq.url.indexOf("//") == -1) {
					reqService = new ServiceType(attreq.url);
				} else {
					fullurl = true;
					reqService = new ServiceURL(attreq.url, 0);
				}

				// if spi is sent, the request must be for a full url and
				// the tag list has to be empty
				if (attreq.spi.equals("")
						|| (fullurl && attreq.tagList.isEmpty())) {
					for (Iterator srvs = services.iterator(); srvs.hasNext();) {
						Service service = (Service) srvs.next();
						if (service.url.matches(reqService)) {
							attResult.addAll(SLPUtils.findMatches(
									attreq.tagList, service.attributes));
						}
					}
				}

			}
			reply = new AttributeReply(attreq, attResult);

			if (SLPCore.CONFIG.getSecurityEnabled()) {
				((AttributeReply) reply).sign(attreq.spi);
			}

			return reply;
		case SLPMessage.SRVTYPERQST:
			ServiceTypeRequest streq = (ServiceTypeRequest) msg;

			ArrayList result = new ArrayList();

			// iterate over scopes
			for (Iterator scopeIter = streq.scopeList.iterator(); scopeIter
					.hasNext();) {

				// iterate over the registered services
				String scope = (String) scopeIter.next();
				List services = ((List) registeredServices
						.get(scope.toLowerCase()));
				if (services == null) {
					continue;
				}
				for (Iterator iter = services.iterator(); iter.hasNext();) {
					Service service = (Service) iter.next();
					ServiceType type = service.url.getServiceType();
					if (streq.namingAuthority.equals("*")
							|| streq.namingAuthority.equals("")
							|| type.getNamingAuthority().equals(
									streq.namingAuthority)) {
						if (!result.contains(type)) {
							result.add(type);
						}
					}
				}
			}
			reply = new ServiceTypeReply(streq, result);

			return reply;

		case SLPMessage.SRVREG:
			registerService((ServiceRegistration) msg);
			reply = new ServiceAcknowledgement(msg, 0);
			return reply;

		case SLPMessage.SRVDEREG:
			deregisterService((ServiceDeregistration) msg);

			reply = new ServiceAcknowledgement(msg, 0);

			return reply;

		case SLPMessage.SRVACK:
			final ReplyMessage rep = (ReplyMessage) msg;
			if (rep.errorCode != 0) {
				SLPCore.platform.logWarning(msg.address
							+ " replied with error code " + rep.errorCode
							+ " (" + rep + ")");
			}
			return null;

		default:
			// this should never happen, message should already cause an
			// exception during parsing
			throw new ServiceLocationException(
					ServiceLocationException.NOT_IMPLEMENTED,
					"The message type " + SLPMessage.getType(msg.funcID)
							+ " is not implemented");
		}

	}

	/**
	 * get informed about a new discovered DA. Registers all services in the
	 * scopes of the new DA.
	 * 
	 * @param advert
	 *            the DA advertisement.
	 */
	public void newDaDiscovered(final DAAdvertisement advert) {
		// so find all services within the scopes of the new DA:
		for (Iterator iter = advert.scopeList.iterator(); iter.hasNext();) {
			String scope = (String) iter.next();
			List services = (List) registeredServices.get(scope.toLowerCase());
			if (services != null) {
				for (Iterator serviceIter = services.iterator(); serviceIter
						.hasNext();) {
					// and try to register it with the new DA
					try {
						Service service = (Service) serviceIter.next();
						ServiceRegistration reg = new ServiceRegistration(
								service.url, service.url.getServiceType(),
								Arrays.asList(new Object[] { scope }), SLPUtils
										.dictToAttrList(service.attributes),
								SLPCore.DEFAULT_LOCALE);
						SLPCore.platform.logDebug("Registering "
									+ service.url + " with new DA "
									+ advert.url);
						announceService(advert.url, reg);
					} catch (ServiceLocationException e) {
						SLPCore.platform.logError(e.getMessage(), e
									.fillInStackTrace());
					}
				}
			}
		}
	}

	/**
	 * register a service with a DA.
	 * 
	 * @param dAAddress
	 *            the IP address of the DA as String
	 * @param reg
	 *            the ServiceRegistration message.
	 * @throws ServiceLocationException
	 *             in case of network errors.
	 */
	private void announceService(final String dAAddress,
			final ServiceRegistration reg) throws ServiceLocationException {
		try {
			reg.address = InetAddress.getByName(dAAddress);
			reg.port = SLPCore.SLP_RESERVED_PORT;
			reg.xid = SLPCore.nextXid();
			if (SLPCore.CONFIG.getSecurityEnabled()) {
				List spiList = (List) SLPCore.dASPIs.get(dAAddress);
				reg.sign(spiList);
			}
			handleMessage(SLPCore.sendMessage(reg, true));
		} catch (UnknownHostException e) {
			SLPCore.platform.logError("Service announcement to "
						+ dAAddress + " failed. ", e.fillInStackTrace());
		}
	}

	/**
	 * TCP server thread.
	 */
	private final class TcpServerThread extends Thread {
		private ServerSocket socket;

		/**
		 * creates and starts a new TCP server thread.
		 * 
		 * @throws IOException
		 *             if socket creation fails.
		 */
		private TcpServerThread() throws IOException {
			socket = new ServerSocket(SLPCore.SLP_PORT, -1, SLPCore.getMyIP());
//			socket = new ServerSocket(SLPCore.SLP_PORT);
			start();
		}

		/**
		 * thread loop.
		 */
		public void run() {
			while (running) {
				try {
					Socket con = socket.accept();
					DataInputStream in = new DataInputStream(
							new BufferedInputStream(con.getInputStream()));
					SLPMessage msg = SLPMessage.parse(con.getInetAddress(), con
							.getPort(), in, true);

					ReplyMessage reply = handleMessage(msg);
					if (reply != null) {
						SLPCore.platform.logTraceMessage("SEND REPLY ("
									+ reply.address + ":" + reply.port + ") "
									+ reply);

						DataOutputStream out = new DataOutputStream(con
								.getOutputStream());
						out.write(reply.getBytes());
						/*
						 * TODO the RFC encourages to keep the connection open
						 * to allow the other side to send multiple requests per
						 * connection. So start a server thread for every
						 * incoming connection instead of closing the connection
						 * after the first request
						 */
						out.close();
					}
					in.close();
					con.close();
				} catch (Exception ioe) {
					SLPCore.platform.logError(
								"Exception in TCP receiver thread", ioe);
				}
			}
		}
	}

	/**
	 * service disposal thread. Removes services from the local registry when
	 * their lifetime has expired.
	 */
	private final class ServiceDisposalThread extends Thread {

		/**
		 * create and start a new instance of this thread.
		 * 
		 */
		private ServiceDisposalThread() {
			start();
		}

		/**
		 * thread's main loop.
		 */
		public void run() {
			try {
				while (running) {
					synchronized (serviceDisposalQueue) {
						if (serviceDisposalQueue.isEmpty()) {
							// nothing to do, sleep until something arrives
							SLPCore.platform
										.logDebug("ServiceDisposalThread sleeping ...");
							serviceDisposalQueue.wait();
						} else {
							// we have work, do everything that is due
							Long nextActivity;
							while (!serviceDisposalQueue.isEmpty()
									&& (nextActivity = ((Long) serviceDisposalQueue
											.firstKey())).longValue() <= System
											.currentTimeMillis()) {
								ServiceURL service = (ServiceURL) serviceDisposalQueue
										.get(nextActivity);

								ServiceDeregistration dereg = new ServiceDeregistration(
										service, null, null,
										SLPCore.DEFAULT_LOCALE);
								try {
									deregisterService(dereg);
								} catch (ServiceLocationException sle) {
									SLPCore.platform.logError(sle
												.getMessage(), sle
												.fillInStackTrace());
								}
								SLPCore.platform
											.logTraceReg("disposed service "
													+ service);
								serviceDisposalQueue.remove(nextActivity);
							}
							if (!serviceDisposalQueue.isEmpty()) {
								/*
								 * there are some activities in the future,
								 * sleep until the first activity becomes due
								 */
								nextActivity = ((Long) serviceDisposalQueue
										.firstKey());
								long waitTime = nextActivity.longValue()
										- System.currentTimeMillis();
								if (waitTime > 0) {
									SLPCore.platform
												.logDebug("sleeping for "
														+ waitTime / 1000
														+ " seconds.");
									serviceDisposalQueue.wait(waitTime);
								}
							}
						}
					}
				}
			} catch (InterruptedException ie) {
				// let the thread stop.
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy