org.jivesoftware.openfire.net.TLSStreamWriter Maven / Gradle / Ivy
/*
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
*
* 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 org.jivesoftware.openfire.net;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
/**
* A TLSStreamWriter
that returns a special OutputStream that hides the ByteBuffers
* used by the underlying Channels.
*
* @author Hao Chen
*
*/
public class TLSStreamWriter {
/**
* TLSWrapper
is a TLS wrapper for connections requiring TLS protocol.
*/
private TLSWrapper wrapper;
private WritableByteChannel wbc;
private ByteBuffer outAppData;
public TLSStreamWriter(TLSWrapper tlsWrapper, Socket socket) throws IOException {
wrapper = tlsWrapper;
// DANIELE: Add code to use directly the socket channel
if (socket.getChannel() != null) {
wbc = ServerTrafficCounter.wrapWritableChannel(socket.getChannel());
}
else {
wbc = Channels.newChannel(
ServerTrafficCounter.wrapOutputStream(socket.getOutputStream()));
}
outAppData = ByteBuffer.allocate(tlsWrapper.getAppBuffSize());
}
private void doWrite(ByteBuffer buff) throws IOException {
if (buff == null) {
// Possibly handshaking process
buff = ByteBuffer.allocate(0);
}
if (wrapper == null) {
writeToSocket(buff);
} else {
tlsWrite(buff);
}
}
private void tlsWrite(ByteBuffer buf) throws IOException {
ByteBuffer tlsBuffer;
ByteBuffer tlsOutput;
do {
// TODO Consider optimizing by not creating new instances each time
tlsBuffer = ByteBuffer.allocate(Math.min(buf.remaining(), wrapper.getAppBuffSize()));
tlsOutput = ByteBuffer.allocate(wrapper.getNetBuffSize());
while (tlsBuffer.hasRemaining() && buf.hasRemaining()) {
tlsBuffer.put(buf.get());
}
tlsBuffer.flip();
wrapper.wrap(tlsBuffer, tlsOutput);
tlsOutput.flip();
writeToSocket(tlsOutput);
tlsOutput.clear();
} while (buf.hasRemaining());
}
/*
* Writes outNetData to the SocketChannel. Returns true when the ByteBuffer has no remaining
* data.
*/
private boolean writeToSocket(ByteBuffer outNetData) throws IOException {
wbc.write(outNetData);
return !outNetData.hasRemaining();
}
public OutputStream getOutputStream() {
return createOutputStream();
}
/*
* Returns an output stream for a ByteBuffer. The write() methods use the relative ByteBuffer
* put() methods.
*/
private OutputStream createOutputStream() {
return new OutputStream() {
@Override
public synchronized void write(int b) throws IOException {
outAppData.put((byte) b);
outAppData.flip();
doWrite(outAppData);
outAppData.clear();
}
@Override
public synchronized void write(byte[] bytes, int off, int len) throws IOException {
outAppData = resizeApplicationBuffer(bytes.length);
outAppData.put(bytes, off, len);
outAppData.flip();
doWrite(outAppData);
outAppData.clear();
}
};
}
private ByteBuffer resizeApplicationBuffer(int increment) {
// TODO Creating new buffers and copying over old one may not scale. Consider using views. Thanks to Noah for the tip.
if (outAppData.remaining() < increment) {
ByteBuffer bb = ByteBuffer.allocate(outAppData.capacity() + wrapper.getAppBuffSize());
outAppData.flip();
bb.put(outAppData);
return bb;
} else {
return outAppData;
}
}
}