/*
* Copyright 2013 Google Inc.
*
* Licensed 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 com.google.devcoin.protocols.niowrapper;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import com.google.devcoin.core.Utils;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.ByteString;
import org.devcoin.paymentchannel.Protos;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static com.google.common.base.Preconditions.checkState;
import static org.junit.Assert.*;
public class NioWrapperTest {
private AtomicBoolean fail;
@Before
public void setUp() {
fail = new AtomicBoolean(false);
}
@After
public void checkFail() {
assertFalse(fail.get());
}
@Test
public void basicClientServerTest() throws Exception {
// Tests creating a basic server, opening a client connection and sending a few messages
final SettableFuture<Void> serverConnectionOpen = SettableFuture.create();
final SettableFuture<Void> clientConnectionOpen = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed = SettableFuture.create();
final SettableFuture<Void> clientConnectionClosed = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage1Received = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage2Received = SettableFuture.create();
ProtobufServer server = new ProtobufServer(new ProtobufParserFactory() {
@Override
public ProtobufParser getNewParser(InetAddress inetAddress, int port) {
return new ProtobufParser<Protos.TwoWayChannelMessage>(new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
handler.write(msg);
handler.write(msg);
}
@Override
public void connectionOpen(ProtobufParser handler) {
serverConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser handler) {
serverConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
}
});
server.start(new InetSocketAddress("localhost", 4243));
ProtobufParser<Protos.TwoWayChannelMessage> clientHandler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public synchronized void messageReceived(ProtobufParser handler, Protos.TwoWayChannelMessage msg) {
if (clientMessage1Received.isDone())
clientMessage2Received.set(msg);
else
clientMessage1Received.set(msg);
}
@Override
public void connectionOpen(ProtobufParser handler) {
clientConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser handler) {
clientConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
ProtobufClient client = new ProtobufClient(new InetSocketAddress("localhost", 4243), clientHandler, 0);
clientConnectionOpen.get();
serverConnectionOpen.get();
Protos.TwoWayChannelMessage msg = Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN).build();
clientHandler.write(msg);
assertEquals(msg, clientMessage1Received.get());
assertEquals(msg, clientMessage2Received.get());
client.closeConnection();
serverConnectionClosed.get();
clientConnectionClosed.get();
server.stop();
}
@Test
public void basicTimeoutTest() throws Exception {
// Tests various timeout scenarios
final SettableFuture<Void> serverConnection1Open = SettableFuture.create();
final SettableFuture<Void> clientConnection1Open = SettableFuture.create();
final SettableFuture<Void> serverConnection1Closed = SettableFuture.create();
final SettableFuture<Void> clientConnection1Closed = SettableFuture.create();
final SettableFuture<Void> serverConnection2Open = SettableFuture.create();
final SettableFuture<Void> clientConnection2Open = SettableFuture.create();
final SettableFuture<Void> serverConnection2Closed = SettableFuture.create();
final SettableFuture<Void> clientConnection2Closed = SettableFuture.create();
ProtobufServer server = new ProtobufServer(new ProtobufParserFactory() {
@Override
public ProtobufParser getNewParser(InetAddress inetAddress, int port) {
return new ProtobufParser<Protos.TwoWayChannelMessage>(new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser handler, Protos.TwoWayChannelMessage msg) {
fail.set(true);
}
@Override
public synchronized void connectionOpen(ProtobufParser handler) {
if (serverConnection1Open.isDone()) {
handler.setSocketTimeout(0);
serverConnection2Open.set(null);
} else
serverConnection1Open.set(null);
}
@Override
public synchronized void connectionClosed(ProtobufParser handler) {
if (serverConnection1Closed.isDone()) {
serverConnection2Closed.set(null);
} else
serverConnection1Closed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 10);
}
});
server.start(new InetSocketAddress("localhost", 4243));
new ProtobufClient(new InetSocketAddress("localhost", 4243), new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser handler, Protos.TwoWayChannelMessage msg) {
fail.set(true);
}
@Override
public void connectionOpen(ProtobufParser handler) {
clientConnection1Open.set(null);
}
@Override
public void connectionClosed(ProtobufParser handler) {
clientConnection1Closed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0), 0);
clientConnection1Open.get();
serverConnection1Open.get();
Thread.sleep(15);
assertTrue(clientConnection1Closed.isDone() && serverConnection1Closed.isDone());
ProtobufParser<Protos.TwoWayChannelMessage> client2Handler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser handler, Protos.TwoWayChannelMessage msg) {
fail.set(true);
}
@Override
public void connectionOpen(ProtobufParser handler) {
clientConnection2Open.set(null);
}
@Override
public void connectionClosed(ProtobufParser handler) {
clientConnection2Closed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
ProtobufClient client2 = new ProtobufClient(new InetSocketAddress("localhost", 4243), client2Handler, 0);
clientConnection2Open.get();
serverConnection2Open.get();
Thread.sleep(15);
assertFalse(clientConnection2Closed.isDone() || serverConnection2Closed.isDone());
client2Handler.setSocketTimeout(10);
Thread.sleep(15);
assertTrue(clientConnection2Closed.isDone() && serverConnection2Closed.isDone());
server.stop();
}
@Test
public void largeDataTest() throws Exception {
/** Test various large-data handling, essentially testing {@link ProtobufParser#receive(java.nio.ByteBuffer)} */
final SettableFuture<Void> serverConnectionOpen = SettableFuture.create();
final SettableFuture<Void> clientConnectionOpen = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed = SettableFuture.create();
final SettableFuture<Void> clientConnectionClosed = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage1Received = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage2Received = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage3Received = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> clientMessage4Received = SettableFuture.create();
ProtobufServer server = new ProtobufServer(new ProtobufParserFactory() {
@Override
public ProtobufParser getNewParser(InetAddress inetAddress, int port) {
return new ProtobufParser<Protos.TwoWayChannelMessage>(new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
handler.write(msg);
}
@Override
public void connectionOpen(ProtobufParser handler) {
serverConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser handler) {
serverConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 0x10000, 0);
}
});
server.start(new InetSocketAddress("localhost", 4243));
ProtobufParser<Protos.TwoWayChannelMessage> clientHandler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public synchronized void messageReceived(ProtobufParser handler, Protos.TwoWayChannelMessage msg) {
if (clientMessage1Received.isDone()) {
if (clientMessage2Received.isDone()) {
if (clientMessage3Received.isDone()) {
if (clientMessage4Received.isDone())
fail.set(true);
clientMessage4Received.set(msg);
} else
clientMessage3Received.set(msg);
} else
clientMessage2Received.set(msg);
} else
clientMessage1Received.set(msg);
}
@Override
public void connectionOpen(ProtobufParser handler) {
clientConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser handler) {
clientConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 0x10000, 0);
ProtobufClient client = new ProtobufClient(new InetSocketAddress("localhost", 4243), clientHandler, 0);
clientConnectionOpen.get();
serverConnectionOpen.get();
// Large message that is larger than buffer and equal to maximum message size
Protos.TwoWayChannelMessage msg = Protos.TwoWayChannelMessage.newBuilder()
.setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN)
.setClientVersion(Protos.ClientVersion.newBuilder()
.setMajor(1)
.setPreviousChannelContractHash(ByteString.copyFrom(new byte[0x10000 - 12])))
.build();
// Small message that fits in the buffer
Protos.TwoWayChannelMessage msg2 = Protos.TwoWayChannelMessage.newBuilder()
.setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN)
.setClientVersion(Protos.ClientVersion.newBuilder()
.setMajor(1)
.setPreviousChannelContractHash(ByteString.copyFrom(new byte[1])))
.build();
// Break up the message into chunks to simulate packet network (with strange MTUs...)
byte[] messageBytes = msg.toByteArray();
byte[] messageLength = new byte[4];
Utils.uint32ToByteArrayBE(messageBytes.length, messageLength, 0);
client.writeBytes(new byte[]{messageLength[0], messageLength[1]});
Thread.sleep(10);
client.writeBytes(new byte[]{messageLength[2], messageLength[3]});
Thread.sleep(10);
client.writeBytes(new byte[]{messageBytes[0], messageBytes[1]});
Thread.sleep(10);
client.writeBytes(Arrays.copyOfRange(messageBytes, 2, messageBytes.length - 1));
Thread.sleep(10);
// Now send the end of msg + msg2 + msg3 all at once
byte[] messageBytes2 = msg2.toByteArray();
byte[] messageLength2 = new byte[4];
Utils.uint32ToByteArrayBE(messageBytes2.length, messageLength2, 0);
byte[] sendBytes = Arrays.copyOf(new byte[] {messageBytes[messageBytes.length-1]}, 1 + messageBytes2.length*2 + messageLength2.length*2);
System.arraycopy(messageLength2, 0, sendBytes, 1, 4);
System.arraycopy(messageBytes2, 0, sendBytes, 5, messageBytes2.length);
System.arraycopy(messageLength2, 0, sendBytes, 5 + messageBytes2.length, 4);
System.arraycopy(messageBytes2, 0, sendBytes, 9 + messageBytes2.length, messageBytes2.length);
client.writeBytes(sendBytes);
assertEquals(msg, clientMessage1Received.get());
assertEquals(msg2, clientMessage2Received.get());
assertEquals(msg2, clientMessage3Received.get());
// Now resent msg2 in chunks, by itself
Utils.uint32ToByteArrayBE(messageBytes2.length, messageLength2, 0);
client.writeBytes(new byte[]{messageLength2[0], messageLength2[1]});
Thread.sleep(10);
client.writeBytes(new byte[]{messageLength2[2], messageLength2[3]});
Thread.sleep(10);
client.writeBytes(new byte[]{messageBytes2[0], messageBytes2[1]});
Thread.sleep(10);
client.writeBytes(new byte[]{messageBytes2[2], messageBytes2[3]});
Thread.sleep(10);
client.writeBytes(Arrays.copyOfRange(messageBytes2, 4, messageBytes2.length));
assertEquals(msg2, clientMessage4Received.get());
Protos.TwoWayChannelMessage msg5 = Protos.TwoWayChannelMessage.newBuilder()
.setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN)
.setClientVersion(Protos.ClientVersion.newBuilder()
.setMajor(1)
.setPreviousChannelContractHash(ByteString.copyFrom(new byte[0x10000 - 11])))
.build();
try {
clientHandler.write(msg5);
} catch (IllegalStateException e) {}
// Override max size and make sure the server drops our connection
byte[] messageBytes5 = msg5.toByteArray();
byte[] messageLength5 = new byte[4];
Utils.uint32ToByteArrayBE(messageBytes5.length, messageLength5, 0);
client.writeBytes(messageBytes5);
client.writeBytes(messageLength5);
serverConnectionClosed.get();
clientConnectionClosed.get();
server.stop();
}
@Test
public void testConnectionEventHandlers() throws Exception {
final SettableFuture<Void> serverConnection1Open = SettableFuture.create();
final SettableFuture<Void> serverConnection2Open = SettableFuture.create();
final SettableFuture<Void> serverConnection3Open = SettableFuture.create();
final SettableFuture<Void> client1ConnectionOpen = SettableFuture.create();
final SettableFuture<Void> client2ConnectionOpen = SettableFuture.create();
final SettableFuture<Void> client3ConnectionOpen = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed1 = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed2 = SettableFuture.create();
final SettableFuture<Void> serverConnectionClosed3 = SettableFuture.create();
final SettableFuture<Void> client1ConnectionClosed = SettableFuture.create();
final SettableFuture<Void> client2ConnectionClosed = SettableFuture.create();
final SettableFuture<Void> client3ConnectionClosed = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> client1MessageReceived = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> client2MessageReceived = SettableFuture.create();
final SettableFuture<Protos.TwoWayChannelMessage> client3MessageReceived = SettableFuture.create();
ProtobufServer server = new ProtobufServer(new ProtobufParserFactory() {
@Override
public ProtobufParser getNewParser(InetAddress inetAddress, int port) {
return new ProtobufParser<Protos.TwoWayChannelMessage>(new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) {
handler.write(msg);
}
@Override
public synchronized void connectionOpen(ProtobufParser handler) {
if (serverConnection1Open.isDone()) {
if (serverConnection2Open.isDone())
serverConnection3Open.set(null);
else
serverConnection2Open.set(null);
} else
serverConnection1Open.set(null);
}
@Override
public synchronized void connectionClosed(ProtobufParser handler) {
if (serverConnectionClosed1.isDone()) {
if (serverConnectionClosed2.isDone()) {
checkState(!serverConnectionClosed3.isDone());
serverConnectionClosed3.set(null);
} else
serverConnectionClosed2.set(null);
} else
serverConnectionClosed1.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
}
});
server.start(new InetSocketAddress("localhost", 4243));
ProtobufParser<Protos.TwoWayChannelMessage> client1Handler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser handler, Protos.TwoWayChannelMessage msg) {
client1MessageReceived.set(msg);
}
@Override
public void connectionOpen(ProtobufParser handler) {
client1ConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser handler) {
client1ConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
ProtobufClient client1 = new ProtobufClient(new InetSocketAddress("localhost", 4243), client1Handler, 0);
client1ConnectionOpen.get();
serverConnection1Open.get();
ProtobufParser<Protos.TwoWayChannelMessage> client2Handler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser handler, Protos.TwoWayChannelMessage msg) {
client2MessageReceived.set(msg);
}
@Override
public void connectionOpen(ProtobufParser handler) {
client2ConnectionOpen.set(null);
}
@Override
public void connectionClosed(ProtobufParser handler) {
client2ConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
ProtobufClient client2 = new ProtobufClient(new InetSocketAddress("localhost", 4243), client2Handler, 0);
client2ConnectionOpen.get();
serverConnection2Open.get();
ProtobufParser<Protos.TwoWayChannelMessage> client3Handler = new ProtobufParser<Protos.TwoWayChannelMessage>(
new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() {
@Override
public void messageReceived(ProtobufParser handler, Protos.TwoWayChannelMessage msg) {
client3MessageReceived.set(msg);
}
@Override
public void connectionOpen(ProtobufParser handler) {
client3ConnectionOpen.set(null);
}
@Override
public synchronized void connectionClosed(ProtobufParser handler) {
checkState(!client3ConnectionClosed.isDone());
client3ConnectionClosed.set(null);
}
}, Protos.TwoWayChannelMessage.getDefaultInstance(), 1000, 0);
ProtobufClient client3 = new ProtobufClient(new InetSocketAddress("localhost", 4243), client3Handler, 0);
client3ConnectionOpen.get();
serverConnection3Open.get();
Protos.TwoWayChannelMessage msg = Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN).build();
client1Handler.write(msg);
assertEquals(msg, client1MessageReceived.get());
Protos.TwoWayChannelMessage msg2 = Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.INITIATE).build();
client2Handler.write(msg2);
assertEquals(msg2, client2MessageReceived.get());
client1.closeConnection();
serverConnectionClosed1.get();
client1ConnectionClosed.get();
Protos.TwoWayChannelMessage msg3 = Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.CLOSE).build();
client3Handler.write(msg3);
assertEquals(msg3, client3MessageReceived.get());
// Try to create a race condition by triggering handlerTread closing and client3 closing at the same time
// This often triggers ClosedByInterruptException in handleKey
server.handlerThread.interrupt();
client3.closeConnection();
client3ConnectionClosed.get();
serverConnectionClosed3.get();
server.handlerThread.join();
client2ConnectionClosed.get();
serverConnectionClosed2.get();
server.stop();
}
}