/*
* Copyright (c) 2012 Alexander Diener,
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.fhkn.in.uce.connectivitymanager.manager.target;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.fhkn.in.uce.connectivitymanager.connection.configuration.ConnectionConfiguration;
import de.fhkn.in.uce.connectivitymanager.investigator.InfrastructreInvestigator;
import de.fhkn.in.uce.connectivitymanager.investigator.InfrastructureInvestigatorImpl;
import de.fhkn.in.uce.connectivitymanager.manager.ConnectionEstablishment;
import de.fhkn.in.uce.connectivitymanager.mediatorconnection.MediatorConnection;
import de.fhkn.in.uce.connectivitymanager.registry.NATTraversalRegistry;
import de.fhkn.in.uce.connectivitymanager.registry.NATTraversalRegistryImpl;
import de.fhkn.in.uce.plugininterface.NATBehavior;
import de.fhkn.in.uce.plugininterface.NATTraversalTechnique;
import de.fhkn.in.uce.plugininterface.message.NATAttributeTypeDecoder;
import de.fhkn.in.uce.plugininterface.message.NATTraversalTechniqueAttribute;
import de.fhkn.in.uce.stun.header.STUNMessageMethod;
import de.fhkn.in.uce.stun.message.Message;
import de.fhkn.in.uce.stun.message.MessageReader;
import de.fhkn.in.uce.stun.util.MessageFormatException;
public final class UnsecureTargetSideConnectionEstablishment implements ConnectionEstablishment {
private final Logger logger = LoggerFactory.getLogger(UnsecureTargetSideConnectionEstablishment.class);
private final NATTraversalRegistry registry;
private final InfrastructreInvestigator investigator;
private final MediatorConnection mediatorConnection;
public UnsecureTargetSideConnectionEstablishment() throws Exception {
this.registry = NATTraversalRegistryImpl.getInstance();
this.investigator = new InfrastructureInvestigatorImpl();
this.mediatorConnection = new MediatorConnection();
}
@Override
public Socket establishConnection(final String targetId, final ConnectionConfiguration config) {
try {
this.register(targetId);
final Socket socketToSource = this.startTarget(targetId);
logger.debug("Returning socket {} to cm", socketToSource.toString()); //$NON-NLS-1$
this.mediatorConnection.close();
return socketToSource;
} catch (final Exception e) {
final String errorMessage = "Exception while creating target-side connection"; //$NON-NLS-1$
this.logger.error(errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
}
private void register(final String targetId) throws Exception {
final List<NATTraversalTechnique> supportedTraversalTechniques = this.registry
.getAllSupportedNATTraversalTechniques();
final List<NATTraversalTechniqueAttribute> travTechAttributes = new ArrayList<NATTraversalTechniqueAttribute>();
for (final NATTraversalTechnique supportedTraversalTechnique : supportedTraversalTechniques) {
travTechAttributes.add(supportedTraversalTechnique.getMetaData().getAttribute());
}
final NATBehavior ownNatBehavior = this.investigator.investigateOwnNat(this.mediatorConnection
.getControlConnection().getLocalPort());
this.mediatorConnection.registerTarget(targetId, ownNatBehavior, travTechAttributes);
}
private Socket startTarget(final String targetId) {
Socket result = null;
boolean checkEstTask = false;
final ExecutorService waitingExecutor = Executors.newSingleThreadExecutor();
final CompletionService<Message> complServiceConnReq = new ExecutorCompletionService<Message>(waitingExecutor);
complServiceConnReq.submit(new ConnectionRequestWaiting(this.mediatorConnection.getControlConnection()));
CompletionService<Socket> complServiceConnEst = null;
while (true) {
final Future<Message> waitingResult = complServiceConnReq.poll();
if (null != waitingResult) {
try {
final Message connRequest = waitingResult.get();
final NATTraversalTechnique usedNatTraversalTechnique = this
.getUsedTraversalTechniqueFromMessage(connRequest);
final ExecutorService estabExecutor = Executors.newSingleThreadExecutor();
complServiceConnEst = new ExecutorCompletionService<Socket>(estabExecutor);
logger.debug(
"Starting target-side with {}", usedNatTraversalTechnique.getMetaData().getTraversalTechniqueName()); //$NON-NLS-1$
complServiceConnEst.submit(new ConnectionEstablishmentTask(usedNatTraversalTechnique, targetId,
this.mediatorConnection.getControlConnection(), connRequest));
checkEstTask = true;
} catch (final Exception e) {
logger.equals(e.getMessage());
}
}
if (checkEstTask) {
final Future<Socket> estResult = complServiceConnEst.poll();
if (null != estResult) {
logger.debug("Connection establishing result is not null, returning socket"); //$NON-NLS-1$
try {
result = estResult.get();
} catch (final Exception e) {
logger.error(e.getMessage());
}
break;
}
}
}
waitingExecutor.shutdownNow();
return result;
}
private NATTraversalTechnique getUsedTraversalTechniqueFromMessage(final Message message) throws Exception {
NATTraversalTechnique result = null;
if (message.hasAttribute(NATTraversalTechniqueAttribute.class)) {
final NATTraversalTechniqueAttribute usedTechAttr = message
.getAttribute(NATTraversalTechniqueAttribute.class);
result = this.registry.getNATTraversalTechniqueByEncoding(usedTechAttr.getEncoded());
} else {
throw new MessageFormatException("NATTraversalTechniqueAttriute not included in message"); //$NON-NLS-1$
}
return result;
}
/**
* Tries to establish a connection via the given
* {@link NATTraversalTechnique}.
*
* @author Alexander Diener (aldiener@htwg-konstanz.de)
*
*/
private final class ConnectionEstablishmentTask implements Callable<Socket> {
final NATTraversalTechnique travTech;
final String targetId;
final Socket controlConnection;
final Message connectionRequestMessage;
/**
* Creates a {@link ConnectionEstablishmentTask} and tries to establish
* a connection.
*
* @param travTechToUse
* the {@link NATTraversalTechnique} to use
* @param targetId
* the id of the target
* @param controlConnection
* the connection to the mediator
* @param connectionRequestMessage
* the connection request message for that connection
* establishment
*/
public ConnectionEstablishmentTask(final NATTraversalTechnique travTechToUse, final String targetId,
final Socket controlConnection, final Message connectionRequestMessage) {
this.travTech = travTechToUse;
this.targetId = targetId;
this.controlConnection = controlConnection;
this.connectionRequestMessage = connectionRequestMessage;
}
@Override
public Socket call() throws Exception {
logger.debug(
"Trying to establish connection via {}", this.travTech.getMetaData().getTraversalTechniqueName()); //$NON-NLS-1$
return this.travTech.createTargetSideConnection(this.targetId, this.controlConnection,
this.connectionRequestMessage);
}
}
/**
* Waits for connection request messages and returns the decoded
* {@link Message}.
*
* @author Alexander Diener (aldiener@htwg-konstanz.de)
*
*/
private final class ConnectionRequestWaiting implements Callable<Message> {
private final Socket controlConnection;
/**
* Creats a {@link ConnectionRequestWaiting} object which uses the given
* control connection to read and return connection request messages.
*
* @param controlConnection
* the control connection to the mediator
*/
public ConnectionRequestWaiting(final Socket controlConnection) {
this.controlConnection = controlConnection;
}
@Override
public Message call() throws Exception {
Message result = null;
final MessageReader messageReader = MessageReader
.createMessageReaderWithCustomAttributeTypeDecoder(new NATAttributeTypeDecoder());
UnsecureTargetSideConnectionEstablishment.this.logger.debug(
"waiting for connection request message from {}", this.controlConnection.toString()); //$NON-NLS-1$
while (this.controlConnection.isConnected()) {
final Message inMessage = messageReader.readSTUNMessage(this.controlConnection.getInputStream());
if (inMessage.isMethod(STUNMessageMethod.CONNECTION_REQUEST)) {
UnsecureTargetSideConnectionEstablishment.this.logger.debug("Got connection request message"); //$NON-NLS-1$
result = inMessage;
break;
}
}
return result;
}
}
}