package io.eguan.nbdsrv;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import io.eguan.nbdsrv.packet.ExportFlagsPacket;
import io.eguan.nbdsrv.packet.GlobalFlagsPacket;
import io.eguan.nbdsrv.packet.InitPacket;
import io.eguan.nbdsrv.packet.NbdException;
import io.eguan.nbdsrv.packet.OptionCmd;
import io.eguan.nbdsrv.packet.OptionPacket;
import io.eguan.nbdsrv.packet.OptionReplyCmd;
import io.eguan.nbdsrv.packet.OptionReplyPacket;
import io.eguan.nbdsrv.packet.Utils;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents the Handshake phase. Handle the different states during this phase
*
* @author oodrive
* @author ebredzinski
*
*/
final class HandshakePhase extends PhaseAbstract {
private static final Logger LOGGER = LoggerFactory.getLogger(HandshakePhase.class);
/** The current state of this phase */
private HandshakeState state;
HandshakePhase(final ClientConnection client) {
super(client);
// Init state
this.state = HandshakeState.INITIALIZATION;
}
@Override
final boolean execute() throws NbdException, IOException {
final ClientConnection connection = getConnection();
switch (this.state) {
case INITIALIZATION:
LOGGER.debug("enter INITIALIZATION phase");
writeInitPacket(connection);
this.state = HandshakeState.GLOBAL_FLAGS_RECEPTION;
return true;
case GLOBAL_FLAGS_RECEPTION:
LOGGER.debug("enter GLOBAL_FLAGS_RECEPTION phase");
final long flags = readGlobalFlagsPacket(connection);
connection.setRemoteFlags(flags);
LOGGER.debug("Remote flags: " + flags);
this.state = HandshakeState.OPTIONS_NEGOCIATION;
return true;
case OPTIONS_NEGOCIATION:
LOGGER.debug("enter OPTIONS_NEGOCIATION phase");
final OptionPacket option = readOptionPacket(connection);
switch (option.getOptionCode()) {
case NBD_OPT_EXPORT_NAME:
final String name = handleExportName(option, connection);
connection.setExportName(name);
connection.setNbdDevice(name);
LOGGER.debug("Export Name: " + name);
EndNegotiation(connection);
connection.setPhase(new DataPushingPhase(connection));
break;
case NBD_OPT_ABORT:
// Session terminated by the client
LOGGER.info("Session aborted by the client");
return false;
case NBD_OPT_LIST:
handleList(connection);
break;
default:
// Skip unknown options
break;
}
break;
default:
LOGGER.warn("Phase not expected " + this.state);
break;
}
return true;
}
/**
* Send the init packet to the client.
*
* @param connection
* the {@link ClientConnection}
* @throws IOException
* if the write on the socket failed
*
*/
private final void writeInitPacket(final ClientConnection connection) throws IOException {
LOGGER.debug("Write Init Packet");
int flags = 0;
flags |= InitPacket.NBD_FLAG_FIXED_NEWSTYLE;
final InitPacket packet = new InitPacket(InitPacket.MAGIC_STR, InitPacket.MAGIC, flags);
final ByteBuffer src = InitPacket.serialize(packet);
try {
connection.write(src);
}
finally {
InitPacket.release(src);
}
}
/**
* Read the global flags from the client.
*
* @param connection
* the {@link ClientConnection}
* @throws IOException
* if the read on the socket failed
* @throws NbdException
* if the packet is not conform to the NBD protocol
*
*/
private final long readGlobalFlagsPacket(final ClientConnection connection) throws NbdException, IOException {
LOGGER.debug("Read GlobalFlags Packet");
final ByteBuffer dst = GlobalFlagsPacket.allocateHeader();
try {
connection.read(dst);
return GlobalFlagsPacket.deserialize(dst);
}
finally {
GlobalFlagsPacket.release(dst);
}
}
/**
* Read Option from the client.
*
* @param connection
* the {@link ClientConnection}
* @throws IOException
* if the read on the socket failed
* @throws NbdException
* if the packet is not conform to the NBD protocol
*
*/
private final OptionPacket readOptionPacket(final ClientConnection connection) throws NbdException, IOException {
LOGGER.debug("Read Option Packet");
final ByteBuffer dst = OptionPacket.allocateHeader();
try {
connection.read(dst);
return OptionPacket.deserialize(dst);
}
finally {
OptionPacket.release(dst);
}
}
/**
* Handle the NBD_OPT_LIST command reply.
*
* @param connection
* the {@link ClientConnection}
* @throws IOException
* if the read on the socket failed
*
*/
private final void handleList(final ClientConnection connection) throws IOException {
LOGGER.debug("Handle List");
final String[] exportsName = connection.getExportList();
// Send list
final OptionReplyPacket packetList = new OptionReplyPacket(OptionReplyPacket.MAGIC, OptionCmd.NBD_OPT_LIST,
OptionReplyCmd.NBD_REP_SERVER);
final ByteBuffer[] list = OptionReplyPacket.serializeMultiple(packetList, exportsName);
try {
connection.write(list);
}
finally {
OptionReplyPacket.release(list);
}
// Send Ack
final OptionReplyPacket packetAck = new OptionReplyPacket(OptionReplyPacket.MAGIC, OptionCmd.NBD_OPT_LIST,
OptionReplyCmd.NBD_REP_ACK);
final ByteBuffer ack = OptionReplyPacket.serialize(packetAck, "");
try {
connection.write(ack);
}
finally {
OptionReplyPacket.release(ack);
}
}
/**
* Handle the export name option.
*
* @param connection
* the {@link ClientConnection}
* @throws IOException
* if the read on the socket failed
*
*/
private final String handleExportName(final OptionPacket option, final ClientConnection connection)
throws IOException {
LOGGER.debug("Handle ExportName");
final ByteBuffer dst = OptionPacket.allocateData(Utils.getUnsignedIntPositive(option.getSize()));
try {
connection.read(dst);
return OptionPacket.getData(dst);
}
finally {
OptionPacket.release(dst);
}
}
/**
* Send server export information and flags to the client.
*
* @param connection
* the {@link ClientConnection}
* @throws IOException
* if the read on the socket failed
* @throws NbdException
* if the export name was not set before
*
*/
private final void EndNegotiation(final ClientConnection connection) throws IOException, NbdException {
LOGGER.debug("End Negotiation");
// End of negotiation : send export flags and size
int flags = ExportFlagsPacket.NBD_FLAG_HAS_FLAGS;
if (connection.isExportReadOnly()) {
LOGGER.debug("Set export read only");
flags |= ExportFlagsPacket.NBD_FLAG_READ_ONLY;
}
if (connection.isTrimEnabled()) {
LOGGER.debug("Set trim allowed");
flags |= ExportFlagsPacket.NBD_FLAG_SEND_TRIM;
}
/*
* Not supported flags |= NBD_FLAG_SEND_FLUSH; flags |= NBD_FLAG_SEND_FUA; flags |= NBD_FLAG_ROTATIONAL;
*/
final ExportFlagsPacket packet = new ExportFlagsPacket(connection.getExportSize(), flags);
final ByteBuffer src = ExportFlagsPacket.serialize(packet);
try {
connection.write(src);
}
finally {
InitPacket.release(src);
}
}
}