class ClientTlsSession {

  public synchronized Optional<HandshakeResult> handleHandshake(final byte[] msg)
      throws CertificateInvalidException {
    if (clientHello == null || serverHello == null) {
      throw new IllegalStateException("Got handshake in unexpected state");
    }

    handshakeBuffer.writeBytes(msg);

    handshakeBuffer.markReaderIndex();
    try {
      final int pos = handshakeBuffer.readerIndex();
      EncryptedExtensions.parse(handshakeBuffer, true);
      final ServerCertificate sc = ServerCertificate.parse(handshakeBuffer);

      final byte[] scvBytes = new byte[handshakeBuffer.readerIndex() - pos];
      handshakeBuffer.resetReaderIndex();
      handshakeBuffer.readBytes(scvBytes);

      final ServerCertificateVerify scv = ServerCertificateVerify.parse(handshakeBuffer);

      validateServerCertificateVerify(sc, scv, scvBytes);

      final byte[] finBytes = new byte[handshakeBuffer.readerIndex() - pos];
      handshakeBuffer.resetReaderIndex();
      handshakeBuffer.readBytes(finBytes);

      final ServerHandshakeFinished fin = ServerHandshakeFinished.parse(handshakeBuffer);

      final byte[] helloHash = Hash.sha256(clientHello, serverHello);
      validateServerFinish(fin, helloHash, finBytes);

      if (!certificateValidator.validate(sc.getServerCertificates())) {
        throw new CertificateInvalidException();
      }

      handshakeBuffer.resetReaderIndex();

      final byte[] hs = Bytes.drainToArray(handshakeBuffer);
      handshakeBuffer = Unpooled.buffer();

      final byte[] handshakeHash = Hash.sha256(clientHello, serverHello, hs);

      final AEAD aead = OneRttAEAD.create(handshakeSecret, handshakeHash, true);

      // TODO dedup
      final byte[] clientHandshakeTrafficSecret =
          HKDF.expandLabel(handshakeSecret, CLIENT_HANDSHAKE_TRAFFIC_SECRET, helloHash, 32);

      final ClientFinished clientFinished =
          ClientFinished.create(clientHandshakeTrafficSecret, handshakeHash);

      final byte[] b = Bytes.write(clientFinished);

      return Optional.of(new HandshakeResult(b, aead));
    } catch (final IndexOutOfBoundsException e) {
      // wait for more data
      log.debug("Need more data, waiting...");
      handshakeBuffer.resetReaderIndex();

      return Optional.empty();
    }
  }

  public  ClientTlsSession(
      final AEAD initialAEAD,
      final TransportParameters transportParameters,
      final CertificateValidator certificateValidator);

}