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