/**
* 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.security.DigestException;
import java.util.Queue;
import org.jscsi.exception.InternetSCSIException;
import org.jscsi.exception.NoSuchSessionException;
import org.jscsi.exception.OperationalTextKeyException;
import org.jscsi.initiator.Configuration;
import org.jscsi.initiator.connection.state.IState;
import org.jscsi.parser.ProtocolDataUnit;
import org.jscsi.parser.datasegment.OperationalTextKey;
import org.jscsi.parser.datasegment.SettingsMap;
import org.jscsi.utils.SerialArithmeticNumber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.carrotsearch.hppc.IntObjectOpenHashMap;
/**
* <h1>AbsConnection</h1>
* <p/>
* This abstract class represents a connection, which is used in the iSCSI Standard (RFC3720). Such a
* connection is directed from the initiator to the target. It is used in Sessions.
*
* @author Volker Wildi, University of Konstanz
* @author Patrice Matthias Brend'amour, University of Konstanz
* @author Sebastian Graf, University of Konstanz
*/
public final class Connection {
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** The logger interface. */
private static final Logger LOGGER = LoggerFactory.getLogger(Connection.class);
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* The <code>Session</code> instance, which contains this <code>Connection</code> instance.
*/
private final Session referenceSession;
/** The <code>Configuration</code> instance for this connection. */
private final Configuration configuration;
/** The current states of this connection. */
private final IntObjectOpenHashMap<IState> states = new IntObjectOpenHashMap<>();
/**
* The ID of this connection. This must be unique within a <code>Session</code>.
*/
private final short connectionID;
/**
* The Expected Status Sequence Number, which is expected to received from
* the target within this connection.
*/
private final SerialArithmeticNumber expectedStatusSequenceNumber;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* The worker caller, which handles the transmission of the packages over
* the network.
*/
private final SenderWorker senderReceiver;
/**
* Method to create and return a new, empty <code>Connection</code> object
* with the configured layer of threading.
*
* @param session
* Reference to the <code>AbsSession</code> object, which
* contains this connection.
* @param initConfiguration
* The configuration to use within this connection.
* @param inetAddress
* The <code>InetSocketAddress</code> to which this connection
* should established.
* @param initConnectionID
* The ID of this connection.
* @throws Exception
* If any error occurs.
*/
public Connection(final Session session, final Configuration initConfiguration,
final InetSocketAddress inetAddress, final short initConnectionID) throws Exception {
senderReceiver = new SenderWorker(this, inetAddress);
configuration = initConfiguration;
connectionID = initConnectionID;
referenceSession = session;
expectedStatusSequenceNumber = new SerialArithmeticNumber();
}
/**
* Updates all entries of the given response key-values with the stored
* settings of this instance.
*
* @param response
* The settings of the response.
* @throws NoSuchSessionException
* if a session with this target name is not open.
*/
public final void update(final SettingsMap response) throws NoSuchSessionException {
configuration.update(referenceSession.getTargetName(), connectionID, response);
}
/**
* Returns the value of the given parameter, which is parsed to an <code>boolean</code>.
*
* @param textKey
* The name of the parameter.
* @return The <code>boolean</code> value of this parameter. So if the value
* is equal to <code>Yes</code>, then <code>true</code> will be
* returned. Else <code>false</code> is returned.
* @throws OperationalTextKeyException
* If the given parameter cannot be found.
*/
public final boolean getSettingAsBoolean(final OperationalTextKey textKey)
throws OperationalTextKeyException {
return getSetting(textKey).compareTo("Yes") == 0;
}
/**
* Returns the value of the given parameter, which is parsed to an <code>integer</code>.
*
* @param textKey
* The name of the parameter.
* @return The <code>integer</code> value of this parameter.
* @throws OperationalTextKeyException
* If the given parameter cannot be found.
*/
public final int getSettingAsInt(final OperationalTextKey textKey) throws OperationalTextKeyException {
return Integer.parseInt(getSetting(textKey));
}
/**
* Returns the value of the given parameter as <code>String</code>.
*
* @param textKey
* The name of the parameter.
* @return The value of this parameter.
* @throws OperationalTextKeyException
* If the given parameter cannot be found.
*/
public final String getSetting(final OperationalTextKey textKey) throws OperationalTextKeyException {
return configuration.getSetting(referenceSession.getTargetName(), connectionID, textKey);
}
/**
* Returns the settings of the given session and connection.
*
* @return The settings of this specific connection.
*/
public final SettingsMap getSettings() {
return configuration.getSettings(referenceSession.getTargetName(), connectionID);
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Increments the Expected Status Sequence Number as defined in RFC1982
* where <code>SERIAL_BITS = 32</code>.
*/
public final void incrementExpectedStatusSequenceNumber() {
expectedStatusSequenceNumber.increment();
}
/**
* Returns the Expected Status Sequence Number of this <code>Connection</code> object.
*
* @return The current Expected Status Sequence Number.
*/
public final SerialArithmeticNumber getExpectedStatusSequenceNumber() {
return expectedStatusSequenceNumber;
}
/**
* Sets the expected Status Sequence Number to the given one from the
* leading Login Response.
*
* @param newExpectedStatusSequenceNumber
* The new value.
*/
public final void setExpectedStatusSequenceNumber(final int newExpectedStatusSequenceNumber) {
expectedStatusSequenceNumber.setValue(newExpectedStatusSequenceNumber);
LOGGER.trace("Set ExpStatSN to " + expectedStatusSequenceNumber);
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Switch to the new state. Start point of the state pattern. All states are
* computed one after another.
*
* @param newState
* The new state.
* @throws InternetSCSIException
* of any kind
*/
public final void nextState(final IState newState, final int task) throws InternetSCSIException {
synchronized (states) {
this.states.put(task, newState);
}
if (getState(task) != null) {
do {
this.getState(task).execute();
LOGGER.info("State is following: " + this.getState(task).nextStateFollowing());
} while (this.getState(task).nextStateFollowing());
}
}
/**
* Returns the current state of this connection.
*
* @return The current <code>IState</code> instance of this <code>Connection</code> instance.
*/
public final IState getState(int task) {
synchronized(states){
return states.get(task);
}
}
/**
* Returns the session, which contains this connection instance.
*
* @return The parent session instance.
*/
public final Session getSession() {
return referenceSession;
}
/**
* Returns the ID of this <code>Connection</code> object.
*
* @return The connection ID.
*/
public final short getConnectionID() {
return connectionID;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* 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 {
senderReceiver.close();
LOGGER.debug("Connection with ID " + connectionID + " closed.");
}
/**
* Enqueue this protocol data unit to the end of the sending queue.
*
* @param protocolDataUnit
* The protocol data unit to add.
* @throws InternetSCSIException
* for nearly everything
*/
public final void send(final ProtocolDataUnit protocolDataUnit) throws InternetSCSIException {
try {
senderReceiver.sendOverWire(protocolDataUnit);
} catch (IOException e) {
throw new InternetSCSIException(e);
} catch (InterruptedException e) {
throw new InternetSCSIException(e);
}
}
/**
* Enqueue all protocol data units to the end of the sending queue.
*
* @param protocolDataUnits
* The list with all protocol data units to add.
* @throws InternetSCSIException
* for nearly everything
*/
public final void send(final Queue<ProtocolDataUnit> protocolDataUnits) throws InternetSCSIException {
for (final ProtocolDataUnit unit : protocolDataUnits) {
send(unit);
}
}
/**
* Reads one <code>ProtocolDataUnit</code> instance from the <code>receivingQueue</code>.
*
* @return An instance of a <code>ProtocolDataUnit</code>.
* @throws InternetSCSIException
* for nearly everything
*/
public final ProtocolDataUnit receive(int initiatorTaskTag) throws InternetSCSIException {
try {
// OODRIVE
return senderReceiver.receiveFromWire(initiatorTaskTag);
} catch (DigestException e) {
throw new InternetSCSIException(e);
} catch (IOException e) {
throw new InternetSCSIException(e);
}
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
}