diff options
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/CMakeLists.txt | 23 | ||||
| -rw-r--r-- | src/server/altservers.c | 2 | ||||
| -rw-r--r-- | src/server/globals.c | 3 | ||||
| -rw-r--r-- | src/server/globals.h | 7 | ||||
| -rw-r--r-- | src/server/image.c | 76 | ||||
| -rw-r--r-- | src/server/image.h | 2 | ||||
| -rw-r--r-- | src/server/iscsi.c | 3535 | ||||
| -rw-r--r-- | src/server/iscsi.h | 5152 | ||||
| -rw-r--r-- | src/server/net.c | 280 | ||||
| -rw-r--r-- | src/server/rpc.c | 2 | ||||
| -rw-r--r-- | src/server/sendfile.c | 60 | ||||
| -rw-r--r-- | src/server/sendfile.h | 18 | ||||
| -rw-r--r-- | src/server/server.c | 7 | ||||
| -rw-r--r-- | src/server/uplink.c | 4 | ||||
| -rw-r--r-- | src/server/uplink.h | 2 |
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); |
