summaryrefslogtreecommitdiffstats
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/CMakeLists.txt23
-rw-r--r--src/server/altservers.c2
-rw-r--r--src/server/globals.c3
-rw-r--r--src/server/globals.h7
-rw-r--r--src/server/image.c76
-rw-r--r--src/server/image.h2
-rw-r--r--src/server/iscsi.c3535
-rw-r--r--src/server/iscsi.h5152
-rw-r--r--src/server/net.c280
-rw-r--r--src/server/rpc.c2
-rw-r--r--src/server/sendfile.c60
-rw-r--r--src/server/sendfile.h18
-rw-r--r--src/server/server.c7
-rw-r--r--src/server/uplink.c4
-rw-r--r--src/server/uplink.h2
15 files changed, 8989 insertions, 184 deletions
diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt
index 9a1e1c4..34eb695 100644
--- a/src/server/CMakeLists.txt
+++ b/src/server/CMakeLists.txt
@@ -18,27 +18,8 @@ find_package(Libatomic REQUIRED)
add_definitions(-D_GNU_SOURCE)
if(DNBD3_SERVER_AFL)
- # check if DNBD3_RELEASE_HARDEN is disabled
- if(DNBD3_RELEASE_HARDEN)
- message(FATAL_ERROR "DNBD3_SERVER_AFL can only be enabled if DNBD3_RELEASE_HARDEN is disabled")
- endif(DNBD3_RELEASE_HARDEN)
-
- # build dnbd3-server with AFL support
message(STATUS "Building dnbd3-server with AFL support")
add_definitions(-DDNBD3_SERVER_AFL)
-
- # change compiler for dnbd3-server sources if AFL enabled
- include(CheckAFLCCompiler)
- check_afl_c_compiler(AFL_C_COMPILER AFL_C_COMPILER_NAME ${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ID})
- if(AFL_C_COMPILER)
- message(STATUS "Check for working AFL C compiler: ${AFL_C_COMPILER} - done")
- # change C compiler to a corresponding AFL C compiler
- set(CMAKE_C_COMPILER "${AFL_C_COMPILER}")
- else(AFL_C_COMPILER)
- # no corresponding AFL C compiler found
- message(STATUS "Check for working AFL C compiler: ${AFL_C_COMPILER_NAME} - failed")
- message(FATAL_ERROR "No corresponding AFL C compiler ${AFL_C_COMPILER_NAME} was found for the C compiler ${CMAKE_C_COMPILER}!")
- endif(AFL_C_COMPILER)
endif(DNBD3_SERVER_AFL)
set(DNBD3_SERVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/altservers.c
@@ -49,10 +30,12 @@ set(DNBD3_SERVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/altservers.c
${CMAKE_CURRENT_SOURCE_DIR}/image.c
${CMAKE_CURRENT_SOURCE_DIR}/ini.c
${CMAKE_CURRENT_SOURCE_DIR}/integrity.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/iscsi.c
${CMAKE_CURRENT_SOURCE_DIR}/locks.c
${CMAKE_CURRENT_SOURCE_DIR}/net.c
${CMAKE_CURRENT_SOURCE_DIR}/reference.c
${CMAKE_CURRENT_SOURCE_DIR}/rpc.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/sendfile.c
${CMAKE_CURRENT_SOURCE_DIR}/server.c
${CMAKE_CURRENT_SOURCE_DIR}/threadpool.c
${CMAKE_CURRENT_SOURCE_DIR}/uplink.c
@@ -65,11 +48,13 @@ set(DNBD3_SERVER_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/altservers.h
${CMAKE_CURRENT_SOURCE_DIR}/image.h
${CMAKE_CURRENT_SOURCE_DIR}/ini.h
${CMAKE_CURRENT_SOURCE_DIR}/integrity.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/iscsi.h
${CMAKE_CURRENT_SOURCE_DIR}/locks.h
${CMAKE_CURRENT_SOURCE_DIR}/net.h
${CMAKE_CURRENT_SOURCE_DIR}/reference.h
${CMAKE_CURRENT_SOURCE_DIR}/reftypes.h
${CMAKE_CURRENT_SOURCE_DIR}/rpc.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/sendfile.h
${CMAKE_CURRENT_SOURCE_DIR}/server.h
${CMAKE_CURRENT_SOURCE_DIR}/threadpool.h
${CMAKE_CURRENT_SOURCE_DIR}/uplink.h
diff --git a/src/server/altservers.c b/src/server/altservers.c
index 4413ca6..269fe28 100644
--- a/src/server/altservers.c
+++ b/src/server/altservers.c
@@ -14,6 +14,8 @@
#include <inttypes.h>
#include <jansson.h>
+#include <dnbd3/afl.h>
+
#define LOG(lvl, msg, ...) logadd(lvl, msg " (%s:%d)", __VA_ARGS__, PIMG(image))
#define LOG_GOTO(jumplabel, lvl, ...) do { LOG(lvl, __VA_ARGS__); goto jumplabel; } while (0);
#define ERROR_GOTO(jumplabel, ...) LOG_GOTO(jumplabel, LOG_ERROR, __VA_ARGS__)
diff --git a/src/server/globals.c b/src/server/globals.c
index f6432cb..37ee3d4 100644
--- a/src/server/globals.c
+++ b/src/server/globals.c
@@ -32,6 +32,7 @@ atomic_bool _vmdkLegacyMode = false;
atomic_bool _proxyPrivateOnly = false;
atomic_bool _pretendClient = false;
atomic_int _autoFreeDiskSpaceDelay = 3600 * 10;
+atomic_bool _iScsiServer = true;
// [limits]
atomic_int _maxClients = SERVER_MAX_CLIENTS;
atomic_int _maxImages = SERVER_MAX_IMAGES;
@@ -93,6 +94,7 @@ static int ini_handler(void *custom UNUSED, const char* section, const char* key
SAVE_TO_VAR_UINT( limits, minRequestSize );
SAVE_TO_VAR_BOOL( dnbd3, pretendClient );
SAVE_TO_VAR_INT( dnbd3, autoFreeDiskSpaceDelay );
+ SAVE_TO_VAR_BOOL( dnbd3, iScsiServer );
if ( strcmp( section, "dnbd3" ) == 0 && strcmp( key, "backgroundReplication" ) == 0 ) {
if ( strcmp( value, "hashblock" ) == 0 ) {
_backgroundReplication = BGR_HASHBLOCK;
@@ -364,6 +366,7 @@ size_t globals_dumpConfig(char *buffer, size_t size)
PBOOL(proxyPrivateOnly);
PBOOL(pretendClient);
PINT(autoFreeDiskSpaceDelay);
+ PBOOL(iScsiServer);
P_ARG("[limits]\n");
PINT(maxClients);
PINT(maxImages);
diff --git a/src/server/globals.h b/src/server/globals.h
index 5129108..900b86d 100644
--- a/src/server/globals.h
+++ b/src/server/globals.h
@@ -142,6 +142,7 @@ struct _dnbd3_image
weakref ref_cacheMap; // cache map telling which parts are locally cached, NULL if complete
uint64_t virtualFilesize; // virtual size of image (real size rounded up to multiple of 4k)
uint64_t realFilesize; // actual file size on disk
+ uint64_t wwn; // WorldWideName
ticks atime; // last access time
ticks nextCompletenessEstimate; // next time the completeness estimate should be updated
uint32_t *crc32; // list of crc32 checksums for each 16MiB block in image
@@ -333,6 +334,12 @@ extern atomic_bool _pretendClient;
extern atomic_int _autoFreeDiskSpaceDelay;
/**
+ * Specifies if the iSCSI server should be initialized, enabled
+ * and used upon start of DNBD3 server.
+ */
+extern atomic_bool _iScsiServer;
+
+/**
* When handling a client request, this sets the maximum amount
* of bytes we prefetch offset right at the end of the client request.
* The prefetch size will be MIN( length * 3, _maxPrefetch ), if
diff --git a/src/server/image.c b/src/server/image.c
index f438c18..5d1f4a1 100644
--- a/src/server/image.c
+++ b/src/server/image.c
@@ -55,12 +55,14 @@ static dnbd3_cache_map_t* image_loadCacheMap(const char * const imagePath, const
static uint32_t* image_loadCrcList(const char * const imagePath, const int64_t fileSize, uint32_t *masterCrc);
static bool image_checkRandomBlocks(dnbd3_image_t *image, const int count, int fromFd);
static void* closeUnusedFds(void*);
+static dnbd3_image_t* runEnsureOpenCheck(dnbd3_image_t *candidate);
static bool isImageFromUpstream(dnbd3_image_t *image);
static void* saveLoadAllCacheMaps(void*);
static void saveCacheMap(dnbd3_image_t *image);
static void allocCacheMap(dnbd3_image_t *image, bool complete);
static void saveMetaData(dnbd3_image_t *image, ticks *now, time_t walltime);
static void loadImageMeta(dnbd3_image_t *image);
+static void calculateImageWwn(dnbd3_image_t *image);
static void cmfree(ref *ref)
{
@@ -334,6 +336,38 @@ dnbd3_image_t* image_byId(int imgId)
}
/**
+ * Get image by its wwn and revision id.
+ * Locks on imageListLock.
+ */
+dnbd3_image_t* image_getByWwn(uint64_t wwn, uint16_t revision, bool ensureFdOpen)
+{
+ int i;
+ dnbd3_image_t *candidate = NULL;
+
+ mutex_lock( &imageListLock );
+ for ( i = 0; i < _num_images; ++i ) {
+ dnbd3_image_t * const image = _images[i];
+ if ( image == NULL || wwn != image->wwn )
+ continue;
+ if ( revision == image->rid ) {
+ candidate = image;
+ break;
+ } else if ( revision == 0 && (candidate == NULL || candidate->rid < image->rid) ) {
+ candidate = image;
+ }
+ }
+ if ( candidate != NULL ) {
+ candidate->users++;
+ }
+ mutex_unlock( &imageListLock );
+
+ if ( candidate != NULL && ensureFdOpen ) {
+ candidate = runEnsureOpenCheck( candidate );
+ }
+ return candidate;
+}
+
+/**
* Get an image by name+rid. This function increases a reference counter,
* so you HAVE TO CALL image_release for every image_get() call at some
* point...
@@ -358,19 +392,19 @@ dnbd3_image_t* image_get(const char *name, uint16_t revision, bool ensureFdOpen)
candidate = image;
}
}
-
- // Not found
- if ( candidate == NULL ) {
- mutex_unlock( &imageListLock );
- return NULL ;
+ if ( candidate != NULL ) {
+ candidate->users++;
}
-
- candidate->users++;
mutex_unlock( &imageListLock );
- if ( !ensureFdOpen ) // Don't want to re-check
- return candidate;
+ if ( candidate != NULL && ensureFdOpen ) {
+ candidate = runEnsureOpenCheck( candidate );
+ }
+ return candidate;
+}
+static dnbd3_image_t* runEnsureOpenCheck(dnbd3_image_t *candidate)
+{
if ( image_ensureOpen( candidate ) && !candidate->problem.read )
return candidate; // We have a read fd and no read or changed problems
@@ -386,7 +420,7 @@ dnbd3_image_t* image_get(const char *name, uint16_t revision, bool ensureFdOpen)
// make a copy of the image struct but keep the old one around. If/When it's not being used
// anymore, it will be freed automatically.
logadd( LOG_DEBUG1, "Reloading image file %s because of read problem/changed", candidate->path );
- dnbd3_image_t *img = calloc( sizeof(dnbd3_image_t), 1 );
+ dnbd3_image_t *img = calloc( 1, sizeof(dnbd3_image_t) );
img->path = strdup( candidate->path );
img->name = strdup( candidate->name );
img->virtualFilesize = candidate->virtualFilesize;
@@ -399,6 +433,7 @@ dnbd3_image_t* image_get(const char *name, uint16_t revision, bool ensureFdOpen)
img->problem.read = true;
img->problem.changed = candidate->problem.changed;
img->ref_cacheMap = NULL;
+ calculateImageWwn( candidate );
mutex_init( &img->lock, LOCK_IMAGE );
if ( candidate->crc32 != NULL ) {
const size_t mb = IMGSIZE_TO_HASHBLOCKS( candidate->virtualFilesize ) * sizeof(uint32_t);
@@ -426,7 +461,8 @@ dnbd3_image_t* image_get(const char *name, uint16_t revision, bool ensureFdOpen)
// readFd == -1 and problem.read == true
}
- return candidate; // We did all we can, hopefully it's working
+ // We did all we can, hopefully it's working
+ return candidate;
}
/**
@@ -908,6 +944,7 @@ static bool image_load(char *base, char *path, bool withUplink)
image->completenessEstimate = -1;
mutex_init( &image->lock, LOCK_IMAGE );
loadImageMeta( image );
+ calculateImageWwn( image );
// Prevent freeing in cleanup
cache = NULL;
@@ -1324,7 +1361,7 @@ server_fail: ;
image_release( image );
}
// If everything worked out, this call should now actually return the image
- image = image_get( name, acceptedRemoteRid, false );
+ image = image_get( name, revision == 0 ? acceptedRemoteRid : revision, false );
if ( image != NULL && uplinkSock != -1 ) {
// If so, init the uplink and pass it the socket
if ( !uplink_init( image, uplinkSock, &uplinkServer, remoteProtocolVersion ) ) {
@@ -1609,9 +1646,10 @@ json_t* image_getListAsJson()
addproblem(uplink, 3);
addproblem(queue, 4);
- jsonImage = json_pack( "{sisssisisisisIsi}",
+ jsonImage = json_pack( "{sisssIsisisisisIsi}",
"id", image->id, // id, name, rid never change, so access them without locking
"name", image->name,
+ "wwn", (json_int_t)image->wwn,
"rid", (int) image->rid,
"users", image->users,
"complete", completeness,
@@ -2125,6 +2163,18 @@ static void loadImageMeta(dnbd3_image_t *image)
timing_gets( &image->atime, offset );
}
+static void calculateImageWwn(dnbd3_image_t *image)
+{
+ uint64_t value = 0ULL;
+ const char *name = image->name;
+
+ while ( *name != '\0' ) {
+ value = (value * 131ULL) + *name++;
+ }
+ const uint64_t id_a = (value & 0xFFF000000ULL) << 24ULL;
+ image->wwn = (value & 0xFFFFFFULL) | 0x2000000347000000ULL | id_a;
+}
+
void image_checkForNextFullCheck(void)
{
int i;
diff --git a/src/server/image.h b/src/server/image.h
index df8ac87..a8ef717 100644
--- a/src/server/image.h
+++ b/src/server/image.h
@@ -19,6 +19,8 @@ bool image_ensureOpen(dnbd3_image_t *image);
dnbd3_image_t* image_byId(int imgId);
+dnbd3_image_t* image_getByWwn(uint64_t wwn, uint16_t revision, bool ensureFdOpen);
+
dnbd3_image_t* image_get(const char *name, uint16_t revision, bool checkIfWorking);
bool image_reopenCacheFd(dnbd3_image_t *image, const bool force);
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 ) {}
+}
diff --git a/src/server/iscsi.h b/src/server/iscsi.h
new file mode 100644
index 0000000..a1582fe
--- /dev/null
+++ b/src/server/iscsi.h
@@ -0,0 +1,5152 @@
+/*
+ * This file is part of the Distributed Network Block Device 3
+ *
+ * Copyright(c) 2011-2012 Johann Latocha <johann@latocha.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.
+ *
+ */
+
+/**
+ * @file iscsi.h
+ * @author Sebastian Vater
+ * @date 07 Jul 2025
+ * @brief iSCSI header for DNBD3.
+ *
+ * This file contains the header file for the iSCSI
+ * implementation according to RFC7143 for dnbd3-server.
+ * @see https://www.rfc-editor.org/rfc/rfc7143
+ */
+
+#ifndef DNBD3_ISCSI_H_
+#define DNBD3_ISCSI_H_
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <dnbd3/types.h>
+
+#include "globals.h"
+
+#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__))
+ // GCC-compatible compiler, targeting x86/x86-64
+ #include <x86intrin.h>
+#elif defined(__GNUC__) && defined(__ARM_NEON__)
+ // GCC-compatible compiler, targeting ARM with NEON
+ #include <arm_neon.h>
+#elif defined(__GNUC__) && defined(__IWMMXT__)
+ // GCC-compatible compiler, targeting ARM with WMMX
+ #include <mmintrin.h>
+#elif (defined(__GNUC__) || defined(__xlC__)) && (defined(__VEC__) || defined(__ALTIVEC__))
+ // XLC or GCC-compatible compiler, targeting PowerPC with VMX/VSX
+ #include <altivec.h>
+#elif defined(__GNUC__) && defined(__SPE__)
+ // GCC-compatible compiler, targeting PowerPC with SPE
+ #include <spe.h>
+#elif defined(_MSC_VER)
+ // Microsoft C/C++-compatible compiler
+ #include <intrin.h>
+#endif
+
+#if defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN) || (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#define iscsi_get_be16(x) (x)
+#define iscsi_get_be24(x) (iscsi_get_be32((*(uint32_t *) ((uint8_t *) x - 1))) & 0xFFFFFFUL)
+#define iscsi_get_be32(x) (x)
+#define iscsi_get_be64(x) (x)
+
+static inline void iscsi_put_be16(uint8_t *data, const uint16_t value)
+{
+ (*(uint16_t *) data) = value;
+}
+
+static inline void iscsi_put_be24(uint8_t *data, const uint32_t value)
+{
+ data--;
+
+ (*(uint32_t *) data) = (((uint32_t ) *data << 24UL) | (value & 0xFFFFFFUL));
+}
+
+static inline void iscsi_put_be32(uint8_t *data, const uint32_t value)
+{
+ (*(uint32_t *) data) = value;
+}
+
+static inline void iscsi_put_be64(uint8_t *data, const uint64_t value)
+{
+ (*(uint64_t *) data) = value;
+}
+
+#elif defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(__i386__) || defined(__i386) || defined(__x86_64)
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
+// GCC or CLang
+#define iscsi_get_be16(x) (__builtin_bswap16(x))
+#define iscsi_get_be24(x) (iscsi_get_be32((*(uint32_t *) ((uint8_t *) x - 1))) & 0xFFFFFFUL)
+#define iscsi_get_be32(x) (__builtin_bswap32(x))
+#define iscsi_get_be64(x) (__builtin_bswap64(x))
+#elif defined(_MSC_VER)
+// MSVC
+#define iscsi_get_be16(x) (_byteswap_ushort(x))
+#define iscsi_get_be24(x) (iscsi_get_be32((*(uint32_t *) ((uint8_t *) x - 1))) & 0xFFFFFFUL)
+#define iscsi_get_be32(x) (_byteswap_ulong(x))
+#define iscsi_get_be64(x) (_byteswap_uint64(x))
+#elif defined(__INTEL_COMPILER) || defined(__ECC)
+// Intel Compiler
+#define iscsi_get_be16(x) (_bswap16(x))
+#define iscsi_get_be24(x) (iscsi_get_be32((*(uint32_t *) ((uint8_t *) x - 1))) & 0xFFFFFFUL)
+#define iscsi_get_be32(x) (_bswap(x))
+#define iscsi_get_be64(x) (_bswap64(x))
+#else
+// Other compilers (use slow conversion method with bit rotation, bit shift and logcal AND)
+#define iscsi_get_be16(x) ((((uint16_t) (x)) << 8U) | (((uint16_t) (x)) >> 8U))
+#define iscsi_get_be24(x) (iscsi_get_be32((*(uint32_t *) ((uint8_t *) x - 1))) & 0xFFFFFFUL)
+#define iscsi_get_be32(x) ((((uint32_t) (x) & 0xFFUL) << 24UL) | (((uint32_t) (x) & 0xFF00UL) << 8UL) | (((uint32_t) (x) & 0xFF0000UL) >> 8UL) | (((uint32_t) (x) >> 24UL)))
+#define iscsi_get_be64(x) ((uint64_t)((((x) & 0xFFULL) << 56ULL) | (((x) & 0xFF00ULL) << 40ULL) | (((x) & 0xFF0000ull) << 24ULL) | (((x) & 0xFF000000ULL) << 8ULL) | (((x) & 0xFF00000000ULL) >> 8ULL) | (((x) & 0xFF0000000000ULL) >> 24ULL) | (((x) & 0xFF000000000000ULL) >> 40ULL) | (((x) & 0xFF00000000000000ULL) >> 56ULL)))
+#endif
+
+static inline void iscsi_put_be16(uint8_t *data, const uint16_t value)
+{
+ (*(uint16_t *) data) = iscsi_get_be16(value);
+}
+
+static inline void iscsi_put_be24(uint8_t *data, const uint32_t value)
+{
+ data--;
+
+ (*(uint32_t *) data) = ((uint32_t ) *data | (iscsi_get_be32(value) & 0xFFFFFF00UL));
+}
+
+static inline void iscsi_put_be32(uint8_t *data, const uint32_t value)
+{
+ (*(uint32_t *) data) = iscsi_get_be32(value);
+}
+
+static inline void iscsi_put_be64(uint8_t *data, const uint64_t value)
+{
+ (*(uint64_t *) data) = iscsi_get_be64(value);
+}
+
+#else
+#error "Unknown CPU endianness"
+#endif
+
+
+/// Determines the next offset after member b of struct a.
+#define ISCSI_NEXT_OFFSET(a, b) (offsetof(struct a, b) + sizeof(((struct a *) 0)->b))
+
+
+/// Bit sequence manipulation double word (32 bits) mask bits: Gets mask for filtering out a bit range between a and b, b may NOT exceed 30 bits range.
+#define ISCSI_BITS_GET_MASK(a, b) (((1U << (a)) - 1U) ^ ((1U << ((b) + 1U)) - 1U))
+
+/// Bit sequence manipulation double word (32 bits) test bits: Tests value x in of a bit range between a and b, b may NOT exceed 30 bits range.
+#define ISCSI_BITS_TST(x, a, b) ((x) & ISCSI_BITS_GET_MASK(a, b))
+
+/// Bit sequence manipulation double word (32 bits) get bits: Extracts a value x out of a bit range between a and b, b may NOT exceed 30 bits range.
+#define ISCSI_BITS_GET(x, a, b) (ISCSI_BITS_TST(x, a, b) >> (a))
+
+/// Bit sequence manipulation double word (32 bits) get bits: Puts a value x into a bit range between a and b, b may NOT exceed 30 bits range.
+#define ISCSI_BITS_PUT(x, a, b) (((x) << (a)) & ISCSI_BITS_GET_MASK(a, b))
+
+
+/// Aligns value x by rounding up, so it's evenly divisable by n.
+#define ISCSI_ALIGN(x, n) (((x) + (n) - 1) & ~((n) - 1))
+
+
+/// Determines the length of a zero terminated string at compile time.
+#define ISCSI_STRLEN(x) ((sizeof(x) / sizeof(uint8_t)) - 1)
+
+
+/* iSCSI protocol stuff (all WORD/DWORD/QWORD values are big endian by default
+ unless specified otherwise). */
+
+/// iSCSI Basic Header Segment (BHS) size.
+#define ISCSI_BHS_SIZE 48UL
+
+/// iSCSI Advanced Header Segment (AHS) maximum allowed size.
+#define ISCSI_MAX_AHS_SIZE (255UL << 2UL)
+
+/// iSCSI DataSegment maximum allowed size.
+#define ISCSI_MAX_DS_SIZE 16777215
+
+/// iSCSI packet data alignment (BHS, AHS and DataSegment).
+#define ISCSI_ALIGN_SIZE 4UL
+
+
+/// iSCSI Default receive DataSegment (DS) size in bytes.
+#define ISCSI_DEFAULT_RECV_DS_LEN 16384UL
+
+/// iSCSI default maximum DataSegment receive length in bytes.
+#define ISCSI_DEFAULT_MAX_RECV_DS_LEN 524288UL
+
+
+/// Current minimum iSCSI protocol version supported by this implementation.
+#define ISCSI_VERSION_MIN 0
+
+/// Current maximum iSCSI protocol version supported by this implementation.
+#define ISCSI_VERSION_MAX 0
+
+
+/// iSCSI initiator (client) command opcode: NOP-Out.
+#define ISCSI_OPCODE_CLIENT_NOP_OUT 0x00
+
+/// iSCSI initiator (client) command opcode: SCSI Command (encapsulates a SCSI Command Descriptor Block).
+#define ISCSI_OPCODE_CLIENT_SCSI_CMD 0x01
+
+/// iSCSI initiator (client) command opcode: SCSI Task Management Function Request.
+#define ISCSI_OPCODE_CLIENT_TASK_FUNC_REQ 0x02
+
+/// iSCSI initiator (client) command opcode: Login Request.
+#define ISCSI_OPCODE_CLIENT_LOGIN_REQ 0x03
+
+/// iSCSI initiator (client) command opcode: Text Request.
+#define ISCSI_OPCODE_CLIENT_TEXT_REQ 0x04
+
+/// iSCSI initiator (client) command opcode: SCSI Data-Out (for write operations).
+#define ISCSI_OPCODE_CLIENT_SCSI_DATA_OUT 0x05
+
+/// iSCSI initiator (client) command opcode: Logout Request.
+#define ISCSI_OPCODE_CLIENT_LOGOUT_REQ 0x06
+
+/// iSCSI initiator (client) command opcode: Selective Negative / Sequence Number Acknowledgment (SNACK) Request.
+#define ISCSI_OPCODE_CLIENT_SNACK_REQ 0x10
+
+/// iSCSI initiator (client) command opcode: Vendor-specific code #1.
+#define ISCSI_OPCODE_CLIENT_VENDOR_CODE1 0x1C
+
+/// iSCSI initiator (client) command opcode: Vendor-specific code #2.
+#define ISCSI_OPCODE_CLIENT_VENDOR_CODE2 0x1D
+
+/// iSCSI initiator (client) command opcode: Vendor-specific code #3.
+#define ISCSI_OPCODE_CLIENT_VENDOR_CODE3 0x1E
+
+/// First iSCSI initiator (client) command opcode.
+#define ISCSI_OPCODE_CLIENT_FIRST 0x00
+
+/// Last iSCSI initiator (client) command opcode.
+#define ISCSI_OPCODE_CLIENT_LAST 0x1F
+
+
+/// iSCSI target (server) command opcode: NOP-In.
+#define ISCSI_OPCODE_SERVER_NOP_IN 0x20
+
+/// iSCSI target (server) command opcode: SCSI Response - contains SCSI status and possibly sense information or other response information.
+#define ISCSI_OPCODE_SERVER_SCSI_RESPONSE 0x21
+
+/// iSCSI target (server) command opcode: SCSI Task Management Function Response.
+#define ISCSI_OPCODE_SERVER_TASK_FUNC_RES 0x22
+
+/// iSCSI target (server) command opcode: Login Response.
+#define ISCSI_OPCODE_SERVER_LOGIN_RES 0x23
+
+/// iSCSI target (server) command opcode: Text Response.
+#define ISCSI_OPCODE_SERVER_TEXT_RES 0x24
+
+/// iSCSI target (server) command opcode: SCSI Data-In (for read operations).
+#define ISCSI_OPCODE_SERVER_SCSI_DATA_IN 0x25
+
+/// iSCSI target (server) command opcode: Logout Response.
+#define ISCSI_OPCODE_SERVER_LOGOUT_RES 0x26
+
+/// iSCSI target (server) command opcode: Ready To Transfer (R2T) - sent by target when it is ready to receive data.
+#define ISCSI_OPCODE_SERVER_READY_XFER 0x31
+
+/// iSCSI target (server) command opcode: Asynchronous Message - sent by target to indicate certain special conditions.
+#define ISCSI_OPCODE_SERVER_ASYNC_MSG 0x32
+
+/// iSCSI target (server) command opcode: Vendor-specific code #1.
+#define ISCSI_OPCODE_SERVER_VENDOR_CODE1 0x3C
+
+/// iSCSI target (server) command opcode: Vendor-specific code #2.
+#define ISCSI_OPCODE_SERVER_VENDOR_CODE2 0x3D
+
+/// iSCSI target (server) command opcode: Vendor-specific code #3.
+#define ISCSI_OPCODE_SERVER_VENDOR_CODE3 0x3E
+
+/// iSCSI target (server) command opcode: Reject.
+#define ISCSI_OPCODE_SERVER_REJECT 0x3F
+
+
+/// First iSCSI target (server) command opcode.
+#define ISCSI_OPCODE_SERVER_FIRST 0x20
+
+/// Last iSCSI target (server) command opcode.
+#define ISCSI_OPCODE_SERVER_LAST 0x3F
+
+
+/// iSCSI opcode bit mask (bits 0-5 used).
+#define ISCSI_OPCODE_MASK 0x3F
+
+/// Macro which extracts iSCSI packet data opcode out of opcode byte.
+#define ISCSI_GET_OPCODE(x) ((x) & ISCSI_OPCODE_MASK)
+
+/// iSCSI opcode flags (I) Immediate bit: For Request PDUs, the I bit set to 1 is an immediate delivery marker.
+#define ISCSI_OPCODE_FLAGS_IMMEDIATE (1 << 6L)
+
+#define ASSERT_IS_BHS(structname) _Static_assert( sizeof(structname) == ISCSI_BHS_SIZE, #structname " messed up" )
+
+/**
+ * @brief iSCSI Basic Header Segment packet data.
+ *
+ * This structure contains the basic iSCSI packet
+ * data and is shared among all opcodes. This has
+ * to be used before the opcode of the packet data
+ * has been determined.
+ */
+typedef struct __attribute__((packed)) iscsi_bhs_packet {
+ /// Command opcode.
+ uint8_t opcode;
+
+ /// Opcode-specific fields.
+ uint8_t opcode_fields[3];
+
+ /// Total length of AHS (Advanced Header Segment).
+ uint8_t total_ahs_len;
+
+ /// Length of Data Segment.
+ uint8_t ds_len[3];
+
+ union {
+ /// SCSI LUN bit mask.
+ uint64_t lun;
+
+ /// Opcode-specific fields.
+ uint8_t opcode_spec[8];
+ } lun_opcode;
+
+ /// Initiator Task Tag (ITT).
+ uint32_t init_task_tag;
+
+ /// Opcode-specific fields.
+ uint8_t opcode_spec_fields[28];
+} iscsi_bhs_packet;
+ASSERT_IS_BHS( iscsi_bhs_packet );
+
+
+/**
+ * @brief iSCSI Advanced Header Segment packet data.
+ *
+ * This structure contains the advanced iSCSI packet
+ * data and is shared among all opcodes. This has
+ * to be used before the opcode of the packet data
+ * has been determined.
+ */
+typedef struct __attribute__((packed)) iscsi_ahs_packet {
+ /// AHSLength.
+ uint16_t len;
+
+ /// AHSType.
+ uint8_t type;
+
+ /// AHS-Specific.
+ uint8_t specific;
+} iscsi_ahs_packet;
+
+
+/**
+ * @brief iSCSI SCSI CDB packet data structure.
+ *
+ * There are 16 bytes in the CDB field to accommodate the commonly used
+ * CDBs. Whenever the CDB is larger than 16 bytes, an Extended CDB AHS
+ * MUST be used to contain the CDB spillover.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cdb {
+ /// SCSI opcode.
+ uint8_t opcode;
+
+ /// Additional op-code specific data.
+ uint8_t data[15];
+} iscsi_scsi_cdb;
+
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for INQUIRY command flags: Enable Vital Product Data (EVPD).
+#define ISCSI_SCSI_CDB_INQUIRY_FLAGS_EVPD (1 << 0)
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for INQUIRY command flags: Command Support Data (CMDDT).
+#define ISCSI_SCSI_CDB_INQUIRY_FLAGS_CMDDT (1 << 1)
+
+
+/**
+ * @brief iSCSI SCSI CDB packet data structure for SCSI INQUIRY command.
+ *
+ * There are 6 bytes in the CDB field for this command.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cdb_inquiry {
+ /// SCSI opcode.
+ uint8_t opcode;
+
+ /// Logical Unit Number (LUN), CMMDT and EVPD.
+ uint8_t lun_flags;
+
+ /// Page code.
+ uint8_t page_code;
+
+ /// Allocation length in bytes.
+ uint16_t alloc_len;
+
+ /// Control.
+ uint8_t control;
+} iscsi_scsi_cdb_inquiry;
+
+
+/**
+ * @brief iSCSI SCSI CDB packet data structure for SCSI READ(6) and WRITE(6) commands.
+ *
+ * There are 6 bytes in the CDB field for this command.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cdb_read_write_6 {
+ /// SCSI opcode.
+ uint8_t opcode;
+
+ /// Logical Block Address (LBA).
+ uint8_t lba[3];
+
+ /// Transfer length in bytes.
+ uint8_t xfer_len;
+
+ /// Control.
+ uint8_t control;
+} iscsi_scsi_cdb_read_write_6;
+
+
+/**
+ * @brief iSCSI SCSI CDB packet data structure for SCSI READ(10) and WRITE(10) commands.
+ *
+ * There are 10 bytes in the CDB field for this command.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cdb_read_write_10 {
+ /// SCSI opcode.
+ uint8_t opcode;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Logical Block Address (LBA).
+ uint32_t lba;
+
+ /// Group number.
+ uint8_t group_num;
+
+ /// Transfer length in bytes.
+ uint16_t xfer_len;
+
+ /// Control.
+ uint8_t control;
+} iscsi_scsi_cdb_read_write_10;
+
+
+/**
+ * @brief iSCSI SCSI CDB packet data structure for SCSI READ(12) and WRITE(12) commands.
+ *
+ * There are 12 bytes in the CDB field for this command.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cdb_read_write_12 {
+ /// SCSI opcode.
+ uint8_t opcode;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Logical Block Address (LBA).
+ uint32_t lba;
+
+ /// Transfer length in bytes.
+ uint32_t xfer_len;
+
+ /// Restricted for MMC-6 and group number.
+ uint8_t restrict_group_num;
+
+ /// Control.
+ uint8_t control;
+} iscsi_scsi_cdb_read_write_12;
+
+
+/**
+ * @brief iSCSI SCSI CDB packet data structure for SCSI READ(16) and WRITE(16) commands.
+ *
+ * There are 16 bytes in the CDB field for this command.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cdb_read_write_16 {
+ /// SCSI opcode.
+ uint8_t opcode;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Logical Block Address (LBA).
+ uint64_t lba;
+
+ /// Transfer length in bytes.
+ uint32_t xfer_len;
+
+ /// Restricted for MMC-6 and group number.
+ uint8_t restrict_group_num;
+
+ /// Control.
+ uint8_t control;
+} iscsi_scsi_cdb_read_write_16;
+
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for REPORT LUNS command select report: Logical unit with addressing method.
+#define ISCSI_SCSI_CDB_REPORT_LUNS_SELECT_REPORT_LU_ADDR_METHOD 0x00
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for REPORT LUNS command select report: Well known logical unit.
+#define ISCSI_SCSI_CDB_REPORT_LUNS_SELECT_REPORT_LU_KNOWN 0x01
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for REPORT LUNS command select report: Logical unit.
+#define ISCSI_SCSI_CDB_REPORT_LUNS_SELECT_REPORT_LU_ALL 0x02
+
+
+/**
+ * @brief iSCSI SCSI CDB packet data structure for REPORT LUNS command.
+ *
+ * There are 12 bytes in the CDB field for this command.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cdb_report_luns {
+ /// SCSI opcode.
+ uint8_t opcode;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved;
+
+ /// Select report.
+ uint8_t select_report;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint16_t reserved2;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved3;
+
+ /// Allocation length in bytes.
+ uint32_t alloc_len;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved4;
+
+ /// Control.
+ uint8_t control;
+} iscsi_scsi_cdb_report_luns;
+
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for SERVICE ACTION IN(16) command service action: READ CAPACITY(16).
+#define ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_READ_CAPACITY_16 0x10
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for SERVICE ACTION IN(16) command service action: READ LONG(16).
+#define ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_READ_LONG_16 0x11
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for SERVICE ACTION IN(16) command service action: First bit of the five bits.
+#define ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_FIRST_BIT 0
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for SERVICE ACTION IN(16) command service action: Last bit of the five bits.
+#define ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_LAST_BIT ((ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_FIRST_BIT) + 5 - 1)
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for SERVICE ACTION IN(16) command service action: Bit mask.
+#define ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_FIRST_BIT, ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for SERVICE ACTION IN(16) command service action: Extracts the service action bits.
+#define ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_GET_ACTION(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_FIRST_BIT, ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for SERVICE ACTION IN(16) command service action: Stores into the service action bits.
+#define ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_PUT_ACTION(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_FIRST_BIT, ISCSI_SCSI_CDB_SERVICE_ACTION_IN_16_ACTION_LAST_BIT))
+
+
+/**
+ * @brief iSCSI SCSI CDB packet data structure for SCSI SERVICE IN ACTION(16) command.
+ *
+ * There are 16 bytes in the CDB field for this command.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cdb_service_action_in_16 {
+ /// SCSI opcode.
+ uint8_t opcode;
+
+ /// Service action.
+ uint8_t action;
+
+ /// Logical Block Address (LBA), obselete by now.
+ uint64_t lba;
+
+ /// Allocation length in bytes.
+ uint32_t alloc_len;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved;
+
+ /// Control.
+ uint8_t control;
+} iscsi_scsi_cdb_service_action_in_16;
+
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command flags: Disable Block Descriptors (DBD).
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_FLAGS_DBD (1 << 3)
+
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page code: First bit of the six bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CODE_FIRST_BIT 0
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page code: Last bit of the six bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CODE_LAST_BIT ((ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CODE_FIRST_BIT) + 6 - 1)
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page code: Bit mask.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CODE_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CODE_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CODE_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page code: Extracts the page code bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_GET_PAGE_CODE(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CODE_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CODE_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page code: Stores into the page code bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PUT_PAGE_CODE(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CODE_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CODE_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page control: Current values.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_CURRENT_VALUES 0x0
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page control: Changeable values.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_CHG_VALUES 0x1
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page control: Default values.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_DEFAULT_VALUES 0x2
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page control: Saved values.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_SAVED_VALUES 0x3
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page control: First bit of the two bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_FIRST_BIT 6
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page control: Last bit of the two bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_LAST_BIT ((ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_FIRST_BIT) + 2 - 1)
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page control: Bit mask.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page control: Extracts the page control bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_GET_PAGE_CONTROL(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(6) command page control: Stores into the page control bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_6_PUT_PAGE_CONTROL(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_6_PAGE_CONTROL_LAST_BIT))
+
+
+/**
+ * @brief iSCSI SCSI CDB packet data structure for SCSI MODE SENSE(6) command.
+ *
+ * There are 6 bytes in the CDB field for this command.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cdb_mode_sense_6 {
+ /// SCSI opcode.
+ uint8_t opcode;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Page code and page control.
+ uint8_t page_code_control;
+
+ /// Sub page code.
+ uint8_t sub_page_code;
+
+ /// Allocation length in bytes.
+ uint8_t alloc_len;
+
+ /// Control.
+ uint8_t control;
+} iscsi_scsi_cdb_mode_sense_6;
+
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command flags: Disable Block Descriptors (DBD).
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_FLAGS_DBD (1 << 3)
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command flags: Long LBA Accepted (LLBAA).
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_FLAGS_LLBAA (1 << 4)
+
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page code: First bit of the six bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CODE_FIRST_BIT 0
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page code: Last bit of the six bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CODE_LAST_BIT ((ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CODE_FIRST_BIT) + 6 - 1)
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page code: Bit mask.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CODE_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CODE_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CODE_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page code: Extracts the page code bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_GET_PAGE_CODE(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CODE_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CODE_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page code: Stores into the page code bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PUT_PAGE_CODE(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CODE_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CODE_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page control: Current values.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_CURRENT_VALUES 0x0
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page control: Changeable values.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_CHG_VALUES 0x1
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page control: Default values.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_DEFAULT_VALUES 0x2
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page control: Saved values.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_SAVED_VALUES 0x3
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page control: First bit of the two bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_FIRST_BIT 6
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page control: Last bit of the two bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_LAST_BIT ((ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_FIRST_BIT) + 2 - 1)
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page control: Bit mask.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page control: Extracts the page control bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_GET_PAGE_CONTROL(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_LAST_BIT))
+
+/// iSCSI SCSI Command Descriptor Block (CDB) for MODE SENSE(10) command page control: Stores into the page control bits.
+#define ISCSI_SCSI_CDB_MODE_SENSE_10_PUT_PAGE_CONTROL(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_FIRST_BIT, ISCSI_SCSI_CDB_MODE_SENSE_10_PAGE_CONTROL_LAST_BIT))
+
+
+/**
+ * @brief iSCSI SCSI CDB packet data structure for SCSI MODE SENSE(10) command.
+ *
+ * There are 10 bytes in the CDB field for this command.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cdb_mode_sense_10 {
+ /// SCSI opcode.
+ uint8_t opcode;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Page code and page control.
+ uint8_t page_code_control;
+
+ /// Sub page code.
+ uint8_t sub_page_code;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint16_t reserved;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved2;
+
+ /// Allocation length in bytes.
+ uint16_t alloc_len;
+
+ /// Control.
+ uint8_t control;
+} iscsi_scsi_cdb_mode_sense_10;
+
+
+/**
+ * @brief iSCSI SCSI DataSegment Command packet structure.
+ *
+ * iSCSI targets MUST support and enable Autosense. If Status is CHECK
+ * CONDITION (0x02), then the data segment MUST contain sense data for
+ * the failed command.
+ *
+ * For some iSCSI responses, the response data segment MAY contain some
+ * response-related information (e.g., for a target failure, it may
+ * contain a vendor-specific detailed description of the failure).
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_ds_cmd_data {
+ /// SenseLength: This field indicates the length of Sense Data.
+ uint16_t len;
+
+ /// The Sense Data contains detailed information about a CHECK CONDITION. SPC3 specifies the format and content of the Sense Data.
+ uint8_t sense_data[];
+} iscsi_scsi_ds_cmd_data;
+
+
+/// iSCSI SCSI Basic Inquiry Data peripheral type: Direct access device.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_DIRECT 0x00
+
+/// iSCSI SCSI Basic Inquiry Data peripheral type: First bit of the five bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_FIRST_BIT 0
+
+/// iSCSI SCSI Basic Inquiry Data peripheral type: Last bit of the five bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_LAST_BIT ((ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_FIRST_BIT) + 5 - 1)
+
+/// iSCSI SCSI Basic Inquiry Data peripheral type: Bit mask.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data peripheral type: Extracts the peripheral device type bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_GET_PERIPHERAL_TYPE(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data peripheral type: Stores into the peripheral device type bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PUT_PERIPHERAL_TYPE(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: The specified peripheral device type is currently connected to this logical unit, or connection state could not be determined.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_POSSIBLE 0x0
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: The target is capable of supporting the specified peripheral device type on this logical unit, but not connected.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_SUPPORTED 0x1
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: The target is not capable of supporting a physical device on this logical unit.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_NEVER 0x3
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: Vendor specific.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_VENDOR_UNIQ 0x4
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: First bit of the three bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_FIRST_BIT 5
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: Last bit of the three bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_LAST_BIT ((ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_FIRST_BIT) + 3 - 1)
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: Bit mask.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: Extracts the peripheral device identifier bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_GET_PERIPHERAL_ID(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: Stores into the peripheral device identifier bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PUT_PERIPHERAL_ID(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_ID_LAST_BIT))
+
+
+/// iSCSI SCSI Basic Inquiry Data peripheral type modifier: First bit of the seven bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_FIRST_BIT 0
+
+/// iSCSI SCSI Basic Inquiry Data peripheral type modifier: Last bit of the seven bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_LAST_BIT ((ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_FIRST_BIT) + 7 - 1)
+
+/// iSCSI SCSI Basic Inquiry Data peripheral type modifier: Bit mask.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: Extracts the peripheral type modifier bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_GET_PERIPHERAL_TYPE_MOD(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data peripheral identifier: Stores into the peripheral type modifier bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PUT_PERIPHERAL_TYPE_MOD(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data peripheral type modifier: Removable media.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PERIPHERAL_TYPE_MOD_FLAGS_REMOVABLE_MEDIA (1 << 7)
+
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: None.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_NONE 0x0
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: SPC.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_SPC 0x3
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: SPC2.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_SPC2 0x4
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: SPC3.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_SPC3 0x5
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: SPC4.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_SPC4 0x6
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: SPC5.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_SPC5 0x7
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: First bit of the three bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_FIRST_BIT 0
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: Last bit of the three bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_LAST_BIT ((ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_FIRST_BIT) + 3 - 1)
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: Bit mask.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: Extracts the ANSI version bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_GET_VERSION_ANSI(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data ANSI version: Stores into the ANSI version bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PUT_VERSION_ANSI(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_VERSION_ANSI_LAST_BIT))
+
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: This structure complies with SCSI-1 specifications.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_LEVEL_0 0x00
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: This structure complies with CCS pseudo specifications.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_CCS 0x01
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: This structure complies with SCSI-2/3 specifications.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_SCSI_2 0x02
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: First bit of the four bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_FIRST_BIT 0
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: Last bit of the four bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_LAST_BIT ((ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_FIRST_BIT) + 4 - 1)
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: Bit mask.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: Extracts the response data format flags bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_GET_RESPONSE_DATA_FMT_FLAGS(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: Stores into the response data format flags bits.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_PUT_RESPONSE_DATA_FMT_FLAGS(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_FIRST_BIT, ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_LAST_BIT))
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: Hierarchical Support.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_HISUP (1 << 4)
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: Normal ACA Supported.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_NORMACA (1 << 5)
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: TERMINATE I/O PROCESS message device support.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_TERMINATE_IO_PROC_MSG (1 << 6)
+
+/// iSCSI SCSI Basic Inquiry Data response data format flags: Asynchronous Event Notification device support.
+#define ISCSI_SCSI_BASIC_INQUIRY_DATA_RESPONSE_DATA_FMT_FLAGS_ASYNC_EVENT_NOTIFY (1 << 7)
+
+
+/**
+ * @brief iSCSI SCSI basic inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * cleared.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_basic_inquiry_data_packet {
+ /// Peripheral device type and qualifier.
+ uint8_t peripheral_type_id;
+
+ /// Peripheral device type modifier and removable media bit.
+ uint8_t peripheral_type_mod_flags;
+
+ /// ANSI-Approved, ECMA and ISO version.
+ uint8_t version;
+
+ /// Response data format, HISUP, NORMACA, AENC and TrmIOP flags.
+ uint8_t response_data_fmt_flags;
+
+ /// Additional length in bytes.
+ uint8_t add_len;
+} iscsi_scsi_basic_inquiry_data_packet;
+
+
+/// iSCSI SCSI Standard Inquiry Data vendor identifier for disk.
+#define ISCSI_SCSI_STD_INQUIRY_DATA_DISK_VENDOR_ID "UNI FRBG"
+
+
+/// iSCSI SCSI Standard Inquiry Data services flags: Multi Port (MULTIP).
+#define ISCSI_SCSI_STD_INQUIRY_DATA_SERVICES_FLAGS_MULTIP (1 << 4)
+
+/// iSCSI SCSI Standard Inquiry Data services flags: VS.
+#define ISCSI_SCSI_STD_INQUIRY_DATA_SERVICES_FLAGS_VS (1 << 5)
+
+/// iSCSI SCSI Standard Inquiry Data services flags: Enclosure Services (ENCSERV).
+#define ISCSI_SCSI_STD_INQUIRY_DATA_SERVICES_FLAGS_ENCSERV (1 << 6)
+
+
+/// iSCSI SCSI Standard Inquiry Data flags: Device responds with soft reset instead of hard reset to reset condition.
+#define ISCSI_SCSI_STD_INQUIRY_DATA_FLAGS_SOFT_RESET (1 << 0)
+
+/// iSCSI SCSI Standard Inquiry Data flags: Device supports tagged command queueing.
+#define ISCSI_SCSI_STD_INQUIRY_DATA_FLAGS_COMMAND_QUEUE (1 << 1)
+
+/// iSCSI SCSI Standard Inquiry Data flags: Device supports linked commands for this logical unit.
+#define ISCSI_SCSI_STD_INQUIRY_DATA_FLAGS_LINKED_CMDS (1 << 3)
+
+/// iSCSI SCSI Standard Inquiry Data flags: Device supports synchronous data transfers.
+#define ISCSI_SCSI_STD_INQUIRY_DATA_FLAGS_SYNC (1 << 4)
+
+/// iSCSI SCSI Standard Inquiry Data flags: Device supports 16-bit wide data transfers.
+#define ISCSI_SCSI_STD_INQUIRY_DATA_FLAGS_WIDE_16_BIT (1 << 5)
+
+/// iSCSI SCSI Standard Inquiry Data flags: Device supports 32-bit wide data transfers.
+#define ISCSI_SCSI_STD_INQUIRY_DATA_FLAGS_WIDE_32_BIT (1 << 6)
+
+/// iSCSI SCSI Standard Inquiry Data flags: Device supports relative addressing mode of this logical unit.
+#define ISCSI_SCSI_STD_INQUIRY_DATA_FLAGS_REL_ADDR (1 << 7)
+
+
+/**
+ * @brief iSCSI SCSI standard inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * cleared.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_std_inquiry_data_packet {
+ /// iSCSI SCSI basic inquiry data packet.
+ iscsi_scsi_basic_inquiry_data_packet basic_inquiry;
+
+ /// PROTECT, 3PC, TPGS, ACC and SCCS.
+ uint8_t tpgs_flags;
+
+ /// MULTIP, VS and ENCSERV.
+ uint8_t services_flags;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Vendor identification.
+ uint8_t vendor_id[8];
+
+ /// Product identification.
+ uint8_t product_id[16];
+
+ /// Product revision level.
+ uint8_t product_rev_level[4];
+} iscsi_scsi_std_inquiry_data_packet;
+
+
+/// iSCSI SCSI Extended Inquiry Data vendor specific.
+#define ISCSI_SCSI_EXT_INQUIRY_DATA_VENDOR_SPEC_ID "UNI FREIBURG DNBD3"
+
+
+/// iSCSI SCSI Extended Inquiry Data version descriptor: iSCSI (no version claimed).
+#define ISCSI_SCSI_EXT_INQUIRY_DATA_VERSION_DESC_ISCSI_NO_VERSION 0x0960
+
+/// iSCSI SCSI Extended Inquiry Data version descriptor: SPC3 (no version claimed).
+#define ISCSI_SCSI_EXT_INQUIRY_DATA_VERSION_DESC_SPC3_NO_VERSION 0x0300
+
+/// iSCSI SCSI Extended Inquiry Data version descriptor: SBC2 (no version claimed).
+#define ISCSI_SCSI_EXT_INQUIRY_DATA_VERSION_DESC_SBC2_NO_VERSION 0x0320
+
+/// iSCSI SCSI Extended Inquiry Data version descriptor: SAM2 (no version claimed).
+#define ISCSI_SCSI_EXT_INQUIRY_DATA_VERSION_DESC_SAM2_NO_VERSION 0x0040
+
+
+/**
+ * @brief iSCSI SCSI extended inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * cleared.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_ext_inquiry_data_packet {
+ /// iSCSI SCSI standard inquiry data packet.
+ iscsi_scsi_std_inquiry_data_packet std_inquiry;
+
+ /// Vendor specific.
+ uint8_t vendor_spec[20];
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint8_t reserved;
+
+ /// Version descriptors.
+ uint16_t version_desc[8];
+
+ /// Reserved for future usage (always MUST be 0).
+ uint64_t reserved2[2];
+
+ /// Reserved for future usage (always MUST be 0).
+ uint32_t reserved3;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint16_t reserved4;
+} iscsi_scsi_ext_inquiry_data_packet;
+
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral type: Direct access device.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_DIRECT 0x00
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral type: First bit of the five bits.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_FIRST_BIT 0
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral type: Last bit of the five bits.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_LAST_BIT ((ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_FIRST_BIT) + 5 - 1)
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral type: Bit mask.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral type: Extracts the peripheral device type bits.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_GET_PERIPHERAL_TYPE(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral type: Stores into the peripheral device type bits.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PUT_PERIPHERAL_TYPE(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_TYPE_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral identifier: The specified peripheral device type is currently connected to this logical unit, or connection state could not be determined.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_POSSIBLE 0x0
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral identifier: The target is capable of supporting the specified peripheral device type on this logical unit, but not connected.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_SUPPORTED 0x1
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral identifier: The target is not capable of supporting a physical device on this logical unit.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_NEVER 0x3
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral identifier: Vendor specific.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_VENDOR_UNIQ 0x4
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral identifier: First bit of the three bits.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_FIRST_BIT 5
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral identifier: Last bit of the three bits.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_LAST_BIT ((ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_FIRST_BIT) + 3 - 1)
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral identifier: Bit mask.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral identifier: Extracts the peripheral device identifier bits.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_GET_PERIPHERAL_ID(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data peripheral identifier: Stores into the peripheral device identifier bits.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PUT_PERIPHERAL_ID(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PERIPHERAL_ID_LAST_BIT))
+
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: Supported VPD pages.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_SUPPORTED_VPD_PAGES 0x00
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: Unit serial number.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_UNIT_SERIAL_NUMBER 0x80
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: Device identification.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_DEVICE_ID 0x83
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: Software interface identification.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_SOFTWARE_IFACE_ID 0x84
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: Management network addresses.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_MANAGEMENT_NETWORK_ADDRS 0x85
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: Extended inquiry data.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_EXTENDED_INQUIRY_DATA 0x86
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: Mode page policy.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_MODE_PAGE_POLICY 0x87
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: SCSI ports.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_SCSI_PORTS 0x88
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: Block limits.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_BLOCK_LIMITS 0xB0
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: Block device characteristics.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_BLOCK_DEV_CHARS 0xB1
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Inquiry Data page code: Thin provisioning.
+#define ISCSI_SCSI_VPD_PAGE_INQUIRY_DATA_PAGE_CODE_THIN_PROVISION 0xB2
+
+
+/**
+ * @brief iSCSI SCSI Vital Product Data (VPD) Page Inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * set.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_vpd_page_inquiry_data_packet {
+ /// Peripheral device type and qualifier.
+ uint8_t peripheral_type_id;
+
+ /// Page code.
+ uint8_t page_code;
+
+ /// Allocation length in bytes.
+ uint16_t alloc_len;
+
+ /// Parameters.
+ uint8_t params[];
+} iscsi_scsi_vpd_page_inquiry_data_packet;
+
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data protocol identifier: iSCSI.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_ISCSI 0x05
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data protocol identifier: First bit of the four bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_FIRST_BIT 4
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data protocol identifier: Last bit of the four bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_LAST_BIT ((ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_FIRST_BIT) + 8 - 1)
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data protocol identifier: Bit mask.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data protocol identifier: Extracts the protocol identifier bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_GET_PROTOCOL_ID(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data protocol identifier: Stores into the protocol identifier bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_PROTOCOL_ID(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PROTOCOL_ID_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data code set: Binary encoding.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_BINARY 0x01
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data code set: ASCII encoding.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_ASCII 0x02
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data code set: UTF-8 encoding.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_UTF8 0x03
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data code set: First bit of the four bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_FIRST_BIT 0
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data code set: Last bit of the four bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_LAST_BIT ((ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_FIRST_BIT) + 4 - 1)
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data code set: Bit mask.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data code set: Extracts the protocol identifier bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_GET_CODE_SET(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data code set: Stores into the protocol identifier bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_PUT_CODE_SET(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_CODE_SET_LAST_BIT))
+
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: Vendor specific.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_VENDOR_SPEC 0x00
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: T10 vendor identifier.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_T10_VENDOR_ID 0x01
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: EUI64.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_EUI64 0x02
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: NAA.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_NAA 0x03
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: Relative target port.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_REL_TARGET_PORT 0x04
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: Target port group.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_TARGET_PORT_GROUP 0x05
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: Logical unit group.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_LOGICAL_UNIT_GROUP 0x06
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: MD5 logical unit.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_MD5_LOGICAL_UNIT 0x07
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: SCSI name.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_SCSI_NAME 0x08
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: First bit of the four bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_FIRST_BIT 0
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: Last bit of the four bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_LAST_BIT ((ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_FIRST_BIT) + 4 - 1)
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: Bit mask.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: Extracts the type bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_GET_TYPE(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags type: Stores into the type bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_TYPE(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_TYPE_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags association: Logical unit.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_LOGICAL_UNIT 0x0
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags association: Target port.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_TARGET_PORT 0x1
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags association: Target device.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_TARGET_DEVICE 0x2
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags association: First bit of the two bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_FIRST_BIT 4
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags association: Last bit of the two bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_LAST_BIT ((ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_FIRST_BIT) + 6 - 1)
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags association: Bit mask.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags association: Extracts the association bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_GET_ASSOC(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags association: Stores into the association bits.
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PUT_ASSOC(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_ASSOC_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data flags: Protocol Identifier Valid (PIV).
+#define ISCSI_SCSI_VPD_PAGE_DESIGN_DESC_INQUIRY_DATA_FLAGS_PIV (1 << 7)
+
+
+/**
+ * @brief iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * set.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_vpd_page_design_desc_inquiry_data_packet {
+ /// Protocol identifier and code set.
+ uint8_t protocol_id_code_set;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint8_t reserved;
+
+ /// Length in bytes.
+ uint8_t len;
+
+ /// Designation descriptor.
+ uint8_t desc[];
+} iscsi_scsi_vpd_page_design_desc_inquiry_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor IEEE NAA Extended Inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * set.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_vpd_page_design_desc_ieee_naa_ext_inquiry_data_packet {
+ /// IEEE NAA Extended.
+ uint64_t ieee_naa_ext;
+} iscsi_scsi_vpd_page_design_desc_ieee_naa_ext_inquiry_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor T10 Vendor ID Inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * set.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_vpd_page_design_desc_t10_vendor_id_inquiry_data_packet {
+ /// Vendor identification.
+ uint8_t vendor_id[8];
+
+ /// Product identification.
+ uint8_t product_id[16];
+
+ /// Unit serial number.
+ uint8_t unit_serial_num[32];
+} iscsi_scsi_vpd_page_design_desc_t10_vendor_id_inquiry_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Relative Target Port Inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * set.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_vpd_page_design_desc_rel_target_port_inquiry_data_packet {
+ /// Reserved for future usage (always MUST be 0).
+ uint16_t reserved;
+
+ /// Port index.
+ uint16_t index;
+} iscsi_scsi_vpd_page_design_desc_rel_target_port_inquiry_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Target Port Group Inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * set.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_vpd_page_design_desc_target_port_group_inquiry_data_packet {
+ /// Reserved for future usage (always MUST be 0).
+ uint16_t reserved;
+
+ /// Port group index.
+ uint16_t index;
+} iscsi_scsi_vpd_page_design_desc_target_port_group_inquiry_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI Vital Product Data (VPD) Page Designation Descriptor Logical Unit Group Inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * set.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_vpd_page_design_desc_logical_unit_group_inquiry_data_packet {
+ /// Reserved for future usage (always MUST be 0).
+ uint16_t reserved;
+
+ /// Logical unit identifier.
+ uint16_t id;
+} iscsi_scsi_vpd_page_design_desc_logical_unit_group_inquiry_data_packet;
+
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Extended Inquiry Data support flags: SIMP support.
+#define ISCSI_SCSI_VPD_PAGE_EXT_INQUIRY_DATA_SUPPORT_FLAGS_SIMPSUP (1 << 0)
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Extended Inquiry Data support flags: HEAD support.
+#define ISCSI_SCSI_VPD_PAGE_EXT_INQUIRY_DATA_SUPPORT_FLAGS_HEADSUP (1 << 2)
+
+
+/**
+ * @brief iSCSI SCSI Vital Product Data (VPD) Extended Inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * set.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_vpd_page_ext_inquiry_data_packet {
+ /// Peripheral device type and qualifier.
+ uint8_t peripheral_type_id;
+
+ /// Page code.
+ uint8_t page_code;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint8_t reserved;
+
+ /// Page length in bytes.
+ uint8_t page_len;
+
+ /// Check flags.
+ uint8_t check_flags;
+
+ /// Support flags.
+ uint8_t support_flags;
+
+ /// More support flags.
+ uint8_t support_flags_2;
+
+ /// LUICLR.
+ uint8_t luiclr;
+
+ /// CBCS.
+ uint8_t cbcs;
+
+ /// Micro DL.
+ uint8_t micro_dl;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint64_t reserved2[6];
+
+ /// Reserved for future usage (always MUST be 0).
+ uint32_t reserved3;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint16_t reserved4;
+} iscsi_scsi_vpd_page_ext_inquiry_data_packet;
+
+/**
+ * @brief iSCSI SCSI Vital Product Data (VPD) Page Block Limits Inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * set.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_vpd_page_block_limits_inquiry_data_packet {
+ /// Flags.
+ uint8_t flags;
+
+ /// Maximum COMPARE AND WRITE length in logical blocks.
+ uint8_t max_cmp_write_len;
+
+ /// Optimal transfer length granularity in logical blocks.
+ uint16_t optimal_granularity_xfer_len;
+
+ /// Maximum transfer length in logical blocks.
+ uint32_t max_xfer_len;
+
+ /// Optimal transfer length in logical blocks.
+ uint32_t optimal_xfer_len;
+
+ /// Maximum prefetch length in logical blocks.
+ uint32_t max_prefetch_len;
+
+ /// Maximum UNMAP LBA count in LBAs.
+ uint32_t max_unmap_lba_cnt;
+
+ /// Maximum UNMAP block descriptor count in block descriptors.
+ uint32_t max_unmap_block_desc_cnt;
+
+ /// Optimal UNMAP granularity in logical blocks.
+ uint32_t optimal_unmap_granularity;
+
+ /// UNMAP granularity alignment (first LBA) and UGAVALID bit.
+ uint32_t unmap_granularity_align_ugavalid;
+
+ /// Maximum WRITE SAME length in logical blocks.
+ uint64_t max_write_same_len;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint64_t reserved[2];
+
+ /// Reserved for future usage (always MUST be 0).
+ uint32_t reserved2;
+} iscsi_scsi_vpd_page_block_limits_inquiry_data_packet;
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data medium rotation rate: Medium rotation rate is not reported.
+#define ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_MEDIUM_ROTATION_RATE_NOT_REPORTED 0x0000
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data medium rotation rate: Non-rotating medium (e.g., solid state).
+#define ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_MEDIUM_ROTATION_RATE_NONE 0x0001
+
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data product type: Not indicated.
+#define ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_PRODUCT_TYPE_NOT_INDICATED 0x00
+
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data flags nominal form factor: Nominal form factor is not reported.
+#define ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_NOT_REPORTED 0x0
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data flags nominal form factor: First bit of the four bits.
+#define ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_FIRST_BIT 0
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data flags nominal form factor: Last bit of the four bits.
+#define ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_LAST_BIT ((ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_FIRST_BIT) + 4 - 1)
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data flags nominal form factor: Bit mask.
+#define ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data flags nominal form factor: Extracts the nominal form factor bits.
+#define ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_GET_NOMINAL_FORM_FACTOR(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_LAST_BIT))
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data flags nominal form factor: Stores into the nominal form factor bits.
+#define ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_PUT_NOMINAL_FORM_FACTOR(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_FIRST_BIT, ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_FLAGS_NOMINAL_FORM_FACTOR_LAST_BIT))
+
+
+/// iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data support flags: Verify Byte Check Unmapped LBA Supported (VBULS).
+#define ISCSI_SCSI_VPD_PAGE_BLOCK_DEV_CHARS_INQUIRY_DATA_SUPPORT_FLAGS_VBULS (1 << 0)
+
+
+/**
+ * @brief iSCSI SCSI Vital Product Data (VPD) Page Block Device Characteristics Inquiry data packet.
+ *
+ * This structure is used by the SCSI INQUIRY command
+ * in order to fill in the result if the EVPD bit is
+ * set.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_vpd_page_block_dev_chars_inquiry_data_packet {
+ /// Medium rotation rate.
+ uint16_t medium_rotation_rate;
+
+ /// Product type.
+ uint8_t product_type;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Support flags.
+ uint8_t support_flags;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint64_t reserved[6];
+
+ /// Reserved for future usage (always MUST be 0).
+ uint32_t reserved2;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint16_t reserved3;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint8_t reserved4;
+} iscsi_scsi_vpd_page_block_dev_chars_inquiry_data_packet;
+
+
+/// iSCSI SCSI sense data response code: Current format.
+#define ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_CURRENT_FMT 0x70
+
+/// iSCSI SCSI sense data response code: Deferred format.
+#define ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_DEFERRED_FMT 0x71
+
+/// iSCSI SCSI sense data response code: First bit of the seven bits.
+#define ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_FIRST_BIT 0
+
+/// iSCSI SCSI sense data response code: Last bit of the seven bits.
+#define ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_LAST_BIT ((ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_FIRST_BIT) + 7 - 1)
+
+/// iSCSI SCSI sense data response code: Bit mask.
+#define ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_FIRST_BIT, ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_LAST_BIT))
+
+/// iSCSI SCSI sense data response code: Extracts the response code bits.
+#define ISCSI_SCSI_SENSE_DATA_GET_RESPONSE_CODE(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_FIRST_BIT, ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_LAST_BIT))
+
+/// iSCSI SCSI sense data response code: Stores into the response code bits.
+#define ISCSI_SCSI_SENSE_DATA_PUT_RESPONSE_CODE(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_FIRST_BIT, ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_LAST_BIT))
+
+/// iSCSI SCSI sense data response code: Valid.
+#define ISCSI_SCSI_SENSE_DATA_RESPONSE_CODE_VALID (1 << 7)
+
+
+/// iSCSI SCSI sense data sense key: First bit of the four bits.
+#define ISCSI_SCSI_SENSE_DATA_SENSE_KEY_FIRST_BIT 0
+
+/// iSCSI SCSI sense data sense key: Last bit of the four bits.
+#define ISCSI_SCSI_SENSE_DATA_SENSE_KEY_LAST_BIT ((ISCSI_SCSI_SENSE_DATA_SENSE_KEY_FIRST_BIT) + 4 - 1)
+
+/// iSCSI SCSI sense data sense key: Bit mask.
+#define ISCSI_SCSI_SENSE_DATA_SENSE_KEY_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_SENSE_DATA_SENSE_KEY_FIRST_BIT, ISCSI_SCSI_SENSE_DATA_SENSE_KEY_LAST_BIT))
+
+/// iSCSI SCSI sense data sense key: Extracts the Sense Key (SK) bits.
+#define ISCSI_SCSI_SENSE_DATA_GET_SENSE_KEY(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_SENSE_DATA_SENSE_KEY_FIRST_BIT, ISCSI_SCSI_SENSE_DATA_SENSE_KEY_LAST_BIT))
+
+/// iSCSI SCSI sense data sense key: Stores into the Sense Key (SK) bits.
+#define ISCSI_SCSI_SENSE_DATA_PUT_SENSE_KEY(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_SENSE_DATA_SENSE_KEY_FIRST_BIT, ISCSI_SCSI_SENSE_DATA_SENSE_KEY_LAST_BIT))
+
+// iSCSI SCSI sense data sense key flags: ILI.
+#define ISCSI_SCSI_SENSE_DATA_SENSE_KEY_FLAGS_ILI (1 << 5)
+
+// iSCSI SCSI sense data sense key flags: EOM.
+#define ISCSI_SCSI_SENSE_DATA_SENSE_KEY_FLAGS_EOM (1 << 6)
+
+// iSCSI SCSI sense data sense key flags: FILEMARK.
+#define ISCSI_SCSI_SENSE_DATA_SENSE_KEY_FLAGS_FILEMARK (1 << 7)
+
+
+/**
+ * @brief iSCSI SCSI basic sense data packet data.
+ *
+ * This is the basic SCSI sense data shared by
+ * all SCSI sense data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_sense_data_packet {
+ /// Response code.
+ uint8_t response_code;
+
+ /// Reserved for future usage (always MUST be 0).
+ uint8_t reserved;
+
+ /// Sense key and flags.
+ uint8_t sense_key_flags;
+
+ /// Information.
+ uint32_t info;
+
+ /// Additional sense length in bytes.
+ uint8_t add_len;
+} iscsi_scsi_sense_data_packet;
+
+/// iSCSI SCSI maximum sense data length.
+#define ISCSI_SCSI_MAX_SENSE_DATA_LEN (sizeof(struct iscsi_scsi_sense_data_packet) + 255U)
+
+
+/**
+ * @brief iSCSI SCSI sense data check condition packet data.
+ *
+ * This is the additional SCSI sense data used by
+ * the check condition status code.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_sense_data_check_cond_packet {
+ /// Basic SCSI sense data packet.
+ iscsi_scsi_sense_data_packet sense_data;
+
+ /// Information.
+ uint32_t cmd_spec_info;
+
+ /// Additional Sense Code (ASC).
+ uint8_t asc;
+
+ /// Additional Sense Code Qualifier (ASCQ).
+ uint8_t ascq;
+
+ /// Field replaceable unit code.
+ uint32_t field_rep_unit_code;
+
+ /// Sense key specific.
+ uint8_t sense_key_spec_flags;
+
+ /// Sense key specific.
+ uint16_t sense_key_spec;
+} iscsi_scsi_sense_data_check_cond_packet;
+
+
+/**
+ * @brief iSCSI SCSI command READ CAPACITY(10) parameter data packet data.
+ *
+ * This returns the Logical Block Address (LBA)
+ * and block length in bytes.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_read_capacity_10_parameter_data_packet {
+ /// Last valid Logical Block Address (LBA).
+ uint32_t lba;
+
+ /// Block length in bytes.
+ uint32_t block_len;
+} iscsi_scsi_read_capacity_10_parameter_data_packet;
+
+
+/// iSCSI SCSI command SERVICE ACTION IN(16) parameter data logical blocks per physical block exponent: First bit of the four bits.
+#define ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_FIRST_BIT 0
+
+/// iSCSI SCSI command SERVICE ACTION IN(16) parameter data logical blocks per physical block exponent: Last bit of the four bits.
+#define ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_LAST_BIT ((ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_FIRST_BIT) + 4 - 1)
+
+/// iSCSI SCSI command SERVICE ACTION IN(16) parameter data logical blocks per physical block exponent: Bit mask.
+#define ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_FIRST_BIT, ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_LAST_BIT))
+
+/// iSCSI SCSI command SERVICE ACTION IN(16) parameter data logical blocks per physical block exponent: Extracts the logical blocks per physical block bits.
+#define ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_GET_LBPPB_EXPONENT(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_FIRST_BIT, ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_LAST_BIT))
+
+/// iSCSI SCSI command SERVICE ACTION IN(16) parameter data logical blocks per physical block exponent: Stores into the logical blocks per physical block bits.
+#define ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_PUT_LBPPB_EXPONENT(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_FIRST_BIT, ISCSI_SCSI_SERVICE_ACTION_IN_16_PARAM_DATA_LBPPB_EXPONENT_LAST_BIT))
+
+
+/**
+ * @brief iSCSI SCSI command SERVICE ACTION IN(16) parameter data packet data.
+ *
+ * This returns the Logical Block Address (LBA),
+ * block length in bytes and LBP information.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_service_action_in_16_parameter_data_packet {
+ /// Last valid Logical Block Address (LBA).
+ uint64_t lba;
+
+ /// Block length in bytes.
+ uint32_t block_len;
+
+ /// Flags: RC_BASIS, P_TYPE and PROT_EN.
+ uint8_t flags;
+
+ /// P_I_EXPONENT and logical blocks per physical block exponent.
+ uint8_t exponents;
+
+ /// Logical Block Provisioning Management Enabled (LBPME), Logical Block Provisioning Read Zeros (LBPRZ) and Lowest Aligned Logical Block Address (LALBA).
+ uint16_t lbp_lalba;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint64_t reserved[2];
+} iscsi_scsi_service_action_in_16_parameter_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI command REPORT LUNS parameter data LUN list packet data.
+ *
+ * This returns the number of entries in the
+ * LUN list in bytes.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_report_luns_parameter_data_lun_list_packet {
+ /// Number of LUN's following this packet in bytes.
+ uint32_t lun_list_len;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint32_t reserved;
+} iscsi_scsi_report_luns_parameter_data_lun_list_packet;
+
+
+/// iSCSI SCSI command MODE SENSE(6) parameter header data flags: DPO and FUA support (DPOFUA).
+#define ISCSI_SCSI_MODE_SENSE_6_PARAM_HDR_DATA_FLAGS_DPOFUA (1 << 4)
+
+/// iSCSI SCSI command MODE SENSE(6) parameter header data flags: Write Protect (WP).
+#define ISCSI_SCSI_MODE_SENSE_6_PARAM_HDR_DATA_FLAGS_WP (1 << 7)
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) parameter header packet data.
+ *
+ * This returns the mode parameter header
+ * data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_6_parameter_header_data_packet {
+ /// Mode data length in bytes.
+ uint8_t mode_data_len;
+
+ /// Medium type.
+ uint8_t medium_type;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Block descriptor length in bytes.
+ uint8_t block_desc_len;
+} iscsi_scsi_mode_sense_6_parameter_header_data_packet;
+
+
+/// iSCSI SCSI command MODE SENSE(10) parameter header data flags: DPO and FUA support (DPOFUA).
+#define ISCSI_SCSI_MODE_SENSE_10_PARAM_HDR_DATA_FLAGS_DPOFUA (1 << 4)
+
+/// iSCSI SCSI command MODE SENSE(10) parameter header data flags: Write Protect (WP).
+#define ISCSI_SCSI_MODE_SENSE_10_PARAM_HDR_DATA_FLAGS_WP (1 << 7)
+
+
+/// iSCSI SCSI command MODE SENSE(10) parameter header data Long Logical Block Address (LONGLBA).
+#define ISCSI_SCSI_MODE_SENSE_10_PARAM_HDR_DATA_LONGLBA (1 << 0)
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(10) parameter header packet data.
+ *
+ * This returns the mode parameter header
+ * data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_10_parameter_header_data_packet {
+ /// Mode data length in bytes.
+ uint16_t mode_data_len;
+
+ /// Medium type.
+ uint8_t medium_type;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Long Logical Block Address (LONGLBA).
+ uint8_t long_lba;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved;
+
+ /// Block descriptor length in bytes.
+ uint16_t block_desc_len;
+} iscsi_scsi_mode_sense_10_parameter_header_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) short LBA mode parameter block descriptor packet data.
+ *
+ * This returns the short Logical Block
+ * Address (LBA) mode parameter block
+ * descriptor data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_lba_parameter_block_desc_data_packet {
+ /// Number of blocks in logical blocks.
+ uint32_t num_blocks;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved;
+
+ /// Logical blcok length in bytes.
+ uint8_t block_len[3];
+} iscsi_scsi_mode_sense_lba_parameter_block_desc_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(10) long LBA mode parameter block descriptor packet data.
+ *
+ * This returns the long Logical Block
+ * Address (LBA) mode parameter block
+ * descriptor data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_long_lba_parameter_block_desc_data_packet {
+ /// Number of blocks in logical blocks.
+ uint64_t num_blocks;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint32_t reserved;
+
+ /// Logical blcok length in bytes.
+ uint32_t block_len;
+} iscsi_scsi_mode_sense_long_lba_parameter_block_desc_data_packet;
+
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Vendor specific.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_VENDOR_SPEC 0x00
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Read/Write error recovery.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_READ_WRITE_ERR_RECOVERY 0x01
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Disconnect / Reconnect.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_DISCONNECT_RECONNECT 0x02
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Format device.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_FORMAT_DEVICE 0x03
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Rigid disk geometry.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_RIGID_DISK_GEOMETRY 0x04
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Rigid disk geometry.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_RIGID_DISK_GEOMETRY_2 0x05
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Verify error recovery.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_VERIFY_ERR_RECOVERY 0x07
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Caching.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_CACHING 0x08
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Control.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_CONTROL 0x0A
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Medium types supported.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_MEDIUM_TYPES_SUPPORTED 0x0B
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Notch and partition.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_NOTCH_AND_PARTITION 0x0C
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: XOR control.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_XOR_CONTROL 0x10
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Enclosure services management.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_ENCLOSURE_SERVICES_MGMT 0x14
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Protocol specific LUN.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_PROTOCOL_SPEC_LUN 0x18
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Protocol specific Port.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_PROTOCOL_SPEC_PORT 0x19
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Power condition.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_POWER_COND 0x1A
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Informational exceptions control.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_INFO_EXCEPTIOS_CONTROL 0x1C
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Report all mode pages.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_REPORT_ALL_MODE_PAGES 0x3F
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode sub page code: Control.
+#define ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_CONTROL 0x00
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode sub page code: Control extension.
+#define ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_CONTROL_EXT 0x01
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode sub page code: All sub pages.
+#define ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_CONTROL_ALL 0xFF
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode sub page code: Report all mode pages.
+#define ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_REPORT_ALL_MODE_PAGES 0x00
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode sub page code: Report all mode pages and sub pages.
+#define ISCSI_SCSI_MODE_SENSE_MODE_SUB_PAGE_CODE_REPORT_ALL_MODE_SUB_PAGES 0xFF
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: First bit of the six bits.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_FIRST_BIT 0
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Last bit of the six bits.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_LAST_BIT ((ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_FIRST_BIT) + 6 - 1)
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Bit mask.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_MASK (ISCSI_BITS_GET_MASK(ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_FIRST_BIT, ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_LAST_BIT))
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Extracts the page code bits.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_GET_PAGE_CODE(x) (ISCSI_BITS_GET((x), ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_FIRST_BIT, ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_LAST_BIT))
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page code: Stores into the page code bits.
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_PUT_PAGE_CODE(x) (ISCSI_BITS_PUT((x), ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_FIRST_BIT, ISCSI_SCSI_MODE_SENSE_MODE_PAGE_CODE_LAST_BIT))
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page flags: Sub Page Format (SPF).
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_FLAGS_SPF (1 << 6)
+
+/// iSCSI SCSI command MODE SENSE(10) parameter header data flags: Parameters Saveable (PS).
+#define ISCSI_SCSI_MODE_SENSE_MODE_PAGE_FLAGS_PS (1 << 7)
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode page packet data.
+ *
+ * This returns mode page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_mode_page_data_header {
+ /// Page code and flags.
+ uint8_t page_code_flags;
+
+ /// Page length in bytes.
+ uint8_t page_len;
+} iscsi_scsi_mode_sense_mode_page_data_header;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) mode sub page packet data.
+ *
+ * This returns mode sub page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_mode_sub_page_data_header {
+ /// Page code and flags.
+ uint8_t page_code_flags;
+
+ /// Sub page code.
+ uint8_t sub_page_code;
+
+ /// Page length in bytes.
+ uint16_t page_len;
+} iscsi_scsi_mode_sense_mode_sub_page_data_header;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) read/write error recovery mode page packet data.
+ *
+ * This returns mode page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_read_write_err_recovery_mode_page_data_packet {
+ /// Mode page.
+ iscsi_scsi_mode_sense_mode_page_data_header mode_page;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Read retry count.
+ uint8_t read_retry_cnt;
+
+ /// Obselete.
+ uint8_t obselete[3];
+
+ /// Restricted for MMC-6.
+ uint8_t restrict_mmc_6;
+
+ /// Write_retry count.
+ uint8_t write_retry_cnt;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved;
+
+ /// Recovery time limit.
+ uint16_t recovery_time_limit;
+} iscsi_scsi_mode_sense_read_write_err_recovery_mode_page_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) disconnect / reconnect mode page packet data.
+ *
+ * This returns mode page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_disconnect_reconnect_mode_page_data_packet {
+ /// Mode page.
+ iscsi_scsi_mode_sense_mode_page_data_header mode_page;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint16_t reserved;
+
+ /// Bus inactivity time limit.
+ uint16_t bus_inactivity_time_limit;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint16_t reserved2;
+
+ /// Maximum connect time limit.
+ uint16_t max_connect_time_limit;
+
+ /// Maximum burst size.
+ uint16_t max_burst_size;
+
+ /// Restricted.
+ uint8_t restricted;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved3;
+
+ /// First burst size.
+ uint16_t first_burst_size;
+} iscsi_scsi_mode_sense_disconnect_reconnect_mode_page_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) verify error recovery mode page packet data.
+ *
+ * This returns mode page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_verify_err_recovery_mode_page_data_packet {
+ /// Mode page.
+ iscsi_scsi_mode_sense_mode_page_data_header mode_page;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Verify retry count.
+ uint8_t verify_retry_cnt;
+
+ /// Obselete.
+ uint8_t obselete;
+
+ /// Head offset count.
+ uint8_t head_offset_cnt;
+
+ /// Data strobe offset count.
+ uint8_t data_strobe_offset_cnt;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved;
+
+ /// Write retry count.
+ uint8_t write_retry_cnt;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved2;
+
+ /// Verify_recovery time limit.
+ uint16_t verify_recovery_time_limit;
+} iscsi_scsi_mode_sense_verify_err_recovery_mode_page_data_packet;
+
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) caching mode page flags: READ Cache Disable (RCD).
+#define ISCSI_SCSI_MODE_SENSE_CACHING_MODE_PAGE_FLAGS_RCD (1 << 0)
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) caching mode page flags: Multiplication factor (MF).
+#define ISCSI_SCSI_MODE_SENSE_CACHING_MODE_PAGE_FLAGS_MF (1 << 1)
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) caching mode page flags: Write Cache Enable (WCE).
+#define ISCSI_SCSI_MODE_SENSE_CACHING_MODE_PAGE_FLAGS_WCE (1 << 2)
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) caching mode page flags: Size Enable (SIZE).
+#define ISCSI_SCSI_MODE_SENSE_CACHING_MODE_PAGE_FLAGS_SIZE (1 << 3)
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) caching mode page flags: Discontinuity (DISC).
+#define ISCSI_SCSI_MODE_SENSE_CACHING_MODE_PAGE_FLAGS_DISC (1 << 4)
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) caching mode page flags: Caching Analysis Permitted (CAP).
+#define ISCSI_SCSI_MODE_SENSE_CACHING_MODE_PAGE_FLAGS_CAP (1 << 5)
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) caching mode page flags: Abort Prefetch (ABPF).
+#define ISCSI_SCSI_MODE_SENSE_CACHING_MODE_PAGE_FLAGS_ABPF (1 << 6)
+
+/// iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) caching mode page flags: Initiator Control (IC).
+#define ISCSI_SCSI_MODE_SENSE_CACHING_MODE_PAGE_FLAGS_IC (1 << 7)
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) caching mode page packet data.
+ *
+ * This returns mode page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_caching_mode_page_data_packet {
+ /// Mode page.
+ iscsi_scsi_mode_sense_mode_page_data_header mode_page;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Retention priority.
+ uint8_t retention_pri;
+
+ /// Disable prefetch transfer length.
+ uint16_t disable_prefetch_xfer_len;
+
+ /// Minimum prefetch.
+ uint16_t min_prefetch;
+
+ /// Maximum prefetch.
+ uint16_t max_prefetch;
+
+ /// Maximum prefetch ceiling.
+ uint16_t max_prefetch_ceil;
+
+ /// Cache flags.
+ uint8_t cache_flags;
+
+ /// Number of cache segments.
+ uint8_t num_cache_segs;
+
+ /// Cache segment size.
+ uint16_t cache_seg_size;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved;
+
+ /// Obselete.
+ uint8_t obselete[3];
+} iscsi_scsi_mode_sense_caching_mode_page_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) control mode page packet data.
+ *
+ * This returns mode page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_control_mode_page_data_packet {
+ /// Mode page.
+ iscsi_scsi_mode_sense_mode_page_data_header mode_page;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Queue flags.
+ uint8_t queue_flags;
+
+ /// Control flags.
+ uint8_t control_flags;
+
+ /// Application task flags.
+ uint8_t app_task_flags;
+
+ /// Ready AER holdoff period.
+ uint16_t ready_aer_holdoff_period;
+
+ /// Busy timeout period.
+ uint16_t busy_timeout_period;
+
+ /// Extended self-test completition time.
+ uint16_t ext_self_test_complete_time;
+} iscsi_scsi_mode_sense_control_mode_page_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) control extension mode sub page packet data.
+ *
+ * This returns mode sub page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_control_ext_mode_page_data_packet {
+ /// Mode page.
+ iscsi_scsi_mode_sense_mode_sub_page_data_header mode_sub_page;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Initial command priority.
+ uint8_t init_cmd_pri;
+
+ /// Maximum sense data length in bytes.
+ uint8_t max_sense_data_len;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint64_t reserved[3];
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved2;
+} iscsi_scsi_mode_sense_control_ext_mode_page_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) XOR extension mode page packet data.
+ *
+ * This returns mode page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_xor_ext_mode_page_data_packet {
+ /// Mode page.
+ iscsi_scsi_mode_sense_mode_page_data_header mode_page;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved;
+
+ /// Maximum XOR write size in logical blocks.
+ uint32_t max_xor_write_size;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint32_t reserved2;
+
+ /// Maximum regenerate size in logical blocks.
+ uint32_t max_regenerate_size;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint32_t reserved3;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint16_t reserved4;
+
+ /// Rebuild delay.
+ uint16_t rebuild_delay;
+} iscsi_scsi_mode_sense_xor_ext_mode_page_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) power condition mode page packet data.
+ *
+ * This returns mode page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_power_cond_mode_page_data_packet {
+ /// Mode page.
+ iscsi_scsi_mode_sense_mode_page_data_header mode_page;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Idle and standby flags.
+ uint8_t idle_standby_flags;
+
+ /// idle_a condition timer.
+ uint32_t idle_a_cond_timer;
+
+ /// standby_z condition timer.
+ uint32_t standby_z_cond_timer;
+
+ /// idle_b condition timer.
+ uint32_t idle_b_cond_timer;
+
+ /// idle_c condition timer.
+ uint32_t idle_c_cond_timer;
+
+ /// standby_y condition timer.
+ uint32_t standby_y_cond_timer;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint64_t reserved;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint32_t reserved2;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint16_t reserved3;
+
+ /// Reserved for future usage (always MUST be 0 for now).
+ uint8_t reserved4;
+
+ /// Check Condition From (CCF) flags.
+ uint8_t ccf_flags;
+} iscsi_scsi_mode_sense_power_cond_mode_page_data_packet;
+
+
+/**
+ * @brief iSCSI SCSI command MODE SENSE(6) and MODE SENSE(10) informational exceptions control mode page packet data.
+ *
+ * This returns mode page specific data.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_mode_sense_info_exceptions_control_mode_page_data_packet {
+ /// Mode page.
+ iscsi_scsi_mode_sense_mode_page_data_header mode_page;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// Method Of Reporting Informational Exceptions (MRIE) flags.
+ uint8_t mrie;
+
+ /// Interval timer.
+ uint32_t interval_timer;
+
+ /// Report count.
+ uint32_t report_cnt;
+} iscsi_scsi_mode_sense_info_exceptions_control_mode_page_data_packet;
+
+
+
+/// SCSI command opcode (embedded in iSCSI protocol): TEST UNIT READY.
+#define ISCSI_SCSI_OPCODE_TESTUNITREADY 0x00
+
+/// SCSI command opcode (embedded in iSCSI protocol): REQUEST SENSE.
+#define ISCSI_SCSI_OPCODE_REQUESTSENSE 0x03
+
+/// SCSI command opcode (embedded in iSCSI protocol): READ(6).
+#define ISCSI_SCSI_OPCODE_READ6 0x08
+
+/// SCSI command opcode (embedded in iSCSI protocol): WRITE(6).
+#define ISCSI_SCSI_OPCODE_WRITE6 0x0A
+
+/// SCSI command opcode (embedded in iSCSI protocol): INQUIRY.
+#define ISCSI_SCSI_OPCODE_INQUIRY 0x12
+
+/// SCSI command opcode (embedded in iSCSI protocol): MODE SELECT(6).
+#define ISCSI_SCSI_OPCODE_MODESELECT6 0x15
+
+/// SCSI command opcode (embedded in iSCSI protocol): RESERVE(6).
+#define ISCSI_SCSI_OPCODE_RESERVE6 0x16
+
+/// SCSI command opcode (embedded in iSCSI protocol): RELEASE(6).
+#define ISCSI_SCSI_OPCODE_RELEASE6 0x17
+
+/// SCSI command opcode (embedded in iSCSI protocol): MODE SENSE(6).
+#define ISCSI_SCSI_OPCODE_MODESENSE6 0x1A
+
+/// SCSI command opcode (embedded in iSCSI protocol): START STOP UNIT.
+#define ISCSI_SCSI_OPCODE_STARTSTOPUNIT 0x1B
+
+/// SCSI command opcode (embedded in iSCSI protocol): PREVENT ALLOW MEDIUM REMOVAL.
+#define ISCSI_SCSI_OPCODE_PREVENTALLOW 0x1E
+
+/// SCSI command opcode (embedded in iSCSI protocol): READ CAPACITY(10).
+#define ISCSI_SCSI_OPCODE_READCAPACITY10 0x25
+
+/// SCSI command opcode (embedded in iSCSI protocol): READ(10).
+#define ISCSI_SCSI_OPCODE_READ10 0x28
+
+/// SCSI command opcode (embedded in iSCSI protocol): WRITE(10).
+#define ISCSI_SCSI_OPCODE_WRITE10 0x2A
+
+/// SCSI command opcode (embedded in iSCSI protocol): WRITE AND VERIFY(10).
+#define ISCSI_SCSI_OPCODE_WRITE_VERIFY10 0x2E
+
+/// SCSI command opcode (embedded in iSCSI protocol): VERIFY(10).
+#define ISCSI_SCSI_OPCODE_VERIFY10 0x2F
+
+/// SCSI command opcode (embedded in iSCSI protocol): PRE-FETCH(10).
+#define ISCSI_SCSI_OPCODE_PREFETCH10 0x34
+
+/// SCSI command opcode (embedded in iSCSI protocol): SYNCHRONIZE CACHE(10).
+#define ISCSI_SCSI_OPCODE_SYNCHRONIZECACHE10 0x35
+
+/// SCSI command opcode (embedded in iSCSI protocol): READ DEFECT DATA(10).
+#define ISCSI_SCSI_OPCODE_READ_DEFECT_DATA10 0x37
+
+/// SCSI command opcode (embedded in iSCSI protocol): WRITE SAME(10).
+#define ISCSI_SCSI_OPCODE_WRITE_SAME10 0x41
+
+/// SCSI command opcode (embedded in iSCSI protocol): UNMAP.
+#define ISCSI_SCSI_OPCODE_UNMAP 0x42
+
+/// SCSI command opcode (embedded in iSCSI protocol): READ TOC/PMA/ATIP.
+#define ISCSI_SCSI_OPCODE_READTOC 0x43
+
+/// SCSI command opcode (embedded in iSCSI protocol): SANITIZE.
+#define ISCSI_SCSI_OPCODE_SANITIZE 0x48
+
+/// SCSI command opcode (embedded in iSCSI protocol): LOG SELECT.
+#define ISCSI_SCSI_OPCODE_LOGSELECT 0x4C
+
+/// SCSI command opcode (embedded in iSCSI protocol): LOG SENSE.
+#define ISCSI_SCSI_OPCODE_LOGSENSE 0x4D
+
+/// SCSI command opcode (embedded in iSCSI protocol): MODE SELECT(10).
+#define ISCSI_SCSI_OPCODE_MODESELECT10 0x55
+
+/// SCSI command opcode (embedded in iSCSI protocol): RESERVE(10).
+#define ISCSI_SCSI_OPCODE_RESERVE10 0x56
+
+/// SCSI command opcode (embedded in iSCSI protocol): RELEASE(10).
+#define ISCSI_SCSI_OPCODE_RELEASE10 0x57
+
+/// SCSI command opcode (embedded in iSCSI protocol): MODE SENSE(10).
+#define ISCSI_SCSI_OPCODE_MODESENSE10 0x5A
+
+/// SCSI command opcode (embedded in iSCSI protocol): PERSISTENT RESERVE IN.
+#define ISCSI_SCSI_OPCODE_PERSISTENT_RESERVE_IN 0x5E
+
+/// SCSI command opcode (embedded in iSCSI protocol): PERSISTENT RESERVE OUT.
+#define ISCSI_SCSI_OPCODE_PERSISTENT_RESERVE_OUT 0x5F
+
+/// SCSI command opcode (embedded in iSCSI protocol): Third-party Copy OUT.
+#define ISCSI_SCSI_OPCODE_EXTENDED_COPY 0x83
+
+/// SCSI command opcode (embedded in iSCSI protocol): Third-party Copy IN.
+#define ISCSI_SCSI_OPCODE_RECEIVE_COPY_RESULTS 0x84
+
+/// SCSI command opcode (embedded in iSCSI protocol): READ(16).
+#define ISCSI_SCSI_OPCODE_READ16 0x88
+
+/// SCSI command opcode (embedded in iSCSI protocol): COMPARE AND WRITE.
+#define ISCSI_SCSI_OPCODE_COMPARE_AND_WRITE 0x89
+
+/// SCSI command opcode (embedded in iSCSI protocol): WRITE(16).
+#define ISCSI_SCSI_OPCODE_WRITE16 0x8A
+
+/// SCSI command opcode (embedded in iSCSI protocol): ORWRITE.
+#define ISCSI_SCSI_OPCODE_ORWRITE 0x8B
+
+/// SCSI command opcode (embedded in iSCSI protocol): WRITE AND VERIFY(16).
+#define ISCSI_SCSI_OPCODE_WRITE_VERIFY16 0x8E
+
+/// SCSI command opcode (embedded in iSCSI protocol): VERIFY(16).
+#define ISCSI_SCSI_OPCODE_VERIFY16 0x8F
+
+/// SCSI command opcode (embedded in iSCSI protocol): PRE-FETCH(16).
+#define ISCSI_SCSI_OPCODE_PREFETCH16 0x90
+
+/// SCSI command opcode (embedded in iSCSI protocol): SYNCHRONIZE CACHE(16).
+#define ISCSI_SCSI_OPCODE_SYNCHRONIZECACHE16 0x91
+
+/// SCSI command opcode (embedded in iSCSI protocol): WRITE SAME(16).
+#define ISCSI_SCSI_OPCODE_WRITE_SAME16 0x93
+
+/// SCSI command opcode (embedded in iSCSI protocol): WRITE ATOMIC(16).
+#define ISCSI_SCSI_OPCODE_WRITE_ATOMIC16 0x9C
+
+/// SCSI command opcode (embedded in iSCSI protocol): SERVICE ACTION IN(16).
+#define ISCSI_SCSI_OPCODE_SERVICE_ACTION_IN_16 0x9E
+
+/// SCSI command opcode (embedded in iSCSI protocol): REPORT LUNS.
+#define ISCSI_SCSI_OPCODE_REPORTLUNS 0xA0
+
+/// SCSI command opcode (embedded in iSCSI protocol): MAINTENANCE IN.
+#define ISCSI_SCSI_OPCODE_MAINTENANCE_IN 0xA3
+
+/// SCSI command opcode (embedded in iSCSI protocol): READ(12).
+#define ISCSI_SCSI_OPCODE_READ12 0xA8
+
+/// SCSI command opcode (embedded in iSCSI protocol): WRITE(12).
+#define ISCSI_SCSI_OPCODE_WRITE12 0xAA
+
+/// SCSI command opcode (embedded in iSCSI protocol): WRITE AND VERIFY(12).
+#define ISCSI_SCSI_OPCODE_WRITE_VERIFY12 0xAE
+
+/// SCSI command opcode (embedded in iSCSI protocol): VERIFY(12).
+#define ISCSI_SCSI_OPCODE_VERIFY12 0xAF
+
+/// SCSI command opcode (embedded in iSCSI protocol): READ DEFECT DATA(12).
+#define ISCSI_SCSI_OPCODE_READ_DEFECT_DATA12 0xB7
+
+
+/**
+ * @brief iSCSI SCSI command flags: No unsolicited data.
+ *
+ * (F) is set to 1 when no unsolicited SCSI Data-Out PDUs
+ * follow this PDU. When F = 1 for a write and if Expected
+ * Data Transfer Length is larger than the
+ * DataSegmentLength, the target may solicit additional data
+ * through R2T.
+ */
+#define ISCSI_SCSI_CMD_FLAGS_TASK_NO_UNSOLICITED_DATA (1 << 7)
+
+
+/// SCSI SCSI command flags: Final.
+#define ISCSI_SCSI_CMD_FLAGS_FINAL (1 << 7)
+
+/**
+ * @brief iSCSI SCSI command flags: Expected input data.
+ *
+ * (R) is set to 1 when the command is expected to input data.
+ */
+#define ISCSI_SCSI_CMD_FLAGS_TASK_READ (1 << 6)
+
+/**
+ * @brief iSCSI SCSI command flags: Expected output data.
+ *
+ * (W) is set to 1 when the command is expected to output data.
+ */
+#define ISCSI_SCSI_CMD_FLAGS_TASK_WRITE (1 << 5)
+
+
+/// SCSI command flags task attribute: Untagged.
+#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_UNTAGGED 0x0
+
+/// SCSI command flags task attribute: Simple.
+#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_SIMPLE 0x1
+
+/// SCSI command flags task attribute: Ordered.
+#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_ORDERED 0x2
+
+/// SCSI command flags task attribute: Head of queue.
+#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_HEAD_QUEUE 0x3
+
+/// SCSI command flags task attribute: ACA.
+#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_ACA 0x4
+
+/// SCSI command flags Task Attributes (ATTR) are encoded in the first three LSBs.
+#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_MASK 0x7
+
+
+/**
+ * @brief iSCSI Flag and Task Attributes for SCSI command packet data.
+ *
+ * Flags and Task Attributes:
+ * At least one of the W and F bits MUST be set to 1.\n
+ * Either or both of R and W MAY be 1 when the Expected Data Transfer
+ * Length and/or the Bidirectional Read Expected Data Transfer Length
+ * are 0, but they MUST NOT both be 0 when the Expected Data Transfer
+ * Length and/or Bidirectional Read Expected Data Transfer Length are
+ * not 0 (i.e., when some data transfer is expected, the transfer
+ * direction is indicated by the R and/or W bit).
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_cmd_packet {
+ /// Always 1 according to the iSCSI specification.
+ uint8_t opcode;
+
+ /// Flags and Task Attributes.
+ uint8_t flags_task;
+
+ /// Reserved for future usage, MUST always be 0.
+ uint16_t reserved;
+
+ /// Total length of AHS.
+ uint8_t total_ahs_len;
+
+ /// Length of DataSegment.
+ uint8_t ds_len[3];
+
+ /// SCSI LUN bit mask.
+ uint64_t lun;
+
+ /// Initiator Task Tag (ITT).
+ uint32_t init_task_tag;
+
+ /**
+ * @brief Expected Data Transfer Length.
+ *
+ * For unidirectional operations, the Expected Data Transfer Length
+ * field contains the number of bytes of data involved in this SCSI
+ * operation. For a unidirectional write operation (W flag set to 1 and
+ * R flag set to 0), the initiator uses this field to specify the number
+ * of bytes of data it expects to transfer for this operation. For a
+ * unidirectional read operation (W flag set to 0 and R flag set to 1),
+ * the initiator uses this field to specify the number of bytes of data
+ * it expects the target to transfer to the initiator. It corresponds
+ * to the SAM-2 byte count.\n
+ * For bidirectional operations (both R and W flags are set to 1), this
+ * field contains the number of data bytes involved in the write
+ * transfer. For bidirectional operations, an additional header segment
+ * MUST be present in the header sequence that indicates the
+ * Bidirectional Read Expected Data Transfer Length. The Expected Data
+ * Transfer Length field and the Bidirectional Read Expected Data
+ * Transfer Length field correspond to the SAM-2 byte count.
+ * If the Expected Data Transfer Length for a write and the length of
+ * the immediate data part that follows the command (if any) are the
+ * same, then no more data PDUs are expected to follow. In this case,
+ * the F bit MUST be set to 1.\n
+ * If the Expected Data Transfer Length is higher than the
+ * FirstBurstLength (the negotiated maximum amount of unsolicited data
+ * the target will accept), the initiator MUST send the maximum amount
+ * of unsolicited data OR ONLY the immediate data, if any.
+ * Upon completion of a data transfer, the target informs the initiator
+ * (through residual counts) of how many bytes were actually processed
+ * (sent and/or received) by the target.
+ */
+ uint32_t exp_xfer_len;
+
+ /// The CmdSN enables ordered delivery across multiple connections in a single session.
+ uint32_t cmd_sn;
+
+ /// Command responses up to ExpStatSN - 1 (modulo 2**32) have been received (acknowledges status) on the connection.
+ uint32_t exp_stat_sn;
+
+ /**
+ * @brief SCSI Command Descriptor Block (CDB).
+ *
+ * There are 16 bytes in the CDB field to accommodate the commonly used
+ * CDBs. Whenever the CDB is larger than 16 bytes, an Extended CDB AHS
+ * MUST be used to contain the CDB spillover.
+ */
+ iscsi_scsi_cdb scsi_cdb;
+} iscsi_scsi_cmd_packet;
+
+
+/**
+ * @brief SCSI response flags: Residual Underflow.
+ *
+ * (U) set for Residual Underflow. In this case, the Residual
+ * Count indicates the number of bytes that were not
+ * transferred out of the number of bytes that were expected
+ * to be transferred. For a bidirectional operation, the
+ * Residual Count contains the residual for the write
+ * operation.
+ *
+ * Bits O and U and bits o and u are mutually exclusive (i.e., having
+ * both o and u or O and U set to 1 is a protocol error).
+ *
+ * For a response other than "Command Completed at Target", bits 3-6
+ * MUST be 0.
+ */
+#define ISCSI_SCSI_RESPONSE_FLAGS_RES_UNDERFLOW (1 << 1)
+
+/**
+ * @brief SCSI response flags: Residual Overflow.
+ *
+ * (O) set for Residual Overflow. In this case, the Residual
+ * Count indicates the number of bytes that were not
+ * transferred because the initiator's Expected Data
+ * Transfer Length was not sufficient. For a bidirectional
+ * operation, the Residual Count contains the residual for
+ * the write operation.
+ *
+ * Bits O and U and bits o and u are mutually exclusive (i.e., having
+ * both o and u or O and U set to 1 is a protocol error).
+ *
+ * For a response other than "Command Completed at Target", bits 3-6
+ * MUST be 0.
+ */
+#define ISCSI_SCSI_RESPONSE_FLAGS_RES_OVERFLOW (1 << 2)
+
+/**
+ * @brief SCSI response flags: Bidirectional Read Residual Underflow.
+ *
+ * (u) set for Bidirectional Read Residual Underflow. In this
+ * case, the Bidirectional Read Residual Count indicates the
+ * number of bytes that were not transferred to the
+ * initiator out of the number of bytes expected to be
+ * transferred.
+ *
+ * Bits O and U and bits o and u are mutually exclusive (i.e., having
+ * both o and u or O and U set to 1 is a protocol error).
+ *
+ * For a response other than "Command Completed at Target", bits 3-6
+ * MUST be 0.
+ */
+#define ISCSI_SCSI_RESPONSE_FLAGS_BIDI_READ_RES_UNDERFLOW (1 << 3)
+
+/**
+ * @brief SCSI response flags: Bidirectional Read Residual Overflow.
+ *
+ + (o) set for Bidirectional Read Residual Overflow. In this
+ * case, the Bidirectional Read Residual Count indicates the
+ * number of bytes that were not transferred to the
+ * initiator because the initiator's Bidirectional Read
+ * Expected Data Transfer Length was not sufficient.
+ *
+ * Bits O and U and bits o and u are mutually exclusive (i.e., having
+ * both o and u or O and U set to 1 is a protocol error).
+ *
+ * For a response other than "Command Completed at Target", bits 3-6
+ * MUST be 0.
+ */
+#define ISCSI_SCSI_RESPONSE_FLAGS_BIDI_READ_RES_OVERFLOW (1 << 4)
+
+/**
+ * @brief SCSI status response code: Good.
+ *
+ * The Status field is used to report the SCSI status of the command (as
+ * specified in SAM2) and is only valid if the response code is
+ * Command Completed at Target.
+ *
+ * If a SCSI device error is detected while data from the initiator is
+ * still expected (the command PDU did not contain all the data and the
+ * target has not received a data PDU with the Final bit set), the
+ * target MUST wait until it receives a data PDU with the F bit set in
+ * the last expected sequence before sending the Response PDU.
+ */
+#define ISCSI_SCSI_RESPONSE_STATUS_GOOD 0x00
+
+/**
+ * @brief SCSI status response code: Check condition.
+ *
+ * The Status field is used to report the SCSI status of the command (as
+ * specified in SAM2) and is only valid if the response code is
+ * Command Completed at Target.
+ *
+ * If a SCSI device error is detected while data from the initiator is
+ * still expected (the command PDU did not contain all the data and the
+ * target has not received a data PDU with the Final bit set), the
+ * target MUST wait until it receives a data PDU with the F bit set in
+ * the last expected sequence before sending the Response PDU.
+ */
+#define ISCSI_SCSI_RESPONSE_STATUS_CHECK_COND 0x02
+
+/**
+ * @brief SCSI status response code: Busy.
+ *
+ * The Status field is used to report the SCSI status of the command (as
+ * specified in SAM2) and is only valid if the response code is
+ * Command Completed at Target.
+ *
+ * If a SCSI device error is detected while data from the initiator is
+ * still expected (the command PDU did not contain all the data and the
+ * target has not received a data PDU with the Final bit set), the
+ * target MUST wait until it receives a data PDU with the F bit set in
+ * the last expected sequence before sending the Response PDU.
+ */
+#define ISCSI_SCSI_RESPONSE_STATUS_BUSY 0x08
+
+/**
+ * @brief SCSI status response code: Residual conflict.
+ *
+ * The Status field is used to report the SCSI status of the command (as
+ * specified in SAM2) and is only valid if the response code is
+ * Command Completed at Target.
+ *
+ * If a SCSI device error is detected while data from the initiator is
+ * still expected (the command PDU did not contain all the data and the
+ * target has not received a data PDU with the Final bit set), the
+ * target MUST wait until it receives a data PDU with the F bit set in
+ * the last expected sequence before sending the Response PDU.
+ */
+#define ISCSI_SCSI_RESPONSE_STATUS_RES_CONFLICT 0x18
+
+/**
+ * @brief SCSI status response code: Task set full.
+ *
+ * The Status field is used to report the SCSI status of the command (as
+ * specified in SAM2) and is only valid if the response code is
+ * Command Completed at Target.
+ *
+ * If a SCSI device error is detected while data from the initiator is
+ * still expected (the command PDU did not contain all the data and the
+ * target has not received a data PDU with the Final bit set), the
+ * target MUST wait until it receives a data PDU with the F bit set in
+ * the last expected sequence before sending the Response PDU.
+ */
+#define ISCSI_SCSI_RESPONSE_STATUS_TASK_SET_FULL 0x28
+
+/**
+ * @brief SCSI status response code: ACA active.
+ *
+ * The Status field is used to report the SCSI status of the command (as
+ * specified in SAM2) and is only valid if the response code is
+ * Command Completed at Target.
+ *
+ * If a SCSI device error is detected while data from the initiator is
+ * still expected (the command PDU did not contain all the data and the
+ * target has not received a data PDU with the Final bit set), the
+ * target MUST wait until it receives a data PDU with the F bit set in
+ * the last expected sequence before sending the Response PDU.
+ */
+#define ISCSI_SCSI_RESPONSE_STATUS_ACA_ACTIVE 0x30
+
+/**
+ * @brief SCSI status response code: Task aborted.
+ *
+ * The Status field is used to report the SCSI status of the command (as
+ * specified in SAM2) and is only valid if the response code is
+ * Command Completed at Target.
+ *
+ * If a SCSI device error is detected while data from the initiator is
+ * still expected (the command PDU did not contain all the data and the
+ * target has not received a data PDU with the Final bit set), the
+ * target MUST wait until it receives a data PDU with the F bit set in
+ * the last expected sequence before sending the Response PDU.
+ */
+#define ISCSI_SCSI_RESPONSE_STATUS_TASK_ABORTED 0x40
+
+
+/// SCSI response code: Command Completed at Target.
+#define ISCSI_SCSI_RESPONSE_CODE_OK 0x00
+
+/// SCSI response code: Target Failure.
+#define ISCSI_SCSI_RESPONSE_CODE_FAIL 0x01
+
+/**
+ * @brief iSCSI SCSI command response packet data.
+ *
+ * The Response field is used to report a service response. The mapping
+ * of the response code into a SCSI service response code value, if
+ * needed, is outside the scope of this document. However, in symbolic
+ * terms, response value 0x00 maps to the SCSI service response (see
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_response_packet {
+ /// Always 0x21 according to specification.
+ uint8_t opcode;
+
+ /// Flags.
+ uint8_t flags;
+
+ /// This field contains the iSCSI service response.
+ uint8_t response;
+
+ /// The Status field is used to report the SCSI status of the command (as specified in SAM2) and is only valid if the response code is Command Completed at Target.
+ uint8_t status;
+
+ /// Total AHS length.
+ uint8_t total_ahs_len;
+
+ /// Data segment length.
+ uint8_t ds_len[3];
+
+ /// Reserved for future usage. Always MUST be 0.
+ uint64_t reserved;
+
+ /// Initiator Task Tag (ITT).
+ uint32_t init_task_tag;
+
+ /**
+ * @brief Copy of the last accepted Selective Negative / Sequence Number Acknowledgment (SNACK) tag.
+ *
+ * This field contains a copy of the SNACK Tag of the last SNACK Tag
+ * accepted by the target on the same connection and for the command for
+ * which the response is issued. Otherwise, it is reserved and should
+ * be set to 0.\n
+ * After issuing a R-Data SNACK, the initiator must discard any SCSI
+ * status unless contained in a SCSI Response PDU carrying the same
+ * SNACK Tag as the last issued R-Data SNACK for the SCSI command on the
+ * current connection.
+ */
+ uint32_t snack_tag;
+
+ /**
+ * @brief StatSN - Status Sequence Number.
+ *
+ * The StatSN is a sequence number that the target iSCSI layer generates
+ * per connection and that in turn enables the initiator to acknowledge
+ * status reception. The StatSN is incremented by 1 for every
+ * response/status sent on a connection, except for responses sent as a
+ * result of a retry or SNACK. In the case of responses sent due to a
+ * retransmission request, the StatSN MUST be the same as the first time
+ * the PDU was sent, unless the connection has since been restarted.
+ */
+ uint32_t stat_sn;
+
+ /**
+ * @brief ExpCmdSN - Next Expected CmdSN from This Initiator.
+ *
+ * The ExpCmdSN is a sequence number that the target iSCSI returns to
+ * the initiator to acknowledge command reception. It is used to update
+ * a local variable with the same name. An ExpCmdSN equal to
+ * MaxCmdSN + 1 indicates that the target cannot accept new commands.
+ */
+ uint32_t exp_cmd_sn;
+
+ /**
+ * @brief MaxCmdSN - Maximum CmdSN from This Initiator.
+ *
+ * The MaxCmdSN is a sequence number that the target iSCSI returns to
+ * the initiator to indicate the maximum CmdSN the initiator can send.
+ * It is used to update a local variable with the same name. If the
+ * MaxCmdSN is equal to ExpCmdSN - 1, this indicates to the initiator
+ * that the target cannot receive any additional commands. When the
+ * MaxCmdSN changes at the target while the target has no pending PDUs
+ * to convey this information to the initiator, it MUST generate a
+ * NOP-In to carry the new MaxCmdSN.
+ */
+ uint32_t max_cmd_sn;
+
+ /**
+ * @brief ExpDataSN or Reserved.
+ *
+ * This field indicates the number of Data-In (read) PDUs the target has
+ * sent for the command.\n
+ * This field MUST be 0 if the response code is not Command Completed at
+ * Target or the target sent no Data-In PDUs for the command.
+ */
+ uint32_t exp_data_sn;
+
+ /**
+ * @brief Bidirectional Read Residual Count or Reserved.
+ *
+ * The Bidirectional Read Residual Count field MUST be valid in the case
+ * where either the u bit or the o bit is set. If neither bit is set,
+ * the Bidirectional Read Residual Count field is reserved. Targets may
+ * set the Bidirectional Read Residual Count, and initiators may use it
+ * when the response code is Command Completed at Target. If the o bit
+ * is set, the Bidirectional Read Residual Count indicates the number of
+ * bytes that were not transferred to the initiator because the
+ * initiator's Bidirectional Read Expected Data Transfer Length was not
+ * sufficient. If the u bit is set, the Bidirectional Read Residual
+ * Count indicates the number of bytes that were not transferred to the
+ * initiator out of the number of bytes expected to be transferred.
+ */
+ uint32_t bidi_read_res_cnt;
+
+ /**
+ * @brief Residual Count or Reserved.
+ *
+ * The Residual Count field MUST be valid in the case where either the U
+ * bit or the O bit is set. If neither bit is set, the Residual Count
+ * field MUST be ignored on reception and SHOULD be set to 0 when
+ * sending. Targets may set the residual count, and initiators may use
+ * it when the response code is Command Completed at Target (even if the
+ * status returned is not GOOD). If the O bit is set, the Residual
+ * Count indicates the number of bytes that were not transferred because
+ * the initiator's Expected Data Transfer Length was not sufficient. If
+ * the U bit is set, the Residual Count indicates the number of bytes
+ * that were not transferred out of the number of bytes expected to be
+ * transferred.
+ */
+ uint32_t res_cnt;
+} iscsi_scsi_response_packet;
+
+
+/**
+ * @brief iSCSI Task Management Function Request packet data.
+ *
+ * This structure is used to explicity control the execution of one
+ * or more tasks (iSCSI and SCSI).
+ */
+typedef struct __attribute__((packed)) iscsi_task_mgmt_func_req_packet {
+ /// Always 2 according to iSCSI specification.
+ uint8_t opcode;
+
+ /**
+ * @brief Function.
+ *
+ * The task management functions provide an initiator with a way to
+ * explicitly control the execution of one or more tasks (SCSI and iSCSI
+ * tasks). The task management function codes are listed below. For a
+ * more detailed description of SCSI task management, see SAM2.
+ */
+ uint8_t func;
+
+ /// Reserved fot future usage, always MUST be 0.
+ uint16_t reserved;
+
+ /// TotalAHSLength (MUST be 0 for this PDU).
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength (MUST be 0 for this PDU).
+ uint8_t ds_len[3];
+
+ /**
+ * @brief Logical Unit Number (LUN) or Reserved.
+ *
+ * This field is required for functions that address a specific LU
+ * (ABORT TASK, CLEAR TASK SET, ABORT TASK SET, CLEAR ACA, LOGICAL UNIT
+ * RESET) and is reserved in all others
+ */
+ uint64_t lun;
+
+ /**
+ * @brief Initiator Task Tag (ITT).
+ *
+ * This is the Initiator Task Tag of the task to be aborted for the
+ * ABORT TASK function or reassigned for the TASK REASSIGN function.
+ * For all the other functions, this field MUST be set to the reserved
+ * value 0xFFFFFFFF.
+ */
+ uint32_t init_task_tag;
+
+ /// Referenced task tag or 0xFFFFFFFF.
+ uint32_t ref_task_tag;
+
+ /// CmdSN.
+ uint32_t cmd_sn;
+
+ /// ExpStatSN
+ uint32_t exp_stat_sn;
+
+ /**
+ * @brief RefCmdSN or Reserved.
+ *
+ * If an ABORT TASK is issued for a task created by an immediate
+ * command, then the RefCmdSN MUST be that of the task management
+ * request itself (i.e., the CmdSN and RefCmdSN are equal).\n
+ * For an ABORT TASK of a task created by a non-immediate command, the
+ * RefCmdSN MUST be set to the CmdSN of the task identified by the
+ * Referenced Task Tag field. Targets must use this field when the task
+ * identified by the Referenced Task Tag field is not with the target.
+ * Otherwise, this field is reserved.
+ */
+ uint32_t ref_cmd_sn;
+
+ /**
+ * @brief ExpDataSN or Reserved.
+ *
+ * For recovery purposes, the iSCSI target and initiator maintain a data
+ * acknowledgment reference number - the first input DataSN number
+ * unacknowledged by the initiator. When issuing a new command, this
+ * number is set to 0. If the function is TASK REASSIGN, which
+ * establishes a new connection allegiance for a previously issued read
+ * or bidirectional command, the ExpDataSN will contain an updated data
+ * acknowledgment reference number or the value 0; the latter indicates
+ * that the data acknowledgment reference number is unchanged. The
+ * initiator MUST discard any data PDUs from the previous execution that
+ * it did not acknowledge, and the target MUST transmit all Data-In PDUs
+ * (if any) starting with the data acknowledgment reference number. The
+ * number of retransmitted PDUs may or may not be the same as the
+ * original transmission, depending on if there was a change in
+ * MaxRecvDataSegmentLength in the reassignment. The target MAY also
+ * send no more Data-In PDUs if all data has been acknowledged.
+ * The value of ExpDataSN MUST be 0 or higher than the DataSN of the
+ * last acknowledged Data-In PDU, but not larger than DataSN + 1 of the
+ * last Data-IN PDU sent by the target. Any other value MUST be ignored
+ * by the target.
+ * For other functions, this field is reserved
+ */
+ uint32_t exp_data_sn;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved2;
+} iscsi_task_mgmt_func_req_packet;
+
+
+/// Task management function response: Function complete.
+#define ISCSI_TASK_MGMT_FUNC_RESPONSE_FUNC_COMPLETE 0x00
+
+/**
+ * @brief iSCSI Task Management Function Response packet data.
+ *
+ * For the functions ABORT TASK, ABORT TASK SET, CLEAR ACA, CLEAR TASK
+ * SET, LOGICAL UNIT RESET, TARGET COLD RESET, TARGET WARM RESET, and
+ * TASK REASSIGN, the target performs the requested task management
+ * function and sends a task management response back to the initiator.
+ * For TASK REASSIGN, the new connection allegiance MUST ONLY become
+ * effective at the target after the target issues the task management
+ * response.
+ */
+typedef struct __attribute__((packed)) iscsi_task_mgmt_func_response_packet {
+ /// Always 0x22 according to specification.
+ uint8_t opcode;
+
+ /// Reserved for future usage (always MUST be 0x80 for now).
+ uint8_t flags;
+
+ /**
+ * @brief Function response.
+ *
+ * For the TARGET COLD RESET and TARGET WARM RESET functions, the target
+ * cancels all pending operations across all LUs known to the issuing
+ * initiator. For the TARGET COLD RESET function, the target MUST then
+ * close all of its TCP connections to all initiators (terminates all
+ * sessions).\n
+ * The mapping of the response code into a SCSI service response code
+ * value, if needed, is outside the scope of this document. However, in
+ * symbolic terms, Response values 0 and 1 map to the SCSI service
+ * response of FUNCTION COMPLETE. Response value 2 maps to the SCSI
+ * service response of INCORRECT LOGICAL UNIT NUMBER. All other
+ * Response values map to the SCSI service response of FUNCTION
+ * REJECTED. If a Task Management Function Response PDU does not arrive
+ * before the session is terminated, the SCSI service response is
+ * SERVICE DELIVERY OR TARGET FAILURE.\n
+ * The response to ABORT TASK SET and CLEAR TASK SET MUST only be issued
+ * by the target after all of the commands affected have been received
+ * by the target, the corresponding task management functions have been
+ * executed by the SCSI target, and the delivery of all responses
+ * delivered until the task management function completion has been
+ * confirmed (acknowledged through the ExpStatSN) by the initiator on
+ * all connections of this session.\n
+ * For the ABORT TASK function,\n
+ * -# if the Referenced Task Tag identifies a valid task leading to a
+ * successful termination, then targets must return the "Function
+ * complete" response.
+ * -# if the Referenced Task Tag does not identify an existing task
+ * but the CmdSN indicated by the RefCmdSN field in the Task
+ * Management Function Request is within the valid CmdSN window
+ * and less than the CmdSN of the Task Management Function Request
+ * itself, then targets must consider the CmdSN as received and
+ * return the "Function complete" response.
+ * -# if the Referenced Task Tag does not identify an existing task
+ * and the CmdSN indicated by the RefCmdSN field in the Task
+ * Management Function Request is outside the valid CmdSN window,
+ * then targets must return the "Task does not exist" response
+ */
+ uint8_t response;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint8_t reserved;
+
+ /// TotalAHSLength (MUST be 0 for this PDU).
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength (MUST be 0 for this PDU).
+ uint8_t ds_len[3];
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved2;
+
+ /// Initiator Task Tag (ITT).
+ uint32_t init_task_tag;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint32_t reserved3;
+
+ /// StatSN.
+ uint32_t stat_sn;
+
+ /// ExpCmdSN.
+ uint32_t exp_cmd_sn;
+
+ /// MaxCmdSN.
+ uint32_t max_cmd_sn;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint32_t reserved4;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved5;
+} iscsi_task_mgmt_func_response_packet;
+
+/**
+ * @brief SCSI Data In reponse flags: Status.
+ *
+ * (S) set to indicate that the Command Status field
+ * contains status. If this bit is set to 1, the
+ * F bit MUST also be set to 1.
+ */
+#define ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_STATUS (1 << 0)
+
+/**
+ * @brief SCSI Data In reponse flags: Residual Underflow.
+ *
+ * (U) set for Residual Underflow. In this case, the Residual
+ * Count indicates the number of bytes that were not
+ * transferred out of the number of bytes that were expected
+ * to be transferred. For a bidirectional operation, the
+ * Residual Count contains the residual for the write
+ * operation.
+ */
+#define ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_RES_UNDERFLOW (1 << 1)
+
+/**
+ * @brief SCSI Data In reponse flags: Residual Overflow.
+ *
+ * (O) set for Residual Overflow. In this case, the Residual
+ * Count indicates the number of bytes that were not
+ * transferred because the initiator's Expected Data
+ * Transfer Length was not sufficient. For a bidirectional
+ * operation, the Residual Count contains the residual for
+ * the write operation.
+ */
+#define ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_RES_OVERFLOW (1 << 2)
+
+/**
+ * @brief SCSI Data In reponse flags: ACK.
+ *
+ * (A) for sessions with ErrorRecoveryLevel=1 or higher, the target sets
+ * this bit to 1 to indicate that it requests a positive acknowledgment
+ * from the initiator for the data received. The target should use the
+ * A bit moderately; it MAY only set the A bit to 1 once every
+ * MaxBurstLength bytes, or on the last Data-In PDU that concludes the
+ * entire requested read data transfer for the task from the target's
+ * perspective, and it MUST NOT do so more frequently. The target MUST
+ * NOT set to 1 the A bit for sessions with ErrorRecoveryLevel=0. The
+ * initiator MUST ignore the A bit set to 1 for sessions with
+ * ErrorRecoveryLevel=0.\n
+ * On receiving a Data-In PDU with the A bit set to 1 on a session with
+ * ErrorRecoveryLevel greater than 0, if there are no holes in the read
+ * data until that Data-In PDU, the initiator MUST issue a SNACK of type
+ * DataACK, except when it is able to acknowledge the status for the
+ * task immediately via the ExpStatSN on other outbound PDUs if the
+ * status for the task is also received. In the latter case
+ * (acknowledgment through the ExpStatSN), sending a SNACK of type
+ * DataACK in response to the A bit is OPTIONAL, but if it is done, it
+ * must not be sent after the status acknowledgment through the
+ * ExpStatSN. If the initiator has detected holes in the read data
+ * prior to that Data-In PDU, it MUST postpone issuing the SNACK of type
+ * DataACK until the holes are filled. An initiator also MUST NOT
+ * acknowledge the status for the task before those holes are filled. A
+ * status acknowledgment for a task that generated the Data-In PDUs is
+ * considered by the target as an implicit acknowledgment of the Data-In
+ * PDUs if such an acknowledgment was requested by the target.
+ */
+#define ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_ACK (1 << 6)
+
+/**
+ * @brief SCSI Data In reponse flags: Final.
+ *
+ * (F) for outgoing data, this bit is 1 for the last PDU of unsolicited
+ * data or the last PDU of a sequence that answers an R2T.
+ * For incoming data, this bit is 1 for the last input (read) data PDU
+ * of a sequence. Input can be split into several sequences, each
+ * having its own F bit. Splitting the data stream into sequences does
+ * not affect DataSN counting on Data-In PDUs. It MAY be used as a
+ * "change direction" indication for bidirectional operations that need
+ * such a change.\n
+ * DataSegmentLength MUST NOT exceed MaxRecvDataSegmentLength for the
+ * direction it is sent, and the total of all the DataSegmentLength of
+ * all PDUs in a sequence MUST NOT exceed MaxBurstLength (or
+ * FirstBurstLength for unsolicited data). However, the number of
+ * individual PDUs in a sequence (or in total) may be higher than the
+ * ratio of MaxBurstLength (or FirstBurstLength) to
+ * MaxRecvDataSegmentLength (as PDUs may be limited in length by the
+ * capabilities of the sender). Using a DataSegmentLength of 0 may
+ * increase beyond what is reasonable for the number of PDUs and should
+ * therefore be avoided.\n
+ * For bidirectional operations, the F bit is 1 for both the end of the
+ * input sequences and the end of the output sequences
+ */
+#define ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_FINAL (1 << 7)
+
+/**
+ * @brief iSCSI SCSI Data In response packet data.
+ *
+ * THis structure is used by iSCSI for SCSI data input
+ * responses, i.e. read operations.
+ */
+typedef struct __attribute__((packed)) iscsi_scsi_data_in_response_packet {
+ /// Always 0x25 according to iSCSI specification.
+ uint8_t opcode;
+
+ /// Incoming data flags. The fields StatSN, Status, and Residual Count only have meaningful content if the S bit is set to 1.
+ uint8_t flags;
+
+ /// Rserved for future usage, always MUST be 0.
+ uint8_t reserved;
+
+ /**
+ * @brief Status or Reserved.
+ *
+ * Status can accompany the last Data-In PDU if the command did not end
+ * with an exception (i.e., the status is "good status" - GOOD,
+ * CONDITION MET, or INTERMEDIATE-CONDITION MET). The presence of
+ * status (and of a residual count) is signaled via the S flag bit.
+ * Although targets MAY choose to send even non-exception status in
+ * separate responses, initiators MUST support non-exception status in
+ * Data-In PDUs.
+ */
+ uint8_t status;
+
+ /// TotalAHSLength.
+ uint8_t total_ahs_len;
+
+ /**
+ * @brief DataSegmentLength.
+ *
+ * This is the data payload length of a SCSI Data-In or SCSI Data-Out
+ * PDU. The sending of 0-length data segments should be avoided, but
+ * initiators and targets MUST be able to properly receive 0-length data
+ * segments.\n
+ * The data segments of Data-In and Data-Out PDUs SHOULD be filled to
+ * the integer number of 4-byte words (real payload), unless the F bit
+ * is set to 1.
+ */
+ uint8_t ds_len[3];
+
+ /**
+ * @brief Logical Unit Number (LUN) or Reserved.
+ *
+ * If the Target Transfer Tag is provided, then the LUN field MUST hold a
+ * valid value and be consistent with whatever was specified with the command;
+ * otherwise, the LUN field is reserved.
+ */
+ uint64_t lun;
+
+ /// Initiator Task Tag (ITT).
+ uint32_t init_task_tag;
+
+ /**
+ * @brief Target Transfer Tag or 0xFFFFFFFF.
+ *
+ * On incoming data, the Target Transfer Tag and LUN MUST be provided by
+ * the target if the A bit is set to 1; otherwise, they are reserved.
+ * The Target Transfer Tag and LUN are copied by the initiator into the
+ * SNACK of type DataACK that it issues as a result of receiving a SCSI
+ * Data-In PDU with the A bit set to 1.\n
+ * The Target Transfer Tag values are not specified by this protocol,
+ * except that the value 0xFFFFFFFF is reserved and means that the
+ * Target Transfer Tag is not supplied.
+ */
+ uint32_t target_xfer_tag;
+
+ /// StatSN.
+ uint32_t stat_sn;
+
+ /// ExpCmdSN.
+
+ uint32_t exp_cmd_sn;
+
+ /// MaxCmdSN.
+ uint32_t max_cmd_sn;
+
+ /**
+ * @brief DataSN.
+ *
+ * For input (read) or bidirectional Data-In PDUs, the DataSN is the
+ * input PDU number within the data transfer for the command identified
+ * by the Initiator Task Tag.\n
+ * R2T and Data-In PDUs, in the context of bidirectional commands, share
+ * the numbering sequence.
+ */
+ uint32_t data_sn;
+
+ /**
+ * @brief Buffer Offset.
+ *
+ * The Buffer Offset field contains the offset of this PDU payload data
+ * within the complete data transfer. The sum of the buffer offset and
+ * length should not exceed the expected transfer length for the
+ * command.\n
+ * The order of data PDUs within a sequence is determined by
+ * DataPDUInOrder. When set to Yes, it means that PDUs have to be in
+ * increasing buffer offset order and overlays are forbidden.\n
+ * The ordering between sequences is determined by DataSequenceInOrder.
+ * When set to Yes, it means that sequences have to be in increasing
+ * buffer offset order and overlays are forbidden.
+ */
+ uint32_t buf_offset;
+
+ /// Residual Count or Reserved.
+ uint32_t res_cnt;
+} iscsi_scsi_data_in_response_packet;
+
+/**
+ * @brief Text Request flags: Continue.
+ *
+ * (C) When set to 1, this bit indicates that the text (set of key=value
+ * pairs) in this Text Request is not complete (it will be continued on
+ * subsequent Text Requests); otherwise, it indicates that this Text
+ * Request ends a set of key=value pairs. A Text Request with the C bit
+ * set to 1 MUST have the F bit set to 0.
+ */
+#define ISCSI_TEXT_REQ_FLAGS_CONTINUE (1 << 6)
+
+/**
+ * @brief Text Request flags: Final.
+ *
+ * (F) When set to 1, this bit indicates that this is the last or only Text
+ * Request in a sequence of Text Requests; otherwise, it indicates that
+ * more Text Requests will follow.
+ */
+#define ISCSI_TEXT_REQ_FLAGS_FINAL (1 << 7)
+
+/**
+ * @brief iSCSI Text Request packet data.
+ *
+ * The Text Request is provided to allow for the exchange of information
+ * and for future extensions. It permits the initiator to inform a
+ * target of its capabilities or request some special operations.
+ *
+ * An initiator MUST NOT have more than one outstanding Text Request on
+ * a connection at any given time.
+ *
+ * On a connection failure, an initiator must either explicitly abort
+ * any active allegiant text negotiation task or cause such a task to be
+ * implicitly terminated by the target.
+ */
+typedef struct __attribute__((packed)) iscsi_text_req_packet {
+ /// Always 0x04 according to iSCSI specification.
+ uint8_t opcode;
+
+ /// Text request flags.
+ uint8_t flags;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint16_t reserved;
+
+ /// TotalAHSLength.
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength.
+ uint8_t ds_len[3];
+
+ /// Logical Unit Number (LUN) or Reserved.
+ uint64_t lun;
+
+ /**
+ * @brief Initiator Task Tag (ITT).
+ *
+ * This is the initiator-assigned identifier for this Text Request. If
+ * the command is sent as part of a sequence of Text Requests and
+ * responses, the Initiator Task Tag MUST be the same for all the
+ * requests within the sequence (similar to linked SCSI commands). The
+ * I bit for all requests in a sequence also MUST be the same.
+ */
+ uint32_t init_task_tag;
+
+ /**
+ * @brief Target Transfer Tag (TTT).
+ *
+ * When the Target Transfer Tag is set to the reserved value 0xFFFFFFFF,
+ * it tells the target that this is a new request, and the target resets
+ * any internal state associated with the Initiator Task Tag (resets the
+ * current negotiation state).\n
+ * The target sets the Target Transfer Tag in a Text Response to a value
+ * other than the reserved value 0xFFFFFFFF whenever it indicates that
+ * it has more data to send or more operations to perform that are
+ * associated with the specified Initiator Task Tag. It MUST do so
+ * whenever it sets the F bit to 0 in the response. By copying the
+ * Target Transfer Tag from the response to the next Text Request, the
+ * initiator tells the target to continue the operation for the specific
+ * Initiator Task Tag. The initiator MUST ignore the Target Transfer
+ * Tag in the Text Response when the F bit is set to 1.\n
+ * This mechanism allows the initiator and target to transfer a large
+ * amount of textual data over a sequence of text-command/text-response
+ * exchanges or to perform extended negotiation sequences.\n
+ * If the Target Transfer Tag is not 0xFFFFFFFF, the LUN field MUST be
+ * sent by the target in the Text Response.\n
+ * A target MAY reset its internal negotiation state if an exchange is
+ * stalled by the initiator for a long time or if it is running out of
+ * resources.\n
+ * Long Text Responses are handled as shown in the following example:\n
+ * @verbatim
+ * I->T Text SendTargets=All (F = 1, TTT = 0xFFFFFFFF)
+ * T->I Text <part 1> (F = 0, TTT = 0x12345678)
+ * I->T Text <empty> (F = 1, TTT = 0x12345678)
+ * T->I Text <part 2> (F = 0, TTT = 0x12345678)
+ * I->T Text <empty> (F = 1, TTT = 0x12345678)
+ * ...
+ * T->I Text <part n> (F = 1, TTT = 0xFFFFFFFF)
+ * @endverbatim
+ */
+ uint32_t target_xfer_tag;
+
+ /// CmdSN.
+ uint32_t cmd_sn;
+
+ /// ExpStatSN.
+ uint32_t exp_stat_sn;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved2[2];
+} iscsi_text_req_packet;
+
+
+/**
+ * @brief Text Response flags: Continue.
+ *
+ * (C) When set to 1, this bit indicates that the text (set of key=value
+ * pairs) in this Text Response is not complete (it will be continued on
+ * subsequent Text Responses); otherwise, it indicates that this Text
+ * Response ends a set of key=value pairs. A Text Response with the
+ * C bit set to 1 MUST have the F bit set to 0.
+ */
+#define ISCSI_TEXT_RESPONSE_FLAGS_CONTINUE (1 << 6)
+
+/**
+ * @brief Text Response flags: Final.
+ *
+ * (F) When set to 1, in response to a Text Request with the Final bit set
+ * to 1, the F bit indicates that the target has finished the whole
+ * operation. Otherwise, if set to 0 in response to a Text Request with
+ * the Final Bit set to 1, it indicates that the target has more work to
+ * do (invites a follow-on Text Request). A Text Response with the
+ * F bit set to 1 in response to a Text Request with the F bit set to 0
+ * is a protocol error.\n
+ * A Text Response with the F bit set to 1 MUST NOT contain key=value
+ * pairs that may require additional answers from the initiator.
+ * A Text Response with the F bit set to 1 MUST have a Target Transfer
+ * Tag field set to the reserved value 0xFFFFFFFF.\n
+ * A Text Response with the F bit set to 0 MUST have a Target Transfer
+ * Tag field set to a value other than the reserved value 0xFFFFFFFF.
+ */
+#define ISCSI_TEXT_RESPONSE_FLAGS_FINAL (1 << 7)
+
+/**
+ * @brief iSCSI Text Response packet data.
+ *
+ * The Text Response PDU contains the target's responses to the
+ * initiator's Text Request. The format of the Text field matches that
+ * of the Text Request.
+ */
+typedef struct __attribute__((packed)) iscsi_text_response_packet {
+ /// Always 0x24 according to iSCSI specification.
+ uint8_t opcode;
+
+ /// Text response flags.
+ uint8_t flags;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint16_t reserved;
+
+ /// TotalAHSLength.
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength.
+ uint8_t ds_len[3];
+
+ /// Logical Unit Number (LUN) or Reserved.
+ uint64_t lun;
+
+ /// The Initiator Task Tag matches the tag used in the initial Text Request.
+ uint32_t init_task_tag;
+
+ /**
+ * @brief Target Transfer Tag (TTT).
+ *
+ * When a target has more work to do (e.g., cannot transfer all the
+ * remaining text data in a single Text Response or has to continue the
+ * negotiation) and has enough resources to proceed, it MUST set the
+ * Target Transfer Tag to a value other than the reserved value
+ * 0xFFFFFFFF. Otherwise, the Target Transfer Tag MUST be set to
+ * 0xFFFFFFFF.\n
+ * When the Target Transfer Tag is not 0xFFFFFFFF, the LUN field may be
+ * significant.\n
+ * The initiator MUST copy the Target Transfer Tag and LUN in its next
+ * request to indicate that it wants the rest of the data.\n
+ * When the target receives a Text Request with the Target Transfer Tag
+ * set to the reserved value 0xFFFFFFFF, it resets its internal
+ * information (resets state) associated with the given Initiator Task
+ * Tag (restarts the negotiation).\n
+ * When a target cannot finish the operation in a single Text Response
+ * and does not have enough resources to continue, it rejects the Text
+ * Request with the appropriate Reject code.\n
+ * A target may reset its internal state associated with an Initiator
+ * Task Tag (the current negotiation state) as expressed through the
+ * Target Transfer Tag if the initiator fails to continue the exchange
+ * for some time. The target may reject subsequent Text Requests with
+ * the Target Transfer Tag set to the "stale" value.
+ */
+ uint32_t target_xfer_tag;
+
+ /// StatSN. The target StatSN variable is advanced by each Text Response sent.
+ uint32_t stat_sn;
+
+ /// ExpCmdSN.
+ uint32_t exp_cmd_sn;
+
+ /// MaxCmdSN.
+ uint32_t max_cmd_sn;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved2[2];
+} iscsi_text_response_packet;
+
+
+/**
+ * @brief iSCSI Initiator Session ID (ISID) packet data.
+ *
+ * This is an initiator-defined component of the session identifier and
+ * is structured as follows:
+ *
+ * For the T field values 00b and 01b, a combination of A and B (for
+ * 00b) or B and C (for 01b) identifies the vendor or organization whose
+ * component (software or hardware) generates this ISID. A vendor or
+ * organization with one or more OUIs, or one or more Enterprise
+ * Numbers, MUST use at least one of these numbers and select the
+ * appropriate value for the T field when its components generate ISIDs.
+ * An OUI or EN MUST be set in the corresponding fields in network byte
+ * order (byte big-endian).
+ *
+ * If the T field is 10b, B and C are set to a random 24-bit unsigned
+ * integer value in network byte order (byte big-endian).
+ *
+ * The Qualifier field is a 16-bit or 24-bit unsigned integer value that
+ * provides a range of possible values for the ISID within the selected
+ * namespace. It may be set to any value within the constraints
+ * specified in the iSCSI protocol.
+ *
+ * If the ISID is derived from something assigned to a hardware adapter
+ * or interface by a vendor as a preset default value, it MUST be
+ * configurable to a value assigned according to the SCSI port behavior
+ * desired by the system in which it is installed. The resultant ISID
+ * MUST also be persistent over power cycles, reboot, card swap, etc.
+ */
+typedef struct __attribute__((packed)) iscsi_isid {
+ /// Meaning depends on T bit, either 22-bit OUI or reserved.
+ uint8_t a;
+
+ /// Meaning depends on T bit, either 22-bit OUI, EN (IANA Enterprise Number) or random.
+ uint16_t b;
+
+ /// Meaning depends on T bit, either 24-bit Qualifier, EN (IANA Enterprise Number) or random.
+ uint8_t c;
+
+ /// Meaning depends on T bit, either 24-bit Qualifier or Qualifier.
+ uint16_t d;
+} iscsi_isid;
+
+
+/**
+ * @brief Login request flags: Next Stage (NSG): First bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with a specific stage in the session (SecurityNegotiation,\n
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move. The Next Stage value is only
+ * valid when the T bit is 1; otherwise, it is reserved.
+ */
+#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_FIRST_BIT 0
+
+/**
+ * @brief Login request flags: Next Stage (NSG): Last bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with a specific stage in the session (SecurityNegotiation,\n
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move. The Next Stage value is only
+ * valid when the T bit is 1; otherwise, it is reserved.
+ */
+#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_LAST_BIT ((ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_FIRST_BIT) + 2 - 1)
+
+/// Login request flags: Next Stage (NSG): Bit mask.
+#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_MASK (ISCSI_BITS_GET_MASK(ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_FIRST_BIT, ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_LAST_BIT))
+
+
+/**
+ * @brief Login request flags: Current Stage (CSG): First bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with aspecific stage in the session (SecurityNegotiation,
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move.
+ */
+#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_FIRST_BIT 2
+
+/**
+ * @brief Login request flags: Current Stage (CSG): Last bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with aspecific stage in the session (SecurityNegotiation,
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move.
+ */
+#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_LAST_BIT ((ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_FIRST_BIT) + 2 - 1)
+
+/// Login request flags: Current Stage (CSG): Bit mask.
+#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_MASK (ISCSI_BITS_GET_MASK(ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_FIRST_BIT, ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_LAST_BIT))
+
+
+/**
+ * @brief Login request flags: Continue.
+ *
+ * (C) When set to 1, this bit indicates that the text (set of key=value
+ * pairs) in this Login Request is not complete (it will be continued on
+ * subsequent Login Requests); otherwise, it indicates that this Login
+ * Request ends a set of key=value pairs. A Login Request with the
+ * C bit set to 1 MUST have the T bit set to 0.
+ */
+#define ISCSI_LOGIN_REQ_FLAGS_CONTINUE (1 << 6)
+
+/**
+ * @brief Login request flags: Transmit.
+ *
+ * (T) When set to 1, this bit indicates that the initiator is ready to
+ * transit to the next stage.\n
+ * If the T bit is set to 1 and the NSG is set to FullFeaturePhase, then
+ * this also indicates that the initiator is ready for the Login
+ * Final-Response.
+ */
+#define ISCSI_LOGIN_REQ_FLAGS_TRANSIT (1 << 7)
+
+
+/**
+ * @brief iSCSI Login Request packet data.
+ *
+ * After establishing a TCP connection between an initiator and a
+ * target, the initiator MUST start a Login Phase to gain further access
+ * to the target's resources.
+ *
+ * The Login Phase consists of a sequence of Login Requests and Login
+ * Responses that carry the same Initiator Task Tag.
+ *
+ * Login Requests are always considered as immediate.
+ */
+typedef struct __attribute__((packed)) iscsi_login_req_packet {
+ /// Always 0x03 according to iSCSI specification.
+ uint8_t opcode;
+
+ /// Login request flags.
+ uint8_t flags;
+
+ /**
+ * @brief Version-max indicates the maximum version number supported.
+ *
+ * All Login Requests within the Login Phase MUST carry the same
+ * Version-max. Currently, this is always 0.\n
+ * The target MUST use the value presented with the first Login Request.
+ */
+ uint8_t version_max;
+
+ /**
+ * @brief Version-min indicates the minimum version number supported.
+ *
+ * All Login Requests within the Login Phase MUST carry the same
+ * Version-min. The target MUST use the value presented with the first
+ * Login Request. Always 0 for now.
+ */
+ uint8_t version_min;
+
+ /// TotalAHSLength.
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength.
+ uint8_t ds_len[3];
+
+ /// Initiator Session ID (ISID).
+ iscsi_isid isid;
+
+ /**
+ * @brief Target Session Identifying Handle (TSIH).
+ *
+ * The TSIH must be set in the first Login Request. The reserved value
+ * 0 MUST be used on the first connection for a new session. Otherwise,
+ * the TSIH sent by the target at the conclusion of the successful login
+ * of the first connection for this session MUST be used. The TSIH
+ * identifies to the target the associated existing session for this new
+ * connection.\n
+ * All Login Requests within a Login Phase MUST carry the same TSIH.
+ * The target MUST check the value presented with the first Login
+ * Request.
+ */
+ uint16_t tsih;
+
+ /// Initiator Task Tag (ITT).
+ uint32_t init_task_tag;
+
+ /**
+ * @brief Connection ID (CID).
+ *
+ * The CID provides a unique ID for this connection within the session.\n
+ * All Login Requests within the Login Phase MUST carry the same CID.
+ * The target MUST use the value presented with the first Login Request.\n
+ * A Login Request with a non-zero TSIH and a CID equal to that of an
+ * existing connection implies a logout of the connection followed by a
+ * login.
+ */
+ uint16_t cid;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint16_t reserved;
+
+ /**
+ * @brief CmdSN.
+ *
+ * The CmdSN is either the initial command sequence number of a session
+ * (for the first Login Request of a session - the "leading" login) or
+ * the command sequence number in the command stream if the login is for
+ * a new connection in an existing session.\n
+ * Examples:
+ * - Login on a leading connection: If the leading login carries the
+ * CmdSN 123, all other Login Requests in the same Login Phase carry
+ * the CmdSN 123, and the first non-immediate command in the Full
+ * Feature Phase also carries the CmdSN 123.
+ * - Login on other than a leading connection: If the current CmdSN at
+ * the time the first login on the connection is issued is 500, then
+ * that PDU carries CmdSN=500. Subsequent Login Requests that are
+ * needed to complete this Login Phase may carry a CmdSN higher than
+ * 500 if non-immediate requests that were issued on other connections
+ * in the same session advance the CmdSN.
+ *
+ * If the Login Request is a leading Login Request, the target MUST use
+ * the value presented in the CmdSN as the target value for the
+ * ExpCmdSN.
+ */
+ uint32_t cmd_sn;
+
+ /**
+ * @brief ExpStatSN.
+ *
+ * For the first Login Request on a connection, this is the ExpStatSN
+ * for the old connection, and this field is only valid if the Login
+ * Request restarts a connection.\n
+ * For subsequent Login Requests, it is used to acknowledge the Login
+ * Responses with their increasing StatSN values.
+ */
+ uint32_t exp_stat_sn;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved2[2];
+} iscsi_login_req_packet;
+ASSERT_IS_BHS( iscsi_login_req_packet );
+
+/// Login response Next Stage (NSG) flags: SecurityNegotiation.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_SECURITY_NEGOTIATION 0x0
+
+/// Login response Next Stage (NSG) flags: LoginOperationalNegotiation.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION 0x1
+
+/// Login response Next Stage (NSG) flags: Reserved for future usage, may NOT be used.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_RESERVED 0x2
+
+/// Login response Next Stage (NSG) flags: FullFeaturePhase.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FULL_FEATURE_PHASE 0x3
+
+/**
+ * @brief Login response flags: Next Stage (NSG): First bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with a specific stage in the session (SecurityNegotiation,
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move The Next Stage value is only
+ * valid when the T bit is 1; otherwise, it is reserved.
+ */
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FIRST_BIT 0
+
+/**
+ * @brief Login response flags: Next Stage (NSG): Last bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with a specific stage in the session (SecurityNegotiation,
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move The Next Stage value is only
+ * valid when the T bit is 1; otherwise, it is reserved.
+ */
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_LAST_BIT ((ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FIRST_BIT) + 2 - 1)
+
+/// Login response flags: Next Stage (NSG): Bit mask.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_MASK (ISCSI_BITS_GET_MASK(ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FIRST_BIT, ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_LAST_BIT))
+
+/// Login response flags: Extracts the Next Stage (NSG) bits.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_GET_NEXT_STAGE(x) (ISCSI_BITS_GET((x), ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FIRST_BIT, ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_LAST_BIT))
+
+/// Login response flags: Stores into the Next Stage (NSG) bits.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_PUT_NEXT_STAGE(x) (ISCSI_BITS_PUT((x), ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FIRST_BIT, ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_LAST_BIT))
+
+
+/// Login response Current Stage (CSG) flags: SecurityNegotiation.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_SECURITY_NEGOTIATION 0x0
+
+/// Login response Current Stage (CSG) flags: LoginOperationalNegotiation.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION 0x1
+
+/// Login response Current Stage (CSG) flags: Reserved for future usage, may NOT be used.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_RESERVED 0x2
+
+/// Login response Current Stage (CSG) flags: FullFeaturePhase.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FULL_FEATURE_PHASE 0x3
+
+/**
+ * @brief Login response flags: Current Stage (CSG): First bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with aspecific stage in the session (SecurityNegotiation,
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move.
+ */
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FIRST_BIT 2
+
+/**
+ * @brief Login response flags: Current Stage (CSG): First bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with aspecific stage in the session (SecurityNegotiation,
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move.
+ */
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_LAST_BIT ((ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FIRST_BIT) + 2 - 1)
+
+/// Login request flags: Current Stage (CSG): Bit mask.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_MASK (ISCSI_BITS_GET_MASK(ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FIRST_BIT, ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_LAST_BIT))
+
+/// Login request flags: Extracts the Current Stage (CSG) bits.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_GET_CURRENT_STAGE(x) (ISCSI_BITS_GET((x), ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FIRST_BIT, ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_LAST_BIT))
+
+/// Login request flags: Stores into the Current Stage (CSG) bits.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_PUT_CURRENT_STAGE(x) (ISCSI_BITS_PUT((x), ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FIRST_BIT, ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_LAST_BIT))
+
+
+/**
+ * @brief Login response flags: Continue.
+ *
+ * (C) When set to 1, this bit indicates that the text (set of key=value
+ * pairs) in this Login Response is not complete (it will be continued
+ * on subsequent Login Responses); otherwise, it indicates that this
+ * Login Response ends a set of key=value pairs. A Login Response with
+ * the C bit set to 1 MUST have the T bit set to 0.
+ */
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CONTINUE (1 << 6)
+
+/**
+ * @brief Login response flags: Transmit.
+ *
+ * (T) The T bit is set to 1 as an indicator of the end of the stage. If
+ * the T bit is set to 1 and the NSG is set to FullFeaturePhase, then
+ * this is also the Login Final-Response. A T bit of 0 indicates a
+ * "partial" response, which means "more negotiation needed".\n
+ * A Login Response with the T bit set to 1 MUST NOT contain key=value
+ * pairs that may require additional answers from the initiator within
+ * the same stage.\n
+ * If the Status-Class is 0, the T bit MUST NOT be set to 1 if the T bit
+ * in the request was set to 0.
+ */
+#define ISCSI_LOGIN_RESPONSE_FLAGS_TRANSIT (1 << 7)
+
+
+/**
+ * @brief Login response status class: Success.
+ *
+ * Indicates that the iSCSI target successfully received, understood,
+ * and accepted the request. The numbering fields (StatSN, ExpCmdSN,
+ * MaxCmdSN) are only valid if Status-Class is 0.
+ */
+#define ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SUCCESS 0x00
+
+/**
+ * @brief Login response status details: Success.
+ *
+ * Login is proceeding OK. If the response T bit is set to 1 in both the
+ * request and the matching response, and the NSG is set to
+ * FullFeaturePhase in both the request and the matching response, the
+ * Login Phase is finished, and the initiator may proceed to issue SCSI
+ * commands.
+ */
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SUCCESS 0x00
+
+
+/**
+ * @brief Login response status class: Redirection.
+ *
+ * Indicates that the initiator must take further action
+ * to complete the request. This is usually due to the
+ * target moving to a different address. All of the redirection
+ * Status-Class responses MUST return one or more text key
+ * parameters of the type "TargetAddress", which indicates the
+ * target's new address. A redirection response MAY be issued by
+ * a target prior to or after completing a security negotiation if
+ * a security negotiation is required. A redirection SHOULD be
+ * accepted by an initiator, even without having the target
+ * complete a security negotiation if any security negotiation is
+ * required, and MUST be accepted by the initiator after the
+ * completion of the security negotiation if any security
+ * negotiation is required.
+ */
+#define ISCSI_LOGIN_RESPONSE_STATUS_CLASS_REDIRECT 0x01
+
+/**
+ * @brief Login response status details: Temporarily redirected.
+ *
+ * The requested iSCSI Target Name (ITN) has temporarily moved
+ * to the address provided.
+ */
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_REDIRECT_TEMP 0x01
+
+/**
+ * @brief Login response status details: Permanently redirected.
+ *
+ * The requested ITN has permanently moved to the address provided.
+ */
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_REDIRECT_PERM 0x02
+
+
+/**
+ * @brief Login response status class: Initiator Error (not a format error).
+ *
+ * Indicates that the initiator most likely caused the error.\n
+ * This MAY be due to a request for a resource for which the
+ * initiator does not have permission. The request should
+ * not be tried again.
+ */
+#define ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR 0x02
+
+/// Login response status details: Miscellaneous iSCSI initiator errors.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISC 0x00
+
+/// Login response status details: The initiator could not be successfully authenticated or target authentication is not supported.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_AUTH_ERR 0x01
+
+/// Login response status details: The initiator is not allowed access to the given target.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_AUTH_FAIL 0x02
+
+/// Login response status details: The requested iSCSI Target Name (ITN) does not exist at this address.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_NOT_FOUND 0x03
+
+/// Login response status details: The requested ITN has been removed, and no forwarding address is provided.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_TARGET_REMOVED 0x04
+
+/// Login response status details: The requested iSCSI version range is not supported by the target.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_WRONG_VERSION 0x05
+
+/// Login response status details: Too many connections on this Session ID (SSID).
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_TOO_MANY_CONNECTIONS 0x06
+
+/// Login response status details: Missing parameters (e.g. iSCSI Initiator Name and/or Target Name).
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISSING_PARAMETER 0x07
+
+/// Login response status details: Target does not support session spanning to this connection (address).
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_NO_SESSION_SPANNING 0x08
+
+/// Login response status details: Target does not support this type of session or not from this initiator.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_SESSION_NO_SUPPORT 0x09
+
+/// Login response status details: Attempt to add a connection to a non-existent session.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_SESSION_NO_EXIST 0x0A
+
+/// Login response status details: Invalid request type during login.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_INVALID_LOGIN_REQ_TYPE 0x0B
+
+
+/**
+ * @brief Login response status class: Target Error.
+ *
+ * Indicates that the target sees no errors in the
+ * initiator's Login Request but is currently incapable of
+ * fulfilling the request. The initiator may retry the same Login
+ * Request later.
+ */
+#define ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SERVER_ERR 0x03
+
+/// Login response status details: Target hardware or software error.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SERVER_ERR_TARGET_ERROR 0x00
+
+/// Login response status details: The iSCSI service or target is not currently operational.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SERVER_ERR_SERVICE_UNAVAILABLE 0x01
+
+/// The target has insufficient session, connection, or other resources.
+#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SERVER_ERR_OUT_OF_RESOURCES 0x02
+
+
+/**
+ * @brief iSCSI Login Response packet data.
+ *
+ * The Login Response indicates the progress and/or end of the Login
+ * Phase.
+ */
+typedef struct __attribute__((packed)) iscsi_login_response_packet {
+ /// Always 0x23 according to iSCSI specification.
+ uint8_t opcode;
+
+ /// Login response flags.
+ uint8_t flags;
+
+ /**
+ * @brief This is the highest version number supported by the target.
+ *
+ * All Login Responses within the Login Phase MUST carry the same
+ * Version-max.
+ */
+ uint8_t version_max;
+
+ /**
+ * @brief Version-active indicates the highest version supported by the target and initiator.
+ *
+ * If the target does not support a version within the
+ * range specified by the initiator, the target rejects the login and
+ * this field indicates the lowest version supported by the target.
+ * All Login Responses within the Login Phase MUST carry the same
+ * Version-active.\n
+ * The initiator MUST use the value presented as a response to the first
+ * Login Request.
+ */
+ uint8_t version_active;
+
+ /// TotalAHSLength.
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength.
+ uint8_t ds_len[3];
+
+ /// Initiator Session ID (ISID).
+ iscsi_isid isid;
+
+ /**
+ * @brief Target Session Identifying Handle (TSIH).
+ *
+ * The TSIH is the target-assigned session-identifying handle. Its
+ * internal format and content are not defined by this protocol, except
+ * for the value 0, which is reserved. With the exception of the Login
+ * Final-Response in a new session, this field should be set to the TSIH
+ * provided by the initiator in the Login Request. For a new session,
+ * the target MUST generate a non-zero TSIH and ONLY return it in the
+ * Login Final-Response.
+ */
+ uint16_t tsih;
+
+ /// Initiator Task Tag (ITT).
+ uint32_t init_task_tag;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint32_t reserved;
+
+ /**
+ * @brief StatSN.
+ *
+ * For the first Login Response (the response to the first Login
+ * Request), this is the starting status sequence number for the
+ * connection. The next response of any kind - including the next
+ * Login Response, if any, in the same Login Phase - will carry this
+ * number + 1. This field is only valid if the Status-Class is 0.
+ */
+ uint32_t stat_sn;
+
+ /// ExpCmdSN.
+ uint32_t exp_cmd_sn;
+
+ /// MaxCmdSN.
+ uint32_t max_cmd_sn;
+
+ /**
+ * @brief Status-class.
+ *
+ * Status-class (see above for details). If the Status-Class is
+ * not 0, the initiator and target MUST close the TCP connection
+ * If the target wishes to reject the Login Request for more than one
+ * reason, it should return the primary reason for the rejection.
+ */
+ uint8_t status_class;
+
+ /// Status-detail.
+ uint8_t status_detail;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint16_t reserved2;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved3;
+} iscsi_login_response_packet;
+
+
+/// Logout request reason code: Close the session. All commands associated with the session (if any) are terminated.
+#define ISCSI_LOGOUT_REQ_REASON_CODE_CLOSE_SESSION 0x00
+
+/// Logout request reason code: Close the connection. All commands associated with the connection (if any) are terminated.
+#define ISCSI_LOGOUT_REQ_REASON_CODE_CLOSE_CONNECTION 0x01
+
+/// Logout request reason code: Remove the connection for recovery. The connection is closed, and all commands associated with it, if any, are to be prepared for a new allegiance.
+#define ISCSI_LOGOUT_REQ_REASON_CODE_REMOVE_CONNECTION_RECOVERY 0x02
+
+/// Mask to get the logout reason from the reason_code field (lower 7 bits)
+#define ISCSI_LOGOUT_REQ_REASON_CODE_MASK 0x7f
+
+/**
+ * @brief Logout request implicit reason code: Session reinstatement.
+ *
+ * The entire logout discussion in this section is also applicable for
+ * an implicit Logout realized by way of a connection reinstatement or
+ * session reinstatement. When a Login Request performs an implicit
+ * Logout, the implicit Logout is performed as if having the reason
+ * codes specified below:
+ */
+#define ISCSI_LOGOUT_REQ_REASON_CODE_IMPLICIT_SESSION_REINSTATEMENT 0x00
+
+/**
+ * @brief Logout request implicit reason code: Connection reinstatement when the operational ErrorRecoveryLevel < 2.
+ *
+ * The entire logout discussion in this section is also applicable for
+ * an implicit Logout realized by way of a connection reinstatement or
+ * session reinstatement. When a Login Request performs an implicit
+ * Logout, the implicit Logout is performed as if having the reason
+ * codes specified below:
+ */
+#define ISCSI_LOGOUT_REQ_REASON_CODE_IMPLICIT_CONNECTION_REINSTATEMENT 0x01
+
+/**
+ * @brief Logout request implicit reason code: Connection reinstatement when the operational ErrorRecoveryLevel = 2.
+ *
+ * The entire logout discussion in this section is also applicable for
+ * an implicit Logout realized by way of a connection reinstatement or
+ * session reinstatement. When a Login Request performs an implicit
+ * Logout, the implicit Logout is performed as if having the reason
+ * codes specified below:
+ */
+#define ISCSI_LOGOUT_REQ_REASON_CODE_IMPLICIT_CONNECTION_REINSTATEMENT_2 0x02
+
+
+/**
+ * @brief iSCSI Logout Request packet data.
+ *
+ * The Logout Request is used to perform a controlled closing of a
+ * connection.
+ *
+ * An initiator MAY use a Logout Request to remove a connection from a
+ * session or to close an entire session.
+ *
+ * After sending the Logout Request PDU, an initiator MUST NOT send any
+ * new iSCSI requests on the closing connection. If the Logout Request
+ * is intended to close the session, new iSCSI requests MUST NOT be sent
+ * on any of the connections participating in the session.
+ *
+ * When receiving a Logout Request with the reason code "close the
+ * connection" or "close the session", the target MUST terminate all
+ * pending commands, whether acknowledged via the ExpCmdSN or not, on
+ * that connection or session, respectively.
+ *
+ * When receiving a Logout Request with the reason code "remove the
+ * connection for recovery", the target MUST discard all requests not
+ * yet acknowledged via the ExpCmdSN that were issued on the specified
+ * connection and suspend all data/status/R2T transfers on behalf of
+ * pending commands on the specified connection.
+ *
+ * The target then issues the Logout Response and half-closes the TCP
+ * connection (sends FIN). After receiving the Logout Response and
+ * attempting to receive the FIN (if still possible), the initiator MUST
+ * completely close the logging-out connection. For the terminated
+ * commands, no additional responses should be expected.
+ *
+ * A Logout for a CID may be performed on a different transport
+ * connection when the TCP connection for the CID has already been
+ * terminated. In such a case, only a logical "closing" of the iSCSI
+ * connection for the CID is implied with a Logout.
+ *
+ * All commands that were not terminated or not completed (with status)
+ * and acknowledged when the connection is closed completely can be
+ * reassigned to a new connection if the target supports connection
+ * recovery.
+ *
+ * If an initiator intends to start recovery for a failing connection,
+ * it MUST use the Logout Request to "clean up" the target end of a
+ * failing connection and enable recovery to start, or use the Login
+ * Request with a non-zero TSIH and the same CID on a new connection for
+ * the same effect. In sessions with a single connection, the
+ * connection can be closed and then a new connection reopened. A
+ * connection reinstatement login can be used for recovery.
+ *
+ * A successful completion of a Logout Request with the reason code
+ * "close the connection" or "remove the connection for recovery"
+ * results at the target in the discarding of unacknowledged commands
+ * received on the connection being logged out. These are commands that
+ * have arrived on the connection being logged out but that have not
+ * been delivered to SCSI because one or more commands with a smaller
+ * CmdSN have not been received by iSCSI. The resulting holes in the
+ * command sequence numbers will have to be handled by appropriate
+ * recovery, unless the session is also closed.
+ */
+typedef struct __attribute__((packed)) iscsi_logout_req_packet {
+ /// Always 6 according to iSCSI specification.
+ uint8_t opcode;
+
+ /**
+ * @brief Reason code.
+ *
+ * A target implicitly terminates the active tasks due to the iSCSI
+ * protocol in the following cases:
+ * -# When a connection is implicitly or explicitly logged out with
+ * the reason code "close the connection" and there are active
+ * tasks allegiant to that connection.
+ * -# When a connection fails and eventually the connection state
+ * times out and there are active tasks allegiant to that
+ * connection
+ * -# When a successful recovery Logout is performed while there are
+ * active tasks allegiant to that connection and those tasks
+ * eventually time out after the Time2Wait and Time2Retain periods
+ * without allegiance reassignment
+ * -# When a connection is implicitly or explicitly logged out with
+ * the reason code "close the session" and there are active tasks
+ * in that session
+ *
+ * If the tasks terminated in any of the above cases are SCSI tasks,
+ * they must be internally terminated as if with CHECK CONDITION status.
+ * This status is only meaningful for appropriately handling the
+ * internal SCSI state and SCSI side effects with respect to ordering,
+ * because this status is never communicated back as a terminating
+ * status to the initiator. However, additional actions may have to be
+ * taken at the SCSI level, depending on the SCSI context as defined by
+ * the SCSI standards (e.g., queued commands and ACA; UA for the next
+ * command on the I_T nexus in cases a), b), and c) above). After the
+ * tasks are terminated, the target MUST report a Unit Attention condition
+ * on the next command processed on any connection for each affected
+ * I_T_L nexus with the status of CHECK CONDITION, the ASC/ASCQ value
+ * of 0x47 / 0x7F ("SOME COMMANDS CLEARED BY ISCSI PROTOCOL EVENT"), etc.
+ */
+ uint8_t reason_code;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint16_t reserved;
+
+ /// TotalAHSLength (MUST be 0 for this PDU).
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength (MUST be 0 for this PDU).
+ uint8_t ds_len[3];
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved2;
+
+ /// Initiator Task Tag (ITT).
+ uint32_t init_task_tag;
+
+ /**
+ * @brief Connection ID (CID).
+ *
+ * This is the connection ID of the connection to be closed (including
+ * closing the TCP stream). This field is only valid if the reason code
+ * is not "close the session".
+ */
+ uint16_t cid;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint16_t reserved3;
+
+ /// CmdSN.
+ uint32_t cmd_sn;
+
+ /// This is the last ExpStatSN value for the connection to be closed.
+ uint32_t exp_stat_sn;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved4[2];
+} iscsi_logout_req_packet;
+
+
+/// Logout response - response code: Connection or session closed successfully.
+#define ISCSI_LOGOUT_RESPONSE_CLOSED_SUCCESSFULLY 0x00
+
+/// Logout response - response code: Connection ID (CID) not found.
+#define ISCSI_LOGOUT_RESPONSE_CID_NOT_FOUND 0x01
+
+/// Logout response - response code: Connection recovery is not supported (i.e., the Logout reason code was "remove the connection for recovery" and the target does not support it as indicated by the operational ErrorRecoveryLevel).
+#define ISCSI_LOGOUT_RESPONSE_CONNECTION_RECOVERY_NOT_SUPPORTED 0x02
+
+/// Logout response - response code: Cleanup failed for various reasons.
+#define ISCSI_LOGOUT_RESPONSE_CLEANUP_FAILED 0x03
+
+/**
+ * @brief iSCSI Logout Response packet data.
+ *
+ * The Logout Response is used by the target to indicate if the cleanup
+ * operation for the connection(s) has completed.
+ *
+ * After Logout, the TCP connection referred by the CID MUST be closed
+ * at both ends (or all connections must be closed if the logout reason
+ * was session close).
+ */
+typedef struct __attribute__((packed)) iscsi_logout_response_packet {
+ /// Always 0x26 according to iSCSI specification.
+ uint8_t opcode;
+
+ /// Reserved for future usage (must be always 0x80 for now).
+ uint8_t flags;
+
+ /// Response.
+ uint8_t response;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint8_t reserved;
+
+ /// TotalAHSLength (MUST be 0 for this PDU).
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength (MUST be 0 for this PDU).
+ uint8_t ds_len[3];
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved2;
+
+ /// Initiator Task Tag (ITT).
+ uint32_t init_task_tag;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint32_t reserved3;
+
+ /// StatSN.
+ uint32_t stat_sn;
+
+ /// ExpCmdSN.
+ uint32_t exp_cmd_sn;
+
+ /// MaxCmdSN.
+ uint32_t max_cmd_sn;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint32_t reserved4;
+
+ /**
+ * @brief Time2Wait.
+ *
+ * If the Logout response code is 0 and the operational
+ * ErrorRecoveryLevel is 2, this is the minimum amount of time, in
+ * seconds, to wait before attempting task reassignment. If the Logout
+ * response code is 0 and the operational ErrorRecoveryLevel is less
+ * than 2, this field is to be ignored.\n
+ * This field is invalid if the Logout response code is 1.\n
+ * If the Logout response code is 2 or 3, this field specifies the
+ * minimum time to wait before attempting a new implicit or explicit
+ * logout.\n
+ * If Time2Wait is 0, the reassignment or a new Logout may be attempted
+ * immediately.
+ */
+ uint16_t time_wait;
+
+ /**
+ * @brief Time2Retain.
+ *
+ * If the Logout response code is 0 and the operational
+ * ErrorRecoveryLevel is 2, this is the maximum amount of time, in
+ * seconds, after the initial wait (Time2Wait) that the target waits for
+ * the allegiance reassignment for any active task, after which the task
+ * state is discarded. If the Logout response code is 0 and the
+ * operational ErrorRecoveryLevel is less than 2, this field is to be
+ * ignored.\n
+ * This field is invalid if the Logout response code is 1.\n
+ * If the Logout response code is 2 or 3, this field specifies the
+ * maximum amount of time, in seconds, after the initial wait
+ * (Time2Wait) that the target waits for a new implicit or explicit
+ * logout.\n
+ * If it is the last connection of a session, the whole session state is
+ * discarded after Time2Retain.\n
+ * If Time2Retain is 0, the target has already discarded the connection
+ * (and possibly the session) state along with the task states. No
+ * reassignment or Logout is required in this case.
+ */
+ uint16_t time_retain;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint32_t reserved5;
+} iscsi_logout_response_packet;
+
+
+/// iSCSI Reject packet data: Reserved, original PDU can't be resent.
+#define ISCSI_REJECT_REASON_RESERVED 0x01
+
+/**
+ * @brief iSCSI Reject packet data: Data (payload) digest error, original PDU can be resent.
+ *
+ * For iSCSI, Data-Out PDU retransmission is only done if the
+ * target requests retransmission with a recovery R2T. However,
+ * if this is the data digest error on immediate data, the
+ * initiator may choose to retransmit the whole PDU, including
+ * the immediate data.
+ */
+#define ISCSI_REJECT_REASON_DATA_DIGEST_ERR 0x02
+
+/// iSCSI Reject reason packet data: SNACK Reject (original PDU can be resent).
+#define ISCSI_REJECT_REASON_SNACK_REJECT 0x03
+
+/// iSCSI Reject reason packet data: Protocol Error (e.g., SNACK Request for a status that was already acknowledged). Original PDU can't be resent.
+#define ISCSI_REJECT_REASON_PROTOCOL_ERR 0x04
+
+/// iSCSI Reject reason packet data: Command not supported (original PDU can't be resent).
+#define ISCSI_REJECT_REASON_COMMAND_NOT_SUPPORTED 0x05
+
+/// iSCSI Reject reason packet data: Immediate command reject - too many immediate commands (original PDU can be resent).
+#define ISCSI_REJECT_REASON_TOO_MANY_IMMEDIATE_COMMANDS 0x06
+
+/// iSCSI Reject reason packet data: Task in progress (original PDU can't be resent).
+#define ISCSI_REJECT_REASON_TASK_IN_PROGRESS 0x07
+
+/// iSCSI Reject reason packet data: Invalid data ack (original PDU can't be resent).
+#define ISCSI_REJECT_REASON_INVALID_DATA_ACK 0x08
+
+/**
+ * @brief iSCSI Reject reason packet data: Invalid PDU field, original PDU can't be resent.
+ *
+ * A target should use this reason code for all invalid values
+ * of PDU fields that are meant to describe a task, a response,
+ * or a data transfer. Some examples are invalid TTT/ITT,
+ * buffer offset, LUN qualifying a TTT, and an invalid sequence
+ * number in a SNACK.
+ */
+#define ISCSI_REJECT_REASON_INVALID_PDU_FIELD 0x09
+
+/// iSCSI Reject reason packet data: Long op reject - Can't generate Target Transfer Tag - out of resources. Original PDU can be resent later.
+#define ISCSI_REJECT_REASON_OUT_OF_RESOURCES 0x0A
+
+/**
+ * @brief iSCSI Reject reason packet data: Deprecated; MUST NOT be used.
+ *
+ * Reason code 0x0B is deprecated and MUST NOT be used by
+ * implementations. An implementation receiving reason code
+ * 0x0B MUST treat it as a negotiation failure that terminates
+ * the Login Phase and the TCP connection.
+ */
+#define ISCSI_REJECT_REASON_DEPRECATED 0x0B
+
+/// iSCSI Reject reason packet data: Waiting for Logout, original PDU can't be resent.
+#define ISCSI_REJECT_REASON_WAITING_FOR_LOGOUT 0x0C
+
+/**
+ * @brief iSCSI Reject packet data.
+ *
+ * This structure will be received or sent, if an iSCSI
+ * packet was rejected or has been rejected for some reason.
+ */
+typedef struct __attribute__((packed)) iscsi_reject_packet {
+ /// Always 0x3F according to iSCSI specification.
+ uint8_t opcode;
+
+ /// Reserved for future usage (must be always 0x80 for now).
+ uint8_t flags;
+
+ /**
+ * @brief Reject reason.
+ *
+ * In all the cases in which a pre-instantiated SCSI task is terminated
+ * because of the reject, the target MUST issue a proper SCSI command
+ * response with CHECK CONDITION. In these cases in which a status for
+ * the SCSI task was already sent before the reject, no additional
+ * status is required. If the error is detected while data from the
+ * initiator is still expected (i.e., the command PDU did not contain
+ * all the data and the target has not received a Data-Out PDU with the
+ * Final bit set to 1 for the unsolicited data, if any, and all
+ * outstanding R2Ts, if any), the target MUST wait until it receives
+ * the last expected Data-Out PDUs with the F bit set to 1 before
+ * sending the Response PDU.
+ */
+ uint8_t reason;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint8_t reserved;
+
+ /// TotalAHSLength.
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength.
+ uint8_t ds_len[3];
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved2;
+
+ /// Always 0xFFFFFFFF for now.
+ uint32_t tag;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint32_t reserved3;
+
+ /**
+ * @brief StatSN.
+ *
+ * This field carries its usual value and is not related to the
+ * rejected command. The StatSN is advanced after a Reject.
+ */
+ uint32_t stat_sn;
+
+ /**
+ * @brief ExpCmdSN.
+ *
+ * This field carries its usual value and is not related to the
+ * rejected command.
+ */
+ uint32_t exp_cmd_sn;
+
+ /**
+ * @brief MaxCmdSN.
+ *
+ * This field carries its usual value and is not related to the
+ * rejected command.
+ */
+ uint32_t max_cmd_sn;
+
+ /**
+ * @brief DataSN / Ready To Transfer Sequence Number (R2TSN) or Reserved.
+ *
+ * This field is only valid if the rejected PDU is a Data/R2T SNACK and
+ * the Reject reason code is "Protocol Error". The DataSN/R2TSN is the
+ * next Data/R2T sequence number that the target would send for the
+ * task, if any.
+ */
+ uint32_t data_r2t_sn;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved4;
+
+ /**
+ * @brief Complete Header of Bad PDU.
+ *
+ * The target returns the header (not including the digest) of the
+ * PDU in error as the data of the response.
+ */
+ iscsi_bhs_packet bad_pdu_hdr;
+} iscsi_reject_packet;
+
+/**
+ * @brief iSCSI NOP-Out packet data.
+ *
+ * NOP-Out may be used by an initiator as a "ping request" to verify
+ * that a connection/session is still active and all its components are
+ * operational. The NOP-In response is the "ping echo".
+ *
+ * A NOP-Out is also sent by an initiator in response to a NOP-In.
+ *
+ * A NOP-Out may also be used to confirm a changed ExpStatSN if another
+ * PDU will not be available for a long time.
+ *
+ * Upon receipt of a NOP-In with the Target Transfer Tag set to a valid
+ * value (not the reserved value 0xffffffff), the initiator MUST respond
+ * with a NOP-Out. In this case, the NOP-Out Target Transfer Tag MUST
+ * contain a copy of the NOP-In Target Transfer Tag. The initiator
+ *
+ * SHOULD NOT send a NOP-Out in response to any other received NOP-In,
+ * in order to avoid lengthy sequences of NOP-In and NOP-Out PDUs sent
+ * in response to each other.
+ */
+typedef struct __attribute__((packed)) iscsi_nop_out_packet {
+ /// Always 0x00 according to iSCSI specification.
+ uint8_t opcode;
+
+ /// Reserved for future usage (must be always 0x80 for now).
+ uint8_t flags;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint16_t reserved;
+
+ /// TotalAHSLength.
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength.
+ uint8_t ds_len[3];
+
+ /// LUN or Reserved.
+ uint64_t lun;
+
+ /**
+ * @brief Initiator Task Tag (ITT).
+ *
+ * The NOP-Out MUST have the Initiator Task Tag set to a valid value
+ * only if a response in the form of a NOP-In is requested (i.e., the
+ * NOP-Out is used as a ping request). Otherwise, the Initiator Task
+ * Tag MUST be set to 0xFFFFFFFF.\n
+ * When a target receives the NOP-Out with a valid Initiator Task Tag,
+ * it MUST respond with a NOP-In Response.\n
+ * If the Initiator Task Tag contains 0xFFFFFFFF, the I bit MUST be set
+ * to 1, and the CmdSN is not advanced after this PDU is sent.
+ */
+ uint32_t init_task_tag;
+
+ /**
+ * @brief Target Transfer Tag (TTT).
+ *
+ * The Target Transfer Tag is a target-assigned identifier for the
+ * operation.\n
+ * The NOP-Out MUST only have the Target Transfer Tag set if it is
+ * issued in response to a NOP-In with a valid Target Transfer Tag. In
+ * this case, it copies the Target Transfer Tag from the NOP-In PDU.\n
+ * Otherwise, the Target Transfer Tag MUST be set to 0xFFFFFFFF.\n
+ * When the Target Transfer Tag is set to a value other than 0xFFFFFFFF,
+ * the LUN field MUST also be copied from the NOP-In.
+ */
+ uint32_t target_xfer_tag;
+
+ /// CmdSN.
+ uint32_t cmd_sn;
+
+ /// ExpStatSN.
+ uint32_t exp_stat_sn;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved2[2];
+} iscsi_nop_out_packet;
+
+
+/**
+ * @brief iSCSI NOP-In packet data.
+ *
+ * NOP-In is sent by a target as either a response to a NOP-Out, a
+ * "ping" to an initiator, or a means to carry a changed ExpCmdSN and/or
+ * MaxCmdSN if another PDU will not be available for a long time (as
+ * determined by the target).
+ *
+ * When a target receives the NOP-Out with a valid Initiator Task Tag
+ * (not the reserved value 0xFFFFFFFF), it MUST respond with a NOP-In
+ * with the same Initiator Task Tag that was provided in the NOP-Out
+ * request. It MUST also duplicate up to the first
+ * MaxRecvDataSegmentLength bytes of the initiator-provided Ping Data.
+ * For such a response, the Target Transfer Tag MUST be 0xFFFFFFFF.
+ *
+ * The target SHOULD NOT send a NOP-In in response to any other received
+ * NOP-Out in order to avoid lengthy sequences of NOP-In and NOP-Out
+ * PDUs sent in response to each other.
+ *
+ * Otherwise, when a target sends a NOP-In that is not a response to a
+ * NOP-Out received from the initiator, the Initiator Task Tag MUST be
+ * set to 0xFFFFFFFF, and the data segment MUST NOT contain any data
+ * (DataSegmentLength MUST be 0).
+ */
+typedef struct __attribute__((packed)) iscsi_nop_in_packet {
+ /// Always 0x20 according to iSCSI specification.
+ uint8_t opcode;
+
+ /// Reserved for future usage (must be always 0x80 for now).
+ uint8_t flags;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint16_t reserved;
+
+ /// TotalAHSLength
+ uint8_t total_ahs_len;
+
+ /// DataSegmentLength.
+ uint8_t ds_len[3];
+
+ /// A LUN MUST be set to a correct value when the Target Transfer Tag is valid (not the reserved value 0xFFFFFFFF).
+ uint64_t lun;
+
+ /// Initiator Task Tag (ITT) or 0xFFFFFFFF.
+ uint32_t init_task_tag;
+
+ /**
+ * @brief Target Transfer Tag (TTT).
+ *
+ * If the target is responding to a NOP-Out, this field is set to the
+ * reserved value 0xFFFFFFFF.\n
+ * If the target is sending a NOP-In as a ping (intending to receive a
+ * corresponding NOP-Out), this field is set to a valid value (not the
+ * reserved value 0xFFFFFFFF).\n
+ * If the target is initiating a NOP-In without wanting to receive a
+ * corresponding NOP-Out, this field MUST hold the reserved value
+ * 0xFFFFFFFF.
+ */
+ uint32_t target_xfer_tag;
+
+ /**
+ * @brief StatSN.
+ *
+ * The StatSN field will always contain the next StatSN. However, when
+ * the Initiator Task Tag is set to 0xFFFFFFFF, the StatSN for the
+ * connection is not advanced after this PDU is sent.
+ */
+ uint32_t stat_sn;
+
+ /// ExpCmdSN.
+ uint32_t exp_cmd_sn; // ExpCmdSN
+
+ /// MaxCmdSN.
+ uint32_t max_cmd_sn;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint32_t reserved2;
+
+ /// Reserved for future usage, always MUST be 0.
+ uint64_t reserved3;
+} iscsi_nop_in_packet;
+
+
+/// Maximum length of a key according to iSCSI specifications.
+#define ISCSI_TEXT_KEY_MAX_LEN 63U
+
+/// Maximum length of value for a simple key type.
+#define ISCSI_TEXT_VALUE_MAX_SIMPLE_LEN 255U
+
+/// Maximum length of value for a normal key.
+#define ISCSI_TEXT_VALUE_MAX_LEN 8192U
+
+
+typedef struct iscsi_connection iscsi_connection;
+
+
+/// Read/write lock for iSCSI global vector. MUST be initialized with iscsi_create before any iSCSI functions are used.
+//extern pthread_rwlock_t iscsi_globvec_rwlock;
+
+
+/// iSCSI SCSI status code: Good.
+#define ISCSI_SCSI_STATUS_GOOD 0x00
+
+/// iSCSI SCSI status code: Check condition.
+#define ISCSI_SCSI_STATUS_CHECK_COND 0x02
+
+/// iSCSI SCSI status code: Condition met.
+#define ISCSI_SCSI_STATUS_COND_MET 0x04
+
+/// iSCSI SCSI status code: Busy.
+#define ISCSI_SCSI_STATUS_BUSY 0x08
+
+/// iSCSI SCSI status code: Intermediate.
+#define ISCSI_SCSI_STATUS_INTERMEDIATE 0x10
+
+/// iSCSI SCSI status code: Intermediate condition met.
+#define ISCSI_SCSI_STATUS_INTERMEDIATE_COND_MET 0x14
+
+/// iSCSI SCSI status code: Reservation conflict.
+#define ISCSI_SCSI_STATUS_RESERVATION_CONFLICT 0x18
+
+/// iSCSI SCSI status code: Task set full.
+#define ISCSI_SCSI_STATUS_TASK_SET_FULL 0x28
+
+/// iSCSI SCSI status code: ACA active.
+#define ISCSI_SCSI_STATUS_ACA_ACTIVE 0x30
+
+/// iSCSI SCSI status code: Task aborted.
+#define ISCSI_SCSI_STATUS_TASK_ABORTED 0x40
+
+
+/// iSCSI SCSI sense key: No sense.
+#define ISCSI_SCSI_SENSE_KEY_NO_SENSE 0x00
+
+/// iSCSI SCSI sense key: Recovered error.
+#define ISCSI_SCSI_SENSE_KEY_RECOVERED_ERR 0x01
+
+/// iSCSI SCSI sense key: Not ready.
+#define ISCSI_SCSI_SENSE_KEY_NOT_READY 0x02
+
+/// iSCSI SCSI sense key: Medium error.
+#define ISCSI_SCSI_SENSE_KEY_MEDIUM_ERR 0x03
+
+/// iSCSI SCSI sense key: Hardware error.
+#define ISCSI_SCSI_SENSE_KEY_HARDWARE_ERR 0x04
+
+/// iSCSI SCSI sense key: Illegal request.
+#define ISCSI_SCSI_SENSE_KEY_ILLEGAL_REQ 0x05
+
+/// iSCSI SCSI sense key: Unit attention.
+#define ISCSI_SCSI_SENSE_KEY_UNIT_ATTENTION 0x06
+
+/// iSCSI SCSI sense key: Data protect.
+#define ISCSI_SCSI_SENSE_KEY_DATA_PROTECT 0x07
+
+/// iSCSI SCSI sense key: Blank check.
+#define ISCSI_SCSI_SENSE_KEY_BLANK_CHECK 0x08
+
+/// iSCSI SCSI sense key: Vendor specific.
+#define ISCSI_SCSI_SENSE_KEY_VENDOR_SPECIFIC 0x09
+
+/// iSCSI SCSI sense key: Copy aborted.
+#define ISCSI_SCSI_SENSE_KEY_COPY_ABORTED 0x0A
+
+/// iSCSI SCSI sense key: Aborted command.
+#define ISCSI_SCSI_SENSE_KEY_ABORTED_COMMAND 0x0B
+
+/// iSCSI SCSI sense key: Volume overflow.
+#define ISCSI_SCSI_SENSE_KEY_VOLUME_OVERFLOW 0x0D
+
+/// iSCSI SCSI sense key: Miscompare.
+#define ISCSI_SCSI_SENSE_KEY_MISCOMPARE 0x0E
+
+
+/// iSCSI SCSI Additional Sense Code (ASC): No additional sense.
+#define ISCSI_SCSI_ASC_NO_ADDITIONAL_SENSE 0x00
+
+/// iSCSI SCSI Additional Sense Code (ASC): Peripheral device write fault.
+#define ISCSI_SCSI_ASC_PERIPHERAL_DEVICE_WRITE_FAULT 0x03
+
+/// iSCSI SCSI Additional Sense Code (ASC): Logical unit not ready.
+#define ISCSI_SCSI_ASC_LOGICAL_UNIT_NOT_READY 0x04
+
+/// iSCSI SCSI Additional Sense Code (ASC): Warning.
+#define ISCSI_SCSI_ASC_WARNING 0x0B
+
+/// iSCSI SCSI Additional Sense Code (ASC): Write error.
+#define ISCSI_SCSI_ASC_WRITE_ERR 0x0C
+
+/// iSCSI SCSI Additional Sense Code (ASC): Block guard check failed.
+#define ISCSI_SCSI_ASC_LOGICAL_BLOCK_GUARD_CHECK_FAIL 0x10
+
+/// iSCSI SCSI Additional Sense Code (ASC): Block application tag checdk failed.
+#define ISCSI_SCSI_ASC_LOGICAL_BLOCK_APP_TAG_CHECK_FAIL 0x10
+
+/// iSCSI SCSI Additional Sense Code (ASC): Block reference tag check failed.
+#define ISCSI_SCSI_ASC_LOGICAL_BLOCK_REF_TAG_CHECK_FAIL 0x10
+
+/// iSCSI SCSI Additional Sense Code (ASC): Unrecovered read error.
+#define ISCSI_SCSI_ASC_UNRECOVERED_READ_ERR 0x11
+
+/// iSCSI SCSI Additional Sense Code (ASC): Miscompare during verify operation.
+#define ISCSI_SCSI_ASC_MISCOMPARE_DURING_VERIFY_OPERATION 0x1D
+
+/// iSCSI SCSI Additional Sense Code (ASC): Invalid command operation code.
+#define ISCSI_SCSI_ASC_INVALID_COMMAND_OPERATION_CODE 0x20
+
+/// iSCSI SCSI Additional Sense Code (ASC): Access denied.
+#define ISCSI_SCSI_ASC_ACCESS_DENIED 0x20
+
+/// iSCSI SCSI Additional Sense Code (ASC): Logical block address out of range.
+#define ISCSI_SCSI_ASC_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x21
+
+/// iSCSI SCSI Additional Sense Code (ASC): Invalid field in CDB.
+#define ISCSI_SCSI_ASC_INVALID_FIELD_IN_CDB 0x24
+
+/// iSCSI SCSI Additional Sense Code (ASC): Logical unit not supported.
+#define ISCSI_SCSI_ASC_LU_NOT_SUPPORTED 0x25
+
+/// iSCSI SCSI Additional Sense Code (ASC): Write protected.
+#define ISCSI_SCSI_ASC_WRITE_PROTECTED 0x27
+
+/// iSCSI SCSI Additional Sense Code (ASC): Data has changed.
+#define ISCSI_SCSI_ASC_CAPACITY_DATA_HAS_CHANGED 0x2A
+
+/// iSCSI SCSI Additional Sense Code (ASC): Format command failed.
+#define ISCSI_SCSI_ASC_FORMAT_COMMAND_FAIL 0x31
+
+/// iSCSI SCSI Additional Sense Code (ASC): Saving parameters not supported.
+#define ISCSI_SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED 0x39
+
+/// iSCSI SCSI Additional Sense Code (ASC): Internal target failure.
+#define ISCSI_SCSI_ASC_INTERNAL_TARGET_FAIL 0x44
+
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): Cause not reportable.
+#define ISCSI_SCSI_ASCQ_CAUSE_NOT_REPORTABLE 0x00
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): Becoming ready.
+#define ISCSI_SCSI_ASCQ_BECOMING_READY 0x01
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): Format command failed.
+#define ISCSI_SCSI_ASCQ_FORMAT_COMMAND_FAIL 0x01
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): Block guard check failed.
+#define ISCSI_SCSI_ASCQ_LOGICAL_BLOCK_GUARD_CHECK_FAIL 0x01
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): Block application tag check failed.
+#define ISCSI_SCSI_ASCQ_LOGICAL_BLOCK_APP_TAG_CHECK_FAIL 0x02
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): No access rights.
+#define ISCSI_SCSI_ASCQ_NO_ACCESS_RIGHTS 0x02
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): Manual intervention required.
+#define ISCSI_SCSI_ASCQ_MANUAL_INTERVENTION_REQUIRED 0x03
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): Block reference tag check failed.
+#define ISCSI_SCSI_ASCQ_LOGICAL_BLOCK_REF_TAG_CHECK_FAIL 0x03
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): Power loss expected.
+#define ISCSI_SCSI_ASCQ_POWER_LOSS_EXPECTED 0x08
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): Invalid logical unit identifier.
+#define ISCSI_SCSI_ASCQ_INVALID_LU_IDENTIFIER 0x09
+
+/// iSCSI SCSI Additional Sense Code Qualifier (ASCQ): Capacity data has changed.
+#define ISCSI_SCSI_ASCQ_CAPACITY_DATA_HAS_CHANGED 0x09
+
+
+
+
+/// iSCSI SCSI task run: Unknown.
+#define ISCSI_SCSI_TASK_RUN_UNKNOWN (-1)
+
+/// iSCSI SCSI task run: Completed.
+#define ISCSI_SCSI_TASK_RUN_COMPLETE 0
+
+
+typedef struct iscsi_scsi_task iscsi_scsi_task;
+typedef struct iscsi_scsi_lun iscsi_scsi_lun;
+
+
+/**
+ * @brief iSCSI SCSI Task.
+ *
+ * This structure is used for the iSCSI SCSI
+ * layer task management.
+ */
+typedef struct iscsi_scsi_task {
+ /// Connection associated with this task.
+ iscsi_connection *connection;
+
+ /// SCSI Command Descriptor Block (CDB).
+ iscsi_scsi_cdb *cdb;
+
+ /// SCSI sense data. If set, owned by this struct.
+ iscsi_scsi_sense_data_packet *sense_data;
+
+ /// Output buffer. If set, owned by this struct.
+ uint8_t *buf;
+
+ /// Offset in bytes in image for DATA-in command.
+ size_t file_offset;
+
+ /// Length of buffer in bytes.
+ uint32_t len;
+
+ /// Expected data transfer length (from iSCSI PDU field)
+ uint32_t exp_xfer_len;
+
+ /// Unique identifier for this task.
+ uint64_t id;
+
+ /// Whether the R bit was set in the iSCSI request (BHS).
+ bool is_read;
+
+ /// Whether the W bit was set in the iSCSI request (BHS).
+ bool is_write;
+
+ /// Sense data length.
+ uint8_t sense_data_len;
+
+ /// iSCSI SCSI status code.
+ uint8_t status;
+
+ /// Uplink read mutex for sync
+ pthread_mutex_t uplink_mutex;
+
+ /// Conditional to signal uplink read complete
+ pthread_cond_t uplink_cond;
+} iscsi_scsi_task;
+
+
+/// iSCSI SCSI emulation physical block size in bytes.
+#define ISCSI_SCSI_EMU_PHYSICAL_BLOCK_SIZE DNBD3_BLOCK_SIZE
+
+/// iSCSI SCSI emulation logical block size in bytes.
+#define ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE (512)
+
+/// Block shift difference between dnbd3 (4k) and iSCSI (512b)
+#define ISCSI_SCSI_EMU_BLOCK_DIFF_SHIFT (3)
+
+_Static_assert( (ISCSI_SCSI_EMU_LOGICAL_BLOCK_SIZE << ISCSI_SCSI_EMU_BLOCK_DIFF_SHIFT) == ISCSI_SCSI_EMU_PHYSICAL_BLOCK_SIZE,
+ "Block size parameters are inconsistent" );
+
+
+/// iSCSI target node WWN identifier prefix string.
+#define ISCSI_TARGET_NODE_WWN_NAME_PREFIX "wwn-0x"
+
+/// iSCSI target node maximum length
+#define ISCSI_TARGET_NODE_MAX_NAME_LEN 223U
+
+
+/**
+ * All mandatory fields in login process.
+ * Set to -1 or NULL if not sent by client.
+ */
+typedef struct iscsi_login_kvp
+{
+ /// Largest PDU client can receive.
+ int MaxRecvDataSegmentLength;
+
+ /// Maximum burst length client can receive.
+ int MaxBurstLength;
+
+ // Maximum unsolicited burst length client can receive.
+ int FirstBurstLength;
+
+ /// Maximum number of connections.
+ int MaxConnections;
+
+ /// Error recovery level.
+ int ErrorRecoveryLevel;
+
+ /// The session type (Discovery, Normal).
+ const char *SessionType;
+
+ /// Desired auth method.
+ const char *AuthMethod;
+
+ /// SendTargets command.
+ const char *SendTargets;
+
+ /// HeaderDigest requested by client.
+ const char *HeaderDigest;
+
+ /// DataDigest requested by client.
+ const char *DataDigest;
+
+ const char *InitiatorName;
+
+ const char *TargetName;
+} iscsi_negotiation_kvp;
+
+/**
+ * Options/limits the client told us that
+ * are relevant for proper communication
+ */
+typedef struct iscsi_session_options
+{
+ /// Largest PDU client can receive.
+ int MaxRecvDataSegmentLength;
+
+ /// Maximum burst length client can receive.
+ int MaxBurstLength;
+
+ // Maximum unsolicited burst length client can receive.
+ int FirstBurstLength;
+} iscsi_session_options;
+
+
+typedef struct iscsi_pdu iscsi_pdu;
+
+
+/// iSCSI connection read packet data return code from iscsi_connection_pdu_read function: Packet parsed successfully.
+#define ISCSI_CONNECT_PDU_READ_OK 0
+
+/// iSCSI connection read packet data return code from iscsi_connection_pdu_read function: Fatail error during packet parsing.
+#define ISCSI_CONNECT_PDU_READ_ERR_FATAL (-1)
+
+/// iSCSI connection read packet data return code from iscsi_connection_pdu_read function: Login error response.
+#define ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE (-2)
+
+/// iSCSI connection read packet data return code from iscsi_connection_pdu_read function: Login parameter error.
+#define ISCSI_CONNECT_PDU_READ_ERR_LOGIN_PARAMETER (-3)
+
+/// iSCSI connection read packet data return code from iscsi_connection_pdu_read function: Login parameter not exchanged once error.
+#define ISCSI_CONNECT_PDU_READ_ERR_LOGIN_PARAMETER_XCHG_NOT_ONCE (-4)
+
+
+/// iSCSI connection state: Fresh connection, no login yet.
+#define ISCSI_CONNECT_STATE_NEW 0
+
+/// iSCSI connection state: Running as session type "normal".
+#define ISCSI_CONNECT_STATE_NORMAL_SESSION 1
+
+/// iSCSI connection state: Exiting, teardown of connection imminent.
+#define ISCSI_CONNECT_STATE_EXITING 2
+
+/// iSCSI connection state: Invalid.
+#define ISCSI_CONNECT_STATE_INVALID 3
+
+
+/// Number of attempts for writing to iSCSI connection socket.
+#define ISCSI_CONNECT_SOCKET_WRITE_RETRIES 3
+
+
+/**
+ * @brief iSCSI incoming connection.
+ *
+ * This structure is used for maintaining incoming iSCSI
+ * connections. Negiotiated text key=value pairs are
+ * stored here, status of the connection, session
+ * and iSCSI portals.
+ */
+typedef struct iscsi_connection {
+ /// Associated dnbd3 client
+ dnbd3_client_t *client;
+
+ /// Internal connection identifier
+ int id;
+
+ /// iSCSI connection flags.
+ int flags;
+
+ /// iSCSI connection state.
+ int state;
+
+ /// Initiator Session ID (ISID).
+ iscsi_isid isid;
+
+ /// Target Session Identifying Handle (TSIH).
+ uint16_t tsih;
+
+ /// Connection ID (CID).
+ uint16_t cid;
+
+ /// StatSN.
+ uint32_t stat_sn;
+
+ /// ExpCmdSN.
+ uint32_t exp_cmd_sn;
+
+ /// MaxCmdSN.
+ uint32_t max_cmd_sn;
+
+ /// Session options client sent in login request.
+ iscsi_session_options opts;
+} iscsi_connection;
+
+
+typedef struct iscsi_task iscsi_task;
+
+
+/// iSCSI PDU will contain a small buffer for sending/receiving trivial PDUs with no/very small DS, and small AH
+#define ISCSI_INTERNAL_BUFFER_SIZE (2 * ISCSI_BHS_SIZE)
+
+/**
+ * @brief This structure is used to partially read PDU data.
+ *
+ * Since TCP/IP packets can be fragmented, this
+ * structure is needed which maintains reading
+ * and filling the BHS, AHS and DS properly.
+ */
+typedef struct iscsi_pdu {
+ /// iSCSI Basic Header Segment (BHS) packet data.
+ iscsi_bhs_packet *bhs_pkt;
+
+ /// iSCSI Advanced Header Segment (AHS) packet data for fast access and is straight after BHS packet in memory.
+ iscsi_ahs_packet *ahs_pkt;
+
+ /// iSCSI DataSegment (DS) packet data for fast access and is straight after BHS, AHS and header digest packet in memory.
+ void *ds_cmd_data;
+
+ /// Flags.
+ int flags;
+
+ /// Bytes of Basic Header Segment (BHS) already read.
+ uint bhs_pos;
+
+ /// AHSLength.
+ uint ahs_len;
+
+ /// DataSegmentLength.
+ uint32_t ds_len;
+
+ /// DS Buffer write pos when filling buffer for sending.
+ uint32_t ds_write_pos;
+
+ /// CmdSN.
+ uint32_t cmd_sn;
+
+ /// If we need a larger area than internal_buffer
+ void *big_alloc;
+
+ /// Used for smaller PDUs to avoid extra malloc/free
+ char internal_buffer[ISCSI_INTERNAL_BUFFER_SIZE];
+} iscsi_pdu;
+
+
+/**
+ * @brief This structure is used for iSCSI task management.
+ *
+ * This structure maintains the iSCSI task handling
+ * including the underlying SCSI layer.
+ */
+typedef struct iscsi_task {
+ /// Underlying SCSI task structure.
+ iscsi_scsi_task scsi_task;
+
+ /// Buffer length in bytes.
+ uint32_t len;
+
+ /// LUN identifier associated with this task (always MUST be between 0 and 7), used for hot removal tracking.
+ int lun_id;
+
+ /// Initiator Task Tag (ITT).
+ uint32_t init_task_tag;
+
+ /// Target Transfer Tag (TTT).
+ uint32_t target_xfer_tag;
+} iscsi_task;
+
+void iscsi_connection_handle(dnbd3_client_t *client, const dnbd3_request_t *request, const int len); // Handles an iSCSI connection until connection is closed
+
+#endif /* DNBD3_ISCSI_H_ */
diff --git a/src/server/net.c b/src/server/net.c
index eb51d29..f2f63b8 100644
--- a/src/server/net.c
+++ b/src/server/net.c
@@ -20,11 +20,13 @@
#include "helper.h"
#include "image.h"
+#include "iscsi.h"
#include "uplink.h"
#include "locks.h"
#include "rpc.h"
#include "altservers.h"
#include "reference.h"
+#include "sendfile.h"
#include <dnbd3/shared/sockhelper.h>
#include <dnbd3/shared/timing.h>
@@ -32,26 +34,19 @@
#include <dnbd3/shared/serialize.h>
#include <assert.h>
+#include <netinet/tcp.h>
-#ifdef __linux__
-#include <sys/sendfile.h>
-#endif
-#ifdef __FreeBSD__
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/uio.h>
-#endif
#include <jansson.h>
#include <inttypes.h>
#include <stdatomic.h>
#include <signal.h>
+#include <dnbd3/afl.h>
+
static dnbd3_client_t *_clients[SERVER_MAX_CLIENTS];
static int _num_clients = 0;
static pthread_mutex_t _clients_lock;
-static char nullbytes[500];
-
static atomic_uint_fast64_t totalBytesSent = 0;
// Adding and removing clients -- list management
@@ -63,9 +58,7 @@ static void uplinkCallback(void *data, uint64_t handle, uint64_t start, uint32_t
static inline bool recv_request_header(int sock, dnbd3_request_t *request)
{
ssize_t ret, fails = 0;
-#ifdef DNBD3_SERVER_AFL
- sock = 0;
-#endif
+
// Read request header from socket
while ( ( ret = recv( sock, request, sizeof(*request), MSG_WAITALL ) ) != sizeof(*request) ) {
if ( errno == EINTR && ++fails < 10 ) continue;
@@ -90,9 +83,6 @@ static inline bool recv_request_header(int sock, dnbd3_request_t *request)
static inline bool recv_request_payload(int sock, uint32_t size, serialized_buffer_t *payload)
{
-#ifdef DNBD3_SERVER_AFL
- sock = 0;
-#endif
if ( size == 0 ) {
logadd( LOG_ERROR, "Called recv_request_payload() to receive 0 bytes" );
return false;
@@ -101,8 +91,9 @@ static inline bool recv_request_payload(int sock, uint32_t size, serialized_buff
logadd( LOG_ERROR, "Called recv_request_payload() for more bytes than the passed buffer could hold!" );
return false;
}
- if ( sock_recv( sock, payload->buffer, size ) != (ssize_t)size ) {
- logadd( LOG_DEBUG1, "Could not receive request payload of length %d\n", (int)size );
+ const ssize_t ret = sock_recv( sock, payload->buffer, size );
+ if ( ret != (ssize_t)size ) {
+ logadd( LOG_DEBUG1, "Could not receive request payload of length %d (got %d, errno %d)\n", (int)size, (int)ret, errno );
return false;
}
// Prepare payload buffer for reading
@@ -111,60 +102,82 @@ static inline bool recv_request_payload(int sock, uint32_t size, serialized_buff
}
/**
- * Send reply with optional payload. payload can be null. The caller has to
- * acquire the sendMutex first.
+ * Send reply with optional payload. payload can be null.
*/
-static inline bool send_reply(int sock, dnbd3_reply_t *reply, const void *payload)
+static bool send_reply(dnbd3_client_t *client, dnbd3_reply_t *reply, const void *payload, const bool lock)
{
- const uint32_t size = reply->size;
+ const uint32_t size = reply->size; // Copy because of fixup_reply()
+
fixup_reply( *reply );
- if ( sock_sendAll( sock, reply, sizeof(dnbd3_reply_t), 1 ) != sizeof(dnbd3_reply_t) ) {
+ if ( lock ) {
+ mutex_lock( &client->sendMutex );
+ }
+ if ( sock_sendAll( client->sock, reply, sizeof(dnbd3_reply_t), 1 ) != sizeof(dnbd3_reply_t) ) {
+ if ( lock ) {
+ mutex_unlock( &client->sendMutex );
+ }
logadd( LOG_DEBUG1, "Sending reply header to client failed" );
return false;
}
if ( size != 0 && payload != NULL ) {
- if ( sock_sendAll( sock, payload, size, 1 ) != (ssize_t)size ) {
+ if ( sock_sendAll( client->sock, payload, size, 1 ) != (ssize_t)size ) {
+ if ( lock ) {
+ mutex_unlock( &client->sendMutex );
+ }
logadd( LOG_DEBUG1, "Sending payload of %"PRIu32" bytes to client failed", size );
return false;
}
}
+ if ( lock ) {
+ mutex_unlock( &client->sendMutex );
+ }
return true;
}
-/**
- * Send given amount of null bytes. The caller has to acquire the sendMutex first.
- */
-static inline bool sendPadding( const int fd, uint32_t bytes )
+void net_init()
{
- ssize_t ret;
- while ( bytes >= sizeof(nullbytes) ) {
- ret = sock_sendAll( fd, nullbytes, sizeof(nullbytes), 2 );
- if ( ret <= 0 )
- return false;
- bytes -= (uint32_t)ret;
- }
- return sock_sendAll( fd, nullbytes, bytes, 2 ) == (ssize_t)bytes;
+ mutex_init( &_clients_lock, LOCK_CLIENT_LIST );
}
-void net_init()
+void initClientStruct(dnbd3_client_t *client)
{
- mutex_init( &_clients_lock, LOCK_CLIENT_LIST );
+ mutex_init( &client->lock, LOCK_CLIENT );
+ mutex_init( &client->sendMutex, LOCK_CLIENT_SEND );
+
+ mutex_lock( &client->lock );
+ host_to_string( &client->host, client->hostName, HOSTNAMELEN );
+ client->hostName[HOSTNAMELEN-1] = '\0';
+ mutex_unlock( &client->lock );
+ client->bytesSent = 0;
+ client->relayedCount = 0;
}
void* net_handleNewConnection(void *clientPtr)
{
dnbd3_client_t * const client = (dnbd3_client_t *)clientPtr;
dnbd3_request_t request;
+ dnbd3_cache_map_t *cache = NULL;
client->thread = pthread_self();
// Await data from client. Since this is a fresh connection, we expect data right away
sock_setTimeout( client->sock, _clientTimeout );
+ // NODELAY makes sense since we're sending a lot of data
+ int e2 = 1;
+ socklen_t optlen = sizeof(e2);
+ setsockopt( client->sock, IPPROTO_TCP, TCP_NODELAY, (void *)&e2, optlen );
+ // Also increase send buffer
+ if ( getsockopt( client->sock, SOL_SOCKET, SO_SNDBUF, (void *)&e2, &optlen ) == 0 ) {
+#ifdef __linux__
+ // Linux doubles the value to account for overhead, get "real" value
+ e2 /= 2;
+#endif
+ if ( e2 < SERVER_TCP_BUFFER_MIN_SIZE_PAYLOAD ) {
+ e2 = SERVER_TCP_BUFFER_MIN_SIZE_PAYLOAD;
+ setsockopt( client->sock, SOL_SOCKET, SO_SNDBUF, &e2, sizeof(e2) );
+ }
+ }
do {
-#ifdef DNBD3_SERVER_AFL
- const int ret = (int)recv( 0, &request, sizeof(request), MSG_WAITALL );
-#else
const int ret = (int)recv( client->sock, &request, sizeof(request), MSG_WAITALL );
-#endif
// It's expected to be a real dnbd3 client
// Check request for validity. This implicitly dictates that all HTTP requests are more than 24 bytes...
if ( ret != (int)sizeof(request) ) {
@@ -177,8 +190,21 @@ void* net_handleNewConnection(void *clientPtr)
if ( ((char*)&request)[0] == 'G' || ((char*)&request)[0] == 'P' ) {
// Close enough...
rpc_sendStatsJson( client->sock, &client->host, &request, ret );
+ } else if ( ((char*)&request)[0] == 0x43 ) { // Login opcode 0x03 + immediate bit (0x40) set
+ if ( !_iScsiServer ) {
+ logadd( LOG_INFO, "Received iSCSI login request from %s, but iSCSI server is not enabled", client->hostName );
+ } else {
+ initClientStruct( client );
+ if ( !addToList( client ) ) {
+ freeClientStruct( client );
+ logadd( LOG_WARNING, "Could not add new iSCSI client to list when connecting" );
+ } else {
+ iscsi_connection_handle( client, &request, ret );
+ goto exit_client_cleanup;
+ }
+ }
} else {
- logadd( LOG_DEBUG1, "Magic in client handshake incorrect" );
+ logadd( LOG_DEBUG1, "Magic in client handshake unknown" );
}
goto fail_preadd;
}
@@ -190,26 +216,17 @@ void* net_handleNewConnection(void *clientPtr)
}
} while (0);
// Fully init client struct
- mutex_init( &client->lock, LOCK_CLIENT );
- mutex_init( &client->sendMutex, LOCK_CLIENT_SEND );
-
- mutex_lock( &client->lock );
- host_to_string( &client->host, client->hostName, HOSTNAMELEN );
- client->hostName[HOSTNAMELEN-1] = '\0';
- mutex_unlock( &client->lock );
- client->bytesSent = 0;
- client->relayedCount = 0;
+ initClientStruct( client );
if ( !addToList( client ) ) {
freeClientStruct( client );
- logadd( LOG_WARNING, "Could not add new client to list when connecting" );
- return NULL;
+ logadd( LOG_WARNING, "Could not add new DNBD3 client to list when connecting" );
+ goto fail_preadd;
}
dnbd3_reply_t reply;
dnbd3_image_t *image = NULL;
- dnbd3_cache_map_t *cache = NULL;
int image_file = -1;
int num;
@@ -300,7 +317,7 @@ void* net_handleNewConnection(void *clientPtr)
serializer_put_uint64( &payload, image->virtualFilesize );
reply.cmd = CMD_SELECT_IMAGE;
reply.size = serializer_get_written_length( &payload );
- if ( !send_reply( client->sock, &reply, &payload ) ) {
+ if ( !send_reply( client, &reply, &payload, false ) ) {
bOk = false;
}
}
@@ -319,7 +336,8 @@ void* net_handleNewConnection(void *clientPtr)
while ( recv_request_header( client->sock, &request ) ) {
if ( _shutdown ) break;
if ( likely ( request.cmd == CMD_GET_BLOCK ) ) {
-
+ // since the relayed count can only increase in this very loop, it is safe to check this here once
+ const bool lock = client->relayedCount > 0;
const uint64_t offset = request.offset_small; // Copy to full uint64 to prevent repeated masking
reply.handle = request.handle;
if ( unlikely( offset >= image->virtualFilesize ) ) {
@@ -327,7 +345,7 @@ void* net_handleNewConnection(void *clientPtr)
logadd( LOG_WARNING, "Client %s requested non-existent block", client->hostName );
reply.size = 0;
reply.cmd = CMD_ERROR;
- send_reply( client->sock, &reply, NULL );
+ send_reply( client, &reply, NULL, lock );
continue;
}
if ( unlikely( offset + request.size > image->virtualFilesize ) ) {
@@ -335,7 +353,17 @@ void* net_handleNewConnection(void *clientPtr)
logadd( LOG_WARNING, "Client %s requested data block that extends beyond image size", client->hostName );
reply.size = 0;
reply.cmd = CMD_ERROR;
- send_reply( client->sock, &reply, NULL );
+ send_reply( client, &reply, NULL, lock );
+ continue;
+ }
+ if ( unlikely( offset >= image->realFilesize ) ) {
+ // Shortcut - only virtual bytes (padding)
+ reply.cmd = CMD_GET_BLOCK;
+ reply.size = request.size;
+ if ( lock ) mutex_lock( &client->sendMutex );
+ send_reply( client, &reply, NULL, false );
+ sock_sendPadding( client->sock, request.size );
+ if ( lock ) mutex_unlock( &client->sendMutex );
continue;
}
@@ -373,78 +401,36 @@ void* net_handleNewConnection(void *clientPtr)
reply.size = request.size;
fixup_reply( reply );
- const bool lock = image->uplinkref != NULL;
if ( lock ) mutex_lock( &client->sendMutex );
// Send reply header
if ( send( client->sock, &reply, sizeof(dnbd3_reply_t), (request.size == 0 ? 0 : MSG_MORE) ) != sizeof(dnbd3_reply_t) ) {
+ logadd( LOG_DEBUG1, "Sending CMD_GET_BLOCK reply header to %s failed (errno=%d)", client->hostName, errno );
if ( lock ) mutex_unlock( &client->sendMutex );
- logadd( LOG_DEBUG1, "Sending CMD_GET_BLOCK reply header to %s failed", client->hostName );
goto exit_client_cleanup;
}
- if ( request.size != 0 ) {
- // Send payload if request length > 0
- size_t done = 0;
- off_t foffset = (off_t)offset;
- size_t realBytes;
- if ( offset + request.size <= image->realFilesize ) {
- realBytes = request.size;
- } else {
- realBytes = (size_t)(image->realFilesize - offset);
+ const size_t realBytes = offset + request.size <= image->realFilesize
+ ? request.size : (image->realFilesize - offset);
+ bool ret = sendfile_all( image_file, client->sock, offset, realBytes );
+ if ( !ret ) {
+ const int err = errno;
+
+ if ( lock ) mutex_unlock( &client->sendMutex );
+ if ( err != EPIPE && err != ECONNRESET && err != ESHUTDOWN
+ && err != EAGAIN && err != EWOULDBLOCK ) {
+ logadd( LOG_DEBUG1, "sendfile to %s failed (%d bytes, errno=%d)",
+ client->hostName, (int)realBytes, err );
}
- while ( done < realBytes ) {
- // TODO: Should we consider EOPNOTSUPP on BSD for sendfile and fallback to read/write?
- // Linux would set EINVAL or ENOSYS instead, which it unfortunately also does for a couple of other failures :/
- // read/write would kill performance anyways so a fallback would probably be of little use either way.
-#ifdef DNBD3_SERVER_AFL
- char buf[1000];
- size_t cnt = realBytes - done;
- if ( cnt > 1000 ) {
- cnt = 1000;
- }
- const ssize_t sent = pread( image_file, buf, cnt, foffset );
- if ( sent > 0 ) {
- //write( client->sock, buf, sent ); // This is not verified in any way, so why even do it...
- } else {
- const int err = errno;
-#elif defined(__linux__)
- const ssize_t sent = sendfile( client->sock, image_file, &foffset, realBytes - done );
- if ( sent <= 0 ) {
- const int err = errno;
-#elif defined(__FreeBSD__)
- off_t sent;
- const int ret = sendfile( image_file, client->sock, foffset, realBytes - done, NULL, &sent, 0 );
- if ( ret == -1 || sent == 0 ) {
- const int err = errno;
- if ( ret == -1 ) {
- if ( err == EAGAIN || err == EINTR ) { // EBUSY? manpage doesn't explicitly mention *sent here.. But then again we dont set the according flag anyways
- done += sent;
- continue;
- }
- sent = -1;
- }
-#endif
- if ( lock ) mutex_unlock( &client->sendMutex );
- if ( sent == -1 ) {
- if ( err != EPIPE && err != ECONNRESET && err != ESHUTDOWN
- && err != EAGAIN && err != EWOULDBLOCK ) {
- logadd( LOG_DEBUG1, "sendfile to %s failed (image to net. sent %d/%d, errno=%d)",
- client->hostName, (int)done, (int)realBytes, err );
- }
- if ( err == EBADF || err == EFAULT || err == EINVAL || err == EIO ) {
- logadd( LOG_INFO, "Disabling %s:%d", image->name, image->rid );
- image->problem.read = true;
- }
- }
- goto exit_client_cleanup;
- }
- done += sent;
+ if ( err == EBADF || err == EFAULT || err == EINVAL || err == EIO ) {
+ logadd( LOG_INFO, "Disabling %s:%d", image->name, image->rid );
+ image->problem.read = true;
}
- if ( request.size > (uint32_t)realBytes ) {
- if ( !sendPadding( client->sock, request.size - (uint32_t)realBytes ) ) {
- if ( lock ) mutex_unlock( &client->sendMutex );
- goto exit_client_cleanup;
- }
+ goto exit_client_cleanup;
+ }
+ if ( request.size > (uint32_t)realBytes ) {
+ if ( !sock_sendPadding( client->sock, request.size - (uint32_t)realBytes ) ) {
+ if ( lock ) mutex_unlock( &client->sendMutex );
+ goto exit_client_cleanup;
}
}
if ( lock ) mutex_unlock( &client->sendMutex );
@@ -466,22 +452,18 @@ void* net_handleNewConnection(void *clientPtr)
num = altservers_getListForClient( client, server_list, NUMBER_SERVERS );
reply.cmd = CMD_GET_SERVERS;
reply.size = (uint32_t)( num * sizeof(dnbd3_server_entry_t) );
- mutex_lock( &client->sendMutex );
- send_reply( client->sock, &reply, server_list );
- mutex_unlock( &client->sendMutex );
- goto set_name;
+ if ( !send_reply( client, &reply, server_list, true ) ) {
+ logadd( LOG_DEBUG1, "Sending CMD_GET_SERVERS reply to %s failed.", client->hostName );
+ goto exit_client_cleanup;
+ }
break;
case CMD_KEEPALIVE:
reply.cmd = CMD_KEEPALIVE;
reply.size = 0;
- mutex_lock( &client->sendMutex );
- send_reply( client->sock, &reply, NULL );
- mutex_unlock( &client->sendMutex );
-set_name: ;
- if ( !hasName ) {
- hasName = true;
- setThreadName( client->hostName );
+ if ( !send_reply( client, &reply, NULL, true ) ) {
+ logadd( LOG_DEBUG1, "Sending CMD_KEEPALIVE reply to %s failed.", client->hostName );
+ goto exit_client_cleanup;
}
break;
@@ -494,14 +476,18 @@ set_name: ;
mutex_lock( &client->sendMutex );
if ( image->crc32 == NULL ) {
reply.size = 0;
- send_reply( client->sock, &reply, NULL );
+ bOk = send_reply( client, &reply, NULL, false );
} else {
const uint32_t size = reply.size = (uint32_t)( (IMGSIZE_TO_HASHBLOCKS(image->realFilesize) + 1) * sizeof(uint32_t) );
- send_reply( client->sock, &reply, NULL );
- send( client->sock, &image->masterCrc32, sizeof(uint32_t), MSG_MORE );
- send( client->sock, image->crc32, size - sizeof(uint32_t), 0 );
+ bOk = send_reply( client, &reply, NULL, false );
+ bOk = bOk && send( client->sock, &image->masterCrc32, sizeof(uint32_t), MSG_MORE ) == sizeof(uint32_t);
+ bOk = bOk && send( client->sock, image->crc32, size - sizeof(uint32_t), 0 ) == size - sizeof(uint32_t);
}
mutex_unlock( &client->sendMutex );
+ if ( !bOk ) {
+ logadd( LOG_DEBUG1, "Sending CMD_GET_CRC32 reply to %s failed.", client->hostName );
+ goto exit_client_cleanup;
+ }
break;
default:
@@ -509,6 +495,10 @@ set_name: ;
break;
} // end switch
+ if ( !hasName ) {
+ hasName = true;
+ setThreadName( client->hostName );
+ }
} // end loop
} // end bOk
exit_client_cleanup: ;
@@ -516,11 +506,11 @@ exit_client_cleanup: ;
removeFromList( client );
totalBytesSent += client->bytesSent;
// Access time, but only if client didn't just probe
- if ( image != NULL && client->bytesSent > DNBD3_BLOCK_SIZE * 10 ) {
- mutex_lock( &image->lock );
- timing_get( &image->atime );
- image->accessed = true;
- mutex_unlock( &image->lock );
+ if ( client->image != NULL && client->bytesSent > DNBD3_BLOCK_SIZE * 10 ) {
+ mutex_lock( &client->image->lock );
+ timing_get( &client->image->atime );
+ client->image->accessed = true;
+ mutex_unlock( &client->image->lock );
}
if ( cache != NULL ) {
ref_put( &cache->reference );
@@ -686,7 +676,7 @@ static dnbd3_client_t* freeClientStruct(dnbd3_client_t *client)
dnbd3_uplink_t *uplink = ref_get_uplink( &client->image->uplinkref );
if ( uplink != NULL ) {
if ( client->relayedCount != 0 ) {
- uplink_removeEntry( uplink, client, &uplinkCallback );
+ uplink_removeEntry( uplink, client );
}
ref_put( &uplink->reference );
}
@@ -751,11 +741,11 @@ static void uplinkCallback(void *data, uint64_t handle, uint64_t start UNUSED, u
.size = length,
};
mutex_lock( &client->sendMutex );
- send_reply( client->sock, &reply, buffer );
+ send_reply( client, &reply, buffer, false );
if ( buffer == NULL ) {
shutdown( client->sock, SHUT_RDWR );
}
- client->relayedCount--;
mutex_unlock( &client->sendMutex );
+ client->relayedCount--;
}
diff --git a/src/server/rpc.c b/src/server/rpc.c
index 119bbd5..528ae43 100644
--- a/src/server/rpc.c
+++ b/src/server/rpc.c
@@ -19,6 +19,8 @@
#include <fcntl.h>
#include <unistd.h>
+#include <dnbd3/afl.h>
+
#if JANSSON_VERSION_HEX < 0x020600
#define json_stringn_nocheck(a,b) json_string_nocheck(a)
#endif
diff --git a/src/server/sendfile.c b/src/server/sendfile.c
new file mode 100644
index 0000000..9e27238
--- /dev/null
+++ b/src/server/sendfile.c
@@ -0,0 +1,60 @@
+#include "sendfile.h"
+
+#if defined(__linux__)
+#include <sys/sendfile.h>
+#elif defined(__FreeBSD__)
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#else
+#error "What platform is this?"
+#endif
+
+#include <errno.h>
+
+bool sendfile_all(const int fd, const int sock, off_t foffset, const size_t bytes)
+{
+ if ( bytes == 0 )
+ return true;
+#ifdef DNBD3_SERVER_AFL
+ errno = 0;
+ return true;
+#elif defined(__linux__)
+ size_t done = 0;
+ int againCount = 0;
+
+ while ( done < bytes ) {
+ const ssize_t sent = sendfile( sock, fd, &foffset, bytes - done );
+ if ( sent == 0 ) // Probably EOF, like with send(), but manpage is not clear :-/
+ return false;
+ if ( sent < 0 ) {
+ if ( errno == EAGAIN || errno == EINTR ) {
+ // Retry once, but give up otherwise - EAGAIN might just be the send timeout
+ if ( ++againCount > 1 )
+ return false;
+ continue;
+ }
+ return false;
+ }
+ done += sent;
+ }
+#elif defined(__FreeBSD__)
+ off_t sent;
+ size_t done = 0;
+ int againCount = 0;
+
+ while ( done < bytes ) {
+ const int ret = sendfile( fd, sock, foffset + done, bytes - done, NULL, &sent, 0 );
+ if ( ret == 0 || errno == EAGAIN || errno == EINTR ) {
+ // Retry once, but give up otherwise - EAGAIN might just be the send timeout
+ if ( sent == 0 && ++againCount > 1 )
+ return false;
+ done += sent;
+ continue;
+ }
+ // Something else went wrong
+ return false;
+ }
+#endif
+ return true;
+} \ No newline at end of file
diff --git a/src/server/sendfile.h b/src/server/sendfile.h
new file mode 100644
index 0000000..e4cc5b7
--- /dev/null
+++ b/src/server/sendfile.h
@@ -0,0 +1,18 @@
+#ifndef SENDFILE_H_
+#define SENDFILE_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <sys/types.h>
+
+/**
+ * Platform-agnostic wrapper around sendfile, with retry logic.
+ * @param fd file to read from
+ * @param sock socket to write to
+ * @param foffset offset in file to start reading from
+ * @param bytes number of bytes to read/send
+ * @return true on success
+ */
+bool sendfile_all(int fd, int sock, off_t foffset, size_t bytes);
+
+#endif \ No newline at end of file
diff --git a/src/server/server.c b/src/server/server.c
index d086930..15a043d 100644
--- a/src/server/server.c
+++ b/src/server/server.c
@@ -314,10 +314,8 @@ int main(int argc, char *argv[])
exit( 3 );
}
{
- struct sockaddr_storage client;
- memset( &client, 0, sizeof client );
- client.ss_family = AF_INET;
- dnbd3_client_t *dnbd3_client = dnbd3_prepareClient( &client, 1 );
+ struct sockaddr_storage client = { .ss_family = AF_INET };
+ dnbd3_client_t *dnbd3_client = dnbd3_prepareClient( &client, 0 );
if ( dnbd3_client == NULL ) {
fprintf( stderr, "New client failed\n" );
exit( 1 );
@@ -365,6 +363,7 @@ int main(int argc, char *argv[])
altservers_init();
integrity_init();
net_init();
+
uplink_globalsInit();
rpc_init();
if ( mountDir != NULL && !dfuse_init( "-oallow_other", mountDir ) ) {
diff --git a/src/server/uplink.c b/src/server/uplink.c
index 8a83124..e05d27c 100644
--- a/src/server/uplink.c
+++ b/src/server/uplink.c
@@ -228,12 +228,12 @@ static void freeUplinkStruct(ref *ref)
* Remove given client from uplink request queue
* Locks on: uplink.queueLock
*/
-void uplink_removeEntry(dnbd3_uplink_t *uplink, void *data, uplink_callback callback)
+void uplink_removeEntry(dnbd3_uplink_t *uplink, void *data)
{
mutex_lock( &uplink->queueLock );
for ( dnbd3_queue_entry_t *it = uplink->queue; it != NULL; it = it->next ) {
for ( dnbd3_queue_client_t **cit = &it->clients; *cit != NULL; ) {
- if ( (**cit).data == data && (**cit).callback == callback ) {
+ if ( (**cit).data == data ) {
(*(**cit).callback)( (**cit).data, (**cit).handle, 0, 0, NULL );
dnbd3_queue_client_t *entry = *cit;
*cit = (**cit).next;
diff --git a/src/server/uplink.h b/src/server/uplink.h
index b6037d6..8d99695 100644
--- a/src/server/uplink.h
+++ b/src/server/uplink.h
@@ -10,7 +10,7 @@ uint64_t uplink_getTotalBytesReceived();
bool uplink_init(dnbd3_image_t *image, int sock, dnbd3_host_t *host, int version);
-void uplink_removeEntry(dnbd3_uplink_t *uplink, void *data, uplink_callback callback);
+void uplink_removeEntry(dnbd3_uplink_t *uplink, void *data);
bool uplink_requestClient(dnbd3_client_t *client, uplink_callback callback, uint64_t handle, uint64_t start, uint32_t length, uint8_t hops);