@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();
    NioServer server =
        new NioServer(
            new StreamParserFactory() {
              @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);
              }
            },
            new InetSocketAddress("localhost", 4243));
    server.startAndWait();

    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);

    MessageWriteTarget client =
        openConnection(new InetSocketAddress("localhost", 4243), clientHandler);

    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.stopAndWait();
    assertFalse(server.isRunning());
  }
  @Test
  public void largeDataTest() throws Exception {
    /**
     * Test various large-data handling, essentially testing {@link
     * ProtobufParser#receiveBytes(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();
    NioServer server =
        new NioServer(
            new StreamParserFactory() {
              @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);
              }
            },
            new InetSocketAddress("localhost", 4243));
    server.startAndWait();

    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);

    MessageWriteTarget client =
        openConnection(new InetSocketAddress("localhost", 4243), clientHandler);

    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[] messageLength5 = new byte[4];
    Utils.uint32ToByteArrayBE(msg5.toByteArray().length, messageLength5, 0);
    client.writeBytes(messageLength5);

    serverConnectionClosed.get();
    clientConnectionClosed.get();

    server.stopAndWait();
  }
  @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();
    NioServer server =
        new NioServer(
            new StreamParserFactory() {
              @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);
              }
            },
            new InetSocketAddress("localhost", 4243));
    server.startAndWait();

    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);
    MessageWriteTarget client1 =
        openConnection(new InetSocketAddress("localhost", 4243), client1Handler);

    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);
    openConnection(new InetSocketAddress("localhost", 4243), client2Handler);

    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);
    NioClient client3 = new NioClient(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 handlerThread closing and client3 closing at the
    // same time
    // This often triggers ClosedByInterruptException in handleKey
    server.stop();
    server.selector.wakeup();
    client3.closeConnection();
    client3ConnectionClosed.get();
    serverConnectionClosed3.get();

    server.stopAndWait();
    client2ConnectionClosed.get();
    serverConnectionClosed2.get();

    server.stopAndWait();
  }
  @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();
    NioServer server =
        new NioServer(
            new StreamParserFactory() {
              @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);
              }
            },
            new InetSocketAddress("localhost", 4243));
    server.startAndWait();

    openConnection(
        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));

    clientConnection1Open.get();
    serverConnection1Open.get();
    long closeDelayStart = System.currentTimeMillis();
    clientConnection1Closed.get();
    serverConnection1Closed.get();
    long closeDelayFinish = System.currentTimeMillis();

    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);
    openConnection(new InetSocketAddress("localhost", 4243), client2Handler);

    clientConnection2Open.get();
    serverConnection2Open.get();
    Thread.sleep((closeDelayFinish - closeDelayStart) * 10);
    assertFalse(clientConnection2Closed.isDone() || serverConnection2Closed.isDone());

    client2Handler.setSocketTimeout(10);
    clientConnection2Closed.get();
    serverConnection2Closed.get();

    server.stopAndWait();
  }