/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2017, Telestax Inc and individual contributors * by the @authors tag. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.restcomm.media.network.netty.channel; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.restcomm.media.network.netty.NettyNetworkManager; import com.google.common.util.concurrent.FutureCallback; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.channel.DefaultChannelProgressivePromise; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.util.concurrent.EventExecutor; /** * @author Henrique Rosa (henrique.rosa@telestax.com) * */ @RunWith(PowerMockRunner.class) @PrepareForTest({Channel.class, NioDatagramChannel.class, DatagramChannel.class}) public class AsyncNettyNetworkChannelTest { private DatagramChannel remotePeer; private EventLoopGroup eventGroup; private EventExecutor executor; @After public void after() { if (this.remotePeer != null) { if (this.remotePeer.isOpen()) { try { this.remotePeer.close(); } catch (IOException e) { e.printStackTrace(); } } this.remotePeer = null; } if (this.executor != null) { if (!this.executor.isShutdown()) { this.executor.shutdownGracefully(0L, 0L, TimeUnit.MILLISECONDS); } this.executor = null; } if (this.eventGroup != null) { this.eventGroup.shutdownGracefully(0L, 0L, TimeUnit.MILLISECONDS); this.eventGroup = null; } } @SuppressWarnings("unchecked") @Test public void testLifecycle() { // given final SocketAddress localAddress = new InetSocketAddress("127.0.0.1", 2427); final SocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 2727); final ChannelHandler channelHandler = mock(ChannelHandler.class); this.eventGroup = new NioEventLoopGroup(); final Bootstrap bootstrap = new Bootstrap().group(eventGroup).handler(channelHandler).channel(NioDatagramChannel.class); final NettyNetworkManager networkManager = new NettyNetworkManager(bootstrap); final AsyncNettyNetworkChannel<Object> networkChannel = new AsyncNettyNetworkChannel<>(networkManager); final FutureCallback<Void> openCallback = mock(FutureCallback.class); final FutureCallback<Void> bindCallback = mock(FutureCallback.class); final FutureCallback<Void> connectCallback = mock(FutureCallback.class); final FutureCallback<Void> disconnectCallback = mock(FutureCallback.class); final FutureCallback<Void> closeCallback = mock(FutureCallback.class); // when - open networkChannel.open(openCallback); // then verify(openCallback, timeout(100)).onSuccess(null); assertTrue(networkChannel.isOpen()); assertFalse(networkChannel.isBound()); assertFalse(networkChannel.isConnected()); // when - bind networkChannel.bind(localAddress, bindCallback); // then verify(bindCallback, timeout(100)).onSuccess(null); assertTrue(networkChannel.isOpen()); assertTrue(networkChannel.isBound()); assertFalse(networkChannel.isConnected()); // when - connect networkChannel.connect(remoteAddress, connectCallback); // then verify(connectCallback, timeout(100)).onSuccess(null); assertTrue(networkChannel.isOpen()); assertTrue(networkChannel.isBound()); assertTrue(networkChannel.isConnected()); // when - disconnect networkChannel.disconnect(disconnectCallback); // then verify(disconnectCallback, timeout(100)).onSuccess(null); assertTrue(networkChannel.isOpen()); assertTrue(networkChannel.isBound()); assertFalse(networkChannel.isConnected()); // when - close networkChannel.close(closeCallback); // then verify(closeCallback, timeout(100)).onSuccess(null); assertFalse(networkChannel.isOpen()); assertFalse(networkChannel.isBound()); assertFalse(networkChannel.isConnected()); } @SuppressWarnings("unchecked") @Test public void testSendConnected() throws Exception { // given final String message = "hello"; final byte[] data = new byte[message.getBytes().length]; final ByteBuffer dataBuffer = ByteBuffer.allocate(data.length); final SocketAddress localAddress = new InetSocketAddress("127.0.0.1", 2427); final SocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 2727); final ChannelHandler channelHandler = new ObjectChannelHandler(); this.eventGroup = new NioEventLoopGroup(); final Bootstrap bootstrap = new Bootstrap().group(eventGroup).handler(channelHandler).channel(NioDatagramChannel.class); final NettyNetworkManager networkManager = new NettyNetworkManager(bootstrap); final AsyncNettyNetworkChannel<Object> networkChannel = new AsyncNettyNetworkChannel<>(networkManager); final FutureCallback<Void> openCallback = mock(FutureCallback.class); final FutureCallback<Void> bindCallback = mock(FutureCallback.class); final FutureCallback<Void> connectCallback = mock(FutureCallback.class); final FutureCallback<Void> sendCallback = mock(FutureCallback.class); final FutureCallback<Void> closeCallback = mock(FutureCallback.class); // when this.remotePeer = DatagramChannel.open(); this.remotePeer.configureBlocking(false); this.remotePeer.bind(remoteAddress); networkChannel.open(openCallback); verify(openCallback, timeout(100)).onSuccess(null); networkChannel.bind(localAddress, bindCallback); verify(bindCallback, timeout(100)).onSuccess(null); networkChannel.connect(remoteAddress, connectCallback); verify(connectCallback, timeout(100)).onSuccess(null); networkChannel.send(Unpooled.copiedBuffer(message.getBytes()), sendCallback); verify(sendCallback, timeout(100)).onSuccess(null); Thread.sleep(20); final SocketAddress msgSender = remotePeer.receive(dataBuffer); dataBuffer.rewind(); dataBuffer.get(data); this.remotePeer.close(); networkChannel.close(closeCallback); // then assertEquals(localAddress, msgSender); assertEquals(message, new String(data)); } @SuppressWarnings("unchecked") @Test public void testSendDisconnected() throws Exception { // given final String message = "hello"; final byte[] data = new byte[message.getBytes().length]; final ByteBuffer dataBuffer = ByteBuffer.allocate(data.length); final SocketAddress localAddress = new InetSocketAddress("127.0.0.1", 2427); final SocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 2727); final ChannelHandler channelHandler = new ObjectChannelHandler(); this.eventGroup = new NioEventLoopGroup(); final Bootstrap bootstrap = new Bootstrap().group(eventGroup).handler(channelHandler).channel(NioDatagramChannel.class); final NettyNetworkManager networkManager = new NettyNetworkManager(bootstrap); final AsyncNettyNetworkChannel<Object> networkChannel = new AsyncNettyNetworkChannel<>(networkManager); final FutureCallback<Void> openCallback = mock(FutureCallback.class); final FutureCallback<Void> bindCallback = mock(FutureCallback.class); final FutureCallback<Void> sendCallback = mock(FutureCallback.class); final FutureCallback<Void> closeCallback = mock(FutureCallback.class); // when this.remotePeer = DatagramChannel.open(); this.remotePeer.configureBlocking(false); this.remotePeer.bind(remoteAddress); networkChannel.open(openCallback); verify(openCallback, timeout(100)).onSuccess(null); networkChannel.bind(localAddress, bindCallback); verify(bindCallback, timeout(100)).onSuccess(null); networkChannel.send(Unpooled.copiedBuffer(message.getBytes()), remoteAddress, sendCallback); verify(sendCallback, timeout(100)).onSuccess(null); Thread.sleep(20); final SocketAddress msgSender = remotePeer.receive(dataBuffer); dataBuffer.rewind(); dataBuffer.get(data); this.remotePeer.close(); networkChannel.close(closeCallback); // then assertEquals(localAddress, msgSender); assertEquals(message, new String(data)); } @SuppressWarnings("unchecked") @Test public void testReceive() throws Exception { // given final String message = "hello"; final byte[] data = message.getBytes(); final ByteBuffer dataBuffer = ByteBuffer.allocate(data.length); final SocketAddress localAddress = new InetSocketAddress("127.0.0.1", 2427); final SocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 2727); final ObjectChannelHandler channelHandler = new ObjectChannelHandler(); this.eventGroup = new NioEventLoopGroup(); final Bootstrap bootstrap = new Bootstrap().group(eventGroup).handler(channelHandler).channel(NioDatagramChannel.class); final NettyNetworkManager networkManager = new NettyNetworkManager(bootstrap); final AsyncNettyNetworkChannel<String> networkChannel = new AsyncNettyNetworkChannel<>(networkManager); final FutureCallback<Void> openCallback = mock(FutureCallback.class); final FutureCallback<Void> bindCallback = mock(FutureCallback.class); final FutureCallback<Void> closeCallback = mock(FutureCallback.class); // when this.remotePeer = DatagramChannel.open(); this.remotePeer.configureBlocking(false); this.remotePeer.bind(remoteAddress); networkChannel.open(openCallback); verify(openCallback, timeout(100)).onSuccess(null); networkChannel.bind(localAddress, bindCallback); verify(bindCallback, timeout(100)).onSuccess(null); dataBuffer.put(data); dataBuffer.flip(); int sent = remotePeer.send(dataBuffer, localAddress); Thread.sleep(20); this.remotePeer.close(); networkChannel.close(closeCallback); // then assertEquals(sent, data.length); assertEquals(message, channelHandler.getMessage()); } private class ObjectChannelHandler extends MessageToMessageDecoder<DatagramPacket> { private String message = ""; public String getMessage() { return message; } @Override protected void decode(ChannelHandlerContext ctx, DatagramPacket msg, List<Object> out) throws Exception { ByteBuf in = msg.content(); byte[] dst = new byte[in.readableBytes()]; in.readBytes(dst); this.message = new String(dst); } } @SuppressWarnings("unchecked") @Test public void testSendToRemoteWhenDisconnected() throws Exception { // given final String message = "hello"; final SocketAddress localAddress = new InetSocketAddress("127.0.0.1", 2427); final ChannelHandler channelHandler = new ObjectChannelHandler(); this.eventGroup = new NioEventLoopGroup(); final Bootstrap bootstrap = new Bootstrap().group(eventGroup).handler(channelHandler).channel(NioDatagramChannel.class); final NettyNetworkManager networkManager = new NettyNetworkManager(bootstrap); final AsyncNettyNetworkChannel<Object> networkChannel = new AsyncNettyNetworkChannel<>(networkManager); final FutureCallback<Void> openCallback = mock(FutureCallback.class); final FutureCallback<Void> bindCallback = mock(FutureCallback.class); final FutureCallback<Void> sendCallback = mock(FutureCallback.class); final FutureCallback<Void> closeCallback = mock(FutureCallback.class); // when networkChannel.open(openCallback); verify(openCallback, timeout(100)).onSuccess(null); networkChannel.bind(localAddress, bindCallback); verify(bindCallback, timeout(100)).onSuccess(null); networkChannel.send(Unpooled.copiedBuffer(message.getBytes()), sendCallback); networkChannel.close(closeCallback); // then verify(sendCallback, times(1)).onFailure(any(IllegalStateException.class)); } @SuppressWarnings("unchecked") @Test public void testSendToSpecificRemoteWhenConnected() throws Exception { // given final String message = "hello"; final SocketAddress localAddress = new InetSocketAddress("127.0.0.1", 2427); final SocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 2727); final ChannelHandler channelHandler = new ObjectChannelHandler(); this.eventGroup = new NioEventLoopGroup(); final Bootstrap bootstrap = new Bootstrap().group(eventGroup).handler(channelHandler).channel(NioDatagramChannel.class); final NettyNetworkManager networkManager = new NettyNetworkManager(bootstrap); final AsyncNettyNetworkChannel<Object> networkChannel = new AsyncNettyNetworkChannel<>(networkManager); final FutureCallback<Void> openCallback = mock(FutureCallback.class); final FutureCallback<Void> bindCallback = mock(FutureCallback.class); final FutureCallback<Void> connectCallback = mock(FutureCallback.class); final FutureCallback<Void> sendCallback = mock(FutureCallback.class); final FutureCallback<Void> closeCallback = mock(FutureCallback.class); // when networkChannel.open(openCallback); verify(openCallback, timeout(100)).onSuccess(null); networkChannel.bind(localAddress, bindCallback); verify(bindCallback, timeout(100)).onSuccess(null); networkChannel.connect(remoteAddress, connectCallback); verify(connectCallback, timeout(100)).onSuccess(null); networkChannel.send(Unpooled.copiedBuffer(message.getBytes()), remoteAddress, sendCallback); networkChannel.close(closeCallback); // then verify(sendCallback, times(1)).onFailure(any(IllegalStateException.class)); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testOpenFailure() { // given this.eventGroup = new NioEventLoopGroup(); final NettyNetworkManager networkManager = mock(NettyNetworkManager.class); final AsyncNettyNetworkChannel<Object> networkChannel = new AsyncNettyNetworkChannel<>(networkManager); final FutureCallback<Void> openCallback = mock(FutureCallback.class); final Exception exception = new RuntimeException("Testing purposes!"); // when - open doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { final FutureCallback callback = invocation.getArgumentAt(0, FutureCallback.class); callback.onFailure(exception); return null; } }).when(networkManager).openChannel(any(FutureCallback.class)); networkChannel.open(openCallback); // then verify(openCallback, timeout(100)).onFailure(exception); assertFalse(networkChannel.isOpen()); assertFalse(networkChannel.isBound()); assertFalse(networkChannel.isConnected()); } @SuppressWarnings("unchecked") @Test public void testBindFailure() { // given final SocketAddress localAddress = new InetSocketAddress("127.0.0.1", 2427); final ChannelFuture channelBindFuture = mock(ChannelFuture.class); final ChannelFuture channelCloseFuture = mock(ChannelFuture.class); final Channel channel = mock(Channel.class); final ChannelHandler channelHandler = mock(ChannelHandler.class); this.eventGroup = new NioEventLoopGroup(); final Bootstrap bootstrap = new Bootstrap().group(eventGroup).handler(channelHandler).channel(NioDatagramChannel.class); final NettyNetworkManager networkManager = new NettyNetworkManager(bootstrap); final NettyNetworkManager networkManagerSpy = spy(networkManager); final AsyncNettyNetworkChannel<Object> networkChannel = new AsyncNettyNetworkChannel<>(networkManagerSpy); final FutureCallback<Void> openCallback = mock(FutureCallback.class); final FutureCallback<Void> bindCallback = mock(FutureCallback.class); final Exception exception = new RuntimeException("Testing purposes!"); when(channel.bind(localAddress)).thenReturn(channelBindFuture); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { final FutureCallback<Channel> callback = invocation.getArgumentAt(0, FutureCallback.class); callback.onSuccess(channel); return null; } }).when(networkManagerSpy).openChannel(any(FutureCallback.class)); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { final ChannelFutureListener listener = invocation.getArgumentAt(0, ChannelFutureListener.class); final ChannelPromise promise = new DefaultChannelProgressivePromise(channel, mock(EventExecutor.class)); promise.setFailure(exception); listener.operationComplete(promise); return null; } }).when(channelBindFuture).addListener(any(ChannelFutureListener.class)); when(channel.close()).thenReturn(channelCloseFuture); // when - open networkChannel.open(openCallback); networkChannel.bind(localAddress, bindCallback); // then verify(bindCallback, timeout(100)).onFailure(exception); assertFalse(networkChannel.isOpen()); assertFalse(networkChannel.isBound()); assertFalse(networkChannel.isConnected()); } // @SuppressWarnings("unchecked") // @Test // public void testConnectAsyncFailure() throws Exception { // // given // final SocketAddress localAddress = new InetSocketAddress("127.0.0.1", 2427); // final SocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 2727); // final Channel channel = mock(Channel.class); // final NettyNetworkManager networkManager = mock(NettyNetworkManager.class); // final AsynchronousNettyNetworkChannel<Object> networkChannel = new AsynchronousNettyNetworkChannel<>(networkManager); // this.executor = new DefaultEventExecutor(); // final ChannelPromise bindPromise = new DefaultChannelPromise(channel, executor); // final ChannelPromise connectPromise = new DefaultChannelPromise(channel, executor); // final FutureCallback<Void> callback = mock(FutureCallback.class); // final Exception exception = new RuntimeException("Testing purposes!"); // // // when // when(networkManager.openChannel()).thenReturn(channel); // when(channel.isOpen()).thenReturn(true); // when(channel.localAddress()).thenReturn(localAddress); // when(channel.remoteAddress()).thenReturn(null); // when(channel.bind(localAddress)).thenReturn(bindPromise); // when(channel.connect(remoteAddress)).thenReturn(connectPromise); // bindPromise.setSuccess(); // connectPromise.setFailure(exception); // // networkChannel.open(); // networkChannel.bind(localAddress); // networkChannel.connect(remoteAddress, callback); // // // then // verify(networkManager, times(1)).openChannel(); // verify(channel, times(1)).bind(localAddress); // verify(callback, timeout(50)).onFailure(exception); // // assertTrue(networkChannel.isOpen()); // assertTrue(networkChannel.isBound()); // assertFalse(networkChannel.isConnected()); // } }