package org.httpkit.client;
import org.httpkit.PriorityQueue;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
public class HttpsRequest extends Request {
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
public HttpsRequest(InetSocketAddress addr, ByteBuffer[] request, IRespListener handler,
PriorityQueue<Request> clients, RequestConfig config, SSLEngine engine) {
super(addr, request, handler, clients, config);
this.engine = engine;
}
SSLEngine engine; // package private
private ByteBuffer myNetData = ByteBuffer.allocate(40 * 1024);
private ByteBuffer peerNetData = ByteBuffer.allocate(40 * 1024);
boolean handshaken = false;
final int unwrapRead(ByteBuffer peerAppData) throws IOException {
// TODO, make sure peerNetData has remaining place
int read = ((SocketChannel) key.channel()).read(peerNetData), unwrapped = 0;
if (read > 0) {
peerNetData.flip();
SSLEngineResult res;
while ((res = engine.unwrap(peerNetData, peerAppData)).getStatus() == Status.OK) {
unwrapped += res.bytesProduced();
if (!peerNetData.hasRemaining())
break;
}
peerNetData.compact();
switch (res.getStatus()) {
case OK:
case BUFFER_UNDERFLOW: // need more data
return unwrapped;
case CLOSED:
return unwrapped > 0 ? unwrapped : -1;
case BUFFER_OVERFLOW:
return -1; // can't => peerAppData is 64k
}
return unwrapped;
} else {
return read;
}
}
private void wrapRequest() throws SSLException {
myNetData.clear();
SSLEngineResult res = engine.wrap(request, myNetData);
if (res.getStatus() != Status.OK) {
// TODO larger buffer, uberflow?
}
myNetData.flip();
}
final void writeWrappedRequest() throws IOException {
if (myNetData.hasRemaining()) {
((SocketChannel) key.channel()).write(myNetData);
} else if (request[request.length - 1].hasRemaining()) {
wrapRequest();
((SocketChannel) key.channel()).write(myNetData);
}
if (myNetData.hasRemaining() || request[request.length - 1].hasRemaining()) {
// need more write
if ((key.interestOps() & SelectionKey.OP_WRITE) == 0)
key.interestOps(SelectionKey.OP_WRITE);
} else {
// OK, request sent
key.interestOps(SelectionKey.OP_READ);
}
}
final int doHandshake(ByteBuffer peerAppData) throws IOException {
SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
while (!handshaken) {
switch (hs) {
case NEED_TASK:
Runnable runnable;
while ((runnable = engine.getDelegatedTask()) != null) {
runnable.run();
}
break;
case NEED_UNWRAP:
int read = ((SocketChannel) key.channel()).read(peerNetData);
if (read < 0) {
return -1;
} else {
peerNetData.flip();
SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
peerNetData.compact();
switch (res.getStatus()) {
case BUFFER_OVERFLOW: // Not possible, peerAppData is 64k
break;
case CLOSED:
return -1;
case BUFFER_UNDERFLOW: // need more data from peer
return 0;
}
// do not flip to write here, since TCP buffer is writable
}
break;
case NEED_WRAP:
SSLEngineResult res = engine.wrap(EMPTY_BUFFER, myNetData);
myNetData.flip();
((SocketChannel) key.channel()).write(myNetData);
if (myNetData.hasRemaining()) {
// TODO, make sure data get written
} else {
myNetData.clear();
if (res.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NEED_WRAP)
key.interestOps(SelectionKey.OP_READ);
}
break;
}
hs = engine.getHandshakeStatus();
handshaken = hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING
|| hs == SSLEngineResult.HandshakeStatus.FINISHED;
if (handshaken) {
wrapRequest();
writeWrappedRequest(); // TCP buffer maybe empty this time
}
}
return 0;
}
public void recycle(Request old) throws SSLException {
super.recycle(old);
this.engine = ((HttpsRequest) old).engine;
this.handshaken = true;
wrapRequest(); // prepare for write
}
}