diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java index 62f210a112..b01228ce3d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java @@ -159,7 +159,7 @@ public class QueryStringDecoder { if (rawPath != null) { hasPath = true; } else { - rawPath =""; + rawPath = ""; hasPath = false; } // Also take care of cut of things like "http://localhost" diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java index d5b2d42434..b2881b7b2b 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java @@ -164,7 +164,7 @@ public abstract class AbstractSocketSpdyEchoTest { protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor); protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor); - @Test + @Test(timeout = 10000) public void testSpdyEcho() throws Throwable { ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor)); ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); diff --git a/example/src/main/java/io/netty/example/sctp/SctpClientHandler.java b/example/src/main/java/io/netty/example/sctp/SctpClientHandler.java index 561fda3d69..4c0d3b62f4 100644 --- a/example/src/main/java/io/netty/example/sctp/SctpClientHandler.java +++ b/example/src/main/java/io/netty/example/sctp/SctpClientHandler.java @@ -25,7 +25,7 @@ import io.netty.channel.ChannelStateEvent; import io.netty.channel.ExceptionEvent; import io.netty.channel.MessageEvent; import io.netty.channel.SimpleChannelUpstreamHandler; -import io.netty.channel.sctp.SctpPayload; +import io.netty.channel.sctp.SctpFrame; /** * Handler implementation for the echo client. It initiates the message @@ -47,7 +47,7 @@ public class SctpClientHandler extends SimpleChannelUpstreamHandler { */ @Override public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent stateEvent) { - stateEvent.getChannel().write(new SctpPayload(0, 0, ChannelBuffers.wrappedBuffer("SCTP ECHO".getBytes()))); + stateEvent.getChannel().write(new SctpFrame(0, 0, ChannelBuffers.wrappedBuffer("SCTP ECHO".getBytes()))); } @Override diff --git a/transport-sctp/pom.xml b/transport-sctp/pom.xml index c14941d722..2d6a2d8b6e 100644 --- a/transport-sctp/pom.xml +++ b/transport-sctp/pom.xml @@ -35,5 +35,36 @@ netty-transport ${project.version} + + ${project.groupId} + netty-codec + ${project.version} + + + ${project.groupId} + netty-handler + ${project.version} + + + + + + maven-surefire-plugin + 2.7.2 + + + default-test + + + true + + + + + + diff --git a/transport-sctp/src/main/java/com/sun/nio/sctp/MessageInfo.java b/transport-sctp/src/main/java/com/sun/nio/sctp/MessageInfo.java index bab8487f3f..1f7eaccbd0 100644 --- a/transport-sctp/src/main/java/com/sun/nio/sctp/MessageInfo.java +++ b/transport-sctp/src/main/java/com/sun/nio/sctp/MessageInfo.java @@ -32,5 +32,7 @@ public abstract class MessageInfo { public abstract int payloadProtocolID(); public abstract MessageInfo payloadProtocolID(int ppid); public abstract boolean isComplete(); + public abstract boolean isUnordered(); + public abstract MessageInfo unordered(boolean b); } diff --git a/transport-sctp/src/main/java/com/sun/nio/sctp/SctpChannel.java b/transport-sctp/src/main/java/com/sun/nio/sctp/SctpChannel.java index 287c40041b..01ad77c0e3 100644 --- a/transport-sctp/src/main/java/com/sun/nio/sctp/SctpChannel.java +++ b/transport-sctp/src/main/java/com/sun/nio/sctp/SctpChannel.java @@ -16,6 +16,7 @@ package com.sun.nio.sctp; import java.io.IOException; +import java.net.InetAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.spi.AbstractSelectableChannel; @@ -45,6 +46,10 @@ public abstract class SctpChannel extends AbstractSelectableChannel { public abstract SctpChannel bind(SocketAddress local) throws IOException; public abstract boolean connect(SocketAddress remote) throws IOException; public abstract boolean finishConnect() throws IOException; + + public abstract SctpChannel bindAddress(InetAddress inetAddress) throws IOException; + public abstract SctpChannel unbindAddress(InetAddress inetAddress) throws IOException; + public abstract MessageInfo receive(ByteBuffer dst, T attachment, NotificationHandler handler) throws IOException; public abstract int send(ByteBuffer src, MessageInfo messageInfo) throws IOException; } diff --git a/transport-sctp/src/main/java/com/sun/nio/sctp/SctpServerChannel.java b/transport-sctp/src/main/java/com/sun/nio/sctp/SctpServerChannel.java index 772a768825..eaf617e5f7 100644 --- a/transport-sctp/src/main/java/com/sun/nio/sctp/SctpServerChannel.java +++ b/transport-sctp/src/main/java/com/sun/nio/sctp/SctpServerChannel.java @@ -16,6 +16,7 @@ package com.sun.nio.sctp; import java.io.IOException; +import java.net.InetAddress; import java.net.SocketAddress; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.SelectorProvider; @@ -41,5 +42,9 @@ public abstract class SctpServerChannel extends AbstractSelectableChannel { public abstract SctpServerChannel bind(SocketAddress local) throws IOException; public abstract SctpServerChannel bind(SocketAddress local, int backlog) throws IOException; + + public abstract SctpServerChannel bindAddress(InetAddress inetAddress) throws IOException; + public abstract SctpServerChannel unbindAddress(InetAddress inetAddress) throws IOException; + public abstract SctpChannel accept() throws IOException; } diff --git a/transport-sctp/src/main/java/com/sun/nio/sctp/SctpStandardSocketOptions.java b/transport-sctp/src/main/java/com/sun/nio/sctp/SctpStandardSocketOptions.java index f5418a9b31..0cbca3061f 100644 --- a/transport-sctp/src/main/java/com/sun/nio/sctp/SctpStandardSocketOptions.java +++ b/transport-sctp/src/main/java/com/sun/nio/sctp/SctpStandardSocketOptions.java @@ -34,5 +34,18 @@ public class SctpStandardSocketOptions { public static final SctpSocketOption SO_SNDBUF = null; public static class InitMaxStreams { + + public static InitMaxStreams create(int i, int i1) { + return null; + } + + public int maxInStreams() { + return 0; + } + + public int maxOutStreams() { + return 0; + } + } } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpChannelConfig.java b/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpChannelConfig.java index 25d667951d..475ad752ae 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpChannelConfig.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpChannelConfig.java @@ -50,10 +50,9 @@ class DefaultSctpChannelConfig extends DefaultChannelConfig implements SctpChann setSendBufferSize(ConversionUtil.toInt(value)); } else if (key.equals("sctpNoDelay")) { setSctpNoDelay(ConversionUtil.toBoolean(value)); - } else if (key.equals("soLinger")) { - setSoLinger(ConversionUtil.toInt(value)); } else if (key.equals("sctpInitMaxStreams")) { - setInitMaxStreams((InitMaxStreams) value); + final Integer maxInOutStreams = ConversionUtil.toInt(value); + setInitMaxStreams(InitMaxStreams.create(maxInOutStreams, maxInOutStreams)); } else { return false; } @@ -78,24 +77,6 @@ class DefaultSctpChannelConfig extends DefaultChannelConfig implements SctpChann } } - @Override - public int getSoLinger() { - try { - return channel.getOption(SO_LINGER); - } catch (IOException e) { - throw new ChannelException(e); - } - } - - @Override - public void setSoLinger(int soLinger) { - try { - channel.setOption(SO_LINGER, soLinger); - } catch (IOException e) { - throw new ChannelException(e); - } - } - @Override public int getSendBufferSize() { try { diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpServerChannelConfig.java b/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpServerChannelConfig.java index b311b31c0b..56cccf2645 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpServerChannelConfig.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpServerChannelConfig.java @@ -49,7 +49,8 @@ public class DefaultSctpServerChannelConfig extends DefaultServerChannelConfig } if (key.equals("sctpInitMaxStreams")) { - setInitMaxStreams((InitMaxStreams) value); + final Integer maxInOutStreams = ConversionUtil.toInt(value); + setInitMaxStreams(InitMaxStreams.create(maxInOutStreams, maxInOutStreams)); } else if (key.equals("backlog")) { setBacklog(ConversionUtil.toInt(value)); } else { diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpBindAddressEvent.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpBindAddressEvent.java new file mode 100644 index 0000000000..40e71b2a4d --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpBindAddressEvent.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelState; +import io.netty.channel.DownstreamChannelStateEvent; + +import java.net.InetAddress; + +public class SctpBindAddressEvent extends DownstreamChannelStateEvent { + + /** + * Creates a new instance. + */ + public SctpBindAddressEvent(Channel channel, ChannelFuture future, InetAddress localAddress) { + super(channel, future, ChannelState.INTEREST_OPS, localAddress); + } + + @Override + public InetAddress getValue() { + return (InetAddress) super.getValue(); + } +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java index df0b9392ac..0c9231588a 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java @@ -17,6 +17,7 @@ package io.netty.channel.sctp; import com.sun.nio.sctp.Association; import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannelConfig; import io.netty.channel.socket.nio.NioSocketChannelConfig; @@ -58,6 +59,17 @@ public interface SctpChannel extends Channel { */ Set getAllRemoteAddresses(); + /** + * Bind a multi-homing address to the already bound channel + */ + ChannelFuture bindAddress(InetAddress localAddress); + + + /** + * Unbind a multi-homing address from a already established channel + */ + ChannelFuture unbindAddress(InetAddress localAddress); + /** * Get the underlying SCTP association */ diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannelConfig.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannelConfig.java index bf67ec3ac2..798285abcc 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannelConfig.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannelConfig.java @@ -32,8 +32,6 @@ import io.netty.channel.ChannelConfig; * * {@code "sctpNoDelay"}{@link #setSctpNoDelay(boolean)}} * - * {@code "soLinger"}{@link #setSoLinger(int)} - * * {@code "receiveBufferSize"}{@link #setReceiveBufferSize(int)} * * {@code "sendBufferSize"}{@link #setSendBufferSize(int)} @@ -54,16 +52,6 @@ public interface SctpChannelConfig extends ChannelConfig { */ void setSctpNoDelay(boolean sctpNoDelay); - /** - * Gets the {@code SO_LINGER} option. - */ - int getSoLinger(); - - /** - * Sets the {@code SO_LINGER} option. - */ - void setSoLinger(int soLinger); - /** * Gets the {@code SO_SNDBUF} option. */ diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannelImpl.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannelImpl.java index a73e42c4a6..e688963c6c 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannelImpl.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpChannelImpl.java @@ -17,6 +17,7 @@ package io.netty.channel.sctp; import static io.netty.channel.Channels.*; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Collections; @@ -29,7 +30,6 @@ import java.util.concurrent.atomic.AtomicInteger; import com.sun.nio.sctp.Association; -import io.netty.buffer.ChannelBuffer; import io.netty.channel.AbstractChannel; import io.netty.channel.Channel; import io.netty.channel.ChannelFactory; @@ -153,6 +153,20 @@ class SctpChannelImpl extends AbstractChannel implements SctpChannel { } } + @Override + public ChannelFuture bindAddress(InetAddress localAddress) { + ChannelFuture future = future(this); + getPipeline().sendDownstream(new SctpBindAddressEvent(this, future, localAddress)); + return future; + } + + @Override + public ChannelFuture unbindAddress(InetAddress localAddress) { + ChannelFuture future = future(this); + getPipeline().sendDownstream(new SctpUnbindAddressEvent(this, future, localAddress)); + return future; + } + @Override public Association association() { try { @@ -297,8 +311,8 @@ class SctpChannelImpl extends AbstractChannel implements SctpChannel { private int getMessageSize(MessageEvent e) { Object m = e.getMessage(); - if (m instanceof ChannelBuffer) { - return ((ChannelBuffer) m).readableBytes(); + if (m instanceof SctpFrame) { + return ((SctpFrame) m).getPayloadBuffer().readableBytes(); } return 0; } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientChannel.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientChannel.java index 37b03eef73..4f12cf512a 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientChannel.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientChannel.java @@ -41,7 +41,7 @@ final class SctpClientChannel extends SctpChannelImpl { try { underlayingChannel = SctpChannel.open(); } catch (IOException e) { - throw new ChannelException("Failed to open a socket.", e); + throw new ChannelException("Failed to open a sctp channel.", e); } boolean success = false; diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java index a571f4e263..363407f7d5 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpClientPipelineSink.java @@ -19,6 +19,7 @@ import static io.netty.channel.Channels.*; import java.io.IOException; import java.net.ConnectException; +import java.net.InetAddress; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; @@ -98,7 +99,15 @@ class SctpClientPipelineSink extends AbstractChannelSink { } break; case INTEREST_OPS: - channel.worker.setInterestOps(channel, future, ((Integer) value).intValue()); + if (event instanceof SctpBindAddressEvent) { + SctpBindAddressEvent bindAddressEvent = (SctpBindAddressEvent) event; + bindAddress(channel, bindAddressEvent.getFuture(), bindAddressEvent.getValue()); + } else if (event instanceof SctpUnbindAddressEvent) { + SctpUnbindAddressEvent unbindAddressEvent = (SctpUnbindAddressEvent) event; + unbindAddress(channel, unbindAddressEvent.getFuture(), unbindAddressEvent.getValue()); + } else { + channel.worker.setInterestOps(channel, future, ((Integer) value).intValue()); + } break; } } else if (e instanceof MessageEvent) { @@ -125,6 +134,32 @@ class SctpClientPipelineSink extends AbstractChannelSink { } } + private void bindAddress( + SctpClientChannel channel, ChannelFuture future, + InetAddress localAddress) { + try { + channel.channel.bindAddress(localAddress); + future.setSuccess(); + } catch (Throwable t) { + future.setFailure(t); + fireExceptionCaught(channel, t); + } + } + + private void unbindAddress( + SctpClientChannel channel, ChannelFuture future, + InetAddress localAddress) { + try { + channel.channel.unbindAddress(localAddress); + future.setSuccess(); + } catch (Throwable t) { + future.setFailure(t); + fireExceptionCaught(channel, t); + } + } + + + private void connect( final SctpClientChannel channel, final ChannelFuture cf, SocketAddress remoteAddress) { diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpPayload.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpFrame.java similarity index 59% rename from transport-sctp/src/main/java/io/netty/channel/sctp/SctpPayload.java rename to transport-sctp/src/main/java/io/netty/channel/sctp/SctpFrame.java index e773a34083..3a12600f4c 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpPayload.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpFrame.java @@ -15,25 +15,36 @@ */ package io.netty.channel.sctp; +import com.sun.nio.sctp.MessageInfo; import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffers; /** */ -public final class SctpPayload { +public final class SctpFrame { private final int streamIdentifier; private final int protocolIdentifier; + private final ChannelBuffer payloadBuffer; + private MessageInfo msgInfo; + /** * Essential data that is being carried within SCTP Data Chunk - * @param streamIdentifier that you want to send the payload * @param protocolIdentifier of payload + * @param streamIdentifier that you want to send the payload * @param payloadBuffer channel buffer */ - public SctpPayload(int streamIdentifier, int protocolIdentifier, ChannelBuffer payloadBuffer) { - this.streamIdentifier = streamIdentifier; + public SctpFrame(int protocolIdentifier, int streamIdentifier, ChannelBuffer payloadBuffer) { this.protocolIdentifier = protocolIdentifier; + this.streamIdentifier = streamIdentifier; + this.payloadBuffer = payloadBuffer; + } + + public SctpFrame(MessageInfo msgInfo, ChannelBuffer payloadBuffer) { + this.msgInfo = msgInfo; + this.streamIdentifier = msgInfo.streamNumber(); + this.protocolIdentifier = msgInfo.payloadProtocolID(); this.payloadBuffer = payloadBuffer; } @@ -53,10 +64,49 @@ public final class SctpPayload { } } + public MessageInfo getMessageInfo() { + return msgInfo; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + SctpFrame sctpFrame = (SctpFrame) o; + + if (protocolIdentifier != sctpFrame.protocolIdentifier) { + return false; + } + + if (streamIdentifier != sctpFrame.streamIdentifier) { + return false; + } + + if (!payloadBuffer.equals(sctpFrame.payloadBuffer)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = streamIdentifier; + result = 31 * result + protocolIdentifier; + result = 31 * result + payloadBuffer.hashCode(); + return result; + } + @Override public String toString() { return new StringBuilder(). - append("SctpPayload{"). + append("SctpFrame{"). append("streamIdentifier="). append(streamIdentifier). append(", protocolIdentifier="). diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationEvent.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationEvent.java index 9835dd24c7..0e209f0430 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationEvent.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationEvent.java @@ -62,4 +62,13 @@ public class SctpNotificationEvent implements ChannelEvent { public Object getValue() { return value; } + + @Override + public String toString() { + return "SctpNotificationEvent{" + + "channel=" + channel + + ", notification=" + notification + + ", value=" + value + + '}'; + } } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java index aa2ecac47f..83f5e53910 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java @@ -23,6 +23,7 @@ import com.sun.nio.sctp.PeerAddressChangeNotification; import com.sun.nio.sctp.SendFailedNotification; import com.sun.nio.sctp.ShutdownNotification; +import io.netty.channel.ChannelPipeline; import io.netty.channel.Channels; /** @@ -31,11 +32,11 @@ import io.netty.channel.Channels; class SctpNotificationHandler extends AbstractNotificationHandler { private final SctpChannelImpl sctpChannel; - private final SctpWorker sctpWorker; + private final ChannelPipeline pipeline; - public SctpNotificationHandler(SctpChannelImpl sctpChannel, SctpWorker sctpWorker) { + SctpNotificationHandler(SctpChannelImpl sctpChannel) { this.sctpChannel = sctpChannel; - this.sctpWorker = sctpWorker; + this.pipeline = sctpChannel.getPipeline(); } @Override @@ -44,12 +45,6 @@ class SctpNotificationHandler extends AbstractNotificationHandler { return HandlerResult.CONTINUE; } - @Override - public HandlerResult handleNotification(Notification notification, Object o) { - fireNotificationReceived(notification, o); - return HandlerResult.CONTINUE; - } - @Override public HandlerResult handleNotification(PeerAddressChangeNotification notification, Object o) { fireNotificationReceived(notification, o); @@ -64,11 +59,11 @@ class SctpNotificationHandler extends AbstractNotificationHandler { @Override public HandlerResult handleNotification(ShutdownNotification notification, Object o) { - sctpWorker.close(sctpChannel, Channels.succeededFuture(sctpChannel)); + Channels.fireChannelDisconnected(sctpChannel); return HandlerResult.RETURN; } private void fireNotificationReceived(Notification notification, Object o) { - sctpChannel.getPipeline().sendUpstream(new SctpNotificationEvent(sctpChannel, notification, o)); + pipeline.sendUpstream(new SctpNotificationEvent(sctpChannel, notification, o)); } } diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpSendBufferPool.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpSendBufferPool.java index 07d5adc8fd..382e5b4fc4 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpSendBufferPool.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpSendBufferPool.java @@ -39,15 +39,15 @@ final class SctpSendBufferPool { } SendBuffer acquire(Object message) { - if (message instanceof SctpPayload) { - return acquire((SctpPayload) message); + if (message instanceof SctpFrame) { + return acquire((SctpFrame) message); } else { throw new IllegalArgumentException( - "unsupported message type: " + message.getClass()); + "unsupported message type: " + message.getClass() + " required: io.netty.channel.sctp.SctpFrame"); } } - private SendBuffer acquire(SctpPayload message) { + private SendBuffer acquire(SctpFrame message) { final ChannelBuffer src = message.getPayloadBuffer(); final int streamNo = message.getStreamIdentifier(); final int protocolId = message.getProtocolIdentifier(); diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannel.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannel.java index 2fb186cb98..3257f080d6 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannel.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannel.java @@ -15,8 +15,10 @@ */ package io.netty.channel.sctp; +import io.netty.channel.ChannelFuture; import io.netty.channel.ServerChannel; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Set; @@ -24,6 +26,17 @@ import java.util.Set; * A SCTP {@link io.netty.channel.ServerChannel} which accepts incoming SCTP connections. */ public interface SctpServerChannel extends ServerChannel { + /** + * Bind a multi-homing address to the already bound channel + */ + ChannelFuture bindAddress(InetAddress localAddress); + + + /** + * Unbind a multi-homing address from a already established channel + */ + ChannelFuture unbindAddress(InetAddress localAddress); + /** * Returns the configuration of this channel. */ diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelImpl.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelImpl.java index 4739e49019..142b1ae83f 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelImpl.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelImpl.java @@ -18,6 +18,7 @@ package io.netty.channel.sctp; import static io.netty.channel.Channels.*; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.Selector; @@ -31,6 +32,7 @@ import java.util.concurrent.locks.ReentrantLock; import io.netty.channel.AbstractServerChannel; import io.netty.channel.ChannelException; import io.netty.channel.ChannelFactory; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelSink; import io.netty.logging.InternalLogger; @@ -62,7 +64,7 @@ class SctpServerChannelImpl extends AbstractServerChannel serverChannel = com.sun.nio.sctp.SctpServerChannel.open(); } catch (IOException e) { throw new ChannelException( - "Failed to open a server socket.", e); + "Failed to open a server sctp channel.", e); } try { @@ -85,6 +87,20 @@ class SctpServerChannelImpl extends AbstractServerChannel fireChannelOpen(this); } + @Override + public ChannelFuture bindAddress(InetAddress localAddress) { + ChannelFuture future = future(this); + getPipeline().sendDownstream(new SctpBindAddressEvent(this, future, localAddress)); + return future; + } + + @Override + public ChannelFuture unbindAddress(InetAddress localAddress) { + ChannelFuture future = future(this); + getPipeline().sendDownstream(new SctpUnbindAddressEvent(this, future, localAddress)); + return future; + } + @Override public SctpServerChannelConfig getConfig() { return config; diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java index 9d21e54633..3a0f86bb16 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpServerPipelineSink.java @@ -18,6 +18,7 @@ package io.netty.channel.sctp; import static io.netty.channel.Channels.*; import java.io.IOException; +import java.net.InetAddress; import java.net.SocketAddress; import java.net.SocketTimeoutException; import java.nio.channels.CancelledKeyException; @@ -94,6 +95,16 @@ class SctpServerPipelineSink extends AbstractChannelSink { } else { close(channel, future); } + case INTEREST_OPS: + if (event instanceof SctpBindAddressEvent) { + SctpBindAddressEvent bindAddressEvent = (SctpBindAddressEvent) event; + bindAddress(channel, bindAddressEvent.getFuture(), bindAddressEvent.getValue()); + } + + if (event instanceof SctpUnbindAddressEvent) { + SctpUnbindAddressEvent unbindAddressEvent = (SctpUnbindAddressEvent) event; + unbindAddress(channel, unbindAddressEvent.getFuture(), unbindAddressEvent.getValue()); + } break; } } @@ -158,6 +169,30 @@ class SctpServerPipelineSink extends AbstractChannelSink { } } + private void bindAddress( + SctpServerChannelImpl channel, ChannelFuture future, + InetAddress localAddress) { + try { + channel.serverChannel.bindAddress(localAddress); + future.setSuccess(); + } catch (Throwable t) { + future.setFailure(t); + fireExceptionCaught(channel, t); + } + } + + private void unbindAddress( + SctpServerChannelImpl channel, ChannelFuture future, + InetAddress localAddress) { + try { + channel.serverChannel.unbindAddress(localAddress); + future.setSuccess(); + } catch (Throwable t) { + future.setFailure(t); + fireExceptionCaught(channel, t); + } + } + private void close(SctpServerChannelImpl channel, ChannelFuture future) { boolean bound = channel.isBound(); try { diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpUnbindAddressEvent.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpUnbindAddressEvent.java new file mode 100644 index 0000000000..f6d7e828bc --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpUnbindAddressEvent.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelState; +import io.netty.channel.DownstreamChannelStateEvent; + +import java.net.InetAddress; + +public class SctpUnbindAddressEvent extends DownstreamChannelStateEvent { + + /** + * Creates a new instance. + */ + public SctpUnbindAddressEvent(Channel channel, ChannelFuture future, InetAddress value) { + super(channel, future, ChannelState.INTEREST_OPS, value); + } + + @Override + public InetAddress getValue() { + return (InetAddress) super.getValue(); + } +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java index 2c9d51980f..a7878d77ba 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/SctpWorker.java @@ -86,7 +86,7 @@ class SctpWorker implements Runnable { boolean server = !(channel instanceof SctpClientChannel); Runnable registerTask = new RegisterTask(channel, future, server); - notificationHandler = new SctpNotificationHandler(channel, this); + notificationHandler = new SctpNotificationHandler(channel); Selector selector; synchronized (startStopLock) { @@ -315,11 +315,11 @@ class SctpWorker implements Runnable { messageInfo = channel.channel.receive(bb, null, notificationHandler); if (messageInfo != null) { messageReceived = true; - if (messageInfo.isComplete()) { + if (!messageInfo.isUnordered()) { failure = false; } else { if (logger.isErrorEnabled()) { - logger.error("Received incomplete sctp packet, can not continue! Expected SCTP_EXPLICIT_COMPLETE message"); + logger.error("Received unordered SCTP Packet"); } failure = true; } @@ -350,9 +350,7 @@ class SctpWorker implements Runnable { // Fire the event. fireMessageReceived(channel, - new SctpPayload(messageInfo.streamNumber(), - messageInfo.payloadProtocolID(), - buffer), + new SctpFrame(messageInfo, buffer), messageInfo.address()); } else { recvBufferPool.release(bb); @@ -771,8 +769,8 @@ class SctpWorker implements Runnable { channel.channel.register( selector, channel.getRawInterestOps(), channel); } + channel.setConnected(); if (future != null) { - channel.setConnected(); future.setSuccess(); } } catch (IOException e) { diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/codec/DefaultInboundStreamFilter.java b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/DefaultInboundStreamFilter.java new file mode 100644 index 0000000000..ae8ae6e35a --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/DefaultInboundStreamFilter.java @@ -0,0 +1,26 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp.codec; + +import io.netty.channel.sctp.SctpChannel; +import io.netty.channel.sctp.SctpFrame; + +public class DefaultInboundStreamFilter implements InboundStreamFilter { + @Override + public boolean filter(SctpChannel sctpChannel, SctpFrame sctpFrame) { + return true; + } +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/codec/DefaultOutboundStreamSelector.java b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/DefaultOutboundStreamSelector.java new file mode 100644 index 0000000000..c712d2052f --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/DefaultOutboundStreamSelector.java @@ -0,0 +1,25 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp.codec; + +import io.netty.channel.sctp.SctpChannel; + +public class DefaultOutboundStreamSelector implements OutboundStreamSelector { + @Override + public int streamIdentifier(SctpChannel sctpChannel, Object msg) { + return 1; + } +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/codec/InboundStreamFilter.java b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/InboundStreamFilter.java new file mode 100644 index 0000000000..a28beb8111 --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/InboundStreamFilter.java @@ -0,0 +1,24 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp.codec; + +import io.netty.channel.sctp.SctpChannel; +import io.netty.channel.sctp.SctpFrame; + +public interface InboundStreamFilter { + + boolean filter(SctpChannel sctpChannel, SctpFrame sctpFrame); +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/codec/OutboundStreamSelector.java b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/OutboundStreamSelector.java new file mode 100644 index 0000000000..188e1eb214 --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/OutboundStreamSelector.java @@ -0,0 +1,22 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp.codec; + +import io.netty.channel.sctp.SctpChannel; + +public interface OutboundStreamSelector { + int streamIdentifier(SctpChannel sctpChannel, Object msg); +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/codec/SctpFrameDecoder.java b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/SctpFrameDecoder.java new file mode 100644 index 0000000000..b3e6a9ca02 --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/SctpFrameDecoder.java @@ -0,0 +1,76 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp.codec; + +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBuffers; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.sctp.SctpChannel; +import io.netty.channel.sctp.SctpFrame; +import io.netty.handler.codec.oneone.OneToOneDecoder; + +/** + * SCTP Frame Decoder which extract payload channel buffer + * Note: Supported SCTP Frame Interleave Level - 0 + */ + +public class SctpFrameDecoder extends OneToOneDecoder { + + private final InboundStreamFilter inboundStreamFilter; + + private volatile ChannelBuffer cumulation; + + public SctpFrameDecoder() { + this.inboundStreamFilter = new DefaultInboundStreamFilter(); + } + + public SctpFrameDecoder(InboundStreamFilter inboundStreamFilter) { + this.inboundStreamFilter = inboundStreamFilter; + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { + if (!(msg instanceof SctpFrame)) { + return msg; + } + final SctpChannel sctpChannel = (SctpChannel) channel; + final SctpFrame sctpFrame = (SctpFrame) msg; + + if (inboundStreamFilter.filter(sctpChannel, sctpFrame)) { + + final boolean complete = sctpFrame.getMessageInfo().isComplete(); + if (complete) { + if (cumulation == null) { + return sctpFrame.getPayloadBuffer(); + } else { + final ChannelBuffer extractedFrame = ChannelBuffers.wrappedBuffer(cumulation, sctpFrame.getPayloadBuffer()); + cumulation = null; + return extractedFrame; + } + } else { + if (cumulation == null) { + cumulation = sctpFrame.getPayloadBuffer(); + } else { + cumulation = ChannelBuffers.wrappedBuffer(cumulation, sctpFrame.getPayloadBuffer()); + } + return null; + } + } else { + return msg; + } + } +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/codec/SctpFrameEncoder.java b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/SctpFrameEncoder.java new file mode 100644 index 0000000000..96dd2b242a --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/SctpFrameEncoder.java @@ -0,0 +1,59 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp.codec; + +import io.netty.buffer.ChannelBuffer; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.sctp.SctpChannel; +import io.netty.channel.sctp.SctpFrame; +import io.netty.handler.codec.oneone.OneToOneEncoder; + +/** + * SCTP Frame Encoder which encode a channel buffer to SctpFrame object + */ +public class SctpFrameEncoder extends OneToOneEncoder { + + private final int protocolIdentifier; + private final OutboundStreamSelector sctpWriteStreamSelector; + + public SctpFrameEncoder() { + this(0); + } + + public SctpFrameEncoder(int protocolIdentifier) { + this(protocolIdentifier, new DefaultOutboundStreamSelector()); + } + + public SctpFrameEncoder(final int protocolIdentifier, final OutboundStreamSelector sctpWriteStreamSelector) { + if (sctpWriteStreamSelector == null) { + throw new NullPointerException("sctpWriteStreamSelector"); + } + this.protocolIdentifier = Math.max(0, protocolIdentifier); + this.sctpWriteStreamSelector = sctpWriteStreamSelector; + } + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { + if (!(msg instanceof ChannelBuffer)) { + return msg; + } else { + SctpChannel sctpChannel = (SctpChannel) channel; + final int streamIdentifier = sctpWriteStreamSelector.streamIdentifier(sctpChannel, msg); + return new SctpFrame(protocolIdentifier, streamIdentifier, (ChannelBuffer) msg); + } + } +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/codec/package-info.java b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/package-info.java new file mode 100644 index 0000000000..d9fd0907d2 --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/codec/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp.codec; diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpChannelHandler.java b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpChannelHandler.java new file mode 100644 index 0000000000..1a14464999 --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpChannelHandler.java @@ -0,0 +1,50 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp.handler; + +import io.netty.channel.ChannelEvent; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelHandler; +import io.netty.channel.sctp.SctpNotificationEvent; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; + +/** + * SCTP Channel Handler (upstream + downstream) with SCTP notification handling + */ + +public class SimpleSctpChannelHandler extends SimpleChannelHandler { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(SimpleSctpUpstreamHandler.class.getName()); + + + @Override + public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent event) throws Exception { + if (!(event instanceof SctpNotificationEvent)) { + super.handleUpstream(ctx, event); + + } + if (event instanceof SctpNotificationEvent) { + sctpNotificationReceived(ctx, (SctpNotificationEvent) event); + } + } + + public void sctpNotificationReceived(ChannelHandlerContext ctx, SctpNotificationEvent event) { + ctx.sendUpstream(event); + } + +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpDownstreamHandler.java b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpDownstreamHandler.java new file mode 100644 index 0000000000..7a70491a37 --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpDownstreamHandler.java @@ -0,0 +1,24 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp.handler; + +import io.netty.channel.SimpleChannelDownstreamHandler; + +/** + * Sctp Downstream handler for sake of completeness + */ +public class SimpleSctpDownstreamHandler extends SimpleChannelDownstreamHandler { +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpUpstreamHandler.java b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpUpstreamHandler.java new file mode 100644 index 0000000000..cf69c86301 --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/SimpleSctpUpstreamHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.channel.sctp.handler; + +import io.netty.channel.ChannelEvent; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelUpstreamHandler; +import io.netty.channel.sctp.SctpNotificationEvent; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; + +/** + * SCTP Upstream Channel Handler with SCTP notification handling + */ +public class SimpleSctpUpstreamHandler extends SimpleChannelUpstreamHandler { + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(SimpleSctpUpstreamHandler.class.getName()); + + + @Override + public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent event) throws Exception { + if (!(event instanceof SctpNotificationEvent)) { + super.handleUpstream(ctx, event); + + } + if (event instanceof SctpNotificationEvent) { + sctpNotificationReceived(ctx, (SctpNotificationEvent) event); + } + } + + public void sctpNotificationReceived(ChannelHandlerContext ctx, SctpNotificationEvent event) { + ctx.sendUpstream(event); + } +} diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/handler/package-info.java b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/package-info.java new file mode 100644 index 0000000000..790d7ec30b --- /dev/null +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/handler/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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. + */ + +/** + * NIO-based socket channel + * API implementation - recommended for a large number of connections (>= 1000). + */ +package io.netty.channel.sctp.handler; diff --git a/transport-sctp/src/main/java/io/netty/channel/sctp/package-info.java b/transport-sctp/src/main/java/io/netty/channel/sctp/package-info.java index d99e070321..8da61a048b 100644 --- a/transport-sctp/src/main/java/io/netty/channel/sctp/package-info.java +++ b/transport-sctp/src/main/java/io/netty/channel/sctp/package-info.java @@ -15,7 +15,6 @@ */ /** - * NIO-based socket channel - * API implementation - recommended for a large number of connections (>= 1000). + * NIO -based SCTP channel API implementation. */ package io.netty.channel.sctp; diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketClientBootstrapTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketClientBootstrapTest.java new file mode 100644 index 0000000000..e8ca6d7fd7 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketClientBootstrapTest.java @@ -0,0 +1,192 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport; + +import com.sun.nio.sctp.SctpChannel; +import com.sun.nio.sctp.SctpServerChannel; +import io.netty.bootstrap.ClientBootstrap; +import io.netty.channel.ChannelFactory; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelPipelineException; +import io.netty.channel.ChannelPipelineFactory; +import io.netty.channel.sctp.codec.SctpFrameDecoder; +import io.netty.channel.sctp.codec.SctpFrameEncoder; +import io.netty.testsuite.util.DummyHandler; +import io.netty.testsuite.util.SctpSocketAddresses; +import io.netty.util.internal.ExecutorUtil; +import org.easymock.EasyMock; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Iterator; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + + +/** + * An abstract test class to test socket client bootstraps + */ +public abstract class AbstractSocketClientBootstrapTest { + + private static ExecutorService executor; + + @BeforeClass + public static void init() { + executor = Executors.newCachedThreadPool(); + } + + @AfterClass + public static void destroy() { + ExecutorUtil.terminate(executor); + } + + protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor); + + @Test(timeout = 10000) + public void testFailedConnectionAttempt() throws Exception { + ClientBootstrap bootstrap = new ClientBootstrap(); + bootstrap.setFactory(newClientSocketChannelFactory(executor)); + bootstrap.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + bootstrap.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + bootstrap.getPipeline().addLast("dummy", new DummyHandler()); + bootstrap.setOption("remoteAddress", new InetSocketAddress("255.255.255.255", 1)); + ChannelFuture future = bootstrap.connect(); + future.awaitUninterruptibly(); + assertFalse(future.isSuccess()); + assertTrue(future.getCause() instanceof IOException); + } + + @Test(timeout = 10000) + public void testSuccessfulConnectionAttempt() throws Throwable { + SctpServerChannel serverChannel = SctpServerChannel.open(); + serverChannel.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + + try { + serverChannel.configureBlocking(false); + + final Iterator serverAddresses = serverChannel.getAllLocalAddresses().iterator(); + InetSocketAddress serverAddress = (InetSocketAddress) serverAddresses.next(); + int serverPort = serverAddress.getPort(); + + ClientBootstrap bootstrap = + new ClientBootstrap(newClientSocketChannelFactory(executor)); + + bootstrap.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + bootstrap.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + bootstrap.getPipeline().addLast("dummy", new DummyHandler()); + bootstrap.setOption( + "remoteAddress", + new InetSocketAddress( + SctpSocketAddresses.LOOP_BACK, + serverPort)); + + ChannelFuture future = bootstrap.connect(); + serverChannel.accept(); + future.awaitUninterruptibly(); + + if (future.getCause() != null) { + throw future.getCause(); + } + assertTrue(future.isSuccess()); + + future.getChannel().close().awaitUninterruptibly(); + } finally { + try { + serverChannel.close(); + } catch (IOException e) { + // Ignore. + } + } + } + + @Test(timeout = 10000) + public void testSuccessfulConnectionAttemptWithLocalAddress() throws Throwable { + SctpServerChannel serverChannel = SctpServerChannel.open(); + + try { + serverChannel.configureBlocking(false); + serverChannel = serverChannel.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + + final Iterator serverAddresses = serverChannel.getAllLocalAddresses().iterator(); + InetSocketAddress serverAddress = (InetSocketAddress) serverAddresses.next(); + int serverPort = serverAddress.getPort(); + ClientBootstrap bootstrap = + new ClientBootstrap(newClientSocketChannelFactory(executor)); + + bootstrap.getPipeline().addLast("dummy", new DummyHandler()); + bootstrap.setOption( + "remoteAddress", + new InetSocketAddress( + SctpSocketAddresses.LOOP_BACK, + serverPort)); + bootstrap.setOption("localAddress", new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + + ChannelFuture future = bootstrap.connect(); + serverChannel.accept(); + future.awaitUninterruptibly(); + + if (future.getCause() != null) { + throw future.getCause(); + } + assertTrue(future.isSuccess()); + + future.getChannel().close().awaitUninterruptibly(); + } finally { + try { + serverChannel.close(); + } catch (IOException e) { + // Ignore. + } + } + } + + @Test(expected = ChannelPipelineException.class) + public void testFailedPipelineInitialization() throws Exception { + ClientBootstrap bootstrap = new ClientBootstrap(EasyMock.createMock(ChannelFactory.class)); + ChannelPipelineFactory pipelineFactory = EasyMock.createMock(ChannelPipelineFactory.class); + bootstrap.setPipelineFactory(pipelineFactory); + + EasyMock.expect(pipelineFactory.getPipeline()).andThrow(new ChannelPipelineException()); + EasyMock.replay(pipelineFactory); + + bootstrap.connect(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 1)); + } + + @Test(expected = IllegalStateException.class) + public void shouldHaveRemoteAddressOption() { + new ClientBootstrap(EasyMock.createMock(ChannelFactory.class)).connect(); + } + + + @Test(expected = NullPointerException.class) + public void shouldDisallowNullRemoteAddressParameter1() { + new ClientBootstrap(EasyMock.createMock(ChannelFactory.class)).connect(null); + } + + @Test(expected = NullPointerException.class) + public void shouldDisallowNullRemoteAddressParameter2() { + new ClientBootstrap(EasyMock.createMock(ChannelFactory.class)).connect(null, null); + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketCompatibleObjectStreamEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketCompatibleObjectStreamEchoTest.java new file mode 100644 index 0000000000..eb2f2d424d --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketCompatibleObjectStreamEchoTest.java @@ -0,0 +1,190 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport; + +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.sctp.codec.SctpFrameDecoder; +import io.netty.channel.sctp.codec.SctpFrameEncoder; +import io.netty.handler.codec.serialization.CompatibleObjectDecoder; +import io.netty.handler.codec.serialization.CompatibleObjectEncoder; +import io.netty.testsuite.util.SctpSocketAddresses; +import io.netty.util.internal.ExecutorUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractSocketCompatibleObjectStreamEchoTest { + + static final Random random = new Random(); + static final String[] data = new String[512];//could not test with jumbo sctp frame + + private static ExecutorService executor; + + static { + for (int i = 0; i < data.length; i ++) { + int eLen = random.nextInt(512); + char[] e = new char[eLen]; + for (int j = 0; j < eLen; j ++) { + e[j] = (char) ('a' + random.nextInt(26)); + } + + data[i] = new String(e); + } + } + + @BeforeClass + public static void init() { + executor = Executors.newCachedThreadPool(); + } + + @AfterClass + public static void destroy() { + ExecutorUtil.terminate(executor); + } + + protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor); + protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor); + + @Test + @SuppressWarnings("deprecation") + public void testCompatibleObjectEcho() throws Throwable { + ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor)); + ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); + + EchoHandler sh = new EchoHandler(); + EchoHandler ch = new EchoHandler(); + + sb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + sb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + sb.getPipeline().addLast("decoder", new CompatibleObjectDecoder()); + sb.getPipeline().addLast("encoder", new CompatibleObjectEncoder()); + sb.getPipeline().addLast("handler", sh); + + cb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + cb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + cb.getPipeline().addLast("decoder", new CompatibleObjectDecoder()); + cb.getPipeline().addLast("encoder", new CompatibleObjectEncoder()); + cb.getPipeline().addLast("handler", ch); + + Channel sc = sb.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + int port = ((InetSocketAddress) sc.getLocalAddress()).getPort(); + + ChannelFuture ccf = cb.connect(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, port)); + assertTrue(ccf.awaitUninterruptibly().isSuccess()); + + Channel cc = ccf.getChannel(); + for (String element : data) { + cc.write(element); + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore. + } + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore. + } + } + + sh.channel.close().awaitUninterruptibly(); + ch.channel.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleChannelUpstreamHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler() { + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + channel = e.getChannel(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + + String m = (String) e.getMessage(); + assertEquals(data[counter], m); + + if (channel.getParent() != null) { + channel.write(m); + } + + counter ++; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + if (exception.compareAndSet(null, e.getCause())) { + e.getChannel().close(); + } + } + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketEchoTest.java new file mode 100644 index 0000000000..90821601e1 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketEchoTest.java @@ -0,0 +1,186 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport; + +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBuffers; +import io.netty.channel.*; +import io.netty.channel.sctp.codec.SctpFrameDecoder; +import io.netty.channel.sctp.codec.SctpFrameEncoder; +import io.netty.testsuite.util.SctpSocketAddresses; +import io.netty.util.internal.ExecutorUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractSocketEchoTest { + + private static final Random random = new Random(); + static final byte[] data = new byte[4096];//could not test ultra jumbo frames + + private static ExecutorService executor; + + static { + random.nextBytes(data); + } + + @BeforeClass + public static void init() { + executor = Executors.newCachedThreadPool(); + } + + @AfterClass + public static void destroy() { + ExecutorUtil.terminate(executor); + } + + protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor); + protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor); + + @Test + public void testSimpleEcho() throws Throwable { + ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor)); + + ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); + + + EchoHandler sh = new EchoHandler(); + EchoHandler ch = new EchoHandler(); + + sb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + sb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + sb.getPipeline().addLast("handler", sh); + + cb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + cb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + cb.getPipeline().addLast("handler", ch); + + Channel sc = sb.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + int port = ((InetSocketAddress) sc.getLocalAddress()).getPort(); + + ChannelFuture ccf = cb.connect(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, port)); + assertTrue(ccf.awaitUninterruptibly().isSuccess()); + + Channel cc = ccf.getChannel(); + for (int i = 0; i < data.length;) { + int length = Math.min(random.nextInt(1024 * 64), data.length - i); + cc.write(ChannelBuffers.wrappedBuffer(data, i, length)); + i += length; + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(5); + } catch (InterruptedException e) { + // Ignore. + } + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(5); + } catch (InterruptedException e) { + // Ignore. + } + } + + sh.channel.close().awaitUninterruptibly(); + ch.channel.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleChannelUpstreamHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler() { + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + channel = e.getChannel(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + ChannelBuffer m = (ChannelBuffer) e.getMessage(); + byte[] actual = new byte[m.readableBytes()]; + m.getBytes(0, actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i ++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + if (channel.getParent() != null) { + channel.write(m); + } + + counter += actual.length; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + if (exception.compareAndSet(null, e.getCause())) { + e.getChannel().close(); + } + } + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketFixedLengthEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketFixedLengthEchoTest.java new file mode 100644 index 0000000000..697d694bb1 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketFixedLengthEchoTest.java @@ -0,0 +1,190 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport; + +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBuffers; +import io.netty.channel.*; +import io.netty.channel.sctp.codec.SctpFrameDecoder; +import io.netty.channel.sctp.codec.SctpFrameEncoder; +import io.netty.handler.codec.frame.FixedLengthFrameDecoder; +import io.netty.testsuite.util.SctpSocketAddresses; +import io.netty.util.internal.ExecutorUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractSocketFixedLengthEchoTest { + + private static final Random random = new Random(); + static final byte[] data = new byte[1024];//could not test with jumbo frames + + private static ExecutorService executor; + + static { + random.nextBytes(data); + } + + @BeforeClass + public static void init() { + executor = Executors.newCachedThreadPool(); + } + + @AfterClass + public static void destroy() { + ExecutorUtil.terminate(executor); + } + + protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor); + protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor); + + @Test + public void testFixedLengthEcho() throws Throwable { + ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor)); + ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); + + EchoHandler sh = new EchoHandler(); + EchoHandler ch = new EchoHandler(); + + sb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + sb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + sb.getPipeline().addLast("decoder", new FixedLengthFrameDecoder(1024)); + sb.getPipeline().addAfter("decoder", "handler", sh); + + + cb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + cb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + cb.getPipeline().addLast("decoder", new FixedLengthFrameDecoder(1024)); + cb.getPipeline().addAfter("decoder", "handler", ch); + + Channel sc = sb.bind(new InetSocketAddress(0)); + int port = ((InetSocketAddress) sc.getLocalAddress()).getPort(); + + ChannelFuture ccf = cb.connect(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, port)); + assertTrue(ccf.awaitUninterruptibly().isSuccess()); + + Channel cc = ccf.getChannel(); + for (int i = 0; i < data.length;) { + int length = Math.min(random.nextInt(1024 * 3), data.length - i); + cc.write(ChannelBuffers.wrappedBuffer(data, i, length)); + i += length; + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore. + } + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore. + } + } + + sh.channel.close().awaitUninterruptibly(); + ch.channel.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleChannelUpstreamHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler() { + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + channel = e.getChannel(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + ChannelBuffer m = (ChannelBuffer) e.getMessage(); + assertEquals(1024, m.readableBytes()); + + byte[] actual = new byte[m.readableBytes()]; + m.getBytes(0, actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i ++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + if (channel.getParent() != null) { + channel.write(m); + } + + counter += actual.length; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + if (exception.compareAndSet(null, e.getCause())) { + e.getChannel().close(); + } + } + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketObjectStreamEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketObjectStreamEchoTest.java new file mode 100644 index 0000000000..59102e3ce8 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketObjectStreamEchoTest.java @@ -0,0 +1,189 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport; + +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.sctp.codec.SctpFrameDecoder; +import io.netty.channel.sctp.codec.SctpFrameEncoder; +import io.netty.handler.codec.serialization.ObjectDecoder; +import io.netty.handler.codec.serialization.ObjectEncoder; +import io.netty.testsuite.util.SctpSocketAddresses; +import io.netty.util.internal.ExecutorUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractSocketObjectStreamEchoTest { + + static final Random random = new Random(); + static final String[] data = new String[512];//could not test jumbo frames + + private static ExecutorService executor; + + static { + for (int i = 0; i < data.length; i ++) { + int eLen = random.nextInt(512); + char[] e = new char[eLen]; + for (int j = 0; j < eLen; j ++) { + e[j] = (char) ('a' + random.nextInt(26)); + } + + data[i] = new String(e); + } + } + + @BeforeClass + public static void init() { + executor = Executors.newCachedThreadPool(); + } + + @AfterClass + public static void destroy() { + ExecutorUtil.terminate(executor); + } + + protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor); + protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor); + + @Test + public void testObjectEcho() throws Throwable { + ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor)); + ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); + + EchoHandler sh = new EchoHandler(); + EchoHandler ch = new EchoHandler(); + + sb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + sb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + sb.getPipeline().addLast("decoder", new ObjectDecoder()); + sb.getPipeline().addLast("encoder", new ObjectEncoder()); + sb.getPipeline().addLast("handler", sh); + + cb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + cb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + cb.getPipeline().addLast("decoder", new ObjectDecoder()); + cb.getPipeline().addLast("encoder", new ObjectEncoder()); + cb.getPipeline().addLast("handler", ch); + + Channel sc = sb.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + int port = ((InetSocketAddress) sc.getLocalAddress()).getPort(); + + ChannelFuture ccf = cb.connect(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, port)); + assertTrue(ccf.awaitUninterruptibly().isSuccess()); + + Channel cc = ccf.getChannel(); + for (String element : data) { + cc.write(element); + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(5); + } catch (InterruptedException e) { + // Ignore. + } + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(5); + } catch (InterruptedException e) { + // Ignore. + } + } + + sh.channel.close().awaitUninterruptibly(); + ch.channel.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleChannelUpstreamHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler() { + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + channel = e.getChannel(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + + String m = (String) e.getMessage(); + assertEquals(data[counter], m); + + if (channel.getParent() != null) { + channel.write(m); + } + + counter ++; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + if (exception.compareAndSet(null, e.getCause())) { + e.getChannel().close(); + } + } + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java new file mode 100644 index 0000000000..28709c363d --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketServerBootstrapTest.java @@ -0,0 +1,217 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport; + +import com.sun.nio.sctp.SctpChannel; +import com.sun.nio.sctp.SctpServerChannel; +import com.sun.nio.sctp.SctpStandardSocketOptions; +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.sctp.SctpChannelConfig; +import io.netty.testsuite.util.DummyHandler; +import io.netty.testsuite.util.SctpSocketAddresses; +import io.netty.util.internal.ExecutorUtil; +import org.easymock.EasyMock; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Iterator; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.junit.Assert.assertEquals; + + +/** + * An abstract test class to test server socket bootstraps + */ +public abstract class AbstractSocketServerBootstrapTest { + + private static final boolean BUFSIZE_MODIFIABLE; + + static { + boolean bufSizeModifiable = true; + + SctpChannel s = null; + try { + s = SctpChannel.open(); + bufSizeModifiable = s.supportedOptions().contains(SctpStandardSocketOptions.SO_RCVBUF); + } catch (Exception e) { + bufSizeModifiable = false; + System.err.println( + "SCTP SO_RCVBUF does not work: " + e); + } finally { + BUFSIZE_MODIFIABLE = bufSizeModifiable; + try { + if (s != null) { + s.close(); + } + } catch (IOException e) { + // Ignore. + } + } + } + + private static ExecutorService executor; + + @BeforeClass + public static void init() { + executor = Executors.newCachedThreadPool(); + } + + @AfterClass + public static void destroy() { + ExecutorUtil.terminate(executor); + } + + protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor); + + @Test(timeout = 30000, expected = ChannelException.class) + public void testFailedBindAttempt() throws Exception { + SctpServerChannel serverChannel = SctpServerChannel.open(); + serverChannel.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + + try { + final Iterator serverAddresses = serverChannel.getAllLocalAddresses().iterator(); + InetSocketAddress serverAddress = (InetSocketAddress) serverAddresses.next(); + final int boundPort = serverAddress.getPort(); + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.setFactory(newServerSocketChannelFactory(executor)); + bootstrap.setOption("localAddress", new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, boundPort)); + bootstrap.bind().close().awaitUninterruptibly(); + } finally { + serverChannel.close(); + } + } + + @Test(timeout = 30000) + public void testSuccessfulBindAttempt() throws Exception { + ServerBootstrap bootstrap = new ServerBootstrap( + newServerSocketChannelFactory(executor)); + + bootstrap.setParentHandler(new ParentChannelHandler()); + bootstrap.setOption("localAddress", new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + bootstrap.setOption("child.receiveBufferSize", 9753); + bootstrap.setOption("child.sendBufferSize", 8642); + + bootstrap.getPipeline().addLast("dummy", new DummyHandler()); + + Channel channel = bootstrap.bind(); + ParentChannelHandler pch = + channel.getPipeline().get(ParentChannelHandler.class); + + SctpChannel sctpChannel = SctpChannel.open(); + try { + sctpChannel.connect( + new InetSocketAddress( + SctpSocketAddresses.LOOP_BACK, + ((InetSocketAddress) channel.getLocalAddress()).getPort())); + + // Wait until the connection is open in the server side. + while (pch.child == null) { + Thread.yield(); + } + + SctpChannelConfig cfg = (SctpChannelConfig) pch.child.getConfig(); + if (BUFSIZE_MODIFIABLE) { + assertEquals(9753, cfg.getReceiveBufferSize()); + assertEquals(8642, cfg.getSendBufferSize()); + } + } finally { + if (sctpChannel != null) { + try { + sctpChannel.close(); + } catch (IOException e) { + // Ignore. + } + } + channel.close().awaitUninterruptibly(); + } + + // Wait until the child connection is closed in the client side. + // We do not use Channel.close() to make sure it is closed automatically. + while (pch.child.isOpen()) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore + } + } + + // Wait until all child events are fired. + while (pch.result.length() < 2) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore + } + } + + // Confirm the received child events. + assertEquals("12", pch.result.toString()); + } + + @Test(expected = ChannelPipelineException.class) + public void testFailedPipelineInitialization() throws Exception { + ClientBootstrap bootstrap = new ClientBootstrap(EasyMock.createMock(ChannelFactory.class)); + ChannelPipelineFactory pipelineFactory = EasyMock.createMock(ChannelPipelineFactory.class); + bootstrap.setPipelineFactory(pipelineFactory); + + EasyMock.expect(pipelineFactory.getPipeline()).andThrow(new ChannelPipelineException()); + EasyMock.replay(pipelineFactory); + + bootstrap.connect(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 1)); + } + + @Test(expected = IllegalStateException.class) + public void shouldHaveLocalAddressOption() { + new ServerBootstrap(EasyMock.createMock(ServerChannelFactory.class)).bind(); + } + + + @Test(expected = NullPointerException.class) + public void shouldDisallowNullLocalAddressParameter() { + new ServerBootstrap(EasyMock.createMock(ServerChannelFactory.class)).bind(null); + } + + private static class ParentChannelHandler extends SimpleChannelUpstreamHandler { + + volatile Channel child; + final StringBuffer result = new StringBuffer(); + + ParentChannelHandler() { + } + + @Override + public void childChannelClosed(ChannelHandlerContext ctx, + ChildChannelStateEvent e) throws Exception { + result.append('2'); + } + + @Override + public void childChannelOpen(ChannelHandlerContext ctx, + ChildChannelStateEvent e) throws Exception { + child = e.getChildChannel(); + result.append('1'); + } + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketSslEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketSslEchoTest.java new file mode 100644 index 0000000000..b169ef6d43 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketSslEchoTest.java @@ -0,0 +1,640 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport; + +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBuffers; +import io.netty.channel.*; +import io.netty.channel.sctp.codec.SctpFrameDecoder; +import io.netty.channel.sctp.codec.SctpFrameEncoder; +import io.netty.handler.execution.ExecutionHandler; +import io.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; +import io.netty.handler.ssl.SslHandler; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; +import io.netty.testsuite.util.SctpSocketAddresses; +import io.netty.util.internal.ExecutorUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.net.ssl.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Security; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractSocketSslEchoTest { + static final InternalLogger logger = + InternalLoggerFactory.getInstance(AbstractSocketSslEchoTest.class); + + private static final Random random = new Random(); + static final byte[] data = new byte[4096];//could not test jumbo frames + + private static ExecutorService executor; + private static ExecutorService eventExecutor; + + static { + random.nextBytes(data); + } + + @BeforeClass + public static void init() { + executor = Executors.newCachedThreadPool(); + eventExecutor = new OrderedMemoryAwareThreadPoolExecutor(16, 0, 0); + } + + @AfterClass + public static void destroy() { + ExecutorUtil.terminate(executor, eventExecutor); + } + + protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor); + protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor); + + protected boolean isExecutorRequired() { + return false; + } + + @Test + public void testSslEcho() throws Throwable { + ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor)); + ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); + + EchoHandler sh = new EchoHandler(true); + EchoHandler ch = new EchoHandler(false); + + SSLEngine sse = BogusSslContextFactory.getServerContext().createSSLEngine(); + SSLEngine cse = BogusSslContextFactory.getClientContext().createSSLEngine(); + sse.setUseClientMode(false); + cse.setUseClientMode(true); + + + sb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + sb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + sb.getPipeline().addLast("ssl", new SslHandler(sse)); + sb.getPipeline().addLast("handler", sh); + + cb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + cb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + cb.getPipeline().addLast("ssl", new SslHandler(cse)); + cb.getPipeline().addLast("handler", ch); + + if (isExecutorRequired()) { + sb.getPipeline().addFirst("executor", new ExecutionHandler(eventExecutor)); + cb.getPipeline().addFirst("executor", new ExecutionHandler(eventExecutor)); + } + + Channel sc = sb.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + int port = ((InetSocketAddress) sc.getLocalAddress()).getPort(); + + ChannelFuture ccf = cb.connect(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, port)); + ccf.awaitUninterruptibly(); + if (!ccf.isSuccess()) { + logger.error("Connection attempt failed", ccf.getCause()); + sc.close().awaitUninterruptibly(); + } + assertTrue(ccf.isSuccess()); + + Channel cc = ccf.getChannel(); + ChannelFuture hf = cc.getPipeline().get(SslHandler.class).handshake(); + hf.awaitUninterruptibly(); + if (!hf.isSuccess()) { + logger.error("Handshake failed", hf.getCause()); + sh.channel.close().awaitUninterruptibly(); + ch.channel.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + } + + assertTrue(hf.isSuccess()); + + for (int i = 0; i < data.length;) { + int length = Math.min(random.nextInt(1024 * 64), data.length - i); + cc.write(ChannelBuffers.wrappedBuffer(data, i, length)); + i += length; + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore. + } + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore. + } + } + + sh.channel.close().awaitUninterruptibly(); + ch.channel.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleChannelUpstreamHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + private final boolean server; + + EchoHandler(boolean server) { + this.server = server; + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + channel = e.getChannel(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + ChannelBuffer m = (ChannelBuffer) e.getMessage(); + byte[] actual = new byte[m.readableBytes()]; + m.getBytes(0, actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i ++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + if (channel.getParent() != null) { + channel.write(m); + } + + counter += actual.length; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + logger.warn( + "Unexpected exception from the " + + (server? "server" : "client") + " side", e.getCause()); + + exception.compareAndSet(null, e.getCause()); + e.getChannel().close(); + } + } + + private static class BogusSslContextFactory { + + private static final String PROTOCOL = "TLS"; + private static final SSLContext SERVER_CONTEXT; + private static final SSLContext CLIENT_CONTEXT; + + static { + String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); + if (algorithm == null) { + algorithm = "SunX509"; + } + + SSLContext serverContext = null; + SSLContext clientContext = null; + try { + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(BogusKeyStore.asInputStream(), + BogusKeyStore.getKeyStorePassword()); + + // Set up key manager factory to use our key store + KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); + kmf.init(ks, BogusKeyStore.getCertificatePassword()); + + // Initialize the SSLContext to work with our key managers. + serverContext = SSLContext.getInstance(PROTOCOL); + serverContext.init(kmf.getKeyManagers(), null, null); + } catch (Exception e) { + throw new Error( + "Failed to initialize the server-side SSLContext", e); + } + + try { + clientContext = SSLContext.getInstance(PROTOCOL); + clientContext.init(null, BogusTrustManagerFactory.getTrustManagers(), null); + } catch (Exception e) { + throw new Error( + "Failed to initialize the client-side SSLContext", e); + } + + SERVER_CONTEXT = serverContext; + CLIENT_CONTEXT = clientContext; + } + + public static SSLContext getServerContext() { + return SERVER_CONTEXT; + } + + public static SSLContext getClientContext() { + return CLIENT_CONTEXT; + } + } + + /** + * Bogus {@link javax.net.ssl.TrustManagerFactorySpi} which accepts any certificate + * even if it is invalid. + */ + private static class BogusTrustManagerFactory extends TrustManagerFactorySpi { + + private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted( + X509Certificate[] chain, String authType) throws CertificateException { + // Always trust - it is an example. + // You should do something in the real world. + // You will reach here only if you enabled client certificate auth, + // as described in SecureChatSslContextFactory. + System.err.println( + "UNKNOWN CLIENT CERTIFICATE: " + chain[0].getSubjectDN()); + } + + @Override + public void checkServerTrusted( + X509Certificate[] chain, String authType) throws CertificateException { + // Always trust - it is an example. + // You should do something in the real world. + System.err.println( + "UNKNOWN SERVER CERTIFICATE: " + chain[0].getSubjectDN()); + } + }; + + public static TrustManager[] getTrustManagers() { + return new TrustManager[] { DUMMY_TRUST_MANAGER }; + } + + @Override + protected TrustManager[] engineGetTrustManagers() { + return getTrustManagers(); + } + + @Override + protected void engineInit(KeyStore keystore) throws KeyStoreException { + // Unused + } + + @Override + protected void engineInit(ManagerFactoryParameters managerFactoryParameters) + throws InvalidAlgorithmParameterException { + // Unused + } + } + + /** + * A bogus key store which provides all the required information to + * create an example SSL connection. + * + * To generate a bogus key store: + *
+     * keytool  -genkey -alias bogus -keysize 2048 -validity 36500
+     *          -keyalg RSA -dname "CN=bogus"
+     *          -keypass secret -storepass secret
+     *          -keystore cert.jks
+     * 
+ */ + private static final class BogusKeyStore { + private static final short[] DATA = { + 0xfe, 0xed, 0xfe, 0xed, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x00, 0x00, 0x01, 0x1a, 0x9f, 0x57, 0xa5, + 0x27, 0x00, 0x00, 0x01, 0x9a, 0x30, 0x82, 0x01, + 0x96, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, + 0x04, 0x01, 0x2a, 0x02, 0x11, 0x01, 0x01, 0x05, + 0x00, 0x04, 0x82, 0x01, 0x82, 0x48, 0x6d, 0xcf, + 0x16, 0xb5, 0x50, 0x95, 0x36, 0xbf, 0x47, 0x27, + 0x50, 0x58, 0x0d, 0xa2, 0x52, 0x7e, 0x25, 0xab, + 0x14, 0x1a, 0x26, 0x5e, 0x2d, 0x8a, 0x23, 0x90, + 0x60, 0x7f, 0x12, 0x20, 0x56, 0xd1, 0x43, 0xa2, + 0x6b, 0x47, 0x5d, 0xed, 0x9d, 0xd4, 0xe5, 0x83, + 0x28, 0x89, 0xc2, 0x16, 0x4c, 0x76, 0x06, 0xad, + 0x8e, 0x8c, 0x29, 0x1a, 0x9b, 0x0f, 0xdd, 0x60, + 0x4b, 0xb4, 0x62, 0x82, 0x9e, 0x4a, 0x63, 0x83, + 0x2e, 0xd2, 0x43, 0x78, 0xc2, 0x32, 0x1f, 0x60, + 0xa9, 0x8a, 0x7f, 0x0f, 0x7c, 0xa6, 0x1d, 0xe6, + 0x92, 0x9e, 0x52, 0xc7, 0x7d, 0xbb, 0x35, 0x3b, + 0xaa, 0x89, 0x73, 0x4c, 0xfb, 0x99, 0x54, 0x97, + 0x99, 0x28, 0x6e, 0x66, 0x5b, 0xf7, 0x9b, 0x7e, + 0x6d, 0x8a, 0x2f, 0xfa, 0xc3, 0x1e, 0x71, 0xb9, + 0xbd, 0x8f, 0xc5, 0x63, 0x25, 0x31, 0x20, 0x02, + 0xff, 0x02, 0xf0, 0xc9, 0x2c, 0xdd, 0x3a, 0x10, + 0x30, 0xab, 0xe5, 0xad, 0x3d, 0x1a, 0x82, 0x77, + 0x46, 0xed, 0x03, 0x38, 0xa4, 0x73, 0x6d, 0x36, + 0x36, 0x33, 0x70, 0xb2, 0x63, 0x20, 0xca, 0x03, + 0xbf, 0x5a, 0xf4, 0x7c, 0x35, 0xf0, 0x63, 0x1a, + 0x12, 0x33, 0x12, 0x58, 0xd9, 0xa2, 0x63, 0x6b, + 0x63, 0x82, 0x41, 0x65, 0x70, 0x37, 0x4b, 0x99, + 0x04, 0x9f, 0xdd, 0x5e, 0x07, 0x01, 0x95, 0x9f, + 0x36, 0xe8, 0xc3, 0x66, 0x2a, 0x21, 0x69, 0x68, + 0x40, 0xe6, 0xbc, 0xbb, 0x85, 0x81, 0x21, 0x13, + 0xe6, 0xa4, 0xcf, 0xd3, 0x67, 0xe3, 0xfd, 0x75, + 0xf0, 0xdf, 0x83, 0xe0, 0xc5, 0x36, 0x09, 0xac, + 0x1b, 0xd4, 0xf7, 0x2a, 0x23, 0x57, 0x1c, 0x5c, + 0x0f, 0xf4, 0xcf, 0xa2, 0xcf, 0xf5, 0xbd, 0x9c, + 0x69, 0x98, 0x78, 0x3a, 0x25, 0xe4, 0xfd, 0x85, + 0x11, 0xcc, 0x7d, 0xef, 0xeb, 0x74, 0x60, 0xb1, + 0xb7, 0xfb, 0x1f, 0x0e, 0x62, 0xff, 0xfe, 0x09, + 0x0a, 0xc3, 0x80, 0x2f, 0x10, 0x49, 0x89, 0x78, + 0xd2, 0x08, 0xfa, 0x89, 0x22, 0x45, 0x91, 0x21, + 0xbc, 0x90, 0x3e, 0xad, 0xb3, 0x0a, 0xb4, 0x0e, + 0x1c, 0xa1, 0x93, 0x92, 0xd8, 0x72, 0x07, 0x54, + 0x60, 0xe7, 0x91, 0xfc, 0xd9, 0x3c, 0xe1, 0x6f, + 0x08, 0xe4, 0x56, 0xf6, 0x0b, 0xb0, 0x3c, 0x39, + 0x8a, 0x2d, 0x48, 0x44, 0x28, 0x13, 0xca, 0xe9, + 0xf7, 0xa3, 0xb6, 0x8a, 0x5f, 0x31, 0xa9, 0x72, + 0xf2, 0xde, 0x96, 0xf2, 0xb1, 0x53, 0xb1, 0x3e, + 0x24, 0x57, 0xfd, 0x18, 0x45, 0x1f, 0xc5, 0x33, + 0x1b, 0xa4, 0xe8, 0x21, 0xfa, 0x0e, 0xb2, 0xb9, + 0xcb, 0xc7, 0x07, 0x41, 0xdd, 0x2f, 0xb6, 0x6a, + 0x23, 0x18, 0xed, 0xc1, 0xef, 0xe2, 0x4b, 0xec, + 0xc9, 0xba, 0xfb, 0x46, 0x43, 0x90, 0xd7, 0xb5, + 0x68, 0x28, 0x31, 0x2b, 0x8d, 0xa8, 0x51, 0x63, + 0xf7, 0x53, 0x99, 0x19, 0x68, 0x85, 0x66, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x05, 0x58, 0x2e, 0x35, + 0x30, 0x39, 0x00, 0x00, 0x02, 0x3a, 0x30, 0x82, + 0x02, 0x36, 0x30, 0x82, 0x01, 0xe0, 0xa0, 0x03, + 0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x59, 0xf1, + 0x92, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, + 0x30, 0x81, 0xa0, 0x31, 0x0b, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4b, 0x52, + 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, + 0x08, 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e, 0x67, + 0x67, 0x69, 0x2d, 0x64, 0x6f, 0x31, 0x14, 0x30, + 0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0b, + 0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61, 0x6d, + 0x2d, 0x73, 0x69, 0x31, 0x1a, 0x30, 0x18, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x54, 0x68, + 0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79, 0x20, + 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x31, + 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0b, + 0x13, 0x0f, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x73, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x13, 0x27, 0x73, 0x65, 0x63, 0x75, + 0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, + 0x65, 0x74, 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65, + 0x61, 0x6d, 0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e, + 0x6e, 0x65, 0x74, 0x30, 0x20, 0x17, 0x0d, 0x30, + 0x38, 0x30, 0x36, 0x31, 0x39, 0x30, 0x35, 0x34, + 0x31, 0x33, 0x38, 0x5a, 0x18, 0x0f, 0x32, 0x31, + 0x38, 0x37, 0x31, 0x31, 0x32, 0x34, 0x30, 0x35, + 0x34, 0x31, 0x33, 0x38, 0x5a, 0x30, 0x81, 0xa0, + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, + 0x06, 0x13, 0x02, 0x4b, 0x52, 0x31, 0x13, 0x30, + 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, + 0x4b, 0x79, 0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d, + 0x64, 0x6f, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, + 0x55, 0x04, 0x07, 0x13, 0x0b, 0x53, 0x65, 0x6f, + 0x6e, 0x67, 0x6e, 0x61, 0x6d, 0x2d, 0x73, 0x69, + 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, + 0x0a, 0x13, 0x11, 0x54, 0x68, 0x65, 0x20, 0x4e, + 0x65, 0x74, 0x74, 0x79, 0x20, 0x50, 0x72, 0x6f, + 0x6a, 0x65, 0x63, 0x74, 0x31, 0x18, 0x30, 0x16, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0f, 0x45, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x73, 0x31, 0x30, + 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, + 0x27, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63, + 0x68, 0x61, 0x74, 0x2e, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x74, + 0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d, 0x79, + 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x65, 0x74, + 0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, + 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, + 0x00, 0xc3, 0xe3, 0x5e, 0x41, 0xa7, 0x87, 0x11, + 0x00, 0x42, 0x2a, 0xb0, 0x4b, 0xed, 0xb2, 0xe0, + 0x23, 0xdb, 0xb1, 0x3d, 0x58, 0x97, 0x35, 0x60, + 0x0b, 0x82, 0x59, 0xd3, 0x00, 0xea, 0xd4, 0x61, + 0xb8, 0x79, 0x3f, 0xb6, 0x3c, 0x12, 0x05, 0x93, + 0x2e, 0x9a, 0x59, 0x68, 0x14, 0x77, 0x3a, 0xc8, + 0x50, 0x25, 0x57, 0xa4, 0x49, 0x18, 0x63, 0x41, + 0xf0, 0x2d, 0x28, 0xec, 0x06, 0xfb, 0xb4, 0x9f, + 0xbf, 0x02, 0x03, 0x01, 0x00, 0x01, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x41, 0x00, + 0x65, 0x6c, 0x30, 0x01, 0xc2, 0x8e, 0x3e, 0xcb, + 0xb3, 0x77, 0x48, 0xe9, 0x66, 0x61, 0x9a, 0x40, + 0x86, 0xaf, 0xf6, 0x03, 0xeb, 0xba, 0x6a, 0xf2, + 0xfd, 0xe2, 0xaf, 0x36, 0x5e, 0x7b, 0xaa, 0x22, + 0x04, 0xdd, 0x2c, 0x20, 0xc4, 0xfc, 0xdd, 0xd0, + 0x82, 0x20, 0x1c, 0x3d, 0xd7, 0x9e, 0x5e, 0x5c, + 0x92, 0x5a, 0x76, 0x71, 0x28, 0xf5, 0x07, 0x7d, + 0xa2, 0x81, 0xba, 0x77, 0x9f, 0x2a, 0xd9, 0x44, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x6d, 0x79, + 0x6b, 0x65, 0x79, 0x00, 0x00, 0x01, 0x1a, 0x9f, + 0x5b, 0x56, 0xa0, 0x00, 0x00, 0x01, 0x99, 0x30, + 0x82, 0x01, 0x95, 0x30, 0x0e, 0x06, 0x0a, 0x2b, + 0x06, 0x01, 0x04, 0x01, 0x2a, 0x02, 0x11, 0x01, + 0x01, 0x05, 0x00, 0x04, 0x82, 0x01, 0x81, 0x29, + 0xa8, 0xb6, 0x08, 0x0c, 0x85, 0x75, 0x3e, 0xdd, + 0xb5, 0xe5, 0x1a, 0x87, 0x68, 0xd1, 0x90, 0x4b, + 0x29, 0x31, 0xee, 0x90, 0xbc, 0x9d, 0x73, 0xa0, + 0x3f, 0xe9, 0x0b, 0xa4, 0xef, 0x30, 0x9b, 0x36, + 0x9a, 0xb2, 0x54, 0x77, 0x81, 0x07, 0x4b, 0xaa, + 0xa5, 0x77, 0x98, 0xe1, 0xeb, 0xb5, 0x7c, 0x4e, + 0x48, 0xd5, 0x08, 0xfc, 0x2c, 0x36, 0xe2, 0x65, + 0x03, 0xac, 0xe5, 0xf3, 0x96, 0xb7, 0xd0, 0xb5, + 0x3b, 0x92, 0xe4, 0x14, 0x05, 0x7a, 0x6a, 0x92, + 0x56, 0xfe, 0x4e, 0xab, 0xd3, 0x0e, 0x32, 0x04, + 0x22, 0x22, 0x74, 0x47, 0x7d, 0xec, 0x21, 0x99, + 0x30, 0x31, 0x64, 0x46, 0x64, 0x9b, 0xc7, 0x13, + 0xbf, 0xbe, 0xd0, 0x31, 0x49, 0xe7, 0x3c, 0xbf, + 0xba, 0xb1, 0x20, 0xf9, 0x42, 0xf4, 0xa9, 0xa9, + 0xe5, 0x13, 0x65, 0x32, 0xbf, 0x7c, 0xcc, 0x91, + 0xd3, 0xfd, 0x24, 0x47, 0x0b, 0xe5, 0x53, 0xad, + 0x50, 0x30, 0x56, 0xd1, 0xfa, 0x9c, 0x37, 0xa8, + 0xc1, 0xce, 0xf6, 0x0b, 0x18, 0xaa, 0x7c, 0xab, + 0xbd, 0x1f, 0xdf, 0xe4, 0x80, 0xb8, 0xa7, 0xe0, + 0xad, 0x7d, 0x50, 0x74, 0xf1, 0x98, 0x78, 0xbc, + 0x58, 0xb9, 0xc2, 0x52, 0xbe, 0xd2, 0x5b, 0x81, + 0x94, 0x83, 0x8f, 0xb9, 0x4c, 0xee, 0x01, 0x2b, + 0x5e, 0xc9, 0x6e, 0x9b, 0xf5, 0x63, 0x69, 0xe4, + 0xd8, 0x0b, 0x47, 0xd8, 0xfd, 0xd8, 0xe0, 0xed, + 0xa8, 0x27, 0x03, 0x74, 0x1e, 0x5d, 0x32, 0xe6, + 0x5c, 0x63, 0xc2, 0xfb, 0x3f, 0xee, 0xb4, 0x13, + 0xc6, 0x0e, 0x6e, 0x74, 0xe0, 0x22, 0xac, 0xce, + 0x79, 0xf9, 0x43, 0x68, 0xc1, 0x03, 0x74, 0x2b, + 0xe1, 0x18, 0xf8, 0x7f, 0x76, 0x9a, 0xea, 0x82, + 0x3f, 0xc2, 0xa6, 0xa7, 0x4c, 0xfe, 0xae, 0x29, + 0x3b, 0xc1, 0x10, 0x7c, 0xd5, 0x77, 0x17, 0x79, + 0x5f, 0xcb, 0xad, 0x1f, 0xd8, 0xa1, 0xfd, 0x90, + 0xe1, 0x6b, 0xb2, 0xef, 0xb9, 0x41, 0x26, 0xa4, + 0x0b, 0x4f, 0xc6, 0x83, 0x05, 0x6f, 0xf0, 0x64, + 0x40, 0xe1, 0x44, 0xc4, 0xf9, 0x40, 0x2b, 0x3b, + 0x40, 0xdb, 0xaf, 0x35, 0xa4, 0x9b, 0x9f, 0xc4, + 0x74, 0x07, 0xe5, 0x18, 0x60, 0xc5, 0xfe, 0x15, + 0x0e, 0x3a, 0x25, 0x2a, 0x11, 0xee, 0x78, 0x2f, + 0xb8, 0xd1, 0x6e, 0x4e, 0x3c, 0x0a, 0xb5, 0xb9, + 0x40, 0x86, 0x27, 0x6d, 0x8f, 0x53, 0xb7, 0x77, + 0x36, 0xec, 0x5d, 0xed, 0x32, 0x40, 0x43, 0x82, + 0xc3, 0x52, 0x58, 0xc4, 0x26, 0x39, 0xf3, 0xb3, + 0xad, 0x58, 0xab, 0xb7, 0xf7, 0x8e, 0x0e, 0xba, + 0x8e, 0x78, 0x9d, 0xbf, 0x58, 0x34, 0xbd, 0x77, + 0x73, 0xa6, 0x50, 0x55, 0x00, 0x60, 0x26, 0xbf, + 0x6d, 0xb4, 0x98, 0x8a, 0x18, 0x83, 0x89, 0xf8, + 0xcd, 0x0d, 0x49, 0x06, 0xae, 0x51, 0x6e, 0xaf, + 0xbd, 0xe2, 0x07, 0x13, 0xd8, 0x64, 0xcc, 0xbf, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x58, 0x2e, + 0x35, 0x30, 0x39, 0x00, 0x00, 0x02, 0x34, 0x30, + 0x82, 0x02, 0x30, 0x30, 0x82, 0x01, 0xda, 0xa0, + 0x03, 0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x59, + 0xf2, 0x84, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, + 0x00, 0x30, 0x81, 0x9d, 0x31, 0x0b, 0x30, 0x09, + 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4b, + 0x52, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, + 0x04, 0x08, 0x13, 0x0a, 0x4b, 0x79, 0x75, 0x6e, + 0x67, 0x67, 0x69, 0x2d, 0x64, 0x6f, 0x31, 0x14, + 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, + 0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67, 0x6e, 0x61, + 0x6d, 0x2d, 0x73, 0x69, 0x31, 0x1a, 0x30, 0x18, + 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x54, + 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, 0x74, 0x79, + 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, + 0x0b, 0x13, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x73, 0x31, + 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x27, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, + 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, 0x65, 0x74, + 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65, 0x61, 0x6d, + 0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x65, + 0x74, 0x30, 0x20, 0x17, 0x0d, 0x30, 0x38, 0x30, + 0x36, 0x31, 0x39, 0x30, 0x35, 0x34, 0x35, 0x34, + 0x30, 0x5a, 0x18, 0x0f, 0x32, 0x31, 0x38, 0x37, + 0x31, 0x31, 0x32, 0x33, 0x30, 0x35, 0x34, 0x35, + 0x34, 0x30, 0x5a, 0x30, 0x81, 0x9d, 0x31, 0x0b, + 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x4b, 0x52, 0x31, 0x13, 0x30, 0x11, 0x06, + 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x4b, 0x79, + 0x75, 0x6e, 0x67, 0x67, 0x69, 0x2d, 0x64, 0x6f, + 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, + 0x07, 0x13, 0x0b, 0x53, 0x65, 0x6f, 0x6e, 0x67, + 0x6e, 0x61, 0x6d, 0x2d, 0x73, 0x69, 0x31, 0x1a, + 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, + 0x11, 0x54, 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, + 0x74, 0x79, 0x20, 0x50, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, + 0x55, 0x04, 0x0b, 0x13, 0x0c, 0x43, 0x6f, 0x6e, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, + 0x73, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x13, 0x27, 0x73, 0x65, 0x63, 0x75, + 0x72, 0x65, 0x63, 0x68, 0x61, 0x74, 0x2e, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6e, + 0x65, 0x74, 0x74, 0x79, 0x2e, 0x67, 0x6c, 0x65, + 0x61, 0x6d, 0x79, 0x6e, 0x6f, 0x64, 0x65, 0x2e, + 0x6e, 0x65, 0x74, 0x30, 0x5c, 0x30, 0x0d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30, + 0x48, 0x02, 0x41, 0x00, 0x95, 0xb3, 0x47, 0x17, + 0x95, 0x0f, 0x57, 0xcf, 0x66, 0x72, 0x0a, 0x7e, + 0x5b, 0x54, 0xea, 0x8c, 0x6f, 0x79, 0xde, 0x94, + 0xac, 0x0b, 0x5a, 0xd4, 0xd6, 0x1b, 0x58, 0x12, + 0x1a, 0x16, 0x3d, 0xfe, 0xdf, 0xa5, 0x2b, 0x86, + 0xbc, 0x64, 0xd4, 0x80, 0x1e, 0x3f, 0xf9, 0xe2, + 0x04, 0x03, 0x79, 0x9b, 0xc1, 0x5c, 0xf0, 0xf1, + 0xf3, 0xf1, 0xe3, 0xbf, 0x3f, 0xc0, 0x1f, 0xdd, + 0xdb, 0xc0, 0x5b, 0x21, 0x02, 0x03, 0x01, 0x00, + 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, + 0x03, 0x41, 0x00, 0x02, 0xd7, 0xdd, 0xbd, 0x0c, + 0x8e, 0x21, 0x20, 0xef, 0x9e, 0x4f, 0x1f, 0xf5, + 0x49, 0xf1, 0xae, 0x58, 0x9b, 0x94, 0x3a, 0x1f, + 0x70, 0x33, 0xf0, 0x9b, 0xbb, 0xe9, 0xc0, 0xf3, + 0x72, 0xcb, 0xde, 0xb6, 0x56, 0x72, 0xcc, 0x1c, + 0xf0, 0xd6, 0x5a, 0x2a, 0xbc, 0xa1, 0x7e, 0x23, + 0x83, 0xe9, 0xe7, 0xcf, 0x9e, 0xa5, 0xf9, 0xcc, + 0xc2, 0x61, 0xf4, 0xdb, 0x40, 0x93, 0x1d, 0x63, + 0x8a, 0x50, 0x4c, 0x11, 0x39, 0xb1, 0x91, 0xc1, + 0xe6, 0x9d, 0xd9, 0x1a, 0x62, 0x1b, 0xb8, 0xd3, + 0xd6, 0x9a, 0x6d, 0xb9, 0x8e, 0x15, 0x51 }; + + public static InputStream asInputStream() { + byte[] data = new byte[DATA.length]; + for (int i = 0; i < data.length; i ++) { + data[i] = (byte) DATA[i]; + } + return new ByteArrayInputStream(data); + } + + public static char[] getCertificatePassword() { + return "secret".toCharArray(); + } + + public static char[] getKeyStorePassword() { + return "secret".toCharArray(); + } + + private BogusKeyStore() { + // Unused + } + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketStringEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketStringEchoTest.java new file mode 100644 index 0000000000..9163d99e32 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/AbstractSocketStringEchoTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport; + +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.sctp.codec.SctpFrameDecoder; +import io.netty.channel.sctp.codec.SctpFrameEncoder; +import io.netty.handler.codec.frame.DelimiterBasedFrameDecoder; +import io.netty.handler.codec.frame.Delimiters; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import io.netty.testsuite.util.SctpSocketAddresses; +import io.netty.util.CharsetUtil; +import io.netty.util.internal.ExecutorUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public abstract class AbstractSocketStringEchoTest { + + static final Random random = new Random(); + static final String[] data = new String[1024]; + + private static ExecutorService executor; + + static { + for (int i = 0; i < data.length; i ++) { + int eLen = random.nextInt(512); + char[] e = new char[eLen]; + for (int j = 0; j < eLen; j ++) { + e[j] = (char) ('a' + random.nextInt(26)); + } + + data[i] = new String(e); + } + } + + @BeforeClass + public static void init() { + executor = Executors.newCachedThreadPool(); + } + + @AfterClass + public static void destroy() { + ExecutorUtil.terminate(executor); + } + + protected abstract ChannelFactory newServerSocketChannelFactory(Executor executor); + protected abstract ChannelFactory newClientSocketChannelFactory(Executor executor); + + @Test + public void testStringEcho() throws Throwable { + ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor)); + ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); + + EchoHandler sh = new EchoHandler(); + EchoHandler ch = new EchoHandler(); + + sb.getPipeline().addFirst("sctp-decoder", new SctpFrameDecoder()); + sb.getPipeline().addFirst("sctp-encoder", new SctpFrameEncoder()); + sb.getPipeline().addLast("framer", new DelimiterBasedFrameDecoder(512, Delimiters.lineDelimiter())); + sb.getPipeline().addLast("decoder", new StringDecoder(CharsetUtil.ISO_8859_1)); + sb.getPipeline().addLast("encoder", new StringEncoder(CharsetUtil.ISO_8859_1)); + sb.getPipeline().addLast("handler", sh); + + cb.getPipeline().addFirst("sctp-decoder", new SctpFrameDecoder()); + cb.getPipeline().addFirst("sctp-encoder", new SctpFrameEncoder()); + cb.getPipeline().addLast("framer", new DelimiterBasedFrameDecoder(512, Delimiters.lineDelimiter())); + cb.getPipeline().addLast("decoder", new StringDecoder(CharsetUtil.ISO_8859_1)); + cb.getPipeline().addLast("encoder", new StringEncoder(CharsetUtil.ISO_8859_1)); + cb.getPipeline().addLast("handler", ch); + + Channel sc = sb.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + int port = ((InetSocketAddress) sc.getLocalAddress()).getPort(); + + ChannelFuture ccf = cb.connect(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, port)); + assertTrue(ccf.awaitUninterruptibly().isSuccess()); + + Channel cc = ccf.getChannel(); + for (String element : data) { + String delimiter = random.nextBoolean() ? "\r\n" : "\n"; + cc.write(element + delimiter); + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore. + } + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore. + } + } + + sh.channel.close().awaitUninterruptibly(); + ch.channel.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleChannelUpstreamHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler() { + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + channel = e.getChannel(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + + String m = (String) e.getMessage(); + assertEquals(data[counter], m); + + if (channel.getParent() != null) { + String delimiter = random.nextBoolean() ? "\r\n" : "\n"; + channel.write(m + delimiter); + } + + counter ++; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + if (exception.compareAndSet(null, e.getCause())) { + e.getChannel().close(); + } + } + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpClientBootstrapTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpClientBootstrapTest.java new file mode 100644 index 0000000000..33845d13b8 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpClientBootstrapTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport.sctp; + +import io.netty.channel.ChannelFactory; +import io.netty.channel.sctp.SctpClientSocketChannelFactory; +import io.netty.testsuite.transport.AbstractSocketClientBootstrapTest; + +import java.util.concurrent.Executor; + +public class SctpClientBootstrapTest extends AbstractSocketClientBootstrapTest { + @Override + protected ChannelFactory newClientSocketChannelFactory(Executor executor) { + return new SctpClientSocketChannelFactory(executor, executor); + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpCompatibleObjectStreamEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpCompatibleObjectStreamEchoTest.java new file mode 100644 index 0000000000..dc38655dce --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpCompatibleObjectStreamEchoTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport.sctp; + +import io.netty.channel.ChannelFactory; +import io.netty.channel.sctp.SctpClientSocketChannelFactory; +import io.netty.channel.sctp.SctpServerSocketChannelFactory; +import io.netty.testsuite.transport.AbstractSocketCompatibleObjectStreamEchoTest; + +import java.util.concurrent.Executor; + +public class SctpCompatibleObjectStreamEchoTest extends AbstractSocketCompatibleObjectStreamEchoTest { + @Override + protected ChannelFactory newServerSocketChannelFactory(Executor executor) { + return new SctpServerSocketChannelFactory(executor, executor); + } + + @Override + protected ChannelFactory newClientSocketChannelFactory(Executor executor) { + return new SctpClientSocketChannelFactory(executor, executor); + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpEchoTest.java new file mode 100644 index 0000000000..bbe24bde2a --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpEchoTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport.sctp; + +import io.netty.channel.ChannelFactory; +import io.netty.channel.sctp.SctpClientSocketChannelFactory; +import io.netty.channel.sctp.SctpServerSocketChannelFactory; +import io.netty.testsuite.transport.AbstractSocketEchoTest; + +import java.util.concurrent.Executor; + +public class SctpEchoTest extends AbstractSocketEchoTest { + @Override + protected ChannelFactory newServerSocketChannelFactory(Executor executor) { + return new SctpServerSocketChannelFactory(executor, executor); + } + + @Override + protected ChannelFactory newClientSocketChannelFactory(Executor executor) { + return new SctpClientSocketChannelFactory(executor, executor); + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpFixedLengthEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpFixedLengthEchoTest.java new file mode 100644 index 0000000000..a1e590438d --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpFixedLengthEchoTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport.sctp; + +import io.netty.channel.ChannelFactory; +import io.netty.channel.sctp.SctpClientSocketChannelFactory; +import io.netty.channel.sctp.SctpServerSocketChannelFactory; +import io.netty.testsuite.transport.AbstractSocketFixedLengthEchoTest; + +import java.util.concurrent.Executor; + +public class SctpFixedLengthEchoTest extends AbstractSocketFixedLengthEchoTest { + @Override + protected ChannelFactory newServerSocketChannelFactory(Executor executor) { + return new SctpServerSocketChannelFactory(executor, executor); + } + + @Override + protected ChannelFactory newClientSocketChannelFactory(Executor executor) { + return new SctpClientSocketChannelFactory(executor, executor); + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiHomingEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiHomingEchoTest.java new file mode 100644 index 0000000000..ace6899480 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiHomingEchoTest.java @@ -0,0 +1,222 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport.sctp; + +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBuffers; +import io.netty.channel.*; +import io.netty.channel.sctp.*; +import io.netty.channel.sctp.codec.SctpFrameDecoder; +import io.netty.channel.sctp.codec.SctpFrameEncoder; +import io.netty.channel.sctp.handler.SimpleSctpChannelHandler; +import io.netty.testsuite.util.SctpSocketAddresses; +import io.netty.util.internal.ExecutorUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SctpMultiHomingEchoTest { + private static final Random random = new Random(); + static final byte[] data = new byte[4096];//could not test ultra jumbo frames + + private static ExecutorService executor; + + static { + random.nextBytes(data); + } + + @BeforeClass + public static void init() { + executor = Executors.newCachedThreadPool(); + } + + @AfterClass + public static void destroy() { + ExecutorUtil.terminate(executor); + } + + protected ChannelFactory newServerSocketChannelFactory(Executor executor) { + return new SctpServerSocketChannelFactory(executor, executor); + } + + protected ChannelFactory newClientSocketChannelFactory(Executor executor) { + return new SctpClientSocketChannelFactory(executor, executor); + } + + @Test(timeout = 15000) + public void testSimpleEcho() throws Throwable { + ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor)); + + ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); + + + EchoHandler sh = new EchoHandler(); + EchoHandler ch = new EchoHandler(); + + sb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + sb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + sb.getPipeline().addLast("handler", sh); + + cb.getPipeline().addLast("sctp-decoder", new SctpFrameDecoder()); + cb.getPipeline().addLast("sctp-encoder", new SctpFrameEncoder()); + cb.getPipeline().addLast("handler", ch); + + SctpServerChannel serverChannel = (SctpServerChannel) sb.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + int port = serverChannel.getLocalAddress().getPort(); + + ChannelFuture multiHomingServerBindFuture = serverChannel.bindAddress(InetAddress.getByName(SctpSocketAddresses.LOOP_BACK2)); + assertTrue(multiHomingServerBindFuture.awaitUninterruptibly().isSuccess()); + + ChannelFuture bindFuture = cb.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + assertTrue(bindFuture.awaitUninterruptibly().isSuccess()); + + SctpChannel clientChannel = (SctpChannel) bindFuture.getChannel(); + + //adding a muti-homing address to client channel + ChannelFuture multiHomingBindFuture = clientChannel.bindAddress(InetAddress.getByName(SctpSocketAddresses.LOOP_BACK2)); + assertTrue(multiHomingBindFuture.awaitUninterruptibly().isSuccess()); + + ChannelFuture connectFuture = clientChannel.connect(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, port)); + assertTrue(connectFuture.awaitUninterruptibly().isSuccess()); + + assertEquals("Client local addresses count should be 2", 2, clientChannel.getAllLocalAddresses().size()); + assertEquals("Client remote addresses count should be 2", 2, clientChannel.getAllRemoteAddresses().size()); + + assertEquals("Server local addresses count should be 2", 2, serverChannel.getAllLocalAddresses().size()); + + for (int i = 0; i < data.length;) { + int length = Math.min(random.nextInt(1024 * 64), data.length - i); + clientChannel.write(ChannelBuffers.wrappedBuffer(data, i, length)); + i += length; + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(5); + } catch (InterruptedException e) { + // Ignore. + } + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(5); + } catch (InterruptedException e) { + // Ignore. + } + } + + //removing already added muti-homing address from client channel + ChannelFuture multiHomingUnbindFuture = clientChannel.unbindAddress(InetAddress.getByName(SctpSocketAddresses.LOOP_BACK2)); + assertTrue(multiHomingUnbindFuture.awaitUninterruptibly().isSuccess()); + + ChannelFuture multiHomingServerUnbindFuture = serverChannel.unbindAddress(InetAddress.getByName(SctpSocketAddresses.LOOP_BACK2)); + assertTrue(multiHomingUnbindFuture.awaitUninterruptibly().isSuccess()); + + + sh.channel.close().awaitUninterruptibly(); + ch.channel.close().awaitUninterruptibly(); + serverChannel.close().awaitUninterruptibly(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleSctpChannelHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler() { + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + channel = e.getChannel(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + ChannelBuffer m = (ChannelBuffer) e.getMessage(); + byte[] actual = new byte[m.readableBytes()]; + m.getBytes(0, actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i ++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + if (channel.getParent() != null) { + channel.write(m); + } + + counter += actual.length; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + if (exception.compareAndSet(null, e.getCause())) { + e.getChannel().close(); + } + } + + @Override + public void sctpNotificationReceived(ChannelHandlerContext ctx, SctpNotificationEvent event) { + System.out.println("SCTP notification event received :" + event); + } + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiStreamingEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiStreamingEchoTest.java new file mode 100644 index 0000000000..9df1837864 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpMultiStreamingEchoTest.java @@ -0,0 +1,176 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport.sctp; + +import io.netty.bootstrap.ClientBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ChannelBuffer; +import io.netty.buffer.ChannelBuffers; +import io.netty.channel.*; +import io.netty.channel.sctp.SctpClientSocketChannelFactory; +import io.netty.channel.sctp.SctpFrame; +import io.netty.channel.sctp.SctpServerSocketChannelFactory; +import io.netty.channel.sctp.codec.SctpFrameDecoder; +import io.netty.channel.sctp.codec.SctpFrameEncoder; +import io.netty.testsuite.util.SctpSocketAddresses; +import io.netty.util.internal.ExecutorUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SctpMultiStreamingEchoTest { + private static final Random random = new Random(); + + static final SctpFrame [] sctpFrames = new SctpFrame [4]; + + + private static ExecutorService executor; + + static ChannelBuffer makeRandomFrame() { + byte [] data = new byte[512]; + random.nextBytes(data); + return ChannelBuffers.wrappedBuffer(data); + } + + static { + int protocolId = 0;//unknown + for(int streamNumber = 0; streamNumber <= 3; streamNumber ++) { + sctpFrames [streamNumber] = new SctpFrame(protocolId, streamNumber, makeRandomFrame()); + } + } + + @BeforeClass + public static void init() { + executor = Executors.newCachedThreadPool(); + } + + @AfterClass + public static void destroy() { + ExecutorUtil.terminate(executor); + } + + protected ChannelFactory newServerSocketChannelFactory(Executor executor) { + return new SctpServerSocketChannelFactory(executor, executor); + } + + protected ChannelFactory newClientSocketChannelFactory(Executor executor) { + return new SctpClientSocketChannelFactory(executor, executor); + } + + @Test(timeout = 10000) + public void testMultiStreamingEcho() throws Throwable { + ServerBootstrap sb = new ServerBootstrap(newServerSocketChannelFactory(executor)); + + ClientBootstrap cb = new ClientBootstrap(newClientSocketChannelFactory(executor)); + cb.setOption("sctpInitMaxStreams", 4); + + EchoHandler sh = new EchoHandler(); + EchoHandler ch = new EchoHandler(); + + sb.getPipeline().addLast("handler", sh); + + cb.getPipeline().addLast("handler", ch); + + Channel sc = sb.bind(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, 0)); + int port = ((InetSocketAddress) sc.getLocalAddress()).getPort(); + + ChannelFuture ccf = cb.connect(new InetSocketAddress(SctpSocketAddresses.LOOP_BACK, port)); + assertTrue(ccf.awaitUninterruptibly().isSuccess()); + + Channel cc = ccf.getChannel(); + + for(SctpFrame sctpFrame: sctpFrames) { + cc.write(sctpFrame); + } + + + while (sh.counter < sctpFrames.length) { + Thread.sleep(5); + } + while (ch.counter < sctpFrames.length) { + Thread.sleep(5); + } + + assertEquals(sctpFrames.length, sh.counter); + assertEquals(sctpFrames.length, ch.counter); + + sh.channel.close().awaitUninterruptibly(); + ch.channel.close().awaitUninterruptibly(); + + + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleChannelUpstreamHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler() { + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + channel = e.getChannel(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + SctpFrame sctpFrame = (SctpFrame) e.getMessage(); + + assertEquals(sctpFrames[counter], sctpFrame); + + if (channel.getParent() != null) { + channel.write(sctpFrame); + } + + counter ++ ; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + if (exception.compareAndSet(null, e.getCause())) { + e.getChannel().close(); + } + } + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpObjectStreamEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpObjectStreamEchoTest.java new file mode 100644 index 0000000000..55c84e0684 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpObjectStreamEchoTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport.sctp; + +import io.netty.channel.ChannelFactory; +import io.netty.channel.sctp.SctpClientSocketChannelFactory; +import io.netty.channel.sctp.SctpServerSocketChannelFactory; +import io.netty.testsuite.transport.AbstractSocketObjectStreamEchoTest; + +import java.util.concurrent.Executor; + +public class SctpObjectStreamEchoTest extends AbstractSocketObjectStreamEchoTest { + @Override + protected ChannelFactory newServerSocketChannelFactory(Executor executor) { + return new SctpServerSocketChannelFactory(executor, executor); + } + + @Override + protected ChannelFactory newClientSocketChannelFactory(Executor executor) { + return new SctpClientSocketChannelFactory(executor, executor); + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpServerBootstrapTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpServerBootstrapTest.java new file mode 100644 index 0000000000..bcb680c1f7 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpServerBootstrapTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport.sctp; + +import io.netty.channel.ChannelFactory; +import io.netty.channel.sctp.SctpClientSocketChannelFactory; +import io.netty.channel.sctp.SctpServerSocketChannelFactory; +import io.netty.testsuite.transport.AbstractSocketServerBootstrapTest; + +import java.util.concurrent.Executor; + +public class SctpServerBootstrapTest extends AbstractSocketServerBootstrapTest { + @Override + protected ChannelFactory newServerSocketChannelFactory(Executor executor) { + return new SctpServerSocketChannelFactory(executor, executor); + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpSslEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpSslEchoTest.java new file mode 100644 index 0000000000..e840cce264 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpSslEchoTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport.sctp; + +import io.netty.channel.ChannelFactory; +import io.netty.channel.sctp.SctpClientSocketChannelFactory; +import io.netty.channel.sctp.SctpServerSocketChannelFactory; +import io.netty.testsuite.transport.AbstractSocketSslEchoTest; + +import java.util.concurrent.Executor; + +public class SctpSslEchoTest extends AbstractSocketSslEchoTest { + @Override + protected ChannelFactory newServerSocketChannelFactory(Executor executor) { + return new SctpServerSocketChannelFactory(executor, executor); + } + + @Override + protected ChannelFactory newClientSocketChannelFactory(Executor executor) { + return new SctpClientSocketChannelFactory(executor, executor); + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpStringEchoTest.java b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpStringEchoTest.java new file mode 100644 index 0000000000..31440bec6a --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/transport/sctp/SctpStringEchoTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.transport.sctp; + +import io.netty.channel.ChannelFactory; +import io.netty.channel.sctp.SctpClientSocketChannelFactory; +import io.netty.channel.sctp.SctpServerSocketChannelFactory; +import io.netty.testsuite.transport.AbstractSocketStringEchoTest; + +import java.util.concurrent.Executor; + +public class SctpStringEchoTest extends AbstractSocketStringEchoTest { + @Override + protected ChannelFactory newServerSocketChannelFactory(Executor executor) { + return new SctpServerSocketChannelFactory(executor, executor); + } + + @Override + protected ChannelFactory newClientSocketChannelFactory(Executor executor) { + return new SctpClientSocketChannelFactory(executor, executor); + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/util/DummyHandler.java b/transport-sctp/src/test/java/io/netty/testsuite/util/DummyHandler.java new file mode 100644 index 0000000000..5ec679a904 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/util/DummyHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.util; + +import io.netty.channel.ChannelDownstreamHandler; +import io.netty.channel.ChannelEvent; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelUpstreamHandler; + +public class DummyHandler implements ChannelUpstreamHandler, ChannelDownstreamHandler { + + public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) + throws Exception { + ctx.sendUpstream(e); + } + + public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) + throws Exception { + ctx.sendDownstream(e); + } +} diff --git a/transport-sctp/src/test/java/io/netty/testsuite/util/SctpSocketAddresses.java b/transport-sctp/src/test/java/io/netty/testsuite/util/SctpSocketAddresses.java new file mode 100644 index 0000000000..1435c414a2 --- /dev/null +++ b/transport-sctp/src/test/java/io/netty/testsuite/util/SctpSocketAddresses.java @@ -0,0 +1,23 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project 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 io.netty.testsuite.util; + +public class SctpSocketAddresses { + //io.netty.util.SocketAddresses.LOCALHOST interface has MTU SIZE issues with SCTP, we have to use local loop back interface for testing + public final static String LOOP_BACK = "127.0.0.1"; + public final static String LOOP_BACK2 = "127.0.0.2"; +} diff --git a/transport/src/main/java/io/netty/bootstrap/ClientBootstrap.java b/transport/src/main/java/io/netty/bootstrap/ClientBootstrap.java index 91f1b05bd5..ce225ef275 100644 --- a/transport/src/main/java/io/netty/bootstrap/ClientBootstrap.java +++ b/transport/src/main/java/io/netty/bootstrap/ClientBootstrap.java @@ -227,4 +227,45 @@ public class ClientBootstrap extends Bootstrap { // Connect. return ch.connect(remoteAddress); } + + /** + * Attempts to bind a channel with the specified {@code localAddress}. later the channel can be connected + * to a remoteAddress by calling {@link Channel#connect(SocketAddress)}.This method is useful where bind and connect + * need to be done in separate steps. (For example, SCTP Multihoming) + * + * @return a future object which notifies when this bind attempt + * succeeds or fails + * + * @throws ChannelPipelineException + * if this bootstrap's {@link #setPipelineFactory(ChannelPipelineFactory) pipelineFactory} + * failed to create a new {@link ChannelPipeline} + */ + public ChannelFuture bind(final SocketAddress localAddress) { + + if (localAddress == null) { + throw new NullPointerException("localAddress"); + } + + ChannelPipeline pipeline; + try { + pipeline = getPipelineFactory().getPipeline(); + } catch (Exception e) { + throw new ChannelPipelineException("Failed to initialize a pipeline.", e); + } + + // Set the options. + Channel ch = getFactory().newChannel(pipeline); + boolean success = false; + try { + ch.getConfig().setOptions(getOptions()); + success = true; + } finally { + if (!success) { + ch.close(); + } + } + + // Bind. + return ch.bind(localAddress); + } } diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java index 65dd184993..251d38ddab 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioChannelConfig.java @@ -25,7 +25,7 @@ import io.netty.channel.ChannelConfig; * Special {@link ChannelConfig} sub-type which offers extra methods which are useful for NIO. * */ -public interface NioChannelConfig extends ChannelConfig{ +public interface NioChannelConfig extends ChannelConfig { /** * Returns the high water mark of the write buffer. If the number of bytes