// Copyright (c) 2007-Present Pivotal Software, Inc. All rights reserved. // // This software, the RabbitMQ Java client library, is triple-licensed under the // Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 // ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see // LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, // please see LICENSE-APACHE2. // // This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, // either express or implied. See the LICENSE file for specific language governing // rights and limitations of this software. // // If you have any questions regarding licensing, please contact us at // info@rabbitmq.com. package com.rabbitmq.client.impl.nio; import com.rabbitmq.client.impl.AMQConnection; import com.rabbitmq.client.impl.Frame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLEngine; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; /** * */ public class SocketChannelFrameHandlerState { private static final Logger LOGGER = LoggerFactory.getLogger(SocketChannelFrameHandlerState.class); /** Time to linger before closing the socket forcefully. */ private static final int SOCKET_CLOSING_TIMEOUT = 1; private final SocketChannel channel; private final BlockingQueue<WriteRequest> writeQueue; private volatile AMQConnection connection; /** should be used only in the NIO read thread */ private long lastActivity; private final SelectorHolder writeSelectorState; private final SelectorHolder readSelectorState; private final int writeEnqueuingTimeoutInMs; final boolean ssl; final SSLEngine sslEngine; /** outbound app data (to be crypted if TLS is on) */ final ByteBuffer plainOut; /** inbound app data (deciphered if TLS is on) */ final ByteBuffer plainIn; /** outbound net data (ciphered if TLS is on) */ final ByteBuffer cipherOut; /** inbound data (ciphered if TLS is on) */ final ByteBuffer cipherIn; final DataOutputStream outputStream; final DataInputStream inputStream; public SocketChannelFrameHandlerState(SocketChannel channel, NioLoopContext nioLoopsState, NioParams nioParams, SSLEngine sslEngine) { this.channel = channel; this.readSelectorState = nioLoopsState.readSelectorState; this.writeSelectorState = nioLoopsState.writeSelectorState; this.writeQueue = new ArrayBlockingQueue<WriteRequest>(nioParams.getWriteQueueCapacity(), true); this.writeEnqueuingTimeoutInMs = nioParams.getWriteEnqueuingTimeoutInMs(); this.sslEngine = sslEngine; if(this.sslEngine == null) { this.ssl = false; this.plainOut = nioLoopsState.writeBuffer; this.cipherOut = null; this.plainIn = nioLoopsState.readBuffer; this.cipherIn = null; this.outputStream = new DataOutputStream( new ByteBufferOutputStream(channel, plainOut) ); this.inputStream = new DataInputStream( new ByteBufferInputStream(channel, plainIn) ); } else { this.ssl = true; this.plainOut = ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize()); this.cipherOut = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize()); this.plainIn = ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize()); this.cipherIn = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize()); this.outputStream = new DataOutputStream( new SslEngineByteBufferOutputStream(sslEngine, plainOut, cipherOut, channel) ); this.inputStream = new DataInputStream( new SslEngineByteBufferInputStream(sslEngine, plainIn, cipherIn, channel) ); } } public SocketChannel getChannel() { return channel; } public Queue<WriteRequest> getWriteQueue() { return writeQueue; } public void sendHeader() throws IOException { sendWriteRequest(new HeaderWriteRequest()); } public void write(Frame frame) throws IOException { sendWriteRequest(new FrameWriteRequest(frame)); } private void sendWriteRequest(WriteRequest writeRequest) throws IOException { try { boolean offered = this.writeQueue.offer(writeRequest, writeEnqueuingTimeoutInMs, TimeUnit.MILLISECONDS); if(offered) { this.writeSelectorState.registerFrameHandlerState(this, SelectionKey.OP_WRITE); this.readSelectorState.selector.wakeup(); } else { throw new IOException("Frame enqueuing failed"); } } catch (InterruptedException e) { LOGGER.warn("Thread interrupted during enqueuing frame in write queue"); } } public void startReading() { this.readSelectorState.registerFrameHandlerState(this, SelectionKey.OP_READ); } public AMQConnection getConnection() { return connection; } public void setConnection(AMQConnection connection) { this.connection = connection; } public void setLastActivity(long lastActivity) { this.lastActivity = lastActivity; } public long getLastActivity() { return lastActivity; } void prepareForWriteSequence() { if(ssl) { plainOut.clear(); cipherOut.clear(); } } void endWriteSequence() { if(!ssl) { plainOut.clear(); } } void prepareForReadSequence() throws IOException { if(ssl) { cipherIn.clear(); plainIn.clear(); cipherIn.flip(); plainIn.flip(); } else { NioHelper.read(channel, plainIn); plainIn.flip(); } } boolean continueReading() throws IOException { if(ssl) { if (!plainIn.hasRemaining() && !cipherIn.hasRemaining()) { // need to try to read something cipherIn.clear(); int bytesRead = NioHelper.read(channel, cipherIn); if (bytesRead <= 0) { return false; } else { cipherIn.flip(); return true; } } else { return true; } } else { if (!plainIn.hasRemaining()) { plainIn.clear(); NioHelper.read(channel, plainIn); plainIn.flip(); } return plainIn.hasRemaining(); } } void close() throws IOException { if(ssl) { SslEngineHelper.close(channel, sslEngine); } if(!channel.isOpen()) { channel.socket().setSoLinger(true, SOCKET_CLOSING_TIMEOUT); channel.close(); } } }