/**
* Copyright (c) 2012, University of Konstanz, Distributed Systems Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the University of Konstanz nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jscsi.initiator.connection;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.DigestException;
import java.util.Iterator;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jscsi.exception.InternetSCSIException;
import org.jscsi.parser.InitiatorMessageParser;
import org.jscsi.parser.ProtocolDataUnit;
import org.jscsi.parser.ProtocolDataUnitFactory;
import org.jscsi.parser.TargetMessageParser;
import org.jscsi.parser.datasegment.OperationalTextKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.carrotsearch.hppc.IntObjectOpenHashMap;
/**
* <h1>SenderWorker</h1>
* <p/>
* The worker caller to send all the protocol data units over the socket of this connection.
*
* @author Volker Wildi
*/
public final class SenderWorker {
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** The logger interface. */
private static final Logger LOGGER = LoggerFactory.getLogger(SenderWorker.class);
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** The <code>Connection</code> instance of this worker caller. */
private final Connection connection;
/**
* Non-blocking socket connection to use for the data transfer.
*/
private final SocketChannel socketChannel;
/**
* Factory class for creating the several <code>ProtocolDataUnit</code> instances.
*/
private final ProtocolDataUnitFactory protocolDataUnitFactory;
// OODRIVE
/**
* Read PDUs.
*/
private final IntObjectOpenHashMap<ProtocolDataUnit> readPDUs = new IntObjectOpenHashMap<>();
private final Lock readPDUsLock = new ReentrantLock();
/**
* Socket selector.
*/
private final Selector selector;
/**
* Timeout on reception.
*
*/
private static final long RECEPTION_TIMEOUT = 20 * 1000;
/**
* Timeout on connection.
*
*/
private static final long CONNECTION_TIMEOUT = 10 * 1000;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Creates a new, empty <code>SenderWorker</code> instance.
*
* @param initConnection
* The reference connection of this worker caller.
* @param inetAddress
* The InetSocketAddress of the Target.
* @throws IOException
* if any IO error occurs.
*/
public SenderWorker(final Connection initConnection, final InetSocketAddress inetAddress)
throws IOException {
connection = initConnection;
// OODRIVE
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
selector = Selector.open();
connectSocket(inetAddress);
// ODRIVE END
socketChannel.socket().setTcpNoDelay(true);
protocolDataUnitFactory = new ProtocolDataUnitFactory();
}
// OODRIVE
private void connectSocket(InetSocketAddress inetAddress) throws IOException{
socketChannel.connect(inetAddress);
socketChannel.register(selector, SelectionKey.OP_CONNECT);
while (selector.select(CONNECTION_TIMEOUT) > 0) {
final Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (key.isValid()) {
if (key.isConnectable()) {
socketChannel.finishConnect();
return ;
}
}
}
}
// Timeout
close();
throw new IOException("Connection Timeout");
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* This method does all the necessary steps, which are needed when a
* connection should be closed.
*
* @throws IOException
* if an I/O error occurs.
*/
public final void close() throws IOException {
socketChannel.close();
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Receives a <code>ProtocolDataUnit</code> from the socket and appends it
* to the end of the receiving queue of this connection.
*
* @return Queue with the resulting units
* @throws IOException
* if an I/O error occurs.
* @throws InternetSCSIException
* if any violation of the iSCSI-Standard emerge.
* @throws DigestException
* if a mismatch of the digest exists.
*/
public ProtocolDataUnit receiveFromWire(final int task) throws DigestException, InternetSCSIException, IOException {
// OODRIVE
long end = System.currentTimeMillis() + RECEPTION_TIMEOUT; // timeout
ProtocolDataUnit protocolDataUnit = null;
do {
synchronized (readPDUs) {
protocolDataUnit = readPDUs.remove(task);
if (protocolDataUnit != null) {
break;
}
}
// Timeout?
if (System.currentTimeMillis() >= end) {
// Timeout
LOGGER.warn("Timeout on response reception");
throw new InternetSCSIException("Timeout");
}
// Attempt read
boolean readLock = readPDUsLock.tryLock();
if (readLock) {
// Can read
try {
try {
final ProtocolDataUnit protocolDataUnitTmp = readProtocolDataUnit();
if (protocolDataUnitTmp != null) {
final int taskTmp = protocolDataUnitTmp.getBasicHeaderSegment().getInitiatorTaskTag();
if (taskTmp == task) {
// Found our PDU
protocolDataUnit = protocolDataUnitTmp;
}
else {
synchronized (readPDUs) {
protocolDataUnit = readPDUs.put(taskTmp, protocolDataUnitTmp);
}
}
}
}
finally {
synchronized (readPDUs) {
readPDUs.notifyAll();
}
}
}
finally {
readPDUsLock.unlock();
}
}
else {
// Another thread is reading
synchronized (readPDUs) {
try {
readPDUs.wait();
}
catch (InterruptedException e) {
throw new InternetSCSIException(e);
}
}
}
} while (protocolDataUnit == null);
// OODRIVE END
final Exception isCorrect = connection.getState(task).isCorrect(protocolDataUnit);
if (isCorrect == null) {
LOGGER.trace("Adding PDU to Receiving Queue.");
final TargetMessageParser parser =
(TargetMessageParser)protocolDataUnit.getBasicHeaderSegment().getParser();
final Session session = connection.getSession();
// the PDU maxCmdSN is greater than the local maxCmdSN, so we
// have to update the local one
if (session.getMaximumCommandSequenceNumber().compareTo(parser.getMaximumCommandSequenceNumber()) < 0) {
session.setMaximumCommandSequenceNumber(parser.getMaximumCommandSequenceNumber());
}
// the PDU expCmdSN is greater than the local expCmdSN, so we
// have to update the local one
if (parser.incrementSequenceNumber()) {
if (connection.getExpectedStatusSequenceNumber().compareTo(parser.getStatusSequenceNumber()) >= 0) {
connection.incrementExpectedStatusSequenceNumber();
} else {
LOGGER.error("Status Sequence Number Mismatch (received, expected): "
+ parser.getStatusSequenceNumber() + ", "
+ (connection.getExpectedStatusSequenceNumber().getValue() - 1));
}
}
} else {
throw new InternetSCSIException(isCorrect);
}
return protocolDataUnit;
}
// OODRIVE
private final ProtocolDataUnit readProtocolDataUnit() throws DigestException, InternetSCSIException, IOException {
final ProtocolDataUnit protocolDataUnit = protocolDataUnitFactory.create(
connection.getSetting(OperationalTextKey.HEADER_DIGEST),
connection.getSetting(OperationalTextKey.DATA_DIGEST));
socketChannel.register(selector, SelectionKey.OP_READ);
while (selector.select(RECEPTION_TIMEOUT) > 0) {
final Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (key.isValid()) {
if (key.isReadable()) {
try {
protocolDataUnit.read(socketChannel, Integer.MAX_VALUE);
}
catch (ClosedChannelException e) {
throw new InternetSCSIException(e);
}
LOGGER.debug("Receiving this PDU: " + protocolDataUnit);
return protocolDataUnit;
}
}
}
}
return null;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Sends the given <code>ProtocolDataUnit</code> instance over the socket to
* the connected iSCSI Target.
*
* @param unit
* The <code>ProtocolDataUnit</code> instances to send.
* @throws InternetSCSIException
* if any violation of the iSCSI-Standard emerge.
* @throws IOException
* if an I/O error occurs.
* @throws InterruptedException
* if another caller interrupted the current caller before or
* while the current caller was waiting for a notification. The
* interrupted status of the current caller is cleared when this
* exception is thrown.
*/
public final synchronized void sendOverWire(final ProtocolDataUnit unit) throws InternetSCSIException, IOException,
InterruptedException {
final Session session = connection.getSession();
/* OODRIVE : initiator task tag is set during pdu creation
unit.getBasicHeaderSegment().setInitiatorTaskTag(session.getInitiatorTaskTag());
*/
final InitiatorMessageParser parser =
(InitiatorMessageParser)unit.getBasicHeaderSegment().getParser();
parser.setCommandSequenceNumber(session.getCommandSequenceNumber());
parser.setExpectedStatusSequenceNumber(connection.getExpectedStatusSequenceNumber().getValue());
unit.write(socketChannel);
LOGGER.debug("Sending this PDU: " + unit);
// increment the Command Sequence Number
if (parser.incrementSequenceNumber()) {
connection.getSession().incrementCommandSequenceNumber();
}
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
}