package io.eguan.nbdsrv.client; /* * #%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.OptionCmd; import io.eguan.nbdsrv.packet.OptionPacket; import io.eguan.nbdsrv.packet.OptionReplyCmd; import io.eguan.nbdsrv.packet.OptionReplyPacket; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents the task to negotiate option during the handshake. * * @author oodrive * @author ebredzinski * */ class OptionNegotiationTask implements Callable<String[]> { private static final Logger LOGGER = LoggerFactory.getLogger(OptionNegotiationTask.class); /** NBD client */ private final NbdClient nbdClient; /** Option to negotiate */ private final OptionCmd cmd; /** Data to send with the option. Can be null **/ private final String data; private static final String[] EMPTY_STRING_ARRAY = new String[0]; OptionNegotiationTask(final NbdClient nbdClient, final OptionCmd cmd, final String data) { this.nbdClient = nbdClient; this.cmd = cmd; this.data = data; } @Override public final String[] call() throws IOException { switch (cmd) { case NBD_OPT_LIST: sendListOption(); return readListReply(); case NBD_OPT_EXPORT_NAME: sendExportNameOption(data); // Next step : server sends its export flags nbdClient.setExportName(data); handleExportFlags(); break; case NBD_OPT_ABORT: sendAbortOption(); // Close the client nbdClient.close(); break; default: break; } return EMPTY_STRING_ARRAY; } /** * Send a request to have the list of the export. * * @throws IOException * if I/O errors occurs */ private final void sendListOption() throws IOException { LOGGER.debug("Ask export list"); final OptionPacket optionPacket = new OptionPacket(OptionPacket.MAGIC, OptionCmd.NBD_OPT_LIST, 0); final ByteBuffer[] src = OptionPacket.serialize(optionPacket, null); try { nbdClient.writeSocket(src); } finally { OptionPacket.release(src); } } /** * Send a request to be logged into an export. * * @param name * the name of the export * @throws IOException * if I/O errors occur */ private final void sendExportNameOption(final String name) throws IOException { LOGGER.debug("Set export name= " + name); final OptionPacket optionPacket = new OptionPacket(OptionPacket.MAGIC, OptionCmd.NBD_OPT_EXPORT_NAME, name.length()); final ByteBuffer[] src = OptionPacket.serialize(optionPacket, name); try { nbdClient.writeSocket(src); } finally { OptionPacket.release(src); } } /** * Send abort to stop the handshake. * * @throws IOException * if I/O errors occur */ private final void sendAbortOption() throws IOException { LOGGER.debug("Abort"); final OptionPacket optionPacket = new OptionPacket(OptionPacket.MAGIC, OptionCmd.NBD_OPT_ABORT, 0); final ByteBuffer[] src = OptionPacket.serialize(optionPacket, null); try { nbdClient.writeSocket(src); } finally { OptionPacket.release(src); } } /** * Read the answer of the server. * * @return a {@link OptionReplyPacket} for the reply * * @throws IOException * if an I/O error occurs */ private final OptionReplyPacket readOptionReply() throws IOException { LOGGER.debug("Read Option Reply"); final ByteBuffer dst = OptionReplyPacket.allocateHeader(); try { nbdClient.readSocket(dst); return OptionReplyPacket.deserialize(dst); } finally { OptionReplyPacket.release(dst); } } /** * Read Data from the option reply. * * @param reply * the {@link OptionReplyPacket} with contains the header of the reply * @return the data as an array of String * * @throws IOException * if an I/O error occurs */ private final String[] readDataOptionReply(final OptionReplyPacket reply) throws IOException { LOGGER.debug("Read data from Option Reply"); final ByteBuffer dst = OptionReplyPacket.allocateData((int) (reply.getDataSize())); try { nbdClient.readSocket(dst); return OptionReplyPacket.getData(dst, reply); } finally { OptionPacket.release(dst); } } /** * Read and save export flags. * * @throws IOException * if I/O error occurs */ private final void handleExportFlags() throws IOException { final ExportFlagsPacket exportFlagsPacket = readExportFlagsPacket(); nbdClient.setExportFlags(exportFlagsPacket.getExportFlags()); nbdClient.setExportSize(exportFlagsPacket.getExportSize()); } /** * Read export flags. * * @return the {@link ExportFlagsPacket} of the server * * @throws IOException * if I/O error occurs */ private final ExportFlagsPacket readExportFlagsPacket() throws IOException { LOGGER.debug("Read Export Flags"); final ByteBuffer dst = ExportFlagsPacket.allocateHeader(); try { nbdClient.readSocket(dst); return ExportFlagsPacket.deserialize(dst); } finally { ExportFlagsPacket.release(dst); } } /** * Read the reply for list request. * * @return the list as an array of String * * @throws IOException * if I/O error occurs */ private final String[] readListReply() throws IOException { LOGGER.debug("Read Export List"); boolean end = false; final List<String> exports = new ArrayList<>(); while (!end) { final OptionReplyPacket packet = readOptionReply(); final OptionCmd cmd = packet.getOptionCmd(); final OptionReplyCmd replyCmd = packet.getReplyCmd(); switch (cmd) { case NBD_OPT_LIST: switch (replyCmd) { case NBD_REP_SERVER: if (packet.getDataSize() != 0) { final String[] s = readDataOptionReply(packet); LOGGER.debug("Receive: " + s[0]); exports.add(s[0]); } break; case NBD_REP_ACK: LOGGER.debug("End of list reception"); end = true; break; default: break; } default: break; } } return exports.toArray(EMPTY_STRING_ARRAY); } }