summaryrefslogtreecommitdiffstats
path: root/src/server/iscsi.c
diff options
context:
space:
mode:
authorSimon Rettberg2025-12-09 15:49:52 +0100
committerSimon Rettberg2025-12-09 15:49:52 +0100
commitb3062b85b6778acb499998216fb8f3fd71119583 (patch)
tree2df3de5930e1430cfaf792f4b6dae7b8993dfc0c /src/server/iscsi.c
parent[KERNEL] Fix build on newer gcc (diff)
parent[SERVER] iscsi: More comments (diff)
downloaddnbd3-b3062b85b6778acb499998216fb8f3fd71119583.tar.gz
dnbd3-b3062b85b6778acb499998216fb8f3fd71119583.tar.xz
dnbd3-b3062b85b6778acb499998216fb8f3fd71119583.zip
Merge branch 'iscsi-refactor'
Diffstat (limited to 'src/server/iscsi.c')
-rw-r--r--src/server/iscsi.c3535
1 files changed, 3535 insertions, 0 deletions
diff --git a/src/server/iscsi.c b/src/server/iscsi.c
new file mode 100644
index 0000000..a24a727
--- /dev/null
+++ b/src/server/iscsi.c
@@ -0,0 +1,3535 @@
+/*
+ * This file is part of the Distributed Network Block Device 3
+ *
+ * Copyright(c) 2025 Sebastian Vater <sebastian.vater@rz.uni-freiburg.de>
+ *
+ * This file may be licensed under the terms of the
+ * GNU General Public License Version 2 (the ``GPL'').
+ *
+ * Software distributed under the License is distributed
+ * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
+ * express or implied. See the GPL for the specific language
+ * governing rights and limitations.
+ *
+ * You should have received a copy of the GPL along with this
+ * program. If not, go to http://www.gnu.org/licenses/gpl.html
+ * or write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <strings.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <dnbd3/config.h>
+#include <dnbd3/shared/log.h>
+#include <dnbd3/shared/sockhelper.h>
+#include <dnbd3/types.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "sendfile.h"
+#include "globals.h"
+#include "helper.h"
+#include "image.h"
+#include "iscsi.h"
+#include "uplink.h"
+#include "reference.h"
+
+#define ISCSI_DEFAULT_LUN 0
+#define ISCSI_DEFAULT_PROTOCOL_ID 1
+#define ISCSI_DEFAULT_DEVICE_ID 1
+#define ISCSI_DEFAULT_QUEUE_DEPTH 16
+
+#include <dnbd3/afl.h>
+
+/**
+ * @file iscsi.c
+ * @author Sebastian Vater
+ * @date 16 Jul 2025
+ * @brief iSCSI implementation for DNBD3.
+ *
+ * This file contains the iSCSI implementation according to
+ * RFC7143 for dnbd3-server.\n
+ * All server-side network sending and client-side network
+ * receiving code is done here.\n
+ * @see https://www.rfc-editor.org/rfc/rfc7143
+ */
+
+//#define malloc(x) (rand() % 100 == 0 ? NULL : malloc(x))
+
+/// Use for stack-allocated iscsi_pdu
+#define CLEANUP_PDU __attribute__((cleanup(iscsi_connection_pdu_destroy)))
+
+static bool iscsi_scsi_emu_block_process(iscsi_scsi_task *scsi_task);
+
+static bool iscsi_scsi_emu_primary_process(iscsi_scsi_task *scsi_task);
+
+static void iscsi_scsi_task_send_reply(iscsi_connection *conn, iscsi_scsi_task *scsi_task, const iscsi_pdu *request_pdu);
+
+static uint64_t iscsi_scsi_lun_get_from_scsi(int lun_id); // Converts an internal representation of a LUN identifier to an iSCSI LUN required for packet data
+static int iscsi_scsi_lun_get_from_iscsi(uint64_t lun); // Converts an iSCSI LUN from packet data to internal SCSI LUN identifier
+
+static int iscsi_scsi_emu_io_blocks_read(iscsi_scsi_task *scsi_task, dnbd3_image_t *image, uint64_t offset_blocks, uint64_t num_blocks); // Reads a number of blocks from a block offset of a DNBD3 image to a specified buffer
+
+static void iscsi_strcpy_pad(char *dst, const char *src, size_t size, int pad); // Copies a string with additional padding character to fill in a specified size
+
+static uint64_t iscsi_target_node_wwn_get(const uint8_t *name); // Calculates the WWN using 64-bit IEEE Extended NAA for a name
+
+static bool iscsi_connection_pdu_init(iscsi_pdu *pdu, uint32_t ds_len, bool no_ds_alloc);
+static void iscsi_connection_pdu_destroy(const iscsi_pdu *pdu);
+
+static iscsi_bhs_packet *iscsi_connection_pdu_resize(iscsi_pdu *pdu, uint ahs_len, uint32_t ds_len); // Appends packet data to an iSCSI PDU structure used by connections
+
+static bool iscsi_connection_pdu_write(iscsi_connection *conn, const iscsi_pdu *pdu);
+
+static int iscsi_connection_handle_reject(iscsi_connection *conn, const iscsi_pdu *pdu, int reason_code);
+
+
+/**
+ * @brief Copies a string with additional padding character to fill in a specified size.
+ *
+ * If the src string is shorter than the destination buffer,
+ * it will be padded with the given character. Otherwise,
+ * the string will be copied and truncated if necessary.
+ * In any case, the resulting string will NOT be null terminated.
+ *
+ * @param[in] dst Pointer to destination string to copy
+ * with padding and must NOT be NULL, so be
+ * careful.
+ * @param[in] src Pointer to string for copying. NULL
+ * is NOT allowed here, take caution.
+ * @param[in] size Total size in bytes for padding.
+ * @param[in] pad Padding character to use.
+ */
+static void iscsi_strcpy_pad(char *dst, const char *src, const size_t size, const int pad)
+{
+ const size_t len = strlen( src );
+
+ if ( len < size ) {
+ memcpy( dst, src, len );
+ memset( (dst + len), pad, (size - len) );
+ } else {
+ memcpy( dst, src, size );
+ }
+}
+
+/**
+ * @brief Parses a string representation of an integer and assigns the result to
+ * the provided destination variable, ensuring it is within valid range.
+ *
+ * This function checks for duplicate entries, empty strings, non-numeric
+ * characters, and out-of-range values. Logs debug messages for invalid or
+ * duplicate inputs and ensures values are clamped between 0 and INT_MAX.
+ *
+ * @param[in] name The name of the key associated with the integer value.
+ * Used for logging purposes.
+ * @param[in, out] dest Pointer to the destination integer variable where the
+ * parsed value will be stored. Must not be NULL. If the pointed
+ * value is -1, the parsed value will be assigned; otherwise,
+ * the function considers it a duplicate and does not update it.
+ * @param[in] src Pointer to the string containing the numeric representation
+ * of the value to parse. Must not be NULL or empty.
+ */
+static void iscsi_copy_kvp_int(const char *name, int *dest, const char *src)
+{
+ long long res = 0;
+ const char *end = NULL;
+
+ if ( *dest != -1 ) {
+ logadd( LOG_DEBUG1, "Received duplicate entry for key '%s', ignoring (new: %s, old: %d)", name, src, *dest );
+ return;
+ }
+
+ if ( *src == '\0' ) {
+ logadd( LOG_DEBUG1, "Empty value for numeric option '%s', ignoring", name );
+ return;
+ }
+ res = strtoll( src, (char **)&end, 10 ); // WTF why is the second arg not const char **
+
+ if ( end == NULL ) {
+ logadd( LOG_DEBUG1, "base 10 not valid! O.o" );
+ return;
+ }
+ if ( *end != '\0' ) {
+ logadd( LOG_DEBUG1, "Invalid non-numeric character in value for '%s': '%c' (0x%02x), ignoring option",
+ name, (int)*end, (int)*end );
+ return;
+ }
+ if ( res < 0 ) {
+ res = 0;
+ } else if ( res > INT_MAX ) {
+ res = INT_MAX;
+ }
+ *dest = (int)res;
+}
+
+/**
+ * @brief Copies a key-value pair string to the destination if it hasn't been copied already.
+ *
+ * This function ensures that a key has a single corresponding value by
+ * checking if the destination pointer has already been assigned. If assigned,
+ * a debug log entry is created, and the new value is ignored.
+ *
+ * @param[in] name The name of the key being assigned. Used for logging.
+ * @param[in,out] dest Pointer to the destination where the string is to be copied.
+ * If the destination is already assigned, the function will log and return.
+ * @param[in] src Pointer to the source string to be assigned to the destination.
+ */
+static void iscsi_copy_kvp_str(const char *name, const char **dest, const char *src)
+{
+ if ( *dest != NULL ) {
+ logadd( LOG_DEBUG1, "Received duplicate entry for key '%s', ignoring (new: %s, old: %s)", name, src, *dest );
+ return;
+ }
+ *dest = src;
+}
+
+/**
+ * @brief Extracts a single text key / value pairs out of an iSCSI packet into a hash map.
+ *
+ * Parses and extracts a specific key and value pair out of an iSCSI packet
+ * data stream amd puts the extracted data into a hash map to be used by
+ * the iSCSI implementation.
+ *
+ * @param[in] key_value_pairs Pointer to hash map containing all related keys and pairs.
+ * Must NOT be NULL, so take caution.
+ * @param[in] packet_data Pointer to key / value pair to be parsed. NULL is
+ * an illegal value, so be careful.
+ * @param[in] len Length of the remaining packet data.
+ * @return Number of bytes used by the extracted key / vair pair or
+ * a negative value in case of an error. This can be used for
+ * incrementing the offset to the next key / value pair.
+ */
+static int iscsi_parse_text_key_value_pair(iscsi_negotiation_kvp *key_value_pairs, const char *packet_data, const uint32_t len)
+{
+ int key_val_len = (int) strnlen( packet_data, len );
+ const char *key_end = memchr( packet_data, '=', key_val_len );
+
+ if ( key_val_len == (int)len ) {
+ logadd( LOG_DEBUG1, "iscsi_parse_text_key_value_pair: Final key/value pair not null-terminated, not spec compliant, aborting" );
+ return -1;
+ }
+ // Account for the trailing nullchar (for return value), which we also consumed
+ key_val_len++;
+
+ if ( key_end == NULL ) {
+ logadd( LOG_DEBUG1, "iscsi_parse_text_key_value_pair: Key/value separator '=' not found, ignoring" );
+ return key_val_len;
+ }
+
+ const uint key_len = (uint) (key_end - packet_data);
+ const uint val_len = (uint) (key_val_len - key_len - 1);
+
+ if ( key_len == 0U ) {
+ logadd( LOG_DEBUG1, "iscsi_parse_text_key_value_pair: Empty key, not allowed according to iSCSI specs, ignoring" );
+ return key_val_len;
+ }
+
+ if ( key_len > ISCSI_TEXT_KEY_MAX_LEN ) {
+ logadd( LOG_DEBUG1, "iscsi_parse_text_key_value_pair: Key is too long (max %d bytes), ignoring", ISCSI_TEXT_KEY_MAX_LEN );
+ return key_val_len;
+ }
+
+ if ( val_len > ISCSI_TEXT_VALUE_MAX_LEN ) {
+ logadd( LOG_DEBUG1, "iscsi_parse_text_key_value_pair: Value for '%.*s' is too long (max %d bytes), ignoring",
+ (int)key_len, packet_data, ISCSI_TEXT_VALUE_MAX_LEN );
+ return key_val_len;
+ }
+
+#define COPY_KVP(type, key) \
+ else if ( strncmp( packet_data, #key, key_len ) == 0 ) iscsi_copy_kvp_ ## type ( #key, &key_value_pairs->key, key_end + 1 )
+
+ if ( 0 ) {}
+ COPY_KVP( int, MaxRecvDataSegmentLength );
+ COPY_KVP( int, MaxBurstLength );
+ COPY_KVP( int, FirstBurstLength );
+ COPY_KVP( int, MaxConnections );
+ COPY_KVP( int, ErrorRecoveryLevel );
+ COPY_KVP( str, SessionType );
+ COPY_KVP( str, AuthMethod );
+ COPY_KVP( str, SendTargets );
+ COPY_KVP( str, HeaderDigest );
+ COPY_KVP( str, DataDigest );
+ COPY_KVP( str, InitiatorName );
+ COPY_KVP( str, TargetName );
+ else {
+ logadd( LOG_DEBUG1, "iscsi_parse_text_key_value_pair: Unknown option: '%.*s'", (int)key_len, packet_data );
+ }
+
+#undef COPY_KVP
+
+ return (int)key_val_len;
+}
+
+/**
+ * @brief Extracts all text key / value pairs out of an iSCSI packet into a hash map.
+ *
+ * Parses and extracts all key and value pairs out of iSCSI packet
+ * data amd puts the extracted data into a hash map to be used by
+ * the iSCSI implementation.
+ *
+ * @param[in] pairs struct to write all key-value-pair options from packet to
+ * extracted keys and pairs. Must NOT be NULL, so take caution.
+ * @param[in] packet_data Pointer to first key and value pair to
+ * be parsed. NULL is an illegal value here, so be careful.
+ * @param[in] len Length of the remaining packet data.
+ * @retval -1 An error occured during parsing key.
+ * @retval 0 Key and value pair was parsed successfully and was added to
+ * kvp struct.
+ */
+static int iscsi_parse_login_key_value_pairs(iscsi_negotiation_kvp *pairs, const uint8_t *packet_data, uint len)
+{
+ memset( pairs, -1 , sizeof(*pairs) );
+ pairs->SessionType = NULL;
+ pairs->AuthMethod = NULL;
+ pairs->SendTargets = NULL;
+ pairs->HeaderDigest = NULL;
+ pairs->DataDigest = NULL;
+ pairs->InitiatorName = NULL;
+ pairs->TargetName = NULL;
+
+ if ( len == 0U )
+ return 0; // iSCSI specs don't allow zero length
+
+ int offset = 0;
+
+ while ( ((uint) offset < len) && (packet_data[offset] != '\0') ) {
+ const int rc = iscsi_parse_text_key_value_pair( pairs, (const char *)(packet_data + offset), (len - offset) );
+
+ if ( rc <= 0 )
+ return -1;
+
+ offset += rc;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Sends a single iSCSI SCSI Data In packet to the client.
+ *
+ * This function reads the data from the
+ * associated DNBD3 image as well and sends
+ * it to the initiator.
+ *
+ * @param[in] conn Pointer to iSCSI connection for which the
+ * packet should be sent for. Must NOT be
+ * NULL, so be careful.
+ * @param[in] task Pointer to iSCSI task which handles the
+ * actual SCSI packet data. NULL is NOT
+ * allowed here, so take caution.
+ * @param[in] pos Offset of data to be sent in bytes.
+ * @param[in] len Length of data to be sent in bytes
+ * @param[in] res_cnt Residual Count.
+ * @param[in] data_sn Data Sequence Number (DataSN).
+ * @param[in] flags Flags for this data packet.
+ * @param[in] immediate whether immediate bit was set in this request
+ * @return true success, false error
+ */
+static bool iscsi_scsi_data_in_send(iscsi_connection *conn, const iscsi_task *task,
+ const uint32_t pos, const uint32_t len, const uint32_t res_cnt, const uint32_t data_sn, const uint8_t flags, bool immediate)
+{
+ iscsi_pdu CLEANUP_PDU response_pdu;
+ if ( !iscsi_connection_pdu_init( &response_pdu, len, true ) )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ iscsi_scsi_data_in_response_packet *scsi_data_in_pkt = (iscsi_scsi_data_in_response_packet *) response_pdu.bhs_pkt;
+
+ scsi_data_in_pkt->opcode = ISCSI_OPCODE_SERVER_SCSI_DATA_IN;
+ scsi_data_in_pkt->flags = flags;
+ scsi_data_in_pkt->reserved = 0U;
+
+ if ( (flags & ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_STATUS) != 0 ) {
+ if ( (flags & ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_FINAL) != 0 && !immediate ) {
+ conn->max_cmd_sn++;
+ }
+ scsi_data_in_pkt->status = task->scsi_task.status;
+ iscsi_put_be32( (uint8_t *) &scsi_data_in_pkt->stat_sn, conn->stat_sn++ );
+ iscsi_put_be32( (uint8_t *) &scsi_data_in_pkt->res_cnt, res_cnt );
+ } else {
+ // these don't carry any meaning if S bit is unset - saves us from doing the endian-conversion
+ scsi_data_in_pkt->status = 0U;
+ scsi_data_in_pkt->stat_sn = 0UL;
+ scsi_data_in_pkt->res_cnt = 0UL;
+ }
+
+ iscsi_put_be32( (uint8_t *) &scsi_data_in_pkt->total_ahs_len, len ); // TotalAHSLength is always 0 and DataSegmentLength is 24-bit, so write in one step.
+ scsi_data_in_pkt->lun = 0ULL; // Not used if we don't set the A bit (we never do)
+ iscsi_put_be32( (uint8_t *) &scsi_data_in_pkt->init_task_tag, task->init_task_tag );
+ scsi_data_in_pkt->target_xfer_tag = 0xFFFFFFFFUL; // Minus one does not require endianess conversion
+ iscsi_put_be32( (uint8_t *) &scsi_data_in_pkt->exp_cmd_sn, conn->exp_cmd_sn );
+ iscsi_put_be32( (uint8_t *) &scsi_data_in_pkt->max_cmd_sn, conn->max_cmd_sn );
+ iscsi_put_be32( (uint8_t *) &scsi_data_in_pkt->data_sn, data_sn );
+
+ iscsi_put_be32( (uint8_t *) &scsi_data_in_pkt->buf_offset, pos );
+
+ if ( !iscsi_connection_pdu_write( conn, &response_pdu ) )
+ return false;
+
+ size_t padding;
+ if ( task->scsi_task.buf != NULL ) {
+ if ( !sock_sendAll( conn->client->sock, (task->scsi_task.buf + pos), len, ISCSI_CONNECT_SOCKET_WRITE_RETRIES ) )
+ return false;
+ padding = ISCSI_ALIGN( len, ISCSI_ALIGN_SIZE ) - len;
+ } else {
+ // sendfile - it must be a DATA-In, in which case len should be a multiple of 4, as it was given in number of
+ // blocks, which is not just a multiple of 4 but usually a power of two.
+ assert( len % 4 == 0 );
+ const uint64_t off = task->scsi_task.file_offset + pos;
+ size_t realBytes = len;
+ if ( off >= conn->client->image->realFilesize ) {
+ padding = len;
+ realBytes = 0;
+ } else if ( off + len > conn->client->image->realFilesize ) {
+ padding = ( off + len ) - conn->client->image->realFilesize;
+ realBytes -= padding;
+ } else {
+ padding = 0;
+ }
+ bool ret = sendfile_all( conn->client->image->readFd, conn->client->sock,
+ (off_t)off, realBytes );
+ if ( !ret )
+ return false;
+ }
+ if ( padding != 0 ) {
+ if ( !sock_sendPadding( conn->client->sock, padding ) )
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * @brief Handles iSCSI task read (incoming) data.
+ *
+ * This function handles iSCSI incoming data
+ * read buffer for both processed and
+ * unprocessed tasks.
+ *
+ * @param[in] conn Pointer to iSCSI connection of which the
+ * incoming data should be handled, must NOT be
+ * NULL, so be careful.
+ * @param[in] task Pointer to iSCSI task for handling
+ * the incoming data. NULL is NOT allowed here,
+ * take caution.
+ * @param immediate
+ * @return true on success, false on error
+ */
+static bool iscsi_task_xfer_scsi_data_in(iscsi_connection *conn, const iscsi_task *task, bool immediate)
+{
+ const uint32_t expected_len = task->scsi_task.exp_xfer_len;
+ uint32_t xfer_len = task->scsi_task.len;
+ uint32_t res_cnt = 0UL;
+ uint8_t flags = 0;
+
+ if ( expected_len < xfer_len ) {
+ res_cnt = (xfer_len - expected_len);
+ xfer_len = expected_len;
+ flags |= ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_RES_OVERFLOW;
+ } else if ( expected_len > xfer_len ) {
+ res_cnt = (expected_len - xfer_len);
+ flags |= ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_RES_UNDERFLOW;
+ }
+ if ( xfer_len == 0UL ) {
+ // Can this even happen? Send empty data-in response...
+ flags |= ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_FINAL | ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_STATUS;
+ return iscsi_scsi_data_in_send( conn, task, 0, 0, res_cnt, 0, flags, immediate );
+ }
+
+ uint32_t data_sn = 0;
+ // Max burst length = total cobined length of payload in all PDUs of one sequence
+ const uint32_t max_burst_len = conn->opts.MaxBurstLength;
+ // Max recv segment length = total length of one individual PDU
+ const uint32_t max_seg_len = conn->opts.MaxRecvDataSegmentLength;
+
+ for ( uint32_t current_burst_start = 0; current_burst_start < xfer_len; current_burst_start += max_burst_len ) {
+ const uint32_t current_burst_end = MIN(xfer_len, current_burst_start + max_burst_len);
+
+ for ( uint32_t offset = current_burst_start; offset < current_burst_end; offset += max_seg_len ) {
+ const uint32_t current_seg_len = MIN(max_seg_len, current_burst_end - offset);
+
+ flags &= ~(ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_STATUS | ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_FINAL);
+
+ if ( (offset + current_seg_len) == current_burst_end ) {
+ // This segment ends the current sequence - set F bit
+ flags |= ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_FINAL;
+
+ if ( (offset + current_seg_len) == xfer_len ) {
+ // This segment ends the entire transfer - set S bit and include status
+ flags |= ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_STATUS;
+ }
+ }
+
+ if ( !iscsi_scsi_data_in_send( conn, task, offset, current_seg_len, res_cnt, data_sn, flags, immediate ) )
+ return false;
+
+ data_sn++;
+ }
+ }
+
+ conn->client->bytesSent += xfer_len;
+
+ return true;
+}
+
+/**
+ * @brief Send reply to a n iSCSI SCSI op.
+ * Called when the request has been handled and the task is set up properly
+ * with the according data to reply with. This is either payload data, for
+ * a transfer, or sense data.
+ *
+ * @param[in] conn Current connection
+ * @param[in] scsi_task Pointer to iSCSI SCSI task to send out a response for
+ * @param request_pdu The request belonging to the response to send
+ */
+static void iscsi_scsi_task_send_reply(iscsi_connection *conn, iscsi_scsi_task *scsi_task, const iscsi_pdu *request_pdu)
+{
+ iscsi_task *task = container_of( scsi_task, iscsi_task, scsi_task );
+
+ iscsi_scsi_cmd_packet *scsi_cmd_pkt = (iscsi_scsi_cmd_packet *) request_pdu->bhs_pkt;
+
+ if ( task->scsi_task.status == ISCSI_SCSI_STATUS_GOOD && scsi_task->sense_data_len == 0 && scsi_task->is_read ) {
+ iscsi_task_xfer_scsi_data_in( conn, task, (scsi_cmd_pkt->opcode & ISCSI_OPCODE_FLAGS_IMMEDIATE) != 0 );
+
+ return;
+ }
+
+ const uint32_t ds_len = (scsi_task->sense_data_len != 0U)
+ ? (scsi_task->sense_data_len + offsetof(struct iscsi_scsi_ds_cmd_data, sense_data))
+ : 0UL;
+
+ iscsi_pdu CLEANUP_PDU response_pdu;
+ if ( !iscsi_connection_pdu_init( &response_pdu, ds_len, false ) )
+ return;
+
+ iscsi_scsi_response_packet *scsi_response_pkt = (iscsi_scsi_response_packet *) response_pdu.bhs_pkt;
+
+ if ( scsi_task->sense_data_len != 0U ) {
+ iscsi_scsi_ds_cmd_data *ds_cmd_data_pkt = response_pdu.ds_cmd_data;
+
+ iscsi_put_be16( (uint8_t *) &ds_cmd_data_pkt->len, scsi_task->sense_data_len );
+ memcpy( ds_cmd_data_pkt->sense_data, scsi_task->sense_data, scsi_task->sense_data_len );
+
+ iscsi_put_be32( (uint8_t *) &scsi_response_pkt->total_ahs_len, ds_len ); // TotalAHSLength is always 0 and DataSegmentLength is 24-bit, so write in one step.
+ } else {
+ *(uint32_t *) &scsi_response_pkt->total_ahs_len = 0UL; // TotalAHSLength and DataSegmentLength are always 0, so write in one step.
+ }
+
+ scsi_response_pkt->opcode = ISCSI_OPCODE_SERVER_SCSI_RESPONSE;
+ scsi_response_pkt->flags = 0x80;
+ scsi_response_pkt->response = ISCSI_SCSI_RESPONSE_CODE_OK;
+ const uint32_t exp_xfer_len = scsi_task->exp_xfer_len;
+
+ if ( (exp_xfer_len != 0UL) && (scsi_task->status == ISCSI_SCSI_STATUS_GOOD) ) {
+ const uint32_t resp_len = ds_len;
+
+ if ( resp_len < exp_xfer_len ) {
+ const uint32_t res_cnt = (exp_xfer_len - resp_len);
+
+ scsi_response_pkt->flags |= ISCSI_SCSI_RESPONSE_FLAGS_RES_UNDERFLOW;
+ iscsi_put_be32( (uint8_t *) &scsi_response_pkt->res_cnt, res_cnt );
+ } else if ( resp_len > exp_xfer_len ) {
+ const uint32_t res_cnt = (resp_len - exp_xfer_len);
+
+ scsi_response_pkt->flags |= ISCSI_SCSI_RESPONSE_FLAGS_RES_OVERFLOW;
+ iscsi_put_be32( (uint8_t *) &scsi_response_pkt->res_cnt, res_cnt );
+ } else {
+ scsi_response_pkt->res_cnt = 0UL;
+ }
+ } else {
+ scsi_response_pkt->res_cnt = 0UL;
+ }
+
+ scsi_response_pkt->status = scsi_task->status;
+ scsi_response_pkt->reserved = 0ULL;
+ iscsi_put_be32( (uint8_t *) &scsi_response_pkt->init_task_tag, task->init_task_tag );
+ scsi_response_pkt->snack_tag = 0UL;
+ iscsi_put_be32( (uint8_t *) &scsi_response_pkt->stat_sn, conn->stat_sn++ );
+
+ if ( (scsi_cmd_pkt->opcode & ISCSI_OPCODE_FLAGS_IMMEDIATE) == 0 ) {
+ conn->max_cmd_sn++;
+ }
+
+ iscsi_put_be32( (uint8_t *) &scsi_response_pkt->exp_cmd_sn, conn->exp_cmd_sn );
+ iscsi_put_be32( (uint8_t *) &scsi_response_pkt->max_cmd_sn, conn->max_cmd_sn );
+ scsi_response_pkt->exp_data_sn = 0UL;
+ scsi_response_pkt->bidi_read_res_cnt = 0UL;
+
+ iscsi_connection_pdu_write( conn, &response_pdu );
+}
+
+/**
+ * @brief Allocates, if necessary and initializes SCSI sense data for check condition status code.
+ *
+ * This function is invoked whenever additional
+ * SCSI sense data for check condition status
+ * code is required for sending to the
+ * initiator.
+ *
+ * @param[in] scsi_task Pointer to iSCSI SCSI task to allocate
+ * and assign the SCSI check condition status
+ * code sense data for. Must NOT be NULL, so
+ * be careful.
+ * @param[in] sense_key Sense Key (SK).
+ * @param[in] asc Additional Sense Code (ASC).
+ * @param[in] ascq Additional Sense Code Qualifier (ASCQ).
+ */
+static void iscsi_scsi_task_sense_data_build(iscsi_scsi_task *scsi_task, const uint8_t sense_key, const uint8_t asc, const uint8_t ascq)
+{
+ iscsi_scsi_sense_data_check_cond_packet *sense_data = (iscsi_scsi_sense_data_check_cond_packet *) scsi_task->sense_data;
+
+ if ( sense_data == NULL ) {
+ sense_data = malloc( sizeof(iscsi_scsi_sense_data_check_cond_packet) );
+
+ if ( sense_data == NULL ) {
+ logadd( LOG_ERROR, "iscsi_scsi_task_sense_data_build: Out of memory allocating iSCSI SCSI conidtion check status code sense data" );
+
+ return;
+ }
+
+ scsi_task->sense_data = (iscsi_scsi_sense_data_packet *) sense_data;
+ }
+
+ sense_data->sense_data.response_code = (int8_t) (ISCSI_SCSI_SENSE_DATA_PUT_RESPONSE_CODE(ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_CURRENT_FMT) | ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_VALID);
+ sense_data->sense_data.reserved = 0U;
+ sense_data->sense_data.sense_key_flags = ISCSI_SCSI_SENSE_DATA_PUT_SENSE_KEY(sense_key);
+ sense_data->sense_data.info = 0UL; // Zero does not require endianess conversion
+ sense_data->sense_data.add_len = (sizeof(iscsi_scsi_sense_data_check_cond_packet) - sizeof(iscsi_scsi_sense_data_packet));
+
+ sense_data->cmd_spec_info = 0UL; // Zero does not require endianess conversion
+ sense_data->asc = asc;
+ sense_data->ascq = ascq;
+ sense_data->field_rep_unit_code = 0UL;
+ sense_data->sense_key_spec_flags = 0U;
+ sense_data->sense_key_spec = 0U; // Zero does not require endianess conversion
+
+ scsi_task->sense_data_len = sizeof(iscsi_scsi_sense_data_check_cond_packet);
+}
+
+/**
+ * @brief Sets an iSCSI SCSI task status code with optional additional details.
+ *
+ * Sense Key (SK), Additional Sense Code (ASC)
+ * and Additional Sense Code Qualifier (ASCQ)
+ * are only generated on check condition SCSI
+ * status code.
+ *
+ * @param[in] scsi_task Pointer to iSCSI SCSI task to set the
+ * SCSI status and additional details for. Must
+ * NOT be NULL, so be careful.
+ * @param[in] status SCSI status code to be set.
+ * @param[in] sense_key Sense Key (SK).
+ * @param[in] asc Additional Sense Code (ASC).
+ * @param[in] ascq Additional Sense Code Qualifier (ASCQ).
+ */
+static void iscsi_scsi_task_status_set(iscsi_scsi_task *scsi_task, const uint8_t status, const uint8_t sense_key, const uint8_t asc, const uint8_t ascq)
+{
+ if ( status == ISCSI_SCSI_STATUS_CHECK_COND ) {
+ iscsi_scsi_task_sense_data_build( scsi_task, sense_key, asc, ascq );
+ }
+
+ scsi_task->status = status;
+}
+
+/**
+ * @brief Converts an internal representation of a LUN identifier to an iSCSI LUN required for packet data.
+ *
+ * This function needs to be called prior
+ * storing the internal SCSI identifier
+ * representation in the iSCSI packet.
+ *
+ * @param[in] lun_id Internal SCSI presentation of LUN
+ * identifier to be converted to iSCSI packet data
+ * representation.
+ * @return iSCSI packet data representation of LUN or
+ * 0 in case of an invalid LUN.
+ */
+static uint64_t iscsi_scsi_lun_get_from_scsi(const int lun_id)
+{
+ uint64_t iscsi_scsi_lun;
+
+ if ( lun_id < 0x100 ) {
+ iscsi_scsi_lun = (uint64_t) (lun_id & 0xFF) << 48ULL;
+ } else if ( lun_id < 0x4000 ) {
+ iscsi_scsi_lun = (1ULL << 62ULL) | (uint64_t) (lun_id & 0x3FFF) << 48ULL;
+ } else {
+ iscsi_scsi_lun = 0ULL;
+ }
+
+ return iscsi_scsi_lun;
+}
+
+/**
+ * @brief Converts an iSCSI LUN from packet data to internal SCSI LUN identifier.
+ *
+ * This function needs to be called prior
+ * storing the iSCSI packet data
+ * representation in the structures
+ * requiring an internal SCSI identifier.
+ *
+ * @param[in] lun iSCSI packet data LUN to be converted
+ * to the internal SCSI LUN identifier
+ * representation.
+ * @return SCSI identifier representation of iSCSI
+ * packet data LUN or 0xFFFF in case of
+ * an error.
+ */
+static int iscsi_scsi_lun_get_from_iscsi(const uint64_t lun)
+{
+ int lun_id = (int) (lun >> 62ULL) & 0x03;
+
+ if ( lun_id == 0x00 ) {
+ lun_id = (int) (lun >> 48ULL) & 0xFF;
+ } else if ( lun_id == 0x01 ) {
+ lun_id = (int) (lun >> 48ULL) & 0x3FFF;
+ } else {
+ lun_id = 0xFFFF;
+ }
+
+ return lun_id;
+}
+
+/**
+ * @brief Retrieves the number of total logical blocks for a DNBD3 image.
+ *
+ * This function depends on DNBD3 image
+ * properties.
+ *
+ * @param[in] image Pointer to DNBD3 image to retrieve
+ * the logical size from. Must NOT be NULL,
+ * so be careful.
+ * @return The number of total logical blocks.
+ */
+static inline uint64_t iscsi_scsi_emu_block_get_count(const dnbd3_image_t *image)
+{
+ return (image->virtualFilesize / ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE);
+}
+
+
+/**
+ * @brief Converts offset and length specified by a block size to offset and length in bytes.
+ *
+ * This function uses bit shifting if
+ * the block size is a power of two.
+ *
+ * @param[out] offset_bytes Pointer where to store the block
+ * in bytes. Must NOT be NULL, so be
+ * careful.
+ * @param[in] offset_blocks Offset in blocks.
+ * @param[in] num_blocks Number of blocks.
+ * @return Number of blocks in bytes.
+ */
+static uint64_t iscsi_scsi_emu_blocks_to_bytes(uint64_t *offset_bytes, const uint64_t offset_blocks, const uint64_t num_blocks)
+{
+ *offset_bytes = (offset_blocks * ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE);
+
+ return (num_blocks * ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE);
+}
+
+/**
+ * @brief Called when data requested via an uplink server has arrived.
+ *
+ * This function is used to retrieve
+ * block data which is NOT locally
+ * available.
+ *
+ * @param[in] data Pointer to related scsi_task. Must NOT
+ * be NULL, so be careful.
+ * @param[in] handle Pointer to destination buffer, as passed to
+ * iscsi_scsi_emu_io_block_read().
+ * @param[in] start Start of range in bytes.
+ * @param[in] length Length of range in bytes, as passed to
+ * uplink_request().
+ * @param[in] buffer Data for requested range.
+ */
+static void iscsi_uplink_callback(void *data, uint64_t handle UNUSED, uint64_t start UNUSED, uint32_t length, const char *buffer)
+{
+ iscsi_scsi_task *scsi_task = (iscsi_scsi_task *) data;
+
+ memcpy( scsi_task->buf, buffer, length );
+
+ pthread_mutex_lock( &scsi_task->uplink_mutex );
+ pthread_cond_signal( &scsi_task->uplink_cond );
+ pthread_mutex_unlock( &scsi_task->uplink_mutex );
+}
+
+/**
+ * @brief Reads a number of blocks from a block offset of a DNBD3 image to a specified buffer.
+ *
+ * This function enqueues the I/O read
+ * process which invokes a callback
+ * function when the read operation has
+ * been finished.
+ *
+ * @param[in] scsi_task Pointer to iSCSI SCSI task which
+ * executes the I/O read operation, must
+ * NOT be NULL, so be careful.
+ * @param[in] image Pointer to DNBD3 image to read
+ * data from and must NOT be NULL, so
+ * be careful.
+ * @param[in] offset_blocks Offset in blocks to start reading from.
+ * @param[in] num_blocks Number of blocks to read.
+ * @return 0 on successful operation, a negative
+ * error code otherwise.
+ */
+static int iscsi_scsi_emu_io_blocks_read(iscsi_scsi_task *scsi_task, dnbd3_image_t *image, const uint64_t offset_blocks, const uint64_t num_blocks)
+{
+ int rc = 0;
+ uint64_t offset_bytes;
+ const uint64_t num_bytes = iscsi_scsi_emu_blocks_to_bytes( &offset_bytes, offset_blocks, num_blocks );
+
+ if ( offset_bytes + num_bytes > image->virtualFilesize )
+ return -ERANGE;
+
+ scsi_task->file_offset = offset_bytes;
+ scsi_task->len = (uint32_t)num_bytes;
+
+ dnbd3_cache_map_t *cache = ref_get_cachemap( image );
+
+ if ( cache != NULL ) {
+ // This is a proxyed image, check if we need to relay the request...
+ const uint64_t start = (offset_bytes & ~(uint64_t)(DNBD3_BLOCK_SIZE - 1));
+ const uint64_t end = ((offset_bytes + num_bytes + DNBD3_BLOCK_SIZE - 1) & ~(uint64_t) (DNBD3_BLOCK_SIZE - 1));
+ bool readFromFile = image_isRangeCachedUnsafe( cache, start, end );
+
+ ref_put( &cache->reference );
+
+ if ( !readFromFile ) {
+ // Not cached, request via uplink
+ scsi_task->buf = malloc( num_bytes );
+ if ( scsi_task->buf == NULL ) {
+ return -ENOMEM;
+ }
+ pthread_mutex_init( &scsi_task->uplink_mutex, NULL );
+ pthread_cond_init( &scsi_task->uplink_cond, NULL );
+ pthread_mutex_lock( &scsi_task->uplink_mutex );
+
+ if ( !uplink_request( image, scsi_task, iscsi_uplink_callback, 0, offset_bytes, (uint32_t)num_bytes ) ) {
+ pthread_mutex_unlock( &scsi_task->uplink_mutex );
+
+ logadd( LOG_DEBUG1, "Could not relay uncached request to upstream proxy for image %s:%d",
+ image->name, image->rid );
+
+ rc = -EIO;
+ } else {
+ // Wait sync (Maybe use pthread_cond_timedwait to detect unavailable uplink instead of hanging...)
+ pthread_cond_wait( &scsi_task->uplink_cond, &scsi_task->uplink_mutex );
+ pthread_mutex_unlock( &scsi_task->uplink_mutex );
+ scsi_task->file_offset = (size_t)-1;
+ }
+ pthread_cond_destroy( &scsi_task->uplink_cond );
+ pthread_mutex_destroy( &scsi_task->uplink_mutex );
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @brief Executes a read operation on a DNBD3 image.
+ *
+ * This function also sets the SCSI
+ * status result code accordingly.
+ *
+ * @param[in] image Pointer to DNBD3 image to read from
+ * @param[in] scsi_task Pointer to iSCSI SCSI task
+ * responsible for this read or write
+ * task. NULL is NOT allowed here, take
+ * caution.
+ * @param[in] lba Logical Block Address (LBA) to start
+ * reading from or writing to.
+ * @param[in] xfer_len Transfer length in logical blocks.
+ */
+static void iscsi_scsi_emu_block_read(dnbd3_image_t *image, iscsi_scsi_task *scsi_task, const uint64_t lba, const uint32_t xfer_len)
+{
+ const uint32_t max_xfer_len = ISCSI_MAX_DS_SIZE / ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE;
+
+ if ( xfer_len > max_xfer_len || !scsi_task->is_read || scsi_task->is_write ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ,
+ ISCSI_SCSI_ASC_INVALID_FIELD_IN_CDB, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return;
+ }
+
+ if ( xfer_len == 0UL ) {
+ scsi_task->status = ISCSI_SCSI_STATUS_GOOD;
+
+ return;
+ }
+
+ int rc = iscsi_scsi_emu_io_blocks_read( scsi_task, image, lba, xfer_len );
+
+ if ( rc == 0 )
+ return;
+
+ if ( rc == -ENOMEM ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_HARDWARE_ERR,
+ ISCSI_SCSI_ASC_INTERNAL_TARGET_FAIL, ISCSI_SCSI_ASC_NO_ADDITIONAL_SENSE );
+
+ return;
+ }
+
+ if ( rc == -ERANGE ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ,
+ ISCSI_SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return;
+ }
+
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NO_SENSE,
+ ISCSI_SCSI_ASC_NO_ADDITIONAL_SENSE, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+}
+
+/**
+ * @brief Executes SCSI block emulation on a DNBD3 image.
+ *
+ * This function determines the block
+ * based SCSI opcode and executes it.
+ *
+ * @param[in] scsi_task Pointer to iSCSI SCSI task
+ * to process the SCSI block operation
+ * for and must NOT be NULL, be careful.
+ * @return true on successful operation, false otherwise.
+ */
+static bool iscsi_scsi_emu_block_process(iscsi_scsi_task *scsi_task)
+{
+ uint64_t lba;
+ uint32_t xfer_len;
+ dnbd3_image_t *image = scsi_task->connection->client->image;
+
+ switch ( scsi_task->cdb->opcode ) {
+ case ISCSI_SCSI_OPCODE_READ6 : {
+ const iscsi_scsi_cdb_read_write_6 *cdb_read_write_6 = (iscsi_scsi_cdb_read_write_6 *) scsi_task->cdb;
+
+ lba = iscsi_get_be24(cdb_read_write_6->lba);
+ xfer_len = cdb_read_write_6->xfer_len;
+
+ if ( xfer_len == 0UL ) {
+ xfer_len = 256UL;
+ }
+
+ iscsi_scsi_emu_block_read( image, scsi_task, lba, xfer_len );
+
+ break;
+ }
+ case ISCSI_SCSI_OPCODE_READ10 : {
+ const iscsi_scsi_cdb_read_write_10 *cdb_read_write_10 = (iscsi_scsi_cdb_read_write_10 *) scsi_task->cdb;
+
+ lba = iscsi_get_be32(cdb_read_write_10->lba);
+ xfer_len = iscsi_get_be16(cdb_read_write_10->xfer_len);
+
+ iscsi_scsi_emu_block_read( image, scsi_task, lba, xfer_len );
+
+ break;
+ }
+ case ISCSI_SCSI_OPCODE_READ12 : {
+ const iscsi_scsi_cdb_read_write_12 *cdb_read_write_12 = (iscsi_scsi_cdb_read_write_12 *) scsi_task->cdb;
+
+ lba = iscsi_get_be32(cdb_read_write_12->lba);
+ xfer_len = iscsi_get_be32(cdb_read_write_12->xfer_len);
+
+ iscsi_scsi_emu_block_read( image, scsi_task, lba, xfer_len );
+
+ break;
+ }
+ case ISCSI_SCSI_OPCODE_READ16 : {
+ const iscsi_scsi_cdb_read_write_16 *cdb_read_write_16 = (iscsi_scsi_cdb_read_write_16 *) scsi_task->cdb;
+
+ lba = iscsi_get_be64(cdb_read_write_16->lba);
+ xfer_len = iscsi_get_be32(cdb_read_write_16->xfer_len);
+
+ iscsi_scsi_emu_block_read( image, scsi_task, lba, xfer_len );
+
+ break;
+ }
+ case ISCSI_SCSI_OPCODE_READCAPACITY10 : {
+ iscsi_scsi_read_capacity_10_parameter_data_packet *buf = malloc( sizeof(iscsi_scsi_read_capacity_10_parameter_data_packet) );
+
+ if ( buf == NULL ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NOT_READY,
+ ISCSI_SCSI_ASC_LOGICAL_UNIT_NOT_READY, ISCSI_SCSI_ASCQ_BECOMING_READY );
+
+ break;
+ }
+
+ lba = iscsi_scsi_emu_block_get_count( image ) - 1ULL;
+
+ if ( lba > 0xFFFFFFFFULL ) {
+ buf->lba = 0xFFFFFFFFUL; // Minus one does not require endianess conversion
+ } else {
+ iscsi_put_be32( (uint8_t *) &buf->lba, (uint32_t) lba );
+ }
+
+ iscsi_put_be32( (uint8_t *) &buf->block_len, ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE );
+
+ scsi_task->buf = (uint8_t *) buf;
+ scsi_task->len = sizeof(*buf);
+ scsi_task->status = ISCSI_SCSI_STATUS_GOOD;
+
+ break;
+ }
+ case ISCSI_SCSI_OPCODE_SERVICE_ACTION_IN_16 : {
+ const iscsi_scsi_cdb_service_action_in_16 *cdb_servce_in_action_16 = (iscsi_scsi_cdb_service_action_in_16 *) scsi_task->cdb;
+
+ if ( ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_GET_ACTION(cdb_servce_in_action_16->action)
+ != ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_READ_CAPACITY_16 ) {
+ return false;
+ }
+ iscsi_scsi_service_action_in_16_parameter_data_packet *buf = malloc( sizeof(iscsi_scsi_service_action_in_16_parameter_data_packet) );
+
+ if ( buf == NULL ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NOT_READY,
+ ISCSI_SCSI_ASC_LOGICAL_UNIT_NOT_READY, ISCSI_SCSI_ASCQ_BECOMING_READY );
+
+ break;
+ }
+
+ lba = iscsi_scsi_emu_block_get_count( image ) - 1ULL;
+
+ iscsi_put_be64( (uint8_t *) &buf->lba, lba );
+ iscsi_put_be32( (uint8_t *) &buf->block_len, ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE );
+
+ buf->flags = 0;
+
+ const uint8_t exponent = ISCSI_SCSI_EMU_BLOCK_DIFF_SHIFT;
+
+ buf->exponents = ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_PUT_LBPPB_EXPONENT((exponent <= ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_MASK) ? exponent : 0U);
+
+ buf->lbp_lalba = 0U;
+ buf->reserved[0] = 0ULL;
+ buf->reserved[1] = 0ULL;
+
+ const uint alloc_len = iscsi_get_be32( cdb_servce_in_action_16->alloc_len );
+
+ scsi_task->buf = (uint8_t *) buf;
+ scsi_task->len = MIN( alloc_len, sizeof(*buf) );
+ scsi_task->status = ISCSI_SCSI_STATUS_GOOD;
+
+ break;
+ }
+ case ISCSI_SCSI_OPCODE_WRITE6 :
+ case ISCSI_SCSI_OPCODE_WRITE10 :
+ case ISCSI_SCSI_OPCODE_WRITE12 :
+ case ISCSI_SCSI_OPCODE_WRITE16 :
+ case ISCSI_SCSI_OPCODE_UNMAP :
+ case ISCSI_SCSI_OPCODE_SYNCHRONIZECACHE10 :
+ case ISCSI_SCSI_OPCODE_SYNCHRONIZECACHE16 : {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NO_SENSE, ISCSI_SCSI_ASC_WRITE_PROTECTED, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ break;
+ }
+ default : {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @brief Calculates the 64-bit IEEE Extended NAA for a name.
+ *
+ * @param[out] buf Pointer to 64-bit output buffer for
+ * storing the IEEE Extended NAA. Must
+ * NOT be NULL, so be careful.
+ * @param[in] name Pointer to string containing the
+ * name to calculate the IEEE Extended
+ * NAA for. NULL is NOT allowed here, so
+ * take caution.
+ */
+static inline void iscsi_scsi_emu_naa_ieee_ext_set(uint64_t *buf, const uint8_t *name)
+{
+ const uint64_t wwn = iscsi_target_node_wwn_get( name );
+
+ iscsi_put_be64( (uint8_t *) buf, wwn );
+}
+
+/**
+ * @brief Copies a SCSI name string and zero pads until total string length is aligned to DWORD boundary.
+ *
+ * @param[out] buf Pointer to copy the aligned SCSI
+ * string to. Must NOT be NULL, so be
+ * careful.
+ * @param[in] name Pointer to string containing the
+ * SCSI name to be copied. NULL is NOT
+ * allowed here, so take caution.
+ * @return The aligned string length in bytes.
+ */
+static size_t iscsi_scsi_emu_pad_scsi_name(uint8_t *buf, const uint8_t *name)
+{
+ size_t len = strlen( (char *) name );
+
+ memcpy( buf, name, len );
+
+ do {
+ buf[len++] = '\0';
+ } while ( (len & (ISCSI_ALIGN_SIZE - 1)) != 0 );
+
+ return len;
+}
+
+/**
+ * @brief Executes an inquiry operation on a DNBD3 image.
+ *
+ * This function also sets the SCSI
+ * status result code accordingly.
+ *
+ * @param[in] image Pointer to DNBD3 image to get
+ * the inquiry data from. Must NOT be
+ * NULL, so be careful.
+ * @param[in] scsi_task Pointer to iSCSI SCSI task
+ * responsible for this inqueiry
+ * request. NULL is NOT allowed here,
+ * take caution.
+ * @param[in] cdb_inquiry Pointer to Command Descriptor
+ * Block (CDB) and must NOT be NULL, be
+ * careful.
+ * @param[in] std_inquiry_data_pkt Pointer to standard inquiry
+ * data packet to fill the inquiry
+ * data with.
+ * @param[in] len Length of inquiry result buffer
+ * in bytes.
+ * @return length of data on successful operation, a negative
+ * error code otherwise.
+ */
+static int iscsi_scsi_emu_primary_inquiry(const dnbd3_image_t *image, iscsi_scsi_task *scsi_task, const iscsi_scsi_cdb_inquiry *cdb_inquiry, iscsi_scsi_std_inquiry_data_packet *std_inquiry_data_pkt, const uint len)
+{
+ if ( len < sizeof(iscsi_scsi_std_inquiry_data_packet) ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NO_SENSE,
+ ISCSI_SCSI_ASC_NO_ADDITIONAL_SENSE, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return -1;
+ }
+
+ const int evpd = (cdb_inquiry->lun_flags & ISCSI_SCSI_CDB_INQUIRY_FLAGS_EVPD);
+ const uint pc = cdb_inquiry->page_code;
+
+ if ( (evpd == 0) && (pc != 0U) ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ,
+ ISCSI_SCSI_ASC_INVALID_FIELD_IN_CDB, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return -1;
+ }
+
+ if ( evpd != 0 ) {
+ // VPD requested
+ iscsi_scsi_vpd_page_inquiry_data_packet *vpd_page_inquiry_data_pkt = (iscsi_scsi_vpd_page_inquiry_data_packet *) std_inquiry_data_pkt;
+ uint alloc_len;
+ const uint8_t pti = ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PUT_PERIPHERAL_TYPE(ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_DIRECT) | ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PUT_PERIPHERAL_ID(ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_POSSIBLE);
+
+ vpd_page_inquiry_data_pkt->peripheral_type_id = pti;
+ vpd_page_inquiry_data_pkt->page_code = (uint8_t) pc;
+
+ switch ( pc ) {
+ case ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_SUPPORTED_VPD_PAGES : {
+ vpd_page_inquiry_data_pkt->params[0] = ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_SUPPORTED_VPD_PAGES;
+ vpd_page_inquiry_data_pkt->params[1] = ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_UNIT_SERIAL_NUMBER;
+ vpd_page_inquiry_data_pkt->params[2] = ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_DEVICE_ID;
+ vpd_page_inquiry_data_pkt->params[3] = ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_EXTENDED_INQUIRY_DATA;
+ vpd_page_inquiry_data_pkt->params[4] = ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_BLOCK_LIMITS;
+ vpd_page_inquiry_data_pkt->params[5] = ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_BLOCK_DEV_CHARS;
+
+ alloc_len = 6U;
+
+ iscsi_put_be16( (uint8_t *) &vpd_page_inquiry_data_pkt->alloc_len, (uint16_t) alloc_len );
+
+ break;
+ }
+ case ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_UNIT_SERIAL_NUMBER : {
+ const char *name = image->name;
+
+ alloc_len = (uint) strlen( name );
+
+ if ( alloc_len >= (len - sizeof(iscsi_scsi_vpd_page_inquiry_data_packet)) ) {
+ alloc_len = (uint) ((len - sizeof(iscsi_scsi_vpd_page_inquiry_data_packet)) - 1U);
+ }
+
+ memcpy( vpd_page_inquiry_data_pkt->params, name, alloc_len );
+ memset( (vpd_page_inquiry_data_pkt->params + alloc_len), '\0', (len - alloc_len - sizeof(iscsi_scsi_vpd_page_inquiry_data_packet)) );
+
+ alloc_len++;
+
+ iscsi_put_be16( (uint8_t *) &vpd_page_inquiry_data_pkt->alloc_len, (uint16_t) alloc_len );
+
+ break;
+ }
+ case ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_DEVICE_ID : {
+ const char *port_name = "Horst";
+ const uint dev_name_len = (uint) (strlen( image->name ) + 1U);
+ const uint port_name_len = (uint) (strlen( port_name ) + 1U);
+
+ // Calculate total length required for all design descriptors we are about to add:
+ // 1. IEEE NAA Extended
+ // 2. T10 Vendor ID
+ // 3. SCSI Device Name
+ // 4. SCSI Target Port Name
+ // 5. Relative Target Port
+ // 6. Target Port Group
+ // 7. Logical Unit Group
+ alloc_len = (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_ieee_naa_ext_inquiry_data_packet)); // 64-bit IEEE NAA Extended
+ alloc_len += (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_t10_vendor_id_inquiry_data_packet)); // T10 Vendor ID
+ alloc_len += (uint) (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + ISCSI_ALIGN(dev_name_len, ISCSI_ALIGN_SIZE)); // SCSI Device Name
+ alloc_len += (uint) (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + ISCSI_ALIGN(port_name_len, ISCSI_ALIGN_SIZE)); // SCSI Target Port Name
+ alloc_len += (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_rel_target_port_inquiry_data_packet)); // Relative Target Port
+ alloc_len += (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_target_port_group_inquiry_data_packet)); // Target Port Group
+ alloc_len += (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_logical_unit_group_inquiry_data_packet)); // Logical Unit Group
+
+ if ( len < (alloc_len + sizeof(iscsi_scsi_vpd_page_inquiry_data_packet)) ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ, ISCSI_SCSI_ASC_INVALID_FIELD_IN_CDB, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return -1;
+ }
+
+ iscsi_scsi_vpd_page_design_desc_inquiry_data_packet *vpd_page_design_desc_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_inquiry_data_packet *) vpd_page_inquiry_data_pkt->params;
+
+ // 1. Descriptor: IEEE NAA Extended
+ vpd_page_design_desc_inquiry_data_pkt->protocol_id_code_set = ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_CODE_SET(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_BINARY) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_PROTOCOL_ID(ISCSI_DEFAULT_PROTOCOL_ID);
+ vpd_page_design_desc_inquiry_data_pkt->flags = (int8_t) (ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_TYPE(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_NAA) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_ASSOC(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_LOGICAL_UNIT) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PIV);
+ vpd_page_design_desc_inquiry_data_pkt->reserved = 0U;
+ vpd_page_design_desc_inquiry_data_pkt->len = sizeof(iscsi_scsi_vpd_page_design_desc_ieee_naa_ext_inquiry_data_packet);
+
+ iscsi_scsi_emu_naa_ieee_ext_set( (uint64_t *) vpd_page_design_desc_inquiry_data_pkt->desc, (uint8_t *) image->name );
+
+ alloc_len = (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_ieee_naa_ext_inquiry_data_packet));
+
+ vpd_page_design_desc_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_inquiry_data_packet *) (((uint8_t *) vpd_page_design_desc_inquiry_data_pkt) + alloc_len);
+ // 2. Descriptor: T10 Vendor ID
+ vpd_page_design_desc_inquiry_data_pkt->protocol_id_code_set = ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_CODE_SET(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_ASCII) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_PROTOCOL_ID(ISCSI_DEFAULT_PROTOCOL_ID);
+ vpd_page_design_desc_inquiry_data_pkt->flags = (int8_t) (ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_TYPE(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_T10_VENDOR_ID) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_ASSOC(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_LOGICAL_UNIT) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PIV);
+ vpd_page_design_desc_inquiry_data_pkt->reserved = 0U;
+ vpd_page_design_desc_inquiry_data_pkt->len = sizeof(iscsi_scsi_vpd_page_design_desc_t10_vendor_id_inquiry_data_packet);
+
+ iscsi_scsi_vpd_page_design_desc_t10_vendor_id_inquiry_data_packet *vpd_page_design_desc_t10_vendor_id_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_t10_vendor_id_inquiry_data_packet *) vpd_page_design_desc_inquiry_data_pkt->desc;
+
+ iscsi_strcpy_pad( (char *) vpd_page_design_desc_t10_vendor_id_inquiry_data_pkt->vendor_id, ISCSI_SCSI_STD_INQUIRY_DATA_DISK_VENDOR_ID, sizeof(vpd_page_design_desc_t10_vendor_id_inquiry_data_pkt->vendor_id), ' ' );
+ iscsi_strcpy_pad( (char *) vpd_page_design_desc_t10_vendor_id_inquiry_data_pkt->product_id, image->name, sizeof(vpd_page_design_desc_t10_vendor_id_inquiry_data_pkt->product_id), ' ' );
+ iscsi_strcpy_pad( (char *) vpd_page_design_desc_t10_vendor_id_inquiry_data_pkt->unit_serial_num, image->name, sizeof(vpd_page_design_desc_t10_vendor_id_inquiry_data_pkt->unit_serial_num), ' ' );
+
+ alloc_len += (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_t10_vendor_id_inquiry_data_packet));
+
+ vpd_page_design_desc_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_inquiry_data_packet *) (((uint8_t *) vpd_page_design_desc_inquiry_data_pkt) + (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_t10_vendor_id_inquiry_data_packet)));
+ // 3. Descriptor: SCSI Device Name
+ vpd_page_design_desc_inquiry_data_pkt->protocol_id_code_set = ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_CODE_SET(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_UTF8) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_PROTOCOL_ID(ISCSI_DEFAULT_PROTOCOL_ID);
+ vpd_page_design_desc_inquiry_data_pkt->flags = (int8_t) (ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_TYPE(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_SCSI_NAME) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_ASSOC(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_TARGET_DEVICE) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PIV);
+ vpd_page_design_desc_inquiry_data_pkt->reserved = 0U;
+ vpd_page_design_desc_inquiry_data_pkt->len = (uint8_t) iscsi_scsi_emu_pad_scsi_name( vpd_page_design_desc_inquiry_data_pkt->desc, (const uint8_t*)image->name );
+
+ alloc_len += (uint) (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + vpd_page_design_desc_inquiry_data_pkt->len);
+
+ vpd_page_design_desc_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_inquiry_data_packet *) (((uint8_t *) vpd_page_design_desc_inquiry_data_pkt) + (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + vpd_page_design_desc_inquiry_data_pkt->len));
+ // 4. Descriptor: SCSI Target Port Name
+ vpd_page_design_desc_inquiry_data_pkt->protocol_id_code_set = ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_CODE_SET(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_UTF8) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_PROTOCOL_ID(ISCSI_DEFAULT_PROTOCOL_ID);
+ vpd_page_design_desc_inquiry_data_pkt->flags = (int8_t) (ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_TYPE(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_SCSI_NAME) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_ASSOC(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_TARGET_PORT) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PIV);
+ vpd_page_design_desc_inquiry_data_pkt->reserved = 0U;
+ vpd_page_design_desc_inquiry_data_pkt->len = (uint8_t) iscsi_scsi_emu_pad_scsi_name( vpd_page_design_desc_inquiry_data_pkt->desc, (const uint8_t*)port_name );
+
+ alloc_len += (uint) (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + vpd_page_design_desc_inquiry_data_pkt->len);
+
+ vpd_page_design_desc_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_inquiry_data_packet *) (((uint8_t *) vpd_page_design_desc_inquiry_data_pkt) + (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + vpd_page_design_desc_inquiry_data_pkt->len));
+ // 5. Descriptor: Relative Target Port
+ vpd_page_design_desc_inquiry_data_pkt->protocol_id_code_set = ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_CODE_SET(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_BINARY) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_PROTOCOL_ID(ISCSI_DEFAULT_PROTOCOL_ID);
+ vpd_page_design_desc_inquiry_data_pkt->flags = (int8_t) (ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_TYPE(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_REL_TARGET_PORT) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_ASSOC(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_TARGET_PORT) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PIV);
+ vpd_page_design_desc_inquiry_data_pkt->reserved = 0U;
+ vpd_page_design_desc_inquiry_data_pkt->len = sizeof(iscsi_scsi_vpd_page_design_desc_rel_target_port_inquiry_data_packet);
+
+ iscsi_scsi_vpd_page_design_desc_rel_target_port_inquiry_data_packet *vpd_page_design_desc_rel_target_port_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_rel_target_port_inquiry_data_packet *) vpd_page_design_desc_inquiry_data_pkt->desc;
+
+ vpd_page_design_desc_rel_target_port_inquiry_data_pkt->reserved = 0U;
+ iscsi_put_be16( (uint8_t *) &vpd_page_design_desc_rel_target_port_inquiry_data_pkt->index, 1 );
+
+ alloc_len += (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_rel_target_port_inquiry_data_packet));
+
+ vpd_page_design_desc_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_inquiry_data_packet *) (((uint8_t *) vpd_page_design_desc_inquiry_data_pkt) + (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_rel_target_port_inquiry_data_packet)));
+ // 6. Descriptor: Target Port Group
+ vpd_page_design_desc_inquiry_data_pkt->protocol_id_code_set = ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_CODE_SET(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_BINARY) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_PROTOCOL_ID(ISCSI_DEFAULT_PROTOCOL_ID);
+ vpd_page_design_desc_inquiry_data_pkt->flags = (int8_t) (ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_TYPE(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_TARGET_PORT_GROUP) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_ASSOC(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_TARGET_PORT) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PIV);
+ vpd_page_design_desc_inquiry_data_pkt->reserved = 0U;
+ vpd_page_design_desc_inquiry_data_pkt->len = sizeof(iscsi_scsi_vpd_page_design_desc_target_port_group_inquiry_data_packet);
+
+ iscsi_scsi_vpd_page_design_desc_target_port_group_inquiry_data_packet *vpd_page_design_desc_target_port_group_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_target_port_group_inquiry_data_packet *) vpd_page_design_desc_inquiry_data_pkt->desc;
+
+ vpd_page_design_desc_target_port_group_inquiry_data_pkt->reserved = 0U;
+ vpd_page_design_desc_target_port_group_inquiry_data_pkt->index = 0U;
+
+ alloc_len += (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_target_port_group_inquiry_data_packet));
+
+ vpd_page_design_desc_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_inquiry_data_packet *) (((uint8_t *) vpd_page_design_desc_inquiry_data_pkt) + (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_target_port_group_inquiry_data_packet)));
+ // 7. Descriptor: Logical Unit Group
+ vpd_page_design_desc_inquiry_data_pkt->protocol_id_code_set = ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_CODE_SET(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_BINARY) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_PROTOCOL_ID(ISCSI_DEFAULT_PROTOCOL_ID);
+ vpd_page_design_desc_inquiry_data_pkt->flags = (int8_t) (ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_TYPE(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_LOGICAL_UNIT_GROUP) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_ASSOC(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_LOGICAL_UNIT) | ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PIV);
+ vpd_page_design_desc_inquiry_data_pkt->reserved = 0U;
+ vpd_page_design_desc_inquiry_data_pkt->len = sizeof(iscsi_scsi_vpd_page_design_desc_logical_unit_group_inquiry_data_packet);
+
+ iscsi_scsi_vpd_page_design_desc_logical_unit_group_inquiry_data_packet *vpd_page_design_desc_logical_unit_group_inquiry_data_pkt = (iscsi_scsi_vpd_page_design_desc_logical_unit_group_inquiry_data_packet*)vpd_page_design_desc_inquiry_data_pkt->desc;
+
+ vpd_page_design_desc_logical_unit_group_inquiry_data_pkt->reserved = 0U;
+ iscsi_put_be16( (uint8_t *) &vpd_page_design_desc_logical_unit_group_inquiry_data_pkt->id, (uint16_t) ISCSI_DEFAULT_DEVICE_ID );
+
+ alloc_len += (sizeof(iscsi_scsi_vpd_page_design_desc_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_design_desc_logical_unit_group_inquiry_data_packet));
+
+ iscsi_put_be16( (uint8_t *) &vpd_page_inquiry_data_pkt->alloc_len, (uint16_t) alloc_len );
+
+ break;
+ }
+ case ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_EXTENDED_INQUIRY_DATA : {
+ iscsi_scsi_vpd_page_ext_inquiry_data_packet *vpd_page_ext_inquiry_data_pkt = (iscsi_scsi_vpd_page_ext_inquiry_data_packet *) vpd_page_inquiry_data_pkt;
+
+ alloc_len = (sizeof(iscsi_scsi_vpd_page_ext_inquiry_data_packet) - sizeof(iscsi_scsi_vpd_page_inquiry_data_packet));
+
+ if ( len < (alloc_len + sizeof(iscsi_scsi_vpd_page_inquiry_data_packet)) ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ, ISCSI_SCSI_ASC_INVALID_FIELD_IN_CDB, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return -1;
+ }
+
+ vpd_page_ext_inquiry_data_pkt->reserved = 0U;
+ vpd_page_ext_inquiry_data_pkt->page_len = (uint8_t) alloc_len;
+ vpd_page_ext_inquiry_data_pkt->check_flags = 0;
+ vpd_page_ext_inquiry_data_pkt->support_flags = (ISCSI_SCSI_VPD_PAGE_EXT_INQUIRY_DATA_SUPPORT_FLAGS_SIMPSUP | ISCSI_SCSI_VPD_PAGE_EXT_INQUIRY_DATA_SUPPORT_FLAGS_HEADSUP);
+ vpd_page_ext_inquiry_data_pkt->support_flags_2 = 0;
+ vpd_page_ext_inquiry_data_pkt->luiclr = 0U;
+ vpd_page_ext_inquiry_data_pkt->cbcs = 0U;
+ vpd_page_ext_inquiry_data_pkt->micro_dl = 0U;
+ vpd_page_ext_inquiry_data_pkt->reserved2[0] = 0ULL;
+ vpd_page_ext_inquiry_data_pkt->reserved2[1] = 0ULL;
+ vpd_page_ext_inquiry_data_pkt->reserved2[2] = 0ULL;
+ vpd_page_ext_inquiry_data_pkt->reserved2[3] = 0ULL;
+ vpd_page_ext_inquiry_data_pkt->reserved2[4] = 0ULL;
+ vpd_page_ext_inquiry_data_pkt->reserved2[5] = 0ULL;
+ vpd_page_ext_inquiry_data_pkt->reserved3 = 0UL;
+ vpd_page_ext_inquiry_data_pkt->reserved4 = 0U;
+
+ iscsi_put_be16( (uint8_t *) &vpd_page_inquiry_data_pkt->alloc_len, (uint16_t) alloc_len );
+
+ break;
+ }
+ case ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_BLOCK_LIMITS : {
+ iscsi_scsi_vpd_page_block_limits_inquiry_data_packet *vpd_page_block_limits_inquiry_data_pkt = (iscsi_scsi_vpd_page_block_limits_inquiry_data_packet *) vpd_page_inquiry_data_pkt->params;
+
+ if ( len < (sizeof(iscsi_scsi_vpd_page_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_block_limits_inquiry_data_packet)) ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ, ISCSI_SCSI_ASC_INVALID_FIELD_IN_CDB, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return -1;
+ }
+
+ alloc_len = sizeof(iscsi_scsi_vpd_page_block_limits_inquiry_data_packet);
+
+ vpd_page_block_limits_inquiry_data_pkt->flags = 0;
+
+ // So, this has caused some headache, nice. With the kernel's iscsi implementation, we have to limit our
+ // reported maximum supported transfer length to the client's maximum supported DS size. If you just do
+ // ISCSI_MAX_DS_SIZE / ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE
+ // the kernel will complain that the maximum transfer size is not a multiple of the physical block size,
+ // so you might be tempted to use
+ // ((ISCSI_MAX_DS_SIZE / ISCSI_SCSI_EMU_PHYSICAL_BLOCK_SIZE)
+ // * ISCSI_SCSI_EMU_PHYSICAL_BLOCK_SIZE) / ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE
+ // to make sure it is. But then *surprise*, maximum transfer speeds drop from ~1.5GB/s to ~250MB/s.
+ // OK, then you revert back to the simple formula and accept that annoying warning in dmesg, only to
+ // realize that while "pv < /dev/sda > /dev/null" and dd with bs=256k are fast, a dd with bs=1M ends up
+ // at about 25MB/s (!!!!)
+ // So what finally, hopefully, seems to work properly is limiting the reported maximum transfer length to
+ // the client's MaxRecvDataSegmentLength, which coincidentally is the same as its FirstBurstLength, so
+ // let's hope picking MaxRecvDataSegmentLength is the right choice here. You'd think the client would
+ // automatically pick a suitable transfer length that it can handle efficiently; the kernel however just
+ // goes for the maximum supported by the server. Even just lowering the reported *optimal* length is not
+ // sufficient. But maybe I'm just not good with computers.
+ const uint32_t blocks = (scsi_task->connection->opts.MaxRecvDataSegmentLength
+ / ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE);
+
+ vpd_page_block_limits_inquiry_data_pkt->max_cmp_write_len = 0;
+
+ iscsi_put_be16( (uint8_t *) &vpd_page_block_limits_inquiry_data_pkt->optimal_granularity_xfer_len,
+ (uint16_t) ISCSI_SCSI_EMU_PHYSICAL_BLOCK_SIZE / ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE );
+ iscsi_put_be32( (uint8_t *) &vpd_page_block_limits_inquiry_data_pkt->max_xfer_len, blocks );
+ iscsi_put_be32( (uint8_t *) &vpd_page_block_limits_inquiry_data_pkt->optimal_xfer_len, blocks );
+ vpd_page_block_limits_inquiry_data_pkt->max_prefetch_len = 0UL;
+
+ vpd_page_block_limits_inquiry_data_pkt->max_unmap_lba_cnt = 0UL;
+ vpd_page_block_limits_inquiry_data_pkt->max_unmap_block_desc_cnt = 0UL;
+
+ vpd_page_block_limits_inquiry_data_pkt->optimal_unmap_granularity = 0UL;
+ vpd_page_block_limits_inquiry_data_pkt->unmap_granularity_align_ugavalid = 0UL;
+ vpd_page_block_limits_inquiry_data_pkt->max_write_same_len = 0;
+ vpd_page_block_limits_inquiry_data_pkt->reserved[0] = 0ULL;
+ vpd_page_block_limits_inquiry_data_pkt->reserved[1] = 0ULL;
+ vpd_page_block_limits_inquiry_data_pkt->reserved2 = 0UL;
+
+ iscsi_put_be16( (uint8_t *) &vpd_page_inquiry_data_pkt->alloc_len, (uint16_t) alloc_len );
+
+ break;
+ }
+ case ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_BLOCK_DEV_CHARS : {
+ iscsi_scsi_vpd_page_block_dev_chars_inquiry_data_packet *chars_resp = (iscsi_scsi_vpd_page_block_dev_chars_inquiry_data_packet *) vpd_page_inquiry_data_pkt->params;
+
+ if ( len < (sizeof(iscsi_scsi_vpd_page_inquiry_data_packet) + sizeof(iscsi_scsi_vpd_page_block_dev_chars_inquiry_data_packet)) ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ, ISCSI_SCSI_ASC_INVALID_FIELD_IN_CDB, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return -1;
+ }
+
+ alloc_len = sizeof(*chars_resp);
+
+ iscsi_put_be16( (uint8_t *)&chars_resp->medium_rotation_rate, ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_MEDIUM_ROTATION_RATE_NONE );
+ chars_resp->product_type = ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_PRODUCT_TYPE_NOT_INDICATED;
+ chars_resp->flags = ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_PUT_NOMINAL_FORM_FACTOR(ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_NOT_REPORTED);
+ chars_resp->support_flags = 0U;
+ chars_resp->reserved[0] = 0ULL;
+ chars_resp->reserved[1] = 0ULL;
+ chars_resp->reserved[2] = 0ULL;
+ chars_resp->reserved[3] = 0ULL;
+ chars_resp->reserved[4] = 0ULL;
+ chars_resp->reserved[5] = 0ULL;
+ chars_resp->reserved2 = 0UL;
+ chars_resp->reserved3 = 0U;
+ chars_resp->reserved4 = 0U;
+
+ iscsi_put_be16( (uint8_t *) &vpd_page_inquiry_data_pkt->alloc_len, (uint16_t) alloc_len );
+
+ break;
+ }
+ default : {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NO_SENSE, ISCSI_SCSI_ASC_NO_ADDITIONAL_SENSE, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return -1;
+
+ break;
+ }
+ }
+
+ return (int) (alloc_len + sizeof(iscsi_scsi_vpd_page_inquiry_data_packet));
+ }
+
+ // Normal INQUIRY, no VPD
+
+ const uint8_t pti = ISCSI_SCSI_BASIC_INQUIRY_DATA_PUT_PERIPHERAL_TYPE(ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_DIRECT) | ISCSI_SCSI_BASIC_INQUIRY_DATA_PUT_PERIPHERAL_ID(ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_POSSIBLE);
+
+ std_inquiry_data_pkt->basic_inquiry.peripheral_type_id = pti;
+ std_inquiry_data_pkt->basic_inquiry.peripheral_type_mod_flags = 0;
+ std_inquiry_data_pkt->basic_inquiry.version = ISCSI_SCSI_BASIC_INQUIRY_DATA_PUT_VERSION_ANSI(ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_SPC3);
+ std_inquiry_data_pkt->basic_inquiry.response_data_fmt_flags = ISCSI_SCSI_BASIC_INQUIRY_DATA_PUT_RESPONSE_DATA_FMT_FLAGS(ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_SCSI_2) | ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_HISUP;
+
+ std_inquiry_data_pkt->tpgs_flags = 0U;
+ std_inquiry_data_pkt->services_flags = ISCSI_SCSI_STD_INQUIRY_DATA_SERVICES_FLAGS_MULTIP;
+ std_inquiry_data_pkt->flags = ISCSI_SCSI_STD_INQUIRY_DATA_FLAGS_COMMAND_QUEUE;
+
+ iscsi_strcpy_pad( (char *) std_inquiry_data_pkt->vendor_id, ISCSI_SCSI_STD_INQUIRY_DATA_DISK_VENDOR_ID, sizeof(std_inquiry_data_pkt->vendor_id), ' ' );
+ iscsi_strcpy_pad( (char *) std_inquiry_data_pkt->product_id, image->name, sizeof(std_inquiry_data_pkt->product_id), ' ' );
+
+ char image_rev[sizeof(std_inquiry_data_pkt->product_rev_level) + 1];
+
+ sprintf( image_rev, "%04" PRIX16, image->rid );
+ iscsi_strcpy_pad( (char *) std_inquiry_data_pkt->product_rev_level, image_rev, sizeof(std_inquiry_data_pkt->product_rev_level), ' ' );
+
+ uint add_len = (sizeof(iscsi_scsi_std_inquiry_data_packet) - sizeof(iscsi_scsi_basic_inquiry_data_packet));
+ iscsi_scsi_ext_inquiry_data_packet *ext_inquiry_data_pkt = (iscsi_scsi_ext_inquiry_data_packet *) std_inquiry_data_pkt;
+
+ if ( len >= ISCSI_NEXT_OFFSET(iscsi_scsi_ext_inquiry_data_packet, vendor_spec) ) {
+ iscsi_strcpy_pad( (char *) ext_inquiry_data_pkt->vendor_spec, ISCSI_SCSI_EXT_INQUIRY_DATA_VENDOR_SPEC_ID, sizeof(ext_inquiry_data_pkt->vendor_spec), ' ' );
+
+ add_len += sizeof(ext_inquiry_data_pkt->vendor_spec);
+ }
+
+ if ( len >= ISCSI_NEXT_OFFSET(iscsi_scsi_ext_inquiry_data_packet, flags) ) {
+ ext_inquiry_data_pkt->flags = 0;
+
+ add_len += sizeof(ext_inquiry_data_pkt->flags);
+ }
+
+ if ( len >= ISCSI_NEXT_OFFSET(iscsi_scsi_ext_inquiry_data_packet, reserved) ) {
+ ext_inquiry_data_pkt->reserved = 0U;
+
+ add_len += sizeof(ext_inquiry_data_pkt->reserved);
+ }
+
+ if ( len >= ISCSI_NEXT_OFFSET(iscsi_scsi_ext_inquiry_data_packet, version_desc[0]) ) {
+ iscsi_put_be16( (uint8_t *) &ext_inquiry_data_pkt->version_desc[0], ISCSI_SCSI_EXT_INQUIRY_DATA_VERSION_DESC_ISCSI_NO_VERSION );
+
+ add_len += sizeof(ext_inquiry_data_pkt->version_desc[0]);
+ }
+
+ if ( len >= ISCSI_NEXT_OFFSET(iscsi_scsi_ext_inquiry_data_packet, version_desc[1]) ) {
+ iscsi_put_be16( (uint8_t *) &ext_inquiry_data_pkt->version_desc[1], ISCSI_SCSI_EXT_INQUIRY_DATA_VERSION_DESC_SPC3_NO_VERSION );
+
+ add_len += sizeof(ext_inquiry_data_pkt->version_desc[1]);
+ }
+
+ if ( len >= ISCSI_NEXT_OFFSET(iscsi_scsi_ext_inquiry_data_packet, version_desc[2]) ) {
+ iscsi_put_be16( (uint8_t *) &ext_inquiry_data_pkt->version_desc[2], ISCSI_SCSI_EXT_INQUIRY_DATA_VERSION_DESC_SBC2_NO_VERSION );
+
+ add_len += sizeof(ext_inquiry_data_pkt->version_desc[2]);
+ }
+
+ if ( len >= ISCSI_NEXT_OFFSET(iscsi_scsi_ext_inquiry_data_packet, version_desc[3]) ) {
+ iscsi_put_be16( (uint8_t *) &ext_inquiry_data_pkt->version_desc[3], ISCSI_SCSI_EXT_INQUIRY_DATA_VERSION_DESC_SAM2_NO_VERSION );
+
+ add_len += sizeof(ext_inquiry_data_pkt->version_desc[3]);
+ }
+
+ if ( len >= ISCSI_NEXT_OFFSET(iscsi_scsi_ext_inquiry_data_packet, version_desc[4]) ) {
+ uint alloc_len = (uint) (len - offsetof(iscsi_scsi_ext_inquiry_data_packet, version_desc[4]));
+
+ if ( alloc_len > (sizeof(iscsi_scsi_ext_inquiry_data_packet) - offsetof(iscsi_scsi_ext_inquiry_data_packet, version_desc[4])) ) {
+ alloc_len = (sizeof(iscsi_scsi_ext_inquiry_data_packet) - offsetof(iscsi_scsi_ext_inquiry_data_packet, version_desc[4]));
+ }
+
+ memset( &ext_inquiry_data_pkt->version_desc[4], 0, alloc_len );
+ add_len += alloc_len;
+ }
+
+ std_inquiry_data_pkt->basic_inquiry.add_len = (uint8_t) add_len;
+
+ return (int) (add_len + sizeof(iscsi_scsi_basic_inquiry_data_packet));
+}
+
+/**
+ * @brief Executes a report LUNs operation on a DNBD3 image.
+ *
+ * This function also sets the SCSI
+ * status result code accordingly.
+ *
+ * @param[in] report_luns_parameter_data_pkt Pointer to report LUNS
+ * parameter data packet to fill the
+ * LUN data data with.
+ * @param[in] len Length of LUN reporting result buffer
+ * in bytes.
+ * @param[in] select_report Selected report.
+ * @return Total length of LUN data on successful
+ * operation, a negative error code
+ * otherwise.
+ */
+static int iscsi_scsi_emu_primary_report_luns( iscsi_scsi_report_luns_parameter_data_lun_list_packet *report_luns_parameter_data_pkt, const uint len, const uint select_report)
+{
+ const uint64_t lun = iscsi_scsi_lun_get_from_scsi( ISCSI_DEFAULT_LUN );
+
+ if ( len < sizeof(iscsi_scsi_report_luns_parameter_data_lun_list_packet) + sizeof(lun) )
+ return -1;
+
+ switch ( select_report ) {
+ case ISCSI_SCSI_CDB_REPORT_LUNS_SELECT_REPORT_LU_ADDR_METHOD :
+ case ISCSI_SCSI_CDB_REPORT_LUNS_SELECT_REPORT_LU_KNOWN :
+ case ISCSI_SCSI_CDB_REPORT_LUNS_SELECT_REPORT_LU_ALL : {
+ break;
+ }
+ default : {
+ return -1;
+ }
+ }
+
+ report_luns_parameter_data_pkt->reserved = 0UL;
+ iscsi_put_be32( (uint8_t *) &report_luns_parameter_data_pkt->lun_list_len, sizeof(lun) );
+ iscsi_put_be64( (uint8_t *) (report_luns_parameter_data_pkt + 1), lun );
+
+ return (int) (sizeof(lun) + sizeof(iscsi_scsi_report_luns_parameter_data_lun_list_packet));
+}
+
+/**
+ * @brief Initializes a mode sense page or sub page and zero fills the parameter data.
+ *
+ * This function also sets the correct
+ * page length and flags either for
+ * the page or sub page. If a sub page
+ * is initialized, the sub page code
+ * will also be set.
+ *
+ * @param[in] buffer Pointer to mode sense parameter
+ * mode page or sub page data packet
+ * to initialize. If this is NULL,
+ * this function does nothing.
+ * @param[in] len Length in bytes to initialize. Any padding will be zeroed.
+ * @param[in] page Page code.
+ * @param[in] sub_page Sub page code.
+ */
+static void iscsi_scsi_emu_primary_mode_sense_page_init(uint8_t *buffer, const uint len, const uint page, const uint sub_page)
+{
+ if ( buffer == NULL )
+ return;
+
+ if ( sub_page == 0U ) {
+ iscsi_scsi_mode_sense_mode_page_data_header *mode_sense_mode_page_pkt = (iscsi_scsi_mode_sense_mode_page_data_header *) buffer;
+ mode_sense_mode_page_pkt->page_code_flags = (uint8_t) ISCSI_SCSI_MODE_SENSE_MODE_PAGE_PUT_PAGE_CODE(page);
+ mode_sense_mode_page_pkt->page_len = (uint8_t) (len - sizeof(*mode_sense_mode_page_pkt));
+
+ memset( mode_sense_mode_page_pkt + 1, 0, (len - sizeof(*mode_sense_mode_page_pkt)) );
+ } else {
+ iscsi_scsi_mode_sense_mode_sub_page_data_header *mode_sense_mode_sub_page_pkt = (iscsi_scsi_mode_sense_mode_sub_page_data_header *) buffer;
+
+ mode_sense_mode_sub_page_pkt->page_code_flags = (uint8_t) (ISCSI_SCSI_MODE_SENSE_MODE_PAGE_PUT_PAGE_CODE(page) | ISCSI_SCSI_MODE_SENSE_MODE_PAGE_FLAGS_SPF);
+ mode_sense_mode_sub_page_pkt->sub_page_code = (uint8_t) sub_page;
+ iscsi_put_be16( (uint8_t *) &mode_sense_mode_sub_page_pkt->page_len, (uint16_t) (len - sizeof(*mode_sense_mode_sub_page_pkt)) );
+
+ memset( mode_sense_mode_sub_page_pkt + 1, 0, (len - sizeof(*mode_sense_mode_sub_page_pkt)) );
+ }
+}
+
+/**
+ * @brief Handles a specific mode sense page or sub page.
+ *
+ * This function also sets the SCSI
+ * status result code accordingly.
+ *
+ * @param[in] image Pointer to DNBD3 image to get
+ * the mode sense data from. Must NOT be
+ * NULL, so be careful.
+ * @param[in] scsi_task Pointer to iSCSI SCSI task
+ * responsible for this mode sense
+ * task. NULL is NOT allowed here,
+ * take caution.
+ * @param[in] buffer Pointer to mode sense parameter
+ * mode page or sub page data packet
+ * to process. If this is NULL, only
+ * the length of page is calculated.
+ * @param[in] pc Page control (PC).
+ * @param[in] page Page code.
+ * @param[in] sub_page Sub page code.
+ * @return Number of bytes occupied or a
+ * negative error code otherwise.
+ */
+static int iscsi_scsi_emu_primary_mode_sense_page(dnbd3_image_t *image, iscsi_scsi_task *scsi_task, uint8_t *buffer, const uint pc, const uint page, const uint sub_page)
+{
+ uint page_len;
+ uint len = 0;
+ int tmplen;
+
+ switch ( pc ) {
+ case ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_CURRENT_VALUES :
+ case ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_CHG_VALUES :
+ case ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_DEFAULT_VALUES : {
+ break;
+ }
+ default : {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ,
+ ISCSI_SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return -1;
+
+ break;
+ }
+ }
+
+ switch ( page ) {
+ case ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_READ_WRITE_ERR_RECOVERY : {
+ if ( sub_page != 0U )
+ break;
+
+ page_len = sizeof(iscsi_scsi_mode_sense_read_write_err_recovery_mode_page_data_packet);
+
+ iscsi_scsi_emu_primary_mode_sense_page_init( buffer, page_len, page, sub_page );
+
+ len += page_len;
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_DISCONNECT_RECONNECT : {
+ if ( sub_page != 0U )
+ break;
+
+ page_len = sizeof(iscsi_scsi_mode_sense_disconnect_reconnect_mode_page_data_packet);
+
+ iscsi_scsi_emu_primary_mode_sense_page_init( buffer, page_len, page, sub_page );
+
+ len += page_len;
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_VERIFY_ERR_RECOVERY : {
+ if ( sub_page != 0U )
+ break;
+
+ page_len = sizeof(iscsi_scsi_mode_sense_verify_err_recovery_mode_page_data_packet);
+
+ iscsi_scsi_emu_primary_mode_sense_page_init( buffer, page_len, page, sub_page );
+
+ len += page_len;
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_CACHING : {
+ if ( sub_page != 0U )
+ break;
+
+ iscsi_scsi_mode_sense_caching_mode_page_data_packet *cache_page = (iscsi_scsi_mode_sense_caching_mode_page_data_packet *) buffer;
+
+ page_len = sizeof(*cache_page);
+
+ iscsi_scsi_emu_primary_mode_sense_page_init( buffer, page_len, page, sub_page );
+
+ if ( cache_page != NULL ) {
+ cache_page->flags |= ISCSI_SCSI_MODE_SENSE_CACHING_MODE_PAGE_FLAGS_DISC;
+ // 0xffff is endian-agnostic, don't need to convert
+ cache_page->disable_prefetch_xfer_len = 0xffff;
+ cache_page->min_prefetch = 0xffff;
+ cache_page->max_prefetch = 0xffff;
+ cache_page->max_prefetch_ceil = 0xffff;
+ }
+
+ len += page_len;
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_CONTROL : {
+ switch ( sub_page ) {
+ case ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_CONTROL : {
+ page_len = sizeof(iscsi_scsi_mode_sense_control_mode_page_data_packet);
+
+ iscsi_scsi_emu_primary_mode_sense_page_init( buffer, page_len, page, sub_page );
+
+ len += page_len;
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_CONTROL_EXT : {
+ /* Control Extension */
+
+ page_len = sizeof(iscsi_scsi_mode_sense_control_ext_mode_page_data_packet);
+
+ iscsi_scsi_emu_primary_mode_sense_page_init( buffer, page_len, page, sub_page );
+
+ len += page_len;
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_CONTROL_ALL : {
+ tmplen = iscsi_scsi_emu_primary_mode_sense_page( image, scsi_task, ((buffer != NULL) ? (buffer + len) : NULL), pc, page, ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_CONTROL );
+ if ( tmplen == -1 )
+ return -1;
+ len += tmplen;
+ tmplen = iscsi_scsi_emu_primary_mode_sense_page( image, scsi_task, ((buffer != NULL) ? (buffer + len) : NULL), pc, page, ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_CONTROL_EXT );
+ if ( tmplen == -1 )
+ return -1;
+ len += tmplen;
+
+ break;
+ }
+ default : {
+ break;
+ }
+ }
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_XOR_CONTROL : {
+ if ( sub_page != 0U )
+ break;
+
+ page_len = sizeof(iscsi_scsi_mode_sense_xor_ext_mode_page_data_packet);
+
+ iscsi_scsi_emu_primary_mode_sense_page_init( buffer, page_len, page, sub_page );
+
+ len += page_len;
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_POWER_COND : {
+ if ( sub_page != 0U )
+ break;
+
+ page_len = sizeof(iscsi_scsi_mode_sense_power_cond_mode_page_data_packet);
+
+ iscsi_scsi_emu_primary_mode_sense_page_init( buffer, page_len, page, sub_page );
+
+ len += page_len;
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_INFO_EXCEPTIOS_CONTROL : {
+ if ( sub_page != 0U )
+ break;
+
+ page_len = sizeof(iscsi_scsi_mode_sense_info_exceptions_control_mode_page_data_packet);
+
+ iscsi_scsi_emu_primary_mode_sense_page_init( buffer, page_len, page, sub_page );
+
+ len += page_len;
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_REPORT_ALL_MODE_PAGES : {
+ switch ( sub_page ) {
+ case ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_REPORT_ALL_MODE_PAGES : {
+ for ( uint i = ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_VENDOR_SPEC; i < ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_REPORT_ALL_MODE_PAGES; i++ ) {
+ tmplen = iscsi_scsi_emu_primary_mode_sense_page( image, scsi_task, ((buffer != NULL) ? (buffer + len) : NULL), pc, i, ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_REPORT_ALL_MODE_PAGES );
+ if ( tmplen == -1 )
+ return -1;
+ len += tmplen;
+ }
+
+ break;
+ }
+ case ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_REPORT_ALL_MODE_SUB_PAGES : {
+ for ( uint i = ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_VENDOR_SPEC; i < ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_REPORT_ALL_MODE_PAGES; i++ ) {
+ tmplen = iscsi_scsi_emu_primary_mode_sense_page( image, scsi_task, ((buffer != NULL) ? (buffer + len) : NULL), pc, i, ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_REPORT_ALL_MODE_PAGES );
+ if ( tmplen == -1 )
+ return -1;
+ len += tmplen;
+ }
+
+ for ( uint i = ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_VENDOR_SPEC; i < ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_REPORT_ALL_MODE_PAGES; i++ ) {
+ tmplen = iscsi_scsi_emu_primary_mode_sense_page( image, scsi_task, ((buffer != NULL) ? (buffer + len) : NULL), pc, i, ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_REPORT_ALL_MODE_SUB_PAGES );
+ if ( tmplen == -1 )
+ return -1;
+ len += tmplen;
+ }
+
+ break;
+ }
+ default : {
+ break;
+ }
+ }
+
+ break;
+ }
+ default : {
+ break;
+ }
+ }
+
+ return (int)len;
+}
+
+/**
+ * @brief Executes a mode sense operation on a DNBD3 image.
+ *
+ * This function also sets the SCSI
+ * status result code accordingly.
+ *
+ * @param[in] image Pointer to DNBD3 image to get
+ * the mode sense data from. Must
+ * NOT be NULL, so be careful.
+ * @param[in] scsi_task Pointer to iSCSI SCSI task
+ * responsible for this mode sense
+ * task. NULL is NOT allowed here,
+ * take caution.
+ * @param[in] buffer Pointer to mode sense parameter
+ * header data packet to fill the
+ * mode sense data with. If this is
+ * NULL, only the length of sense
+ * data is calculated.
+ * @param[in] hdr_len Length of parameter header in bytes.
+ * @param[in] block_desc_len Length of LBA parameter block
+ * descriptor in bytes.
+ * @param[in] long_lba Long Logical Block Address (LONG_LBA) bit.
+ * @param[in] pc Page control (PC).
+ * @param[in] page_code Page code.
+ * @param[in] sub_page_code Sub page code.
+ * @return Total length of sense data on successful
+ * operation, a negative error code
+ * otherwise.
+ */
+static int iscsi_scsi_emu_primary_mode_sense(dnbd3_image_t *image, iscsi_scsi_task *scsi_task, uint8_t *buffer,
+ const uint hdr_len, const uint block_desc_len, const uint long_lba, const uint pc, const uint page_code, const uint sub_page_code)
+{
+ // Pointer to right after header and LBA block description; where the pages go
+ uint8_t *mode_sense_payload = (buffer != NULL) ? (buffer + hdr_len + block_desc_len) : NULL;
+ const int page_len = iscsi_scsi_emu_primary_mode_sense_page( image, scsi_task, mode_sense_payload, pc, page_code, sub_page_code );
+
+ if ( page_len < 0 )
+ return -1;
+
+ const uint alloc_len = (hdr_len + block_desc_len + page_len);
+
+ if ( buffer == NULL )
+ return (int)alloc_len;
+
+ if ( hdr_len == sizeof(iscsi_scsi_mode_sense_6_parameter_header_data_packet) ) {
+ iscsi_scsi_mode_sense_6_parameter_header_data_packet *mode_sense_6_parameter_hdr_data_pkt = (iscsi_scsi_mode_sense_6_parameter_header_data_packet *) buffer;
+ mode_sense_6_parameter_hdr_data_pkt->mode_data_len = (uint8_t) (alloc_len - sizeof(uint8_t));
+ mode_sense_6_parameter_hdr_data_pkt->medium_type = 0U;
+ mode_sense_6_parameter_hdr_data_pkt->flags = ISCSI_SCSI_MODE_SENSE_6_PARAM_HDR_DATA_FLAGS_WP;
+ mode_sense_6_parameter_hdr_data_pkt->block_desc_len = (uint8_t) block_desc_len;
+ } else if ( hdr_len == sizeof(iscsi_scsi_mode_sense_10_parameter_header_data_packet) ) {
+ iscsi_scsi_mode_sense_10_parameter_header_data_packet *mode_sense_10_parameter_hdr_data_pkt = (iscsi_scsi_mode_sense_10_parameter_header_data_packet *) buffer;
+
+ iscsi_put_be16( (uint8_t *) &mode_sense_10_parameter_hdr_data_pkt->mode_data_len, (uint16_t) (alloc_len - sizeof(uint16_t)) );
+ mode_sense_10_parameter_hdr_data_pkt->medium_type = 0U;
+ mode_sense_10_parameter_hdr_data_pkt->flags = ISCSI_SCSI_MODE_SENSE_10_PARAM_HDR_DATA_FLAGS_WP;
+ mode_sense_10_parameter_hdr_data_pkt->long_lba = (uint8_t) long_lba;
+ mode_sense_10_parameter_hdr_data_pkt->reserved = 0U;
+ iscsi_put_be16( (uint8_t *) &mode_sense_10_parameter_hdr_data_pkt->block_desc_len, (uint16_t) block_desc_len );
+ } else {
+ logadd( LOG_DEBUG1, "iscsi_scsi_emu_primary_mode_sense: invalid parameter header length %u", hdr_len );
+ return -1;
+ }
+
+ const uint64_t num_blocks = iscsi_scsi_emu_block_get_count( image );
+ const uint32_t block_size = ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE;
+
+ if ( block_desc_len == sizeof(iscsi_scsi_mode_sense_lba_parameter_block_desc_data_packet) ) {
+ iscsi_scsi_mode_sense_lba_parameter_block_desc_data_packet *lba_parameter_block_desc = (iscsi_scsi_mode_sense_lba_parameter_block_desc_data_packet *) (buffer + hdr_len);
+
+ if ( num_blocks > 0xFFFFFFFFULL ) {
+ lba_parameter_block_desc->num_blocks = 0xFFFFFFFFUL; // Minus one does not require endianess conversion
+ } else {
+ iscsi_put_be32( (uint8_t *) &lba_parameter_block_desc->num_blocks, (uint32_t) num_blocks );
+ }
+
+ lba_parameter_block_desc->reserved = 0U;
+ iscsi_put_be24( (uint8_t *) &lba_parameter_block_desc->block_len, block_size );
+ } else if ( block_desc_len == sizeof(iscsi_scsi_mode_sense_long_lba_parameter_block_desc_data_packet) ) {
+ iscsi_scsi_mode_sense_long_lba_parameter_block_desc_data_packet *long_lba_parameter_block_desc = (iscsi_scsi_mode_sense_long_lba_parameter_block_desc_data_packet *) (buffer + hdr_len);
+
+ iscsi_put_be64( (uint8_t *) &long_lba_parameter_block_desc->num_blocks, num_blocks );
+ long_lba_parameter_block_desc->reserved = 0UL;
+ iscsi_put_be32( (uint8_t *) &long_lba_parameter_block_desc->block_len, block_size );
+ }
+
+ return (int)alloc_len;
+}
+
+/**
+ * @brief Determines the temporary allocation size for a SCSI reply.
+ *
+ * This function calculates the temporary allocation size to be used for SCSI
+ * commands based on the requested allocation size. It ensures the allocation
+ * size has a minimum size, to simplify buffer-filling. The response can then
+ * later be truncated if it's larger than the alloc_size.
+ * If the requested size exceeds the default maximum allowed size, a SCSI task
+ * status with an error condition is set, and the allocation size is returned
+ * as zero.
+ *
+ * @param[in] scsi_task Pointer to the SCSI task, used to set error status.
+ * @param[in] alloc_size The client-requested allocation size in bytes.
+ *
+ * @return The determined temporary allocation size. Returns 0 if the size
+ * exceeds the maximum allowed limit; otherwise, the size is either adjusted
+ * to the default size or remains the requested size.
+ */
+static uint32_t iscsi_get_temporary_allocation_size(iscsi_scsi_task *scsi_task, uint32_t alloc_size)
+{
+ if ( alloc_size > ISCSI_DEFAULT_RECV_DS_LEN ) {
+ // Don't allocate gigabytes of memory just because the client says so
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NO_SENSE,
+ ISCSI_SCSI_ASC_NO_ADDITIONAL_SENSE, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+
+ return 0;
+ }
+ if ( alloc_size < ISCSI_DEFAULT_RECV_DS_LEN )
+ return ISCSI_DEFAULT_RECV_DS_LEN;
+
+ return alloc_size;
+}
+
+/**
+ * @brief Executes SCSI non-block emulation on a DNBD3 image.
+ *
+ * This function determines the
+ * non-block based SCSI opcode and
+ * executes it.
+ *
+ * @param[in] scsi_task Pointer to iSCSI SCSI task
+ * to process the SCSI non-block
+ * operation for and must NOT be NULL,
+ * be careful.
+ * @return true on successful operation, false otherwise.
+ */
+static bool iscsi_scsi_emu_primary_process(iscsi_scsi_task *scsi_task)
+{
+ uint len;
+ int rc;
+
+ switch ( scsi_task->cdb->opcode ) {
+ case ISCSI_SCSI_OPCODE_INQUIRY : {
+ const iscsi_scsi_cdb_inquiry *cdb_inquiry = (iscsi_scsi_cdb_inquiry *) scsi_task->cdb;
+ const uint alloc_len = iscsi_get_be16(cdb_inquiry->alloc_len);
+
+ len = iscsi_get_temporary_allocation_size( scsi_task, alloc_len );
+ if ( len == 0 )
+ break;
+
+ iscsi_scsi_std_inquiry_data_packet *std_inquiry_data_pkt = malloc( len );
+
+ if ( std_inquiry_data_pkt == NULL ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NOT_READY,
+ ISCSI_SCSI_ASC_LOGICAL_UNIT_NOT_READY, ISCSI_SCSI_ASCQ_BECOMING_READY );
+
+ break;
+ }
+
+ rc = iscsi_scsi_emu_primary_inquiry( scsi_task->connection->client->image, scsi_task, cdb_inquiry, std_inquiry_data_pkt, len );
+
+ if ( rc >= 0 ) {
+ scsi_task->buf = (uint8_t *) std_inquiry_data_pkt;
+ scsi_task->len = MIN( (uint)rc, alloc_len );
+ scsi_task->status = ISCSI_SCSI_STATUS_GOOD;
+ } else {
+ free( std_inquiry_data_pkt );
+ }
+
+ break;
+ }
+ case ISCSI_SCSI_OPCODE_REPORTLUNS : {
+ const iscsi_scsi_cdb_report_luns *cdb_report_luns = (iscsi_scsi_cdb_report_luns *) scsi_task->cdb;
+ const uint alloc_len = iscsi_get_be32(cdb_report_luns->alloc_len);
+
+ len = iscsi_get_temporary_allocation_size( scsi_task, alloc_len );
+ if ( len == 0 )
+ break;
+
+ iscsi_scsi_report_luns_parameter_data_lun_list_packet *report_luns_parameter_data_pkt = malloc( len );
+
+ if ( report_luns_parameter_data_pkt == NULL ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NOT_READY,
+ ISCSI_SCSI_ASC_LOGICAL_UNIT_NOT_READY, ISCSI_SCSI_ASCQ_BECOMING_READY );
+
+ break;
+ }
+
+ rc = iscsi_scsi_emu_primary_report_luns( report_luns_parameter_data_pkt, len, cdb_report_luns->select_report );
+
+ if ( rc >= 0 ) {
+ scsi_task->buf = (uint8_t *) report_luns_parameter_data_pkt;
+ scsi_task->len = MIN( (uint)rc, alloc_len );
+ scsi_task->status = ISCSI_SCSI_STATUS_GOOD;
+ } else {
+ free( report_luns_parameter_data_pkt );
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NO_SENSE,
+ ISCSI_SCSI_ASC_NO_ADDITIONAL_SENSE, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+ }
+
+ break;
+ }
+ case ISCSI_SCSI_OPCODE_MODESENSE6 : {
+ const iscsi_scsi_cdb_mode_sense_6 *cdb_mode_sense_6 = (iscsi_scsi_cdb_mode_sense_6 *) scsi_task->cdb;
+ const uint alloc_len = cdb_mode_sense_6->alloc_len;
+
+ const uint block_desc_len = ((cdb_mode_sense_6->flags & ISCSI_SCSI_CDB_MODE_SENSE_6_FLAGS_DBD) == 0) ? sizeof(iscsi_scsi_mode_sense_lba_parameter_block_desc_data_packet) : 0U;
+ const uint pc = ISCSI_SCSI_CDB_MODE_SENSE_6_GET_PAGE_CONTROL(cdb_mode_sense_6->page_code_control);
+ const uint page = ISCSI_SCSI_CDB_MODE_SENSE_6_GET_PAGE_CODE(cdb_mode_sense_6->page_code_control);
+ const uint sub_page = cdb_mode_sense_6->sub_page_code;
+
+ rc = iscsi_scsi_emu_primary_mode_sense( scsi_task->connection->client->image, scsi_task, NULL, sizeof(iscsi_scsi_mode_sense_6_parameter_header_data_packet), block_desc_len, 0U, pc, page, sub_page );
+
+ if ( rc < 0 )
+ break;
+
+ len = rc;
+
+ uint8_t *mode_sense_6_parameter_hdr_data_pkt = malloc( len );
+
+ if ( mode_sense_6_parameter_hdr_data_pkt == NULL ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NOT_READY, ISCSI_SCSI_ASC_LOGICAL_UNIT_NOT_READY, ISCSI_SCSI_ASCQ_BECOMING_READY );
+
+ break;
+ }
+
+ rc = iscsi_scsi_emu_primary_mode_sense( scsi_task->connection->client->image, scsi_task, mode_sense_6_parameter_hdr_data_pkt, sizeof(iscsi_scsi_mode_sense_6_parameter_header_data_packet), block_desc_len, 0U, pc, page, sub_page );
+
+ if ( rc >= 0 ) {
+ scsi_task->buf = mode_sense_6_parameter_hdr_data_pkt;
+ scsi_task->len = MIN( (uint)rc, alloc_len );
+ scsi_task->status = ISCSI_SCSI_STATUS_GOOD;
+ } else {
+ free( mode_sense_6_parameter_hdr_data_pkt );
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NO_SENSE,
+ ISCSI_SCSI_ASC_NO_ADDITIONAL_SENSE, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+ }
+
+ break;
+ }
+ case ISCSI_SCSI_OPCODE_MODESENSE10 : {
+ const iscsi_scsi_cdb_mode_sense_10 *cdb_mode_sense_10 = (iscsi_scsi_cdb_mode_sense_10 *) scsi_task->cdb;
+ const uint alloc_len = iscsi_get_be16(cdb_mode_sense_10->alloc_len);
+
+ const uint long_lba = (((cdb_mode_sense_10->flags & ISCSI_SCSI_CDB_MODE_SENSE_10_FLAGS_LLBAA) != 0) ? ISCSI_SCSI_MODE_SENSE_10_PARAM_HDR_DATA_LONGLBA : 0U);
+ const uint block_desc_len = (((cdb_mode_sense_10->flags & ISCSI_SCSI_CDB_MODE_SENSE_10_FLAGS_DBD) == 0) ? ((long_lba != 0) ? sizeof(iscsi_scsi_mode_sense_long_lba_parameter_block_desc_data_packet) : sizeof(iscsi_scsi_mode_sense_lba_parameter_block_desc_data_packet)) : 0U);
+ const uint pc10 = ISCSI_SCSI_CDB_MODE_SENSE_10_GET_PAGE_CONTROL(cdb_mode_sense_10->page_code_control);
+ const uint page10 = ISCSI_SCSI_CDB_MODE_SENSE_10_GET_PAGE_CODE(cdb_mode_sense_10->page_code_control);
+ const uint sub_page10 = cdb_mode_sense_10->sub_page_code;
+
+ rc = iscsi_scsi_emu_primary_mode_sense( scsi_task->connection->client->image, scsi_task, NULL, sizeof(iscsi_scsi_mode_sense_10_parameter_header_data_packet), block_desc_len, long_lba, pc10, page10, sub_page10 );
+
+ if ( rc < 0 )
+ break;
+
+ len = rc;
+
+ uint8_t *mode_sense_10_parameter_hdr_data_pkt = malloc( len );
+
+ if ( mode_sense_10_parameter_hdr_data_pkt == NULL ) {
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NOT_READY, ISCSI_SCSI_ASC_LOGICAL_UNIT_NOT_READY, ISCSI_SCSI_ASCQ_BECOMING_READY );
+
+ break;
+ }
+
+ rc = iscsi_scsi_emu_primary_mode_sense( scsi_task->connection->client->image, scsi_task, mode_sense_10_parameter_hdr_data_pkt, sizeof(iscsi_scsi_mode_sense_10_parameter_header_data_packet), block_desc_len, long_lba, pc10, page10, sub_page10 );
+
+ if ( rc >= 0 ) {
+ scsi_task->buf = mode_sense_10_parameter_hdr_data_pkt;
+ scsi_task->len = MIN( (uint)rc, alloc_len );
+ scsi_task->status = ISCSI_SCSI_STATUS_GOOD;
+ } else {
+ free( mode_sense_10_parameter_hdr_data_pkt );
+ iscsi_scsi_task_status_set( scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_NO_SENSE,
+ ISCSI_SCSI_ASC_NO_ADDITIONAL_SENSE, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+ }
+
+ break;
+ }
+ case ISCSI_SCSI_OPCODE_TESTUNITREADY :
+ case ISCSI_SCSI_OPCODE_STARTSTOPUNIT : {
+ scsi_task->status = ISCSI_SCSI_STATUS_GOOD;
+
+ break;
+ }
+ default : {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @brief Calculates the WWN using 64-bit IEEE Extended NAA for a name.
+ *
+ * @param[in] name Pointer to string containing the
+ * name to calculate the IEEE Extended
+ * NAA for. NULL is NOT allowed here, so
+ * take caution.
+ * @return A 64-bit unsigned integer for
+ * storing the IEEE Extended NAA.
+ */
+static uint64_t iscsi_target_node_wwn_get(const uint8_t *name)
+{
+ uint64_t value = 0ULL;
+ int i = 0;
+
+ while ( name[i] != '\0' ) {
+ value = (value * 131ULL) + name[i++];
+ }
+
+ const uint64_t id_a = ((value & 0xFFF000000ULL) << 24ULL);
+
+ return ((value & 0xFFFFFFULL) | 0x2000000347000000ULL | id_a);
+}
+
+/**
+ * @brief Appends a key and value pair to DataSegment packet data.
+ *
+ * This function adds any non-declarative key
+ * and value pair to an output DataSegment
+ * buffer and truncates if necessary.
+ *
+ * @param[in] number true = int, false = char*
+ * @param[in] key Pointer to key to be written to output
+ * buffer. NULL is NOT allowed, take caution.
+ * @param[in] value Pointer to value of the key that should
+ * be written to output buffer which must
+ * NOT be NULL, so take caution.
+ * @param[in] buf Pointer to output buffer to write the
+ * key and value pair to. NULL is
+ * prohibited, so be careful.
+ * @param[in] pos Position of buffer in bytes to start
+ * writing to.
+ * @param[in] buflen Total length of buffer in bytes.
+ * @return -1 if buffer is already full, otherwise the number
+ * of bytes that are written or would have been written to
+ * the buffer.
+ */
+static int iscsi_append_key_value_pair_packet(const bool number, const char *key, const char *value, char *buf, const uint32_t pos, const uint32_t buflen)
+{
+ if ( pos >= buflen )
+ return -1;
+
+ const ssize_t maxlen = buflen - pos;
+ if ( number ) {
+ return (int)snprintf( (buf + pos), maxlen, "%s=%d", key, (const int)(const size_t)value ) + 1;
+ }
+ return (int)snprintf( (buf + pos), maxlen, "%s=%s", key, value ) + 1;
+}
+
+
+#define CLAMP(val, min, max) ((val) < (min) ? (min) : ((val) > (max) ? (max) : (val)))
+
+/**
+ * @brief Updates selected iSCSI connection options from negotiated key-value pairs.
+ *
+ * Copies and clamps a subset of negotiated options (MaxBurstLength,
+ * FirstBurstLength, MaxRecvDataSegmentLength) into the connection's
+ * options.
+ *
+ * @param[in] conn Pointer to ISCSI connection which should be updated.
+ * @param[in] pairs Set of readily parsed key-value pairs to apply.
+ */
+static void iscsi_connection_update_key_value_pairs(iscsi_connection *conn, const iscsi_negotiation_kvp *pairs)
+{
+ conn->opts.MaxBurstLength = CLAMP(pairs->MaxBurstLength, 512, ISCSI_MAX_DS_SIZE);
+ conn->opts.FirstBurstLength = CLAMP(pairs->FirstBurstLength, 512, pairs->MaxBurstLength);
+ conn->opts.MaxRecvDataSegmentLength = CLAMP(pairs->MaxRecvDataSegmentLength, 512, ISCSI_MAX_DS_SIZE);
+}
+
+/**
+ * @brief Prepares an iSCSI login response PDU and sends it via TCP/IP.
+ *
+ * This function constructs the login response PDU
+ * to be sent via TCP/IP.
+ *
+ * @param[in] conn Pointer to ISCSI connection to send the TCP/IP
+ * packet with. Must NOT be NULL, so be
+ * careful.
+ * @param[in] resp_pdu Pointer to login response PDU to
+ * be sent via TCP/IP. NULL is NOT
+ * allowed here, take caution.
+ * @return 0 if the login response has been sent
+ * successfully, a negative error code otherwise.
+ */
+static int iscsi_send_login_response_pdu(iscsi_connection *conn, iscsi_pdu *resp_pdu)
+{
+ iscsi_login_response_packet *login_response_pkt =
+ (iscsi_login_response_packet *) iscsi_connection_pdu_resize( resp_pdu, resp_pdu->ahs_len, resp_pdu->ds_write_pos );
+
+ login_response_pkt->version_max = ISCSI_VERSION_MAX;
+ login_response_pkt->version_active = ISCSI_VERSION_MAX;
+
+ iscsi_put_be32( (uint8_t *) &login_response_pkt->total_ahs_len, resp_pdu->ds_len ); // TotalAHSLength is always 0 and DataSegmentLength is 24-bit, so write in one step.
+ iscsi_put_be32( (uint8_t *) &login_response_pkt->stat_sn, conn->stat_sn++ );
+
+ if ( conn->state != ISCSI_CONNECT_STATE_NEW ) {
+ iscsi_put_be32( (uint8_t *) &login_response_pkt->exp_cmd_sn, conn->exp_cmd_sn );
+ iscsi_put_be32( (uint8_t *) &login_response_pkt->max_cmd_sn, conn->max_cmd_sn );
+ } else {
+ iscsi_put_be32( (uint8_t *) &login_response_pkt->exp_cmd_sn, resp_pdu->cmd_sn );
+ iscsi_put_be32( (uint8_t *) &login_response_pkt->max_cmd_sn, resp_pdu->cmd_sn );
+ }
+
+ if ( login_response_pkt->status_class != ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SUCCESS ) {
+ login_response_pkt->flags &= (int8_t) ~(ISCSI_LOGIN_RESPONSE_FLAGS_TRANSIT | ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_MASK | ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_MASK );
+ }
+
+ return iscsi_connection_pdu_write( conn, resp_pdu ) ? 0 : -1;
+}
+
+/**
+ * @brief Initializes an iSCSI login response PDU structure.
+ *
+ * This function initializes the internal login
+ * response data structure which is part of the iSCSI
+ * login procedure.
+ *
+ * @param[in] login_response_pdu Pointer to login response PDU, NULL
+ * is not an allowed value here, so take caution.
+ * @param[in] pdu Pointer to login request PDU from client,
+ * must NOT be NULL, so be careful.
+ * @return 0 if initialization was successful, a negative error
+ * code otherwise.
+ */
+static int iscsi_connection_pdu_login_response_init(iscsi_pdu *login_response_pdu, const iscsi_pdu *pdu)
+{
+ iscsi_login_req_packet *login_req_pkt = (iscsi_login_req_packet *) pdu->bhs_pkt;
+ iscsi_login_response_packet *login_response_pkt = (iscsi_login_response_packet *) login_response_pdu->bhs_pkt;
+
+ login_response_pkt->opcode = ISCSI_OPCODE_SERVER_LOGIN_RES;
+ login_response_pkt->flags = (int8_t) (login_req_pkt->flags & (ISCSI_LOGIN_REQ_FLAGS_TRANSIT | ISCSI_LOGIN_REQ_FLAGS_CONTINUE | ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_MASK));
+
+ if ( (login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_TRANSIT) != 0 ) {
+ login_response_pkt->flags |= (login_req_pkt->flags & ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_MASK);
+ }
+
+ login_response_pkt->isid = login_req_pkt->isid;
+ login_response_pkt->tsih = 0;
+ login_response_pkt->init_task_tag = login_req_pkt->init_task_tag; // Copying over doesn't change endianess.
+ login_response_pkt->reserved = 0UL;
+ login_response_pdu->cmd_sn = iscsi_get_be32(login_req_pkt->cmd_sn);
+ login_response_pkt->stat_sn = 0UL;
+ login_response_pkt->reserved2 = 0U;
+ login_response_pkt->reserved3 = 0ULL;
+
+ if ( login_req_pkt->tsih != 0 ) {
+ // Session resumption, not supported
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_SESSION_NO_EXIST;
+ } else if ( ((login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_TRANSIT) != 0) && ((login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_CONTINUE) != 0) ) {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISC;
+ } else if ( (ISCSI_VERSION_MAX < login_req_pkt->version_min) || (ISCSI_VERSION_MIN > login_req_pkt->version_max) ) {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_WRONG_VERSION;
+ } else if ( (ISCSI_LOGIN_RESPONSE_FLAGS_GET_NEXT_STAGE(login_response_pkt->flags) == ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_RESERVED) && ((login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_TRANSIT) != 0) ) {
+ login_response_pkt->flags &= (int8_t) ~(ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_MASK | ISCSI_LOGIN_RESPONSE_FLAGS_TRANSIT | ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_MASK);
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISC;
+ } else {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SUCCESS;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SUCCESS;
+
+ return ISCSI_CONNECT_PDU_READ_OK;
+ }
+
+ return ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+}
+
+/**
+ * @brief Determines the session type of login.
+ *
+ * This function is used to retrieve the
+ * login session type and checks the
+ * relevant key and value pair for
+ * errors.
+ *
+ * @param[in] login_response_pdu Pointer to login response PDU,
+ * NULL is not allowed, so take caution.
+ * @param[in] type_str Pointer to key and value pairs which
+ * contain the session type parameter to be evaluated,
+ * which must NOT be NULL, so take caution.
+ * @param[in] type Write session type constant to this int.
+ * Must not be null.
+ * @return 0 on successful operation, a negative error code
+ * otherwise. The output session 'type' is unchanged, if
+ * an invalid session type value was retrieved.
+ */
+static int iscsi_login_parse_session_type(const iscsi_pdu *login_response_pdu, const char *type_str, int *type)
+{
+ iscsi_login_response_packet *login_response_pkt = (iscsi_login_response_packet *) login_response_pdu->bhs_pkt;
+
+ if ( type_str != NULL && strcasecmp( type_str, "Normal" ) == 0 ) {
+ *type = ISCSI_CONNECT_STATE_NORMAL_SESSION;
+ return ISCSI_CONNECT_PDU_READ_OK;
+ }
+
+ *type = ISCSI_CONNECT_STATE_INVALID;
+ logadd( LOG_DEBUG1, "Unsupported session type: %s", type_str );
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISSING_PARAMETER;
+
+ return ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+}
+
+/**
+ * @brief Checks the target node info and sets login response PDU accordingly.
+ *
+ * This function also checks if the target node is
+ * redirected and if so, sets the response to the
+ * client response to the temporarily redirection
+ * URL.\n
+ * THe accessibility of the target node is
+ * also checked.
+ *
+ * @param[in] conn Pointer to iSCSI connection which must NOT be
+ * NULL, so be careful.
+ * @param[in] login_response_pdu Pointer to login response PDU
+ * to set the parameters for. NULL is NOT allowed
+ * here, so take caution.
+ * @param[in] target_name Pointer to target node name and must
+ * NOT be NULL, be careful.
+ * @return 0 if the check was successful or a negative
+ * error code otherwise.
+ */
+static int iscsi_image_from_target(const iscsi_connection *conn, const iscsi_pdu *login_response_pdu, const char *target_name)
+{
+ iscsi_login_response_packet *login_response_pkt = (iscsi_login_response_packet *) login_response_pdu->bhs_pkt;
+
+ char *image_rev = NULL;
+ char *tmpbuf = strdup( target_name );
+ char *image_name = tmpbuf;
+ char *tmp = strchr( tmpbuf, ':' );
+
+ if ( tmpbuf == NULL ) {
+ logadd( LOG_ERROR, "iscsi_target_node_image_get: Out of memory while allocating DNBD3 image name for iSCSI target node" );
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SERVER_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SERVER_ERR_OUT_OF_RESOURCES;
+
+ return ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+ }
+
+ while ( tmp != NULL ) {
+ *tmp++ = '\0';
+ if ( image_rev != NULL ) {
+ image_name = image_rev;
+ }
+ image_rev = tmp;
+ tmp = strchr( tmp, ':' );
+ }
+
+ uint16_t rev = 0;
+ if ( image_rev != NULL ) {
+ char *end = NULL;
+ long rid = strtol( image_rev, &end, 10 );
+ if ( end == NULL || *end != '\0' || rid < 0 || rid > 0xFFFF ) {
+ logadd( LOG_DEBUG1, "iscsi_image_from_target: Invalid revision number (%s) in iSCSI target node name: '%s'", image_rev, target_name );
+ } else {
+ rev = (uint16_t)rid;
+ }
+ }
+ dnbd3_image_t *image = image_getOrLoad( image_name, rev );
+
+ if ( image == NULL && image_rev != NULL ) {
+ image = image_getOrLoad( image_rev, rev );
+ }
+
+ if ( image == NULL && strncasecmp( image_name, ISCSI_TARGET_NODE_WWN_NAME_PREFIX, ISCSI_STRLEN(ISCSI_TARGET_NODE_WWN_NAME_PREFIX) ) == 0 ) {
+ uint64_t wwn = strtoull( (image_name + ISCSI_STRLEN(ISCSI_TARGET_NODE_WWN_NAME_PREFIX)), NULL, 16 );
+
+ image = image_getByWwn( wwn, rev, true );
+
+ if ( image == NULL ) {
+ wwn = strtoull( (tmp + ISCSI_STRLEN(ISCSI_TARGET_NODE_WWN_NAME_PREFIX)), NULL, 16 );
+ image = image_getByWwn( wwn, rev, true );
+ }
+ }
+
+ free( tmpbuf );
+
+ if ( image == NULL ) {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_NOT_FOUND;
+
+ return ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+ }
+ conn->client->image = image;
+
+ return ISCSI_CONNECT_PDU_READ_OK;
+}
+
+/**
+ * @brief Initializes an iSCSI Protocol Data Unit (PDU) object for use in iSCSI communication.
+ *
+ * Allocates and assigns the required memory for the Basic Header Segment (BHS) packet
+ * and optionally for the aligned data segment (DS). Resets and initializes various fields
+ * within the given PDU structure. Ensures proper memory alignment for data segment if
+ * applicable, and zeroes out unused buffer regions.
+ *
+ * @param[in,out] pdu Pointer to the iSCSI PDU structure to initialize. Must not be NULL.
+ * @param[in] ds_len Length of the Data Segment (DS) in bytes. Must not exceed ISCSI_MAX_DS_SIZE.
+ * @param[in] no_ds_alloc If true, the Data Segment memory allocation is skipped.
+ *
+ * @retval true if initialization is successful.
+ * @retval false if memory allocation for the BHS packet fails or ds_len exceeds the maximum allowed size.
+ */
+static bool iscsi_connection_pdu_init(iscsi_pdu *pdu, const uint32_t ds_len, bool no_ds_alloc)
+{
+ // Always set this pointer to NULL before any sanity checks,
+ // so the attribute-cleanup magic won't screw up if init fails
+ pdu->big_alloc = NULL;
+
+ if ( ds_len > ISCSI_MAX_DS_SIZE ) {
+ logadd( LOG_ERROR, "iscsi_pdu_init: Invalid DS length" );
+ return false;
+ }
+
+ const uint32_t pkt_ds_len = no_ds_alloc ? 0 : ISCSI_ALIGN( ds_len, ISCSI_ALIGN_SIZE );
+ const uint32_t alloc_len = (uint32_t) ( sizeof(iscsi_bhs_packet) + pkt_ds_len );
+
+ if ( alloc_len > ISCSI_INTERNAL_BUFFER_SIZE ) {
+ pdu->bhs_pkt = pdu->big_alloc = malloc( alloc_len );
+ if ( pdu->bhs_pkt == NULL ) {
+ logadd( LOG_ERROR, "iscsi_pdu_init: Out of memory while allocating iSCSI BHS packet" );
+ return false;
+ }
+ } else {
+ pdu->bhs_pkt = (iscsi_bhs_packet *)pdu->internal_buffer;
+ }
+
+ pdu->ahs_pkt = NULL;
+ pdu->ds_cmd_data = (pkt_ds_len != 0UL)
+ ? (iscsi_scsi_ds_cmd_data *) (((uint8_t *) pdu->bhs_pkt) + sizeof(iscsi_bhs_packet))
+ : NULL;
+ pdu->flags = 0;
+ pdu->bhs_pos = 0U;
+ pdu->ahs_len = 0;
+ pdu->ds_len = ds_len;
+ pdu->ds_write_pos = 0;
+ pdu->cmd_sn = 0UL;
+
+ if ( pkt_ds_len > ds_len ) {
+ memset( (((uint8_t *) pdu->ds_cmd_data) + ds_len), 0, (pkt_ds_len - ds_len) );
+ }
+
+ return true;
+}
+
+/**
+ * @brief Frees resources associated with an iSCSI PDU (Protocol Data Unit).
+ *
+ * This function releases memory allocated for certain members of the iSCSI
+ * PDU structure. It ensures that the allocated resources are properly freed.
+ * If the provided PDU pointer is NULL, the function returns immediately without
+ * performing any operations.
+ *
+ * @param[in] pdu Pointer to the iSCSI PDU structure to be destroyed.
+ * If NULL, the function has no effect.
+ */
+static void iscsi_connection_pdu_destroy(const iscsi_pdu *pdu)
+{
+ if ( pdu == NULL )
+ return;
+ free( pdu->big_alloc );
+}
+
+/**
+ * @brief Appends packet data to an iSCSI PDU structure used by connections.
+ *
+ * This function adjusts the pointers if
+ * the packet data size needs to be
+ * extended.
+ *
+ * @param[in] pdu Pointer to iSCSI PDU where to append
+ * the packet data to. Must NOT be NULL, so
+ * be careful.
+ * @param[in] ahs_len Length of AHS packet data to be appended.
+ * @param[in] ds_len Length of DataSegment packet data to be appended.
+ * May not exceed 16MiB - 1 (16777215 bytes).
+ * @return Pointer to allocated and zero filled PDU or NULL
+ * in case of an error (usually memory exhaustion).
+ */
+static iscsi_bhs_packet *iscsi_connection_pdu_resize(iscsi_pdu *pdu, const uint ahs_len, const uint32_t ds_len)
+{
+ if ( (ahs_len != pdu->ahs_len) || (ds_len != pdu->ds_len) ) {
+ if ( (ahs_len > ISCSI_MAX_AHS_SIZE) || (ds_len > ISCSI_MAX_DS_SIZE) || (ahs_len % ISCSI_ALIGN_SIZE != 0) ) {
+ logadd( LOG_ERROR, "iscsi_connection_pdu_resize: Invalid AHS or DataSegment packet size" );
+ return NULL;
+ }
+ if ( pdu->ds_len != 0 && pdu->ds_cmd_data == NULL ) {
+ // If you really ever need this, handle it properly below (old_len, no copying, etc.)
+ logadd( LOG_ERROR, "iscsi_connection_pdu_resize: Cannot resize PDU with virtual DS" );
+ return NULL;
+ }
+ if ( pdu->ds_len != 0 && pdu->ahs_len != ahs_len && ds_len != 0 ) {
+ // Cannot resize the AHS of a PDU that already has a DS and should keep the DS - we'd need to move the data
+ // around. Implement this when needed (and make sure it works).
+ logadd( LOG_ERROR, "iscsi_connection_pdu_resize: Cannot resize PDU's AHS that also has a DS" );
+ return NULL;
+ }
+
+ iscsi_bhs_packet *bhs_pkt;
+ const uint32_t pkt_ds_len = ISCSI_ALIGN(ds_len, ISCSI_ALIGN_SIZE);
+ const size_t old_len = (sizeof(iscsi_bhs_packet) + (uint32_t) pdu->ahs_len + ISCSI_ALIGN(pdu->ds_len, ISCSI_ALIGN_SIZE));
+ const size_t new_len = (sizeof(iscsi_bhs_packet) + (uint32_t) ahs_len + pkt_ds_len);
+ const bool old_alloced = pdu->big_alloc != NULL;
+ const bool new_alloced = new_len > ISCSI_INTERNAL_BUFFER_SIZE;
+
+ if ( new_len == old_len ) {
+ // Nothing changed
+ bhs_pkt = pdu->bhs_pkt;
+ } else {
+ if ( new_alloced ) {
+ // New block doesn't fit in internal buffer - (re)allocate big buffer
+ bhs_pkt = realloc( pdu->big_alloc, new_len );
+ if ( bhs_pkt == NULL ) {
+ logadd( LOG_ERROR, "iscsi_connection_pdu_resize: Out of memory while reallocating iSCSI PDU packet data" );
+ return NULL;
+ }
+ if ( !old_alloced ) {
+ // Old was in internal buffer, copy contents to the new heap buffer
+ memcpy( bhs_pkt, pdu->internal_buffer, MIN(new_len, old_len) );
+ }
+ // Update PDU's BHS pointer
+ pdu->big_alloc = bhs_pkt;
+ pdu->bhs_pkt = bhs_pkt;
+ } else {
+ // New block fits into internal buffer.
+ // If we are already using big_alloc, we keep it to avoid realloc/free overhead,
+ // as PDUs are short-lived.
+ // Keep using old BHS pointer (which might be big_alloc or internal_buffer)
+ bhs_pkt = pdu->bhs_pkt;
+ }
+ }
+
+ pdu->ahs_pkt = (ahs_len != 0U) ? (iscsi_ahs_packet *) (((uint8_t *) bhs_pkt) + sizeof(iscsi_bhs_packet)) : NULL;
+ pdu->ds_cmd_data = (pkt_ds_len != 0UL) ? (iscsi_scsi_ds_cmd_data *) (((uint8_t *) bhs_pkt) + sizeof(iscsi_bhs_packet) + ahs_len) : NULL;
+ pdu->ahs_len = ahs_len;
+ pdu->ds_len = ds_len;
+
+ if ( pkt_ds_len != 0UL ) {
+ memset( (((uint8_t *) pdu->ds_cmd_data) + ds_len), 0, (pkt_ds_len - ds_len) );
+ }
+ }
+
+ return pdu->bhs_pkt;
+}
+
+/**
+ * @brief Writes and sends a response PDU to the client.
+ *
+ * Sends the provided response PDU over the TCP connection. On send failure,
+ * the connection state is set to ISCSI_CONNECT_STATE_EXITING.
+ *
+ * If a header or data segment is present, its size is rounded up to the iSCSI
+ * alignment before transmission, so the underlying buffer MUST include padding
+ * if neccesary.
+ *
+ * @param[in] conn Pointer to iSCSI connection to handle. Must
+ * NOT be NULL, so take caution.
+ * @param[in] pdu Pointer to iSCSI server response PDU to send.
+ * Must NOT be NULL, so be careful.
+ * @retval true if the entire PDU was sent successfully.
+ * @retval false on failure (connection state will be set to EXITING).
+ */
+static bool iscsi_connection_pdu_write(iscsi_connection *conn, const iscsi_pdu *pdu)
+{
+ if ( conn->state >= ISCSI_CONNECT_STATE_EXITING ) {
+ return false;
+ }
+
+ // During allocation we already round up to ISCSI_ALIGN_SIZE, but store the requested size in the ds_len
+ // member, so it's safe to round up here before sending, the accessed memory will be valid and zeroed
+ const size_t len = (sizeof(iscsi_bhs_packet) + pdu->ahs_len
+ + (pdu->ds_cmd_data == NULL ? 0 : ISCSI_ALIGN(pdu->ds_len, ISCSI_ALIGN_SIZE)));
+ const ssize_t rc = sock_sendAll( conn->client->sock, pdu->bhs_pkt, len, ISCSI_CONNECT_SOCKET_WRITE_RETRIES );
+
+ if ( rc != (ssize_t)len ) {
+ conn->state = ISCSI_CONNECT_STATE_EXITING;
+ return false;
+ }
+ return true;
+}
+
+/**
+ * @brief Compares if the first iSCSI 32-bit sequence numbers is smaller than the second one.
+ *
+ * This function almost does the same as an
+ * unsigned compare but with special
+ * handling for "negative" numbers.
+ *
+ * @param[in] seq_num First iSCSI sequence number to be compared.
+ * @param[in] seq_num_2 Second iSCSI sequence number to be compared.
+ * @retval true if first sequence number is smaller than
+ * the second one.
+ * @retval false if first sequence number is equal or
+ * larger than the second one.
+ */
+static inline int iscsi_seq_num_cmp_lt(const uint32_t seq_num, const uint32_t seq_num_2)
+{
+ return (seq_num != seq_num_2) && (((seq_num < seq_num_2) && ((seq_num_2 - seq_num) < 2147483648UL)) || ((seq_num > seq_num_2) && ((seq_num - seq_num_2)) > 2147483648UL));
+}
+
+/**
+ * @brief Compares if the first iSCSI 32-bit sequence numbers is larger than the second one.
+ *
+ * This function almost does the same as an
+ * unsigned compare but with special
+ * handling for "negative" numbers.
+ *
+ * @param[in] seq_num First iSCSI sequence number to be compared.
+ * @param[in] seq_num_2 Second iSCSI sequence number to be compared.
+ * @retval true if first sequence number is larger than
+ * the second one.
+ * @retval false if first sequence number is equal or
+ * smaller than the second one.
+ */
+static inline int iscsi_seq_num_cmp_gt(const uint32_t seq_num, const uint32_t seq_num_2)
+{
+ return (seq_num != seq_num_2) && (((seq_num < seq_num_2) && ((seq_num_2 - seq_num) > 2147483648UL)) || ((seq_num > seq_num_2) && ((seq_num - seq_num_2)) < 2147483648UL));
+}
+
+/**
+ * @brief Constructs and sends an iSCSI reject response to the client.
+ *
+ * This function constructs an reject response PDU with its
+ * packet data.\n
+ * The original rejected packet data is appended as DataSegment
+ * according by iSCSI standard specification.
+ *
+ * @param[in] conn Pointer to iSCSI connection for reject packet construction.
+ * @param[in] pdu Pointer to iSCSI source PDU which contains the rejected packet data.
+ * @param[in] reason_code Reason code for rejected packet data.
+ * @retval -1 An error ocurred during reject packet generation,
+ * currently only happens on memory exhaustion.
+ * @retval 0 Reject packet and PDU constructed and sent successfully to the client.
+ */
+static int iscsi_connection_handle_reject(iscsi_connection *conn, const iscsi_pdu *pdu, const int reason_code)
+{
+ const uint32_t ds_len = (uint32_t) sizeof(iscsi_bhs_packet) + (uint32_t) (pdu->bhs_pkt->total_ahs_len * ISCSI_ALIGN_SIZE);
+ iscsi_pdu CLEANUP_PDU response_pdu;
+ if ( !iscsi_connection_pdu_init( &response_pdu, ds_len, false ) )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ iscsi_reject_packet *reject_pkt = (iscsi_reject_packet *) response_pdu.bhs_pkt;
+
+ reject_pkt->opcode = ISCSI_OPCODE_SERVER_REJECT;
+ reject_pkt->flags = 0x80;
+ reject_pkt->reason = (uint8_t) reason_code;
+ reject_pkt->reserved = 0U;
+ iscsi_put_be32( (uint8_t *) &reject_pkt->total_ahs_len, ds_len ); // TotalAHSLength is always 0 and DataSegmentLength is 24-bit, so write in one step.
+ reject_pkt->reserved2 = 0ULL;
+ reject_pkt->tag = 0xFFFFFFFFUL; // Minus one does not require endianess conversion
+ reject_pkt->reserved3 = 0UL;
+ iscsi_put_be32( (uint8_t *) &reject_pkt->stat_sn, conn->stat_sn++ );
+
+ if ( conn->state != ISCSI_CONNECT_STATE_NEW ) {
+ iscsi_put_be32( (uint8_t *) &reject_pkt->exp_cmd_sn, conn->exp_cmd_sn );
+ iscsi_put_be32( (uint8_t *) &reject_pkt->max_cmd_sn, conn->max_cmd_sn );
+ } else {
+ iscsi_put_be32( (uint8_t *) &reject_pkt->exp_cmd_sn, 1UL );
+ iscsi_put_be32( (uint8_t *) &reject_pkt->max_cmd_sn, 1UL );
+ }
+
+ reject_pkt->reserved4 = 0ULL;
+
+ memcpy( response_pdu.ds_cmd_data, pdu->bhs_pkt, ds_len );
+
+ iscsi_connection_pdu_write( conn, &response_pdu );
+
+ return ISCSI_CONNECT_PDU_READ_OK;
+}
+
+/**
+ * @brief Updates the expected command sequence number (ExpCmdSN) and validates sequence number bounds.
+ *
+ * This function extracts the CmdSN and checks whether it fits within the session's
+ * expected command sequence range, considering session type and iSCSI operation types.
+ * Also updates session-related sequence numbers as needed based on the received command.
+ *
+ * @param[in] conn Pointer to the iSCSI connection. Must not be NULL, and its session pointer should also be valid.
+ * @param[in] request_pdu Pointer to the iSCSI PDU (Protocol Data Unit) containing command information. Must not be NULL.
+ *
+ * @return Returns `ISCSI_CONNECT_PDU_READ_OK` (0) on success or
+ * `ISCSI_CONNECT_PDU_READ_ERR_FATAL` (-1) if sequence numbers or other data are invalid.
+ */
+static int iscsi_connection_handle_cmd_sn(iscsi_connection *conn, iscsi_pdu *request_pdu)
+{
+ if ( conn->state == ISCSI_CONNECT_STATE_NEW )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ iscsi_scsi_cmd_packet *scsi_cmd_pkt = (iscsi_scsi_cmd_packet *) request_pdu->bhs_pkt;
+ const int opcode = ISCSI_GET_OPCODE(scsi_cmd_pkt->opcode);
+
+ request_pdu->cmd_sn = iscsi_get_be32(scsi_cmd_pkt->cmd_sn);
+
+ if ( (scsi_cmd_pkt->opcode & ISCSI_OPCODE_FLAGS_IMMEDIATE) == 0 ) {
+ if ( (iscsi_seq_num_cmp_lt( request_pdu->cmd_sn, conn->exp_cmd_sn )
+ || iscsi_seq_num_cmp_gt( request_pdu->cmd_sn, conn->max_cmd_sn ))
+ && ((conn->state == ISCSI_CONNECT_STATE_NORMAL_SESSION) && (opcode != ISCSI_OPCODE_CLIENT_SCSI_DATA_OUT)) ) {
+ logadd( LOG_WARNING, "Seqnum messup. Is: %u, want >= %u, < %u",
+ request_pdu->cmd_sn, conn->exp_cmd_sn, conn->max_cmd_sn );
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+ }
+ } else if ( (request_pdu->cmd_sn != conn->exp_cmd_sn) && (opcode != ISCSI_OPCODE_CLIENT_NOP_OUT) ) {
+ logadd( LOG_WARNING, "Seqnum messup. Is: %u, want: %u",
+ request_pdu->cmd_sn, conn->exp_cmd_sn );
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+ }
+
+ if ( ((scsi_cmd_pkt->opcode & ISCSI_OPCODE_FLAGS_IMMEDIATE) == 0) && (opcode != ISCSI_OPCODE_CLIENT_SCSI_DATA_OUT) ) {
+ conn->exp_cmd_sn++;
+ }
+
+ return ISCSI_CONNECT_PDU_READ_OK;
+}
+
+/**
+ * @brief Handles an incoming iSCSI header logout request PDU.
+ *
+ * This function handles logout request header
+ * data sent by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn Pointer to iSCSI connection to handle. Must
+ * NOT be NULL, so take caution.
+ * @param[in] request_pdu Pointer to iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_handle_logout_req(iscsi_connection *conn, const iscsi_pdu *request_pdu)
+{
+ iscsi_logout_req_packet *logout_req_pkt = (iscsi_logout_req_packet *) request_pdu->bhs_pkt;
+
+ if ( (conn->state == ISCSI_CONNECT_STATE_NEW)
+ || ((logout_req_pkt->reason_code & ISCSI_LOGOUT_REQ_REASON_CODE_MASK) != ISCSI_LOGOUT_REQ_REASON_CODE_CLOSE_SESSION) ) {
+ logadd( LOG_DEBUG1, "Invalid logout request in state %d, reason_code %d", conn->state, logout_req_pkt->reason_code );
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+ }
+
+ iscsi_pdu CLEANUP_PDU response_pdu;
+ if ( !iscsi_connection_pdu_init( &response_pdu, 0, false ) )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ iscsi_logout_response_packet *logout_response_pkt = (iscsi_logout_response_packet *) response_pdu.bhs_pkt;
+
+ logout_response_pkt->opcode = ISCSI_OPCODE_SERVER_LOGOUT_RES;
+ logout_response_pkt->flags = 0x80;
+
+ const uint16_t cid = iscsi_get_be16(logout_req_pkt->cid);
+
+ if ( cid == conn->cid ) {
+ logout_response_pkt->response = ISCSI_LOGOUT_RESPONSE_CLOSED_SUCCESSFULLY;
+ } else {
+ logout_response_pkt->response = ISCSI_LOGOUT_RESPONSE_CID_NOT_FOUND;
+ }
+
+ logout_response_pkt->reserved = 0U;
+ *(uint32_t *) &logout_response_pkt->total_ahs_len = 0UL; // TotalAHSLength and DataSegmentLength are always 0, so write in one step.
+ logout_response_pkt->reserved2 = 0ULL;
+ logout_response_pkt->init_task_tag = logout_req_pkt->init_task_tag; // Copying over doesn't change endianess.
+ logout_response_pkt->reserved3 = 0UL;
+ iscsi_put_be32( (uint8_t *) &logout_response_pkt->stat_sn, conn->stat_sn++ );
+
+ if ( conn->state != ISCSI_CONNECT_STATE_NEW ) {
+ conn->max_cmd_sn++;
+
+ iscsi_put_be32( (uint8_t *) &logout_response_pkt->exp_cmd_sn, conn->exp_cmd_sn );
+ iscsi_put_be32( (uint8_t *) &logout_response_pkt->max_cmd_sn, conn->max_cmd_sn );
+ } else {
+ iscsi_put_be32( (uint8_t *) &logout_response_pkt->exp_cmd_sn, request_pdu->cmd_sn );
+ iscsi_put_be32( (uint8_t *) &logout_response_pkt->max_cmd_sn, request_pdu->cmd_sn );
+ }
+
+ logout_response_pkt->reserved4 = 0UL;
+ logout_response_pkt->time_wait = 0U;
+ logout_response_pkt->time_retain = 0U;
+ logout_response_pkt->reserved5 = 0UL;
+
+ bool ret = iscsi_connection_pdu_write( conn, &response_pdu );
+
+ if ( cid == conn->cid ) {
+ conn->state = ISCSI_CONNECT_STATE_EXITING;
+ }
+
+ return ret ? ISCSI_CONNECT_PDU_READ_OK : ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+}
+
+/**
+ * @brief Handles an iSCSI task management function request and generates an appropriate response.
+ *
+ * This function processes an incoming iSCSI task management function request PDU,
+ * constructs a corresponding response PDU, and sends it back to the initiator.
+ *
+ * @param[in] conn Pointer to the iSCSI connection structure. Must not be NULL.
+ * This represents the connection for which the request is being handled.
+ * @param[in] request_pdu Pointer to the incoming iSCSI task management function
+ * request PDU. Must not be NULL.
+ *
+ * @return 0 on successful PDU write, or -1 on failure.
+ */
+static int iscsi_connection_handle_task_func_req(iscsi_connection *conn, const iscsi_pdu *request_pdu)
+{
+ iscsi_pdu CLEANUP_PDU response_pdu;
+ if ( !iscsi_connection_pdu_init( &response_pdu, 0, false ) )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+ iscsi_task_mgmt_func_response_packet *mgmt_resp = (iscsi_task_mgmt_func_response_packet *) response_pdu.bhs_pkt;
+ iscsi_task_mgmt_func_req_packet *mgmt_req = (iscsi_task_mgmt_func_req_packet *) request_pdu->bhs_pkt;
+
+ mgmt_resp->opcode = ISCSI_OPCODE_SERVER_TASK_FUNC_RES;
+ mgmt_resp->response = ISCSI_TASK_MGMT_FUNC_RESPONSE_FUNC_COMPLETE;
+ mgmt_resp->flags = 0x80;
+ mgmt_resp->init_task_tag = mgmt_req->init_task_tag; // Copying over doesn't change endianess.
+ iscsi_put_be32( (uint8_t *) &mgmt_resp->stat_sn, conn->stat_sn++ );
+ iscsi_put_be32( (uint8_t *) &mgmt_resp->exp_cmd_sn, conn->exp_cmd_sn );
+ iscsi_put_be32( (uint8_t *) &mgmt_resp->max_cmd_sn, conn->max_cmd_sn );
+
+ return iscsi_connection_pdu_write( conn, &response_pdu ) ? 0 : -1;
+}
+
+/**
+ * @brief Handles receiving and sending of NOP in/out packets.
+ *
+ * This method can handle a received NOP-Out request and send
+ * an according NOP-In response if applicable, i.e. the NOP-Out
+ * wasn't sent as a reply to a NOP-In by us.\n
+ * This method can also send an unsolicited NOP-In to the client
+ * if we want to check whether the connection is still good.
+ *
+ * @param[in] conn Pointer to iSCSI connection to handle. Must
+ * NOT be NULL, so take caution.
+ * @param[in] request_pdu Pointer to iSCSI client request PDU to handle,
+ * or NULL for sending a connection alive check.
+ * @return 0 on success. A negative value indicates
+ * an error.
+ */
+static int iscsi_connection_handle_nop(iscsi_connection *conn, const iscsi_pdu *request_pdu)
+{
+ if ( conn->state != ISCSI_CONNECT_STATE_NORMAL_SESSION )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ if ( request_pdu != NULL && request_pdu->ds_len > ISCSI_DEFAULT_MAX_RECV_DS_LEN )
+ return iscsi_connection_handle_reject( conn, request_pdu, ISCSI_REJECT_REASON_PROTOCOL_ERR );
+
+ iscsi_nop_out_packet *nop_out_pkt = request_pdu == NULL ? NULL : (iscsi_nop_out_packet *) request_pdu->bhs_pkt;
+ const uint32_t target_xfer_tag = nop_out_pkt == NULL ? 0xFFFFFFFFUL : iscsi_get_be32(nop_out_pkt->target_xfer_tag);
+ const uint32_t init_task_tag = nop_out_pkt == NULL ? 0 : iscsi_get_be32(nop_out_pkt->init_task_tag);
+ uint32_t ds_len = request_pdu == NULL ? 0 : request_pdu->ds_len;
+ const uint64_t lun = nop_out_pkt == NULL ? 0 : iscsi_get_be64(nop_out_pkt->lun);
+
+ if ( init_task_tag == 0xFFFFFFFFUL ) // Was response to a NOP by us, or no response desired - do not reply
+ return ISCSI_CONNECT_PDU_READ_OK;
+
+ if ( target_xfer_tag != 0xFFFFFFFFUL ) // If the initiator tag is not the special value, the target tag has to be.
+ return iscsi_connection_handle_reject( conn, request_pdu, ISCSI_REJECT_REASON_INVALID_PDU_FIELD );
+
+ if ( ds_len > (uint32_t)conn->opts.MaxRecvDataSegmentLength ) {
+ ds_len = conn->opts.MaxRecvDataSegmentLength;
+ }
+
+ iscsi_pdu CLEANUP_PDU response_pdu;
+ if ( !iscsi_connection_pdu_init( &response_pdu, ds_len, false ) )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ iscsi_nop_in_packet *nop_in_pkt = (iscsi_nop_in_packet *) response_pdu.bhs_pkt;
+
+ nop_in_pkt->opcode = ISCSI_OPCODE_SERVER_NOP_IN;
+ nop_in_pkt->flags = 0x80;
+ nop_in_pkt->reserved = 0U;
+ iscsi_put_be32( (uint8_t *) &nop_in_pkt->total_ahs_len, ds_len ); // TotalAHSLength is always 0 and DataSegmentLength is 24-bit, so write in one step.
+ iscsi_put_be64( (uint8_t *) &nop_in_pkt->lun, lun );
+ if ( nop_out_pkt == NULL ) {
+ // Send a request which needs a reply, set target tag to anything but the special value
+ nop_in_pkt->init_task_tag = 0xFFFFFFFFUL;
+ nop_in_pkt->target_xfer_tag = 0;
+ iscsi_put_be32( (uint8_t *) &nop_in_pkt->stat_sn, conn->stat_sn ); // Don't inc
+ } else {
+ // This is a reply, set target tag to special value to indicate we don't want a NOP-Out in response
+ iscsi_put_be32( (uint8_t *) &nop_in_pkt->init_task_tag, init_task_tag );
+ nop_in_pkt->target_xfer_tag = 0xFFFFFFFFUL;
+ iscsi_put_be32( (uint8_t *) &nop_in_pkt->stat_sn, conn->stat_sn++ ); // Inc
+ }
+
+ if ( nop_out_pkt == NULL || (nop_out_pkt->opcode & ISCSI_OPCODE_FLAGS_IMMEDIATE) == 0 ) {
+ conn->max_cmd_sn++;
+ }
+
+ iscsi_put_be32( (uint8_t *) &nop_in_pkt->exp_cmd_sn, conn->exp_cmd_sn );
+ iscsi_put_be32( (uint8_t *) &nop_in_pkt->max_cmd_sn, conn->max_cmd_sn );
+ nop_in_pkt->reserved2 = 0UL;
+ nop_in_pkt->reserved3 = 0ULL;
+
+ if ( ds_len != 0UL ) {
+ memcpy( response_pdu.ds_cmd_data, request_pdu->ds_cmd_data, ds_len );
+ }
+
+ return iscsi_connection_pdu_write( conn, &response_pdu ) ? 0 : -1;
+}
+
+/**
+ * @brief Handles an incoming iSCSI payload data SCSI command request PDU.
+ *
+ * This function handles SCSI command request payload
+ * data sent by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn Pointer to iSCSI connection to handle. Must
+ * NOT be NULL, so take caution.
+ * @param[in] request_pdu Pointer to iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_handle_scsi_cmd(iscsi_connection *conn, const iscsi_pdu *request_pdu)
+{
+ bool handled = false;
+ iscsi_scsi_cmd_packet *scsi_cmd_pkt = (iscsi_scsi_cmd_packet *) request_pdu->bhs_pkt;
+ uint32_t exp_xfer_len = iscsi_get_be32(scsi_cmd_pkt->exp_xfer_len);
+ iscsi_task task = {
+ .lun_id = iscsi_scsi_lun_get_from_iscsi( iscsi_get_be64(scsi_cmd_pkt->lun) ),
+ .init_task_tag = iscsi_get_be32(scsi_cmd_pkt->init_task_tag),
+
+ .scsi_task.cdb = &scsi_cmd_pkt->scsi_cdb,
+ .scsi_task.exp_xfer_len = exp_xfer_len,
+ .scsi_task.status = ISCSI_SCSI_STATUS_GOOD,
+ .scsi_task.connection = conn,
+ };
+
+ // Per iSCSI, READ/WRITE bits in flags_task indicate data direction for this CDB
+ if ( (scsi_cmd_pkt->flags_task & ISCSI_SCSI_CMD_FLAGS_TASK_READ) != 0 ) {
+ task.scsi_task.is_read = true;
+ } else {
+ if ( exp_xfer_len != 0UL ) {
+ // Not a read request, but expecting data - not valid
+ iscsi_scsi_task_status_set( &task.scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ,
+ ISCSI_SCSI_ASC_INVALID_FIELD_IN_CDB, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+ handled = true;
+ }
+ }
+
+ if ( !handled ) {
+ task.scsi_task.is_write = (scsi_cmd_pkt->flags_task & ISCSI_SCSI_CMD_FLAGS_TASK_WRITE) != 0;
+
+ // Single-LUN target for now, reject unknown LUNs with ILLEGAL REQUEST
+ if ( task.lun_id != ISCSI_DEFAULT_LUN ) {
+ logadd( LOG_WARNING, "Received SCSI command for unknown LUN %d", task.lun_id );
+ iscsi_scsi_task_status_set( &task.scsi_task, ISCSI_SCSI_STATUS_CHECK_COND, ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ,
+ ISCSI_SCSI_ASC_LU_NOT_SUPPORTED, ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+ handled = true;
+ } else {
+ task.scsi_task.status = ISCSI_SCSI_STATUS_GOOD;
+
+ // Try block commands first (READ/WRITE family), then primary (INQUIRY, MODE SENSE, etc.)
+ handled = iscsi_scsi_emu_block_process( &task.scsi_task ) || iscsi_scsi_emu_primary_process( &task.scsi_task );
+
+ if ( !handled ) {
+ iscsi_scsi_task_status_set( &task.scsi_task, ISCSI_SCSI_STATUS_CHECK_COND,
+ ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ, ISCSI_SCSI_ASC_INVALID_COMMAND_OPERATION_CODE,
+ ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE );
+ }
+ }
+ }
+
+ iscsi_scsi_task_send_reply( conn, &task.scsi_task, request_pdu );
+ // Free any buffers that were allocated for this task
+ free( task.scsi_task.buf );
+ free( task.scsi_task.sense_data );
+
+ return ISCSI_CONNECT_PDU_READ_OK;
+}
+
+/**
+ * @brief Handles iSCSI connection login phase none.
+ *
+ * This function negotiates the login phase
+ * without a session.
+ *
+ * @param[in] conn Pointer to iSCSI connection,
+ * must NOT be NULL, so be careful.
+ * @param[in] login_response_pdu Pointer to login response PDU.
+ * NULL is not allowed here, so take caution.
+ * @param[in] kvpairs Pointer to key and value pairs.
+ * which must NOT be NULL, so take caution.
+ * @return 0 on success, a negative error code otherwise.
+ */
+static int iscsi_connection_handle_login_phase_none(iscsi_connection *conn, const iscsi_pdu *login_response_pdu, const iscsi_negotiation_kvp *kvpairs)
+{
+ int type;
+ iscsi_login_response_packet *login_response_pkt = (iscsi_login_response_packet *) login_response_pdu->bhs_pkt;
+ int rc = iscsi_login_parse_session_type( login_response_pdu, kvpairs->SessionType, &type );
+
+ if ( rc < 0 )
+ return rc;
+
+ if ( type != ISCSI_CONNECT_STATE_NORMAL_SESSION ) {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_SESSION_NO_SUPPORT;
+ rc = ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+ } else if ( kvpairs->TargetName != NULL ) {
+ rc = iscsi_image_from_target( conn, login_response_pdu, kvpairs->TargetName );
+ } else {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISSING_PARAMETER;
+ rc = ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+ }
+
+ if ( rc < 0 )
+ return rc;
+
+ if ( conn->state == ISCSI_CONNECT_STATE_NEW ) {
+ conn->stat_sn = iscsi_get_be32(login_response_pkt->stat_sn);
+
+ conn->exp_cmd_sn = login_response_pdu->cmd_sn;
+ conn->max_cmd_sn = (uint32_t) (login_response_pdu->cmd_sn + ISCSI_DEFAULT_QUEUE_DEPTH - 1UL);
+ }
+
+ return ISCSI_CONNECT_PDU_READ_OK;
+}
+
+/**
+ * @brief Writes login options to a PDU (Protocol Data Unit).
+ *
+ * This function processes key-value pairs of login negotiation options and
+ * appends them to the specified PDU. The function ensures the payload of the
+ * response PDU does not exceed its designated length.
+ *
+ * @param[in] conn Pointer to the iSCSI connection structure containing session
+ * options and other connection-specific information.
+ * @param[in] pairs Pointer to the iSCSI negotiation key-value pairs structure
+ * that holds applicable key-value options for the login phase.
+ * @param[in,out] response_pdu Pointer to the PDU where the login options should
+ * be added. The PDU's fields, such as data segment and payload length, are
+ * updated within the function.
+ *
+ * @return The updated payload length of the response PDU if successful.
+ * Returns -1 if an error occurs during key-value pair appending.
+ */
+static int iscsi_write_login_options_to_pdu(const iscsi_connection *conn, const iscsi_negotiation_kvp *pairs, iscsi_pdu *response_pdu)
+{
+ uint payload_len = response_pdu->ds_write_pos;
+
+# define ADD_KV_INTERNAL(num, key, value) do { \
+int rc = iscsi_append_key_value_pair_packet( num, key, value, (char *)response_pdu->ds_cmd_data, payload_len, response_pdu->ds_len ); \
+if ( rc < 0 ) return -1; \
+payload_len += rc; \
+} while (0)
+# define ADD_KV_OPTION_INT(key) do { \
+if ( pairs->key != -1 ) ADD_KV_INTERNAL( true, #key, (const char *)(size_t)conn->opts.key ); \
+} while (0)
+# define ADD_KV_OPTION_STR(key) do { \
+if ( pairs->key != NULL ) ADD_KV_INTERNAL( false, #key, conn->opts.key ); \
+} while (0)
+# define ADD_KV_PLAIN_INT(key, value) do { \
+if ( pairs->key != -1 ) ADD_KV_INTERNAL( true, #key, (const char *)(size_t)(value) ); \
+} while (0)
+# define ADD_KV_PLAIN_STR(key, value) do { \
+if ( pairs->key != NULL ) ADD_KV_INTERNAL( false, #key, value ); \
+} while (0)
+ // Reply with these settings with actually negotiated values
+ ADD_KV_OPTION_INT( MaxRecvDataSegmentLength );
+ ADD_KV_OPTION_INT( MaxBurstLength );
+ ADD_KV_OPTION_INT( FirstBurstLength );
+ // These are always hard-coded to specific value, we don't support anything else
+ ADD_KV_PLAIN_INT( MaxConnections, 1 );
+ ADD_KV_PLAIN_INT( ErrorRecoveryLevel, 0 );
+ ADD_KV_PLAIN_STR( HeaderDigest, "None" );
+ ADD_KV_PLAIN_STR( DataDigest, "None" );
+# undef ADD_KV_PLAIN
+# undef ADD_KV_OPTION_INT
+# undef ADD_KV_OPTION_STR
+
+ if ( payload_len <= response_pdu->ds_len ) {
+ response_pdu->ds_write_pos = payload_len;
+ } else {
+ response_pdu->ds_write_pos = response_pdu->ds_len;
+ }
+ return (int)payload_len;
+}
+
+/**
+ * @brief Handles iSCSI connection login response.
+ *
+ * This function negotiates the login parameters
+ * and determines the authentication method.
+ *
+ * @param[in] conn Pointer to iSCSI connection,
+ * must NOT be NULL, so be careful.
+ * @param[in] login_response_pdu Pointer to login response PDU.
+ * NULL is not allowed here, so take caution.
+ * @param[in] pairs Readily parsed key-value-pairs from according request
+ * @return 0 on success, a negative error code otherwise.
+ */
+static int iscsi_connection_handle_login_response(iscsi_connection *conn, iscsi_pdu *login_response_pdu, const iscsi_negotiation_kvp *pairs)
+{
+ if ( iscsi_connection_pdu_resize( login_response_pdu, 0, ISCSI_DEFAULT_RECV_DS_LEN ) == NULL ) {
+ return ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+ }
+ iscsi_login_response_packet *login_response_pkt = (iscsi_login_response_packet *) login_response_pdu->bhs_pkt;
+
+ // Handle current stage (CSG bits)
+ switch ( ISCSI_LOGIN_RESPONSE_FLAGS_GET_CURRENT_STAGE(login_response_pkt->flags) ) {
+ case ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_SECURITY_NEGOTIATION : {
+ logadd( LOG_DEBUG1, "security nego" );
+ if ( pairs->AuthMethod == NULL || strcasecmp( pairs->AuthMethod, "None" ) != 0 ) {
+ // Only "None" supported
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_AUTH_ERR;
+
+ return ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+ }
+
+ break;
+ }
+ case ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION : {
+ // Nothing to do, expect client to request transition to full feature phase
+ break;
+ }
+ case ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FULL_FEATURE_PHASE :
+ default : {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISC;
+
+ return ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+ }
+ }
+
+ if ( (login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_TRANSIT) != 0 ) {
+ // Client set the transition bit - requests to move on to next stage
+ switch ( ISCSI_LOGIN_RESPONSE_FLAGS_GET_NEXT_STAGE(login_response_pkt->flags) ) {
+ case ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FULL_FEATURE_PHASE : {
+
+ iscsi_put_be16( (uint8_t *) &login_response_pkt->tsih, 42 );
+
+ conn->state = ISCSI_CONNECT_STATE_NORMAL_SESSION;
+
+ iscsi_connection_update_key_value_pairs( conn, pairs );
+ int payload_len = iscsi_write_login_options_to_pdu( conn, pairs, login_response_pdu );
+
+ if ( payload_len < 0 || (uint32_t)payload_len > login_response_pdu->ds_len ) {
+ logadd( LOG_DEBUG1, "iscsi_connecction_handle_login_response: Invalid payload length %d, ds_len: %u, write_pos: %u",
+ payload_len, login_response_pdu->ds_len, login_response_pdu->ds_write_pos );
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SERVER_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SERVER_ERR_OUT_OF_RESOURCES;
+
+ return ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+ }
+ char tname[50];
+ snprintf( tname, sizeof(tname), "i%s", conn->client->hostName );
+ setThreadName( tname );
+
+ break;
+ }
+ default : {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISC;
+
+ return ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+ }
+ }
+ }
+
+ return ISCSI_CONNECT_PDU_READ_OK;
+}
+
+/**
+ * @brief Handles an incoming iSCSI login request PDU.
+ *
+ * Parses the login request, builds a corresponding login response PDU and
+ * sends it. Performs basic validation (e.g., MaxRecvDataSegmentLength and
+ * login phase/state). On certain errors, a reject or error response is sent.
+ *
+ * @param[in] conn Pointer to iSCSI connection to handle. Must
+ * NOT be NULL, so take caution.
+ * @param[in] request_pdu Pointer to iSCSI client request PDU to handle.
+ * Must NOT be NULL.
+ * @return ISCSI_CONNECT_PDU_READ_OK on success; a negative
+ * ISCSI_CONNECT_PDU_READ_ERR_* code on error.
+ */
+static int iscsi_connection_handle_login_req(iscsi_connection *conn, iscsi_pdu *request_pdu)
+{
+ // Reject malformed login PDUs:
+ // - DataSegmentLength must fit our initial receive buffer (we don't support multi-PDU login here)
+ // - Login is only valid in NEW state (before full feature phase)
+ if ( request_pdu->ds_len > ISCSI_DEFAULT_RECV_DS_LEN || conn->state != ISCSI_CONNECT_STATE_NEW )
+ return iscsi_connection_handle_reject( conn, request_pdu, ISCSI_REJECT_REASON_PROTOCOL_ERR );
+
+ const iscsi_login_req_packet *login_req_pkt = (iscsi_login_req_packet *) request_pdu->bhs_pkt;
+
+ request_pdu->cmd_sn = iscsi_get_be32(login_req_pkt->cmd_sn);
+
+ // Prepare a response PDU; helper will size DS as needed later
+ iscsi_pdu CLEANUP_PDU login_response_pdu;
+ if ( !iscsi_connection_pdu_init( &login_response_pdu, 0, false ) )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ int rc = iscsi_connection_pdu_login_response_init( &login_response_pdu, request_pdu );
+
+ if ( rc < 0 ) {
+ // response_init already encoded an error in the response PDU - send and bail
+ return iscsi_send_login_response_pdu( conn, &login_response_pdu );
+ }
+
+ iscsi_negotiation_kvp pairs;
+ iscsi_login_response_packet *login_response_pkt = (iscsi_login_response_packet *) login_response_pdu.bhs_pkt;
+ // Parse key=value pairs from the login text payload
+ rc = iscsi_parse_login_key_value_pairs( &pairs, (uint8_t *) request_pdu->ds_cmd_data, request_pdu->ds_len );
+
+ if ( rc < 0 ) {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_AUTH_ERR;
+
+ return iscsi_send_login_response_pdu( conn, &login_response_pdu );
+ }
+
+ // Handle security/operational negotiation for this stage
+ rc = iscsi_connection_handle_login_phase_none( conn, &login_response_pdu, &pairs );
+
+ if ( rc != ISCSI_CONNECT_PDU_READ_OK ) {
+ return iscsi_send_login_response_pdu( conn, &login_response_pdu );
+ }
+
+ // Possibly transition to next stage depending on flags
+ iscsi_connection_handle_login_response( conn, &login_response_pdu, &pairs );
+ if ( conn->state == ISCSI_CONNECT_STATE_NORMAL_SESSION ) {
+ // Record ConnectionID from request once we enter full feature phase
+ conn->cid = iscsi_get_be16(login_req_pkt->cid);
+ }
+ return iscsi_send_login_response_pdu( conn, &login_response_pdu );
+}
+
+/**
+ * @brief Handles an incoming iSCSI text request PDU.
+ *
+ * Parses the key-value pairs in the text request and responds with a text
+ * response PDU containing the negotiated values. Multi-PDU continuation of
+ * text requests is not supported.
+ *
+ * @param[in] conn Pointer to iSCSI connection to handle. Must NOT be NULL.
+ * @param[in] request_pdu Pointer to iSCSI client request PDU to handle.
+ * Must NOT be NULL.
+ * @return ISCSI_CONNECT_PDU_READ_OK on success; a negative
+ * ISCSI_CONNECT_PDU_READ_ERR_* code on error or when a reject is sent.
+ */
+static int iscsi_connection_handle_text_req(iscsi_connection *conn, const iscsi_pdu *request_pdu)
+{
+ iscsi_text_req_packet *text_req_pkt = (iscsi_text_req_packet *) request_pdu->bhs_pkt;
+
+ if ( request_pdu->ds_len > ISCSI_MAX_DS_SIZE )
+ return iscsi_connection_handle_reject( conn, request_pdu, ISCSI_REJECT_REASON_PROTOCOL_ERR );
+
+ if ( (text_req_pkt->flags & (ISCSI_TEXT_REQ_FLAGS_CONTINUE | ISCSI_TEXT_REQ_FLAGS_FINAL))
+ == (ISCSI_TEXT_REQ_FLAGS_CONTINUE | ISCSI_TEXT_REQ_FLAGS_FINAL) ) {
+ // Continue and Final at the same time is invalid
+ return iscsi_connection_handle_reject( conn, request_pdu, ISCSI_REJECT_REASON_PROTOCOL_ERR );
+ }
+ if ( (text_req_pkt->flags & ISCSI_TEXT_REQ_FLAGS_FINAL) == 0 ) {
+ // Text request spread across multiple PDUs not supported
+ return iscsi_connection_handle_reject( conn, request_pdu, ISCSI_REJECT_REASON_COMMAND_NOT_SUPPORTED );
+ }
+ if ( text_req_pkt->target_xfer_tag != 0xFFFFFFFFUL ) {
+ // Initial request must have this set to all 1
+ return iscsi_connection_handle_reject( conn, request_pdu, ISCSI_REJECT_REASON_PROTOCOL_ERR );
+ }
+
+ const uint32_t exp_stat_sn = iscsi_get_be32(text_req_pkt->exp_stat_sn);
+ if ( exp_stat_sn != conn->stat_sn ) {
+ conn->stat_sn = exp_stat_sn;
+ }
+
+ iscsi_negotiation_kvp pairs;
+ int rc = iscsi_parse_login_key_value_pairs( &pairs, (uint8_t *) request_pdu->ds_cmd_data, request_pdu->ds_len );
+
+ if ( rc < 0 ) {
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+ }
+
+ iscsi_pdu CLEANUP_PDU response_pdu;
+ if ( !iscsi_connection_pdu_init( &response_pdu, MIN( 8192, conn->opts.MaxRecvDataSegmentLength ), false ) )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ iscsi_connection_update_key_value_pairs( conn, &pairs );
+
+ // TODO: Handle SendTargets
+ int payload_len = iscsi_write_login_options_to_pdu( conn, &pairs, &response_pdu );
+
+ if ( payload_len < 0 || (uint32_t)payload_len > response_pdu.ds_len ) {
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+ }
+
+ iscsi_text_response_packet *text_response_pkt =
+ (iscsi_text_response_packet *) iscsi_connection_pdu_resize( &response_pdu, 0, response_pdu.ds_write_pos );
+
+ text_response_pkt->opcode = ISCSI_OPCODE_SERVER_TEXT_RES;
+ text_response_pkt->flags = (int8_t) ISCSI_TEXT_RESPONSE_FLAGS_FINAL;
+
+ text_response_pkt->reserved = 0;
+
+ // TotalAHSLength is always 0 and DataSegmentLength is 24-bit, so write in one step.
+ iscsi_put_be32( (uint8_t *) &text_response_pkt->total_ahs_len, response_pdu.ds_write_pos );
+ text_response_pkt->lun = text_req_pkt->lun; // Copying over doesn't change endianess.
+ text_response_pkt->init_task_tag = text_req_pkt->init_task_tag; // Copying over doesn't change endianess.
+ text_response_pkt->target_xfer_tag = 0xFFFFFFFFUL; // Minus one does not require endianess conversion
+
+ iscsi_put_be32( (uint8_t *) &text_response_pkt->stat_sn, conn->stat_sn++ );
+
+ conn->max_cmd_sn++;
+
+ iscsi_put_be32( (uint8_t *) &text_response_pkt->exp_cmd_sn, conn->exp_cmd_sn );
+ iscsi_put_be32( (uint8_t *) &text_response_pkt->max_cmd_sn, conn->max_cmd_sn );
+ text_response_pkt->reserved2[0] = 0ULL;
+ text_response_pkt->reserved2[1] = 0ULL;
+
+ return iscsi_connection_pdu_write( conn, &response_pdu ) ? ISCSI_CONNECT_PDU_READ_OK : ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+}
+
+/**
+ * @brief Dispatches and handles a single incoming iSCSI request PDU.
+ *
+ * Parses the opcode from the request PDU and invokes the corresponding
+ * handler. On protocol errors, a REJECT may be sent. Fatal errors set the
+ * connection to exiting state via lower-level helpers.
+ *
+ * @param[in] conn Pointer to iSCSI connection to handle. Must NOT be NULL.
+ * @param[in] request_pdu Pointer to iSCSI client request PDU to handle.
+ * Must NOT be NULL.
+ * @return ISCSI_CONNECT_PDU_READ_OK on success; a negative
+ * ISCSI_CONNECT_PDU_READ_ERR_* code on error.
+ */
+static int iscsi_connection_pdu_handle(iscsi_connection *conn, iscsi_pdu *request_pdu)
+{
+ int rc = 0;
+
+ const uint8_t opcode = ISCSI_GET_OPCODE(request_pdu->bhs_pkt->opcode);
+
+ if ( conn->state == ISCSI_CONNECT_STATE_NEW ) {
+ // Fresh connection, not logged in yet - per RFC7143 only LOGIN PDUs are valid here.
+ if ( opcode == ISCSI_OPCODE_CLIENT_LOGIN_REQ ) {
+ rc = iscsi_connection_handle_login_req( conn, request_pdu );
+ } else {
+ rc = iscsi_connection_handle_reject( conn, request_pdu, ISCSI_REJECT_REASON_PROTOCOL_ERR );
+ }
+ } else if ( conn->state == ISCSI_CONNECT_STATE_EXITING ) {
+ // Already transitioning to close: ignore further work but report OK so caller can unwind.
+ rc = ISCSI_CONNECT_PDU_READ_OK;
+ } else if ( conn->state == ISCSI_CONNECT_STATE_NORMAL_SESSION ) {
+ // Normal full-feature phase operation.
+ // First validate/advance CmdSN window semantics (ExpCmdSN/MaxCmdSN handling).
+ rc = iscsi_connection_handle_cmd_sn( conn, request_pdu );
+ if ( rc != 0 )
+ return rc;
+
+ switch ( opcode ) {
+ case ISCSI_OPCODE_CLIENT_NOP_OUT : {
+ // Keep-alive ping from initiator or response to our NOP-In
+ rc = iscsi_connection_handle_nop( conn, request_pdu );
+
+ break;
+ }
+ case ISCSI_OPCODE_CLIENT_SCSI_CMD : {
+ // SCSI CDB request - may entail data-in or data-out depending on flags
+ rc = iscsi_connection_handle_scsi_cmd( conn, request_pdu );
+
+ break;
+ }
+ case ISCSI_OPCODE_CLIENT_TEXT_REQ : {
+ // Text negotiation/SendTargets style key=value exchange
+ rc = iscsi_connection_handle_text_req( conn, request_pdu );
+
+ break;
+ }
+ case ISCSI_OPCODE_CLIENT_LOGOUT_REQ : {
+ // Session/connection logout (transition to exiting handled in callee)
+ rc = iscsi_connection_handle_logout_req( conn, request_pdu );
+
+ break;
+ }
+ case ISCSI_OPCODE_CLIENT_TASK_FUNC_REQ : {
+ // Task management functions (ABORT TASK, CLEAR TASK SET, etc.)
+ rc = iscsi_connection_handle_task_func_req( conn, request_pdu );
+
+ break;
+ }
+ default : {
+ // Unknown/unsupported opcode - protocol error
+ rc = iscsi_connection_handle_reject( conn, request_pdu, ISCSI_REJECT_REASON_PROTOCOL_ERR );
+
+ break;
+ }
+ }
+ }
+
+ if ( rc < 0 ) {
+ logadd( LOG_ERROR, "Fatal error during payload handler (opcode 0x%02x) detected for client %s", (int) opcode, conn->client->hostName );
+ }
+
+ return rc;
+}
+
+/**
+ * @brief Reads and processes incoming iSCSI connection PDUs in a loop.
+ *
+ * This function continuously reads Protocol Data Units (PDUs) on an iSCSI
+ * connection and performs operations based on the type and content of the
+ * received data. The function processes Basic Header Segment (BHS), Additional
+ * Header Segment (AHS), and Data Segment (DS) as part of the PDU handling. If
+ * any errors occur during the process, the function gracefully exits the loop.
+ *
+ * @param[in] conn Pointer to the iSCSI connection object. Must not be NULL and
+ * contains the state and data required for processing the iSCSI connection.
+ * @param[in] request Pointer to the initial received data for the PDU. This
+ * serves as the partially received data of the BHS. Must not be NULL.
+ * @param[in] len Length of the already received portion of the BHS in bytes.
+ */
+static void iscsi_connection_pdu_read_loop(iscsi_connection *conn, const dnbd3_request_t *request, const int len)
+{
+ ssize_t ret;
+ iscsi_pdu CLEANUP_PDU request_pdu;
+
+ if ( !iscsi_connection_pdu_init( &request_pdu, 0, false ) )
+ return;
+
+ // 1) Receive BHS (partially already received, in "request", merge and finish)
+ memcpy( request_pdu.bhs_pkt, request, len );
+ if ( (size_t)sock_recv( conn->client->sock, ((uint8_t *)request_pdu.bhs_pkt) + len, sizeof(iscsi_bhs_packet) - len )
+ != sizeof(iscsi_bhs_packet) - len ) {
+ logadd( LOG_INFO, "Cannot receive first BHS from client %s", conn->client->hostName );
+ return;
+ }
+
+ do {
+ // 2) Evaluate BHS regarding length of AHS and DS
+ // total_ahs_len is encoded in 4-byte units per RFC; ds_len is 24-bit big-endian.
+ iscsi_bhs_packet *bhs_pkt = request_pdu.bhs_pkt;
+ const uint ahs_len = ((uint) bhs_pkt->total_ahs_len * ISCSI_ALIGN_SIZE);
+ const uint32_t ds_len = iscsi_get_be24(bhs_pkt->ds_len);
+
+ bhs_pkt = iscsi_connection_pdu_resize( &request_pdu, ahs_len, ds_len );
+
+ if ( bhs_pkt == NULL ) {
+ // Allocation/size sanity failed; cannot proceed with this PDU
+ logadd( LOG_WARNING, "Cannot resize PDU for client %s", conn->client->hostName );
+ break;
+ }
+
+ // 3) Receive the optional AHS
+ if ( ahs_len != 0 && sock_recv( conn->client->sock, request_pdu.ahs_pkt, ahs_len ) != ahs_len ) {
+ logadd( LOG_DEBUG1, "Could not receive AHS from client %s", conn->client->hostName );
+ break;
+ }
+
+ // 4) Receive the optional DS
+ if ( request_pdu.ds_len != 0U ) {
+ const uint32_t padded_ds_len = ISCSI_ALIGN( request_pdu.ds_len, ISCSI_ALIGN_SIZE );
+
+ if ( sock_recv( conn->client->sock, request_pdu.ds_cmd_data, padded_ds_len ) != padded_ds_len ) {
+ logadd( LOG_DEBUG1, "Could not receive DS from client %s", conn->client->hostName );
+ break;
+ }
+ }
+
+ // 5) Handle PDU
+ if ( iscsi_connection_pdu_handle( conn, &request_pdu ) != ISCSI_CONNECT_PDU_READ_OK
+ || conn->state == ISCSI_CONNECT_STATE_EXITING ) {
+ // Either handler reported a fatal/terminal condition or connection is shutting down
+ break;
+ }
+
+ // In case we needed an extra buffer, reset
+ if ( request_pdu.big_alloc != NULL ) {
+ iscsi_connection_pdu_destroy( &request_pdu );
+ if ( !iscsi_connection_pdu_init( &request_pdu, 0, false ) ) {
+ logadd( LOG_WARNING, "Cannot re-initialize PDU for client %s", conn->client->hostName );
+ break;
+ }
+ }
+
+ // Move first part of next iteration last in this loop, as we completed the first, partial
+ // header before the loop - this saves us from accounting for this within the mainloop
+
+ // 1) Receive entire BHS
+ ret = sock_recv( conn->client->sock, request_pdu.bhs_pkt, sizeof(iscsi_bhs_packet) );
+ if ( ret == -1 && errno == EAGAIN ) {
+ // Receive timeout - send a NOP-In and try recv one more time; a healthy initiator should reply NOP-Out
+ if ( iscsi_connection_handle_nop( conn, NULL ) != ISCSI_CONNECT_PDU_READ_OK ) {
+ logadd( LOG_DEBUG1, "Cannot send NOP-In to idle client %s - connection dead", conn->client->hostName );
+ break;
+ }
+ ret = sock_recv( conn->client->sock, request_pdu.bhs_pkt, sizeof(iscsi_bhs_packet) );
+ }
+ if ( ret != sizeof(iscsi_bhs_packet) ) {
+ logadd( LOG_DEBUG1, "Cannot receive BHS from client %s (%d/%d)", conn->client->hostName, (int)ret, (int)errno );
+ break;
+ }
+ } while ( !_shutdown );
+}
+
+/**
+ * @brief Handles an iSCSI connection until it is closed.
+ *
+ * Initializes a per-connection state object, processes incoming PDUs by
+ * delegating to the PDU read loop (which continues until an error, shutdown,
+ * or explicit exit state), and finally shuts down the write side of the socket
+ * while draining any remaining incoming data.
+ *
+ * @param[in] client Pointer to DNBD3 client structure.
+ * Must NOT be NULL.
+ * @param[in] request Pointer to the already-received initial bytes (partial
+ * Basic Header Segment) of the first iSCSI PDU. Must NOT be NULL.
+ * @param[in] len Length in bytes of the initial data in 'request'.
+ */
+void iscsi_connection_handle(dnbd3_client_t *client, const dnbd3_request_t *request, const int len)
+{
+ _Static_assert( sizeof(dnbd3_request_t) <= sizeof(iscsi_bhs_packet),
+ "DNBD3 request size larger than iSCSI BHS packet data size - Manual intervention required!" );
+
+ iscsi_connection conn = {
+ .state = ISCSI_CONNECT_STATE_NEW,
+ .client = client,
+ };
+
+ static atomic_int CONN_ID = 0;
+ conn.id = ++CONN_ID;
+
+ iscsi_connection_pdu_read_loop( &conn, request, len );
+
+ // Wait for the client to receive any pending outgoing PDUs
+ shutdown( client->sock, SHUT_WR );
+ sock_setTimeout( client->sock, 100 );
+ while ( recv( client->sock, (void *)request, len, 0 ) > 0 ) {}
+}