/*
* Copyright (c) 2007-2009, 2011, 2012 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.net4j.internal.tcp;
import org.eclipse.net4j.Net4jUtil;
import org.eclipse.net4j.buffer.IBuffer;
import org.eclipse.net4j.channel.ChannelException;
import org.eclipse.net4j.connector.ConnectorException;
import org.eclipse.net4j.internal.tcp.bundle.OM;
import org.eclipse.net4j.internal.tcp.messages.Messages;
import org.eclipse.net4j.protocol.IProtocol;
import org.eclipse.net4j.util.ImplementationError;
import org.eclipse.net4j.util.concurrent.ConcurrencyUtil;
import org.eclipse.net4j.util.concurrent.ISynchronizer;
import org.eclipse.net4j.util.concurrent.SynchronizingCorrelator;
import org.eclipse.net4j.util.concurrent.TimeoutRuntimeException;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import org.eclipse.net4j.util.security.INegotiationContext;
import org.eclipse.net4j.util.security.INegotiationContext.Receiver;
import org.eclipse.internal.net4j.buffer.BufferUtil;
import org.eclipse.spi.net4j.Channel;
import org.eclipse.spi.net4j.InternalChannel;
import org.eclipse.spi.net4j.InternalChannelMultiplexer;
import java.nio.ByteBuffer;
import java.text.MessageFormat;
/**
* @author Eike Stepper
*/
public class ControlChannel extends Channel
{
public static final short CONTROL_CHANNEL_INDEX = InternalChannelMultiplexer.RESERVED_CHANNEL;
public static final byte OPCODE_NEGOTIATION = 1;
/**
* @deprecated Indicates Net4j version before 4.2. As of 4.2 Net4j uses {@link #OPCODE_REGISTRATION_VERSIONED}.
*/
@Deprecated
public static final byte OPCODE_REGISTRATION = 2;
public static final byte OPCODE_REGISTRATION_ACK = 3;
public static final byte OPCODE_DEREGISTRATION = 4;
public static final byte OPCODE_REGISTRATION_VERSIONED = 5;
private static final String SUCCESS = "Success";
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, ControlChannel.class);
private SynchronizingCorrelator<Short, String> acknowledgements = new SynchronizingCorrelator<Short, String>();
public ControlChannel(TCPConnector connector)
{
setID(CONTROL_CHANNEL_INDEX);
setMultiplexer(connector);
setReceiveExecutor(connector.getConfig().getReceiveExecutor());
setUserID(connector.getUserID());
}
public TCPConnector getConnector()
{
return (TCPConnector)getMultiplexer();
}
public void registerChannel(short channelID, long timeout, IProtocol<?> protocol)
{
if (TRACER.isEnabled())
{
TRACER.format("Registering channel {0} with protocol {1}", channelID, protocol); //$NON-NLS-1$
}
assertValidChannelID(channelID);
ISynchronizer<String> acknowledgement = acknowledgements.correlate(channelID);
int protocolVersion = Net4jUtil.getProtocolVersion(protocol);
String protocolID = Net4jUtil.getProtocolID(protocol);
IBuffer buffer = provideBuffer();
ByteBuffer byteBuffer = buffer.startPutting(CONTROL_CHANNEL_INDEX);
byteBuffer.put(OPCODE_REGISTRATION_VERSIONED);
byteBuffer.putShort(channelID);
byteBuffer.putInt(protocolVersion);
BufferUtil.putString(byteBuffer, protocolID, false);
handleBuffer(buffer);
String error = acknowledgement.get(timeout);
if (error == null)
{
throw new TimeoutRuntimeException(MessageFormat.format(Messages.getString("ControlChannel_0"), timeout)); //$NON-NLS-1$
}
if (error != SUCCESS)
{
throw new ChannelException("Failed to register channel with peer: " + error); //$NON-NLS-1$
}
}
public void deregisterChannel(short channelID)
{
if (TRACER.isEnabled())
{
TRACER.format("Deregistering channel {0}", channelID); //$NON-NLS-1$
}
assertValidChannelID(channelID);
IBuffer buffer = provideBuffer();
ByteBuffer byteBuffer = buffer.startPutting(CONTROL_CHANNEL_INDEX);
byteBuffer.put(OPCODE_DEREGISTRATION);
byteBuffer.putShort(channelID);
handleBuffer(buffer);
}
@Override
public void handleBufferFromMultiplexer(IBuffer buffer)
{
try
{
ByteBuffer byteBuffer = buffer.getByteBuffer();
byte opcode = byteBuffer.get();
switch (opcode)
{
case OPCODE_NEGOTIATION:
{
assertNegotiating();
INegotiationContext negotiationContext = getConnector().getNegotiationContext();
while (negotiationContext == null)
{
ConcurrencyUtil.sleep(20);
negotiationContext = getConnector().getNegotiationContext();
}
Receiver receiver = negotiationContext.getReceiver();
receiver.receiveBuffer(negotiationContext, byteBuffer);
break;
}
case OPCODE_REGISTRATION:
OM.LOG.error("Deprecated opcode: Client should use newer Net4j version"); //$NON-NLS-1$
getConnector().deactivate();
break;
case OPCODE_REGISTRATION_VERSIONED:
{
assertConnected();
short channelID = byteBuffer.getShort();
assertValidChannelID(channelID);
String error = null;
try
{
int protocolVersion = byteBuffer.getInt();
String protocolID = BufferUtil.getString(byteBuffer);
InternalChannel channel = getConnector().inverseOpenChannel(channelID, protocolID, protocolVersion);
if (channel == null)
{
throw new ConnectorException(Messages.getString("ControlChannel_4")); //$NON-NLS-1$
}
}
catch (Exception ex)
{
error = ex.getMessage();
if (TRACER.isEnabled())
{
TRACER.trace("Problem during channel registration", ex); //$NON-NLS-1$
}
}
sendStatus(OPCODE_REGISTRATION_ACK, channelID, error);
break;
}
case OPCODE_DEREGISTRATION:
{
assertConnected();
short channelID = byteBuffer.getShort();
if (channelID == CONTROL_CHANNEL_INDEX)
{
throw new ImplementationError();
}
try
{
getConnector().inverseCloseChannel(channelID);
}
catch (Exception ex)
{
if (TRACER.isEnabled())
{
TRACER.trace("Problem during channel deregistration", ex); //$NON-NLS-1$
}
}
break;
}
case OPCODE_REGISTRATION_ACK:
{
assertConnected();
short channelID = byteBuffer.getShort();
String error = BufferUtil.getString(byteBuffer);
if (error == null)
{
error = SUCCESS;
}
acknowledgements.put(channelID, error);
break;
}
default:
OM.LOG.error("Invalid opcode: " + opcode); //$NON-NLS-1$
getConnector().deactivate();
}
}
finally
{
buffer.release();
}
}
@Override
public String toString()
{
return MessageFormat.format("Channel[Control, {0}]", getLocation()); //$NON-NLS-1$
}
@Override
protected void unregisterFromMultiplexer()
{
// Do nothing
}
private void sendStatus(byte opcode, short channelID, String error)
{
IBuffer buffer = provideBuffer();
ByteBuffer byteBuffer = buffer.startPutting(CONTROL_CHANNEL_INDEX);
byteBuffer.put(opcode);
byteBuffer.putShort(channelID);
BufferUtil.putString(byteBuffer, error, true);
handleBuffer(buffer);
}
private void assertNegotiating()
{
if (!getConnector().isNegotiating())
{
getConnector().deactivate();
throw new IllegalStateException("Connector is not negotiating"); //$NON-NLS-1$
}
}
private void assertConnected()
{
if (!getConnector().isConnected())
{
throw new IllegalStateException("Connector is not connected"); //$NON-NLS-1$
}
}
private void assertValidChannelID(short channelID)
{
if (channelID == CONTROL_CHANNEL_INDEX)
{
throw new IllegalArgumentException("Bad channelID"); //$NON-NLS-1$
}
}
}