#ifndef _GPXE_ISCSI_H
#define _GPXE_ISCSI_H

/** @file
 *
 * iSCSI protocol
 *
 */

#include <stdint.h>
#include <gpxe/socket.h>
#include <gpxe/scsi.h>
#include <gpxe/chap.h>
#include <gpxe/refcnt.h>
#include <gpxe/xfer.h>
#include <gpxe/process.h>

/** Default iSCSI port */
#define ISCSI_PORT 3260

/**
 * iSCSI segment lengths
 *
 * iSCSI uses an icky structure with one one-byte field (a dword
 * count) and one three-byte field (a byte count).  This structure,
 * and the accompanying macros, relieve some of the pain.
 */
union iscsi_segment_lengths {
	struct {
		/** The AHS length (measured in dwords) */
		uint8_t ahs_len;
		/** The data length (measured in bytes), in network
		 * byte order
		 */
		uint8_t data_len[3];
	} bytes;
	/** Ths data length (measured in bytes), in network byte
	 * order, with ahs_len as the first byte.
	 */
	uint32_t ahs_and_data_len;
};

/** The length of the additional header segment, in dwords */
#define ISCSI_AHS_LEN( segment_lengths ) \
	( (segment_lengths).bytes.ahs_len )

/** The length of the data segment, in bytes, excluding any padding */
#define ISCSI_DATA_LEN( segment_lengths ) \
	( ntohl ( (segment_lengths).ahs_and_data_len ) & 0xffffff )

/** The padding of the data segment, in bytes */
#define ISCSI_DATA_PAD_LEN( segment_lengths ) \
	( ( 0 - (segment_lengths).bytes.data_len[2] ) & 0x03 )

/** Set additional header and data segment lengths */
#define ISCSI_SET_LENGTHS( segment_lengths, ahs_len, data_len ) do {	\
	(segment_lengths).ahs_and_data_len =				\
		htonl ( data_len | ( ahs_len << 24 ) );			\
	} while ( 0 )

/**
 * iSCSI basic header segment common fields
 *
 */
struct iscsi_bhs_common {
	/** Opcode */
	uint8_t opcode;
	/** Flags */
	uint8_t flags;
	/** Fields specific to the PDU type */
	uint8_t other_a[2];
	/** Segment lengths */
	union iscsi_segment_lengths lengths;
	/** Fields specific to the PDU type */
	uint8_t other_b[8];
	/** Initiator Task Tag */
	uint32_t itt;
	/** Fields specific to the PDU type */
	uint8_t other_c[28];
};

/** Opcode mask */
#define ISCSI_OPCODE_MASK 0x3f

/** Immediate delivery */
#define ISCSI_FLAG_IMMEDIATE 0x40

/** Final PDU of a sequence */
#define ISCSI_FLAG_FINAL 0x80

/**
 * iSCSI basic header segment common request fields
 *
 */
struct iscsi_bhs_common_response {
	/** Opcode */
	uint8_t opcode;
	/** Flags */
	uint8_t flags;
	/** Fields specific to the PDU type */
	uint8_t other_a[2];
	/** Segment lengths */
	union iscsi_segment_lengths lengths;
	/** Fields specific to the PDU type */
	uint8_t other_b[8];
	/** Initiator Task Tag */
	uint32_t itt;
	/** Fields specific to the PDU type */
	uint8_t other_c[4];
	/** Status sequence number */
	uint32_t statsn;
	/** Expected command sequence number */
	uint32_t expcmdsn;
	/** Fields specific to the PDU type */
	uint8_t other_d[16];
};

/**
 * iSCSI login request basic header segment
 *
 */
struct iscsi_bhs_login_request {
	/** Opcode */
	uint8_t opcode;
	/** Flags */
	uint8_t flags;
	/** Maximum supported version number */
	uint8_t version_max;
	/** Minimum supported version number */
	uint8_t version_min;
	/** Segment lengths */
	union iscsi_segment_lengths lengths;
	/** Initiator session ID (IANA format) enterprise number and flags */
	uint32_t isid_iana_en;
	/** Initiator session ID (IANA format) qualifier */
	uint16_t isid_iana_qual;
	/** Target session identifying handle */
	uint16_t tsih;
	/** Initiator Task Tag */
	uint32_t itt;
	/** Connection ID */
	uint16_t cid;
	/** Reserved */
	uint16_t reserved_a;
	/** Command sequence number */
	uint32_t cmdsn;
	/** Expected status sequence number */
	uint32_t expstatsn;
	/** Reserved */
	uint8_t reserved_b[16];
};

/** Login request opcode */
#define ISCSI_OPCODE_LOGIN_REQUEST 0x03

/** Willingness to transition to next stage */
#define ISCSI_LOGIN_FLAG_TRANSITION 0x80

/** Key=value pairs continued in subsequent request */
#define ISCSI_LOGIN_FLAG_CONTINUE 0x40

/* Current stage values and mask */
#define ISCSI_LOGIN_CSG_MASK 0x0c
#define ISCSI_LOGIN_CSG_SECURITY_NEGOTIATION 0x00
#define ISCSI_LOGIN_CSG_OPERATIONAL_NEGOTIATION 0x04
#define ISCSI_LOGIN_CSG_FULL_FEATURE_PHASE 0x0c

/* Next stage values and mask */
#define ISCSI_LOGIN_NSG_MASK 0x03
#define ISCSI_LOGIN_NSG_SECURITY_NEGOTIATION 0x00
#define ISCSI_LOGIN_NSG_OPERATIONAL_NEGOTIATION 0x01
#define ISCSI_LOGIN_NSG_FULL_FEATURE_PHASE 0x03

/** ISID IANA format marker */
#define ISCSI_ISID_IANA 0x40000000

/** Fen Systems Ltd. IANA enterprise number
 *
 * Permission is hereby granted to use Fen Systems Ltd.'s IANA
 * enterprise number with this iSCSI implementation.
 */
#define IANA_EN_FEN_SYSTEMS 10019

/**
 * iSCSI login response basic header segment
 *
 */
struct iscsi_bhs_login_response {
	/** Opcode */
	uint8_t opcode;
	/** Flags */
	uint8_t flags;
	/** Maximum supported version number */
	uint8_t version_max;
	/** Minimum supported version number */
	uint8_t version_min;
	/** Segment lengths */
	union iscsi_segment_lengths lengths;
	/** Initiator session ID (IANA format) enterprise number and flags */
	uint32_t isid_iana_en;
	/** Initiator session ID (IANA format) qualifier */
	uint16_t isid_iana_qual;
	/** Target session identifying handle */
	uint16_t tsih;
	/** Initiator Task Tag */
	uint32_t itt;
	/** Reserved */
	uint32_t reserved_a;
	/** Status sequence number */
	uint32_t statsn;
	/** Expected command sequence number */
	uint32_t expcmdsn;
	/** Maximum command sequence number */
	uint32_t maxcmdsn;
	/** Status class */
	uint8_t status_class;
	/** Status detail */
	uint8_t status_detail;
	/** Reserved */
	uint8_t reserved_b[10];
};

/** Login response opcode */
#define ISCSI_OPCODE_LOGIN_RESPONSE 0x23

/* Login response status codes */
#define ISCSI_STATUS_SUCCESS			0x00
#define ISCSI_STATUS_REDIRECT			0x01
#define ISCSI_STATUS_INITIATOR_ERROR		0x02
#define ISCSI_STATUS_INITIATOR_ERROR_AUTHENTICATION	0x01
#define ISCSI_STATUS_INITIATOR_ERROR_AUTHORISATION	0x02
#define ISCSI_STATUS_INITIATOR_ERROR_NOT_FOUND		0x03
#define ISCSI_STATUS_INITIATOR_ERROR_REMOVED		0x04
#define ISCSI_STATUS_TARGET_ERROR		0x03

/**
 * iSCSI SCSI command basic header segment
 *
 */
struct iscsi_bhs_scsi_command {
	/** Opcode */
	uint8_t opcode;
	/** Flags */
	uint8_t flags;
	/** Reserved */
	uint16_t reserved_a;
	/** Segment lengths */
	union iscsi_segment_lengths lengths;
	/** SCSI Logical Unit Number */
	uint64_t lun;
	/** Initiator Task Tag */
	uint32_t itt;
	/** Expected data transfer length */
	uint32_t exp_len;
	/** Command sequence number */
	uint32_t cmdsn;
	/** Expected status sequence number */
	uint32_t expstatsn;
	/** SCSI Command Descriptor Block (CDB) */
	union scsi_cdb cdb;
};

/** SCSI command opcode */
#define ISCSI_OPCODE_SCSI_COMMAND 0x01

/** Command will read data */
#define ISCSI_COMMAND_FLAG_READ 0x40

/** Command will write data */
#define ISCSI_COMMAND_FLAG_WRITE 0x20

/* Task attributes */
#define ISCSI_COMMAND_ATTR_UNTAGGED 0x00
#define ISCSI_COMMAND_ATTR_SIMPLE 0x01
#define ISCSI_COMMAND_ATTR_ORDERED 0x02
#define ISCSI_COMMAND_ATTR_HEAD_OF_QUEUE 0x03
#define ISCSI_COMMAND_ATTR_ACA 0x04

/**
 * iSCSI SCSI response basic header segment
 *
 */
struct iscsi_bhs_scsi_response {
	/** Opcode */
	uint8_t opcode;
	/** Flags */
	uint8_t flags;
	/** Response code */
	uint8_t response;
	/** SCSI status code */
	uint8_t status;
	/** Segment lengths */
	union iscsi_segment_lengths lengths;
	/** Reserved */
	uint8_t reserved_a[8];
	/** Initiator Task Tag */
	uint32_t itt;
	/** SNACK tag */
	uint32_t snack;
	/** Status sequence number */
	uint32_t statsn;
	/** Expected command sequence number */
	uint32_t expcmdsn;
	/** Maximum command sequence number */
	uint32_t maxcmdsn;
	/** Expected data sequence number */
	uint32_t expdatasn;
	/** Reserved */
	uint8_t reserved_b[8];
};

/** SCSI response opcode */
#define ISCSI_OPCODE_SCSI_RESPONSE 0x21

/** SCSI command completed at target */
#define ISCSI_RESPONSE_COMMAND_COMPLETE 0x00

/** SCSI target failure */
#define ISCSI_RESPONSE_TARGET_FAILURE 0x01

/** SCSI sense response code offset
 *
 * The SCSI response may contain unsolicited sense data in the data
 * segment.  If it does, this is the offset to the sense response code
 * byte, which is the only byte we care about.
 */
#define ISCSI_SENSE_RESPONSE_CODE_OFFSET 2

/**
 * iSCSI data-in basic header segment
 *
 */
struct iscsi_bhs_data_in {
	/** Opcode */
	uint8_t opcode;
	/** Flags */
	uint8_t flags;
	/** Reserved */
	uint8_t reserved_a;
	/** SCSI status code */
	uint8_t status;
	/** Segment lengths */
	union iscsi_segment_lengths lengths;
	/** Logical Unit Number */
	uint64_t lun;
	/** Initiator Task Tag */
	uint32_t itt;
	/** Target Transfer Tag */
	uint32_t ttt;
	/** Status sequence number */
	uint32_t statsn;
	/** Expected command sequence number */
	uint32_t expcmdsn;
	/** Maximum command sequence number */
	uint32_t maxcmdsn;
	/** Data sequence number */
	uint32_t datasn;
	/** Buffer offset */
	uint32_t offset;
	/** Residual count */
	uint32_t residual_count;
};

/** Data-in opcode */
#define ISCSI_OPCODE_DATA_IN 0x25

/** Data requires acknowledgement */
#define ISCSI_DATA_FLAG_ACKNOWLEDGE 0x40

/** Data overflow occurred */
#define ISCSI_DATA_FLAG_OVERFLOW 0x04

/** Data underflow occurred */
#define ISCSI_DATA_FLAG_UNDERFLOW 0x02

/** SCSI status code and overflow/underflow flags are valid */
#define ISCSI_DATA_FLAG_STATUS 0x01

/**
 * iSCSI data-out basic header segment
 *
 */
struct iscsi_bhs_data_out {
	/** Opcode */
	uint8_t opcode;
	/** Flags */
	uint8_t flags;
	/** Reserved */
	uint16_t reserved_a;
	/** Segment lengths */
	union iscsi_segment_lengths lengths;
	/** Logical Unit Number */
	uint64_t lun;
	/** Initiator Task Tag */
	uint32_t itt;
	/** Target Transfer Tag */
	uint32_t ttt;
	/** Reserved */
	uint32_t reserved_b;
	/** Expected status sequence number */
	uint32_t expstatsn;
	/** Reserved */
	uint32_t reserved_c;
	/** Data sequence number */
	uint32_t datasn;
	/** Buffer offset */
	uint32_t offset;
	/** Reserved */
	uint32_t reserved_d;
};

/** Data-out opcode */
#define ISCSI_OPCODE_DATA_OUT 0x05

/**
 * iSCSI request to transfer basic header segment
 *
 */
struct iscsi_bhs_r2t {
	/** Opcode */
	uint8_t opcode;
	/** Flags */
	uint8_t flags;
	/** Reserved */
	uint16_t reserved_a;
	/** Segment lengths */
	union iscsi_segment_lengths lengths;
	/** Logical Unit Number */
	uint64_t lun;
	/** Initiator Task Tag */
	uint32_t itt;
	/** Target Transfer Tag */
	uint32_t ttt;
	/** Status sequence number */
	uint32_t statsn;
	/** Expected command sequence number */
	uint32_t expcmdsn;
	/** Maximum command sequence number */
	uint32_t maxcmdsn;
	/** R2T sequence number */
	uint32_t r2tsn;
	/** Buffer offset */
	uint32_t offset;
	/** Desired data transfer length */
	uint32_t len;
};

/** R2T opcode */
#define ISCSI_OPCODE_R2T 0x31

/**
 * An iSCSI basic header segment
 */
union iscsi_bhs {
	struct iscsi_bhs_common common;
	struct iscsi_bhs_common_response common_response;
	struct iscsi_bhs_login_request login_request;
	struct iscsi_bhs_login_response login_response;
	struct iscsi_bhs_scsi_command scsi_command;
	struct iscsi_bhs_scsi_response scsi_response;
	struct iscsi_bhs_data_in data_in;
	struct iscsi_bhs_data_out data_out;
	struct iscsi_bhs_r2t r2t;
	unsigned char bytes[ sizeof ( struct iscsi_bhs_common ) ];
};

/** State of an iSCSI TX engine */
enum iscsi_tx_state {
	/** Nothing to send */
	ISCSI_TX_IDLE = 0,
	/** Sending the basic header segment */
	ISCSI_TX_BHS,
	/** Sending the additional header segment */
	ISCSI_TX_AHS,
	/** Sending the data segment */
	ISCSI_TX_DATA,
	/** Sending the data segment padding */
	ISCSI_TX_DATA_PADDING,
};

/** State of an iSCSI RX engine */
enum iscsi_rx_state {
	/** Receiving the basic header segment */
	ISCSI_RX_BHS = 0,
	/** Receiving the additional header segment */
	ISCSI_RX_AHS,
	/** Receiving the data segment */
	ISCSI_RX_DATA,
	/** Receiving the data segment padding */
	ISCSI_RX_DATA_PADDING,
};

/** An iSCSI session */
struct iscsi_session {
	/** Reference counter */
	struct refcnt refcnt;

	/** Transport-layer socket */
	struct xfer_interface socket;

	/** Target address */
	char *target_address;
	/** Target port */
	unsigned int target_port;
	/** Target IQN */
	char *target_iqn;
	/** Logical Unit Number (LUN) */
	uint64_t lun;
	/** Target socket address (recorded only for iBFT) */
	struct sockaddr target_sockaddr;

	/** Session status
	 *
	 * This is the bitwise-OR of zero or more ISCSI_STATUS_XXX
	 * constants.
	 */
	int status;
	/** Retry count
	 *
	 * Number of times that the connection has been retried.
	 * Reset upon a successful connection.
	 */
	int retry_count;

	/** Username (if any) */
	char *username;
	/** Password (if any) */
	char *password;
	/** CHAP challenge/response */
	struct chap_challenge chap;

	/** Target session identifying handle
	 *
	 * This is assigned by the target when we first log in, and
	 * must be reused on subsequent login attempts.
	 */
	uint16_t tsih;
	/** Initiator task tag
	 *
	 * This is the tag of the current command.  It is incremented
	 * whenever a new command is started.
	 */
	uint32_t itt;
	/** Target transfer tag
	 *
	 * This is the tag attached to a sequence of data-out PDUs in
	 * response to an R2T.
	 */
	uint32_t ttt;
	/**
	 * Transfer offset
	 *
	 * This is the offset for an in-progress sequence of data-out
	 * PDUs in response to an R2T.
	 */
	uint32_t transfer_offset;
	/**
	 * Transfer length
	 *
	 * This is the length for an in-progress sequence of data-out
	 * PDUs in response to an R2T.
	 */
	uint32_t transfer_len;
	/** Command sequence number
	 *
	 * This is the sequence number of the current command, used to
	 * fill out the CmdSN field in iSCSI request PDUs.  It is
	 * updated with the value of the ExpCmdSN field whenever we
	 * receive an iSCSI response PDU containing such a field.
	 */
	uint32_t cmdsn;
	/** Status sequence number
	 *
	 * This is the most recent status sequence number present in
	 * the StatSN field of an iSCSI response PDU containing such a
	 * field.  Whenever we send an iSCSI request PDU, we fill out
	 * the ExpStatSN field with this value plus one.
	 */
	uint32_t statsn;
	
	/** Basic header segment for current TX PDU */
	union iscsi_bhs tx_bhs;
	/** State of the TX engine */
	enum iscsi_tx_state tx_state;
	/** TX process */
	struct process process;

	/** Basic header segment for current RX PDU */
	union iscsi_bhs rx_bhs;
	/** State of the RX engine */
	enum iscsi_rx_state rx_state;
	/** Byte offset within the current RX state */
	size_t rx_offset;
	/** Length of the current RX state */
	size_t rx_len;
	/** Buffer for received data (not always used) */
	void *rx_buffer;

	/** Current SCSI command
	 *
	 * Set to NULL when command is complete.
	 */
	struct scsi_command *command;
	/** SCSI command return code
	 *
	 * Set to -EINPROGRESS while command is processing.
	 */
	int rc;
	/** Instant return code
	 *
	 * Set to a non-zero value if all requests should return
	 * immediately.  This can be used to e.g. avoid retrying
	 * logins that are doomed to fail authentication.
	 */
	int instant_rc;
};

/** iSCSI session is currently in the security negotiation phase */
#define ISCSI_STATUS_SECURITY_NEGOTIATION_PHASE		\
	( ISCSI_LOGIN_CSG_SECURITY_NEGOTIATION |	\
	  ISCSI_LOGIN_NSG_OPERATIONAL_NEGOTIATION )

/** iSCSI session is currently in the operational parameter
 * negotiation phase
 */
#define ISCSI_STATUS_OPERATIONAL_NEGOTIATION_PHASE	\
	( ISCSI_LOGIN_CSG_OPERATIONAL_NEGOTIATION |	\
	  ISCSI_LOGIN_NSG_FULL_FEATURE_PHASE )

/** iSCSI session is currently in the full feature phase */
#define ISCSI_STATUS_FULL_FEATURE_PHASE	ISCSI_LOGIN_CSG_FULL_FEATURE_PHASE

/** Mask for all iSCSI session phases */
#define ISCSI_STATUS_PHASE_MASK ( ISCSI_LOGIN_CSG_MASK | ISCSI_LOGIN_NSG_MASK )

/** iSCSI session needs to send the initial security negotiation strings */
#define ISCSI_STATUS_STRINGS_SECURITY 0x0100

/** iSCSI session needs to send the CHAP_A string */
#define ISCSI_STATUS_STRINGS_CHAP_ALGORITHM 0x0200

/** iSCSI session needs to send the CHAP response */
#define ISCSI_STATUS_STRINGS_CHAP_RESPONSE 0x0400

/** iSCSI session needs to send the operational negotiation strings */
#define ISCSI_STATUS_STRINGS_OPERATIONAL 0x0800

/** Mask for all iSCSI "needs to send" flags */
#define ISCSI_STATUS_STRINGS_MASK 0xff00

/** Maximum number of retries at connecting */
#define ISCSI_MAX_RETRIES 2

extern int iscsi_attach ( struct scsi_device *scsi, const char *root_path );
extern void iscsi_detach ( struct scsi_device *scsi );
extern const char * iscsi_initiator_iqn ( void );

#endif /* _GPXE_ISCSI_H */
