From 84482f4420eef8a754cf1719ccfe0002a5bf9056 Mon Sep 17 00:00:00 2001 From: Shuhei Matsumoto Date: Thu, 24 Oct 2019 10:05:06 +0900 Subject: [PATCH] ut/iscsi: Unit test for PDU header handling Add unit tests for PDU header handling except for SNACK Request PDU because the caes that Error Recovery Level is not matured yet. The purpose is mainly for increasing coverage, and most were reviewed separately, and so merge multiple PDU types into a single patch. Signed-off-by: Shuhei Matsumoto Change-Id: Ice8c522bee0fc8c0dadb0c6326be9f65d323d52e Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/471955 Tested-by: SPDK CI Jenkins Reviewed-by: Changpeng Liu Reviewed-by: Ben Walker Reviewed-by: Jim Harris --- test/unit/lib/iscsi/common.c | 11 + test/unit/lib/iscsi/iscsi.c/iscsi_ut.c | 699 +++++++++++++++++++++++++ 2 files changed, 710 insertions(+) diff --git a/test/unit/lib/iscsi/common.c b/test/unit/lib/iscsi/common.c index 8d50dc06a..94f7f60bb 100644 --- a/test/unit/lib/iscsi/common.c +++ b/test/unit/lib/iscsi/common.c @@ -16,6 +16,9 @@ SPDK_LOG_REGISTER_COMPONENT("iscsi", SPDK_LOG_ISCSI) TAILQ_HEAD(, spdk_iscsi_pdu) g_write_pdu_list = TAILQ_HEAD_INITIALIZER(g_write_pdu_list); +static bool g_task_pool_is_empty = false; +static bool g_pdu_pool_is_empty = false; + struct spdk_iscsi_task * spdk_iscsi_task_get(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *parent, @@ -23,6 +26,10 @@ spdk_iscsi_task_get(struct spdk_iscsi_conn *conn, { struct spdk_iscsi_task *task; + if (g_task_pool_is_empty) { + return NULL; + } + task = calloc(1, sizeof(*task)); if (!task) { return NULL; @@ -63,6 +70,10 @@ spdk_get_pdu(void) { struct spdk_iscsi_pdu *pdu; + if (g_pdu_pool_is_empty) { + return NULL; + } + pdu = malloc(sizeof(*pdu)); if (!pdu) { return NULL; diff --git a/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c b/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c index 5a2a03acc..dd9443c59 100644 --- a/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c +++ b/test/unit/lib/iscsi/iscsi.c/iscsi_ut.c @@ -1510,6 +1510,698 @@ build_iovs_with_md_test(void) free(data); } +static void +check_iscsi_reject(struct spdk_iscsi_pdu *pdu, uint8_t reason) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_reject *reject_bhs; + + CU_ASSERT(pdu->is_rejected == true); + rsp_pdu = TAILQ_FIRST(&g_write_pdu_list); + CU_ASSERT(rsp_pdu != NULL); + reject_bhs = (struct iscsi_bhs_reject *)&rsp_pdu->bhs; + CU_ASSERT(reject_bhs->reason == reason); + + TAILQ_REMOVE(&g_write_pdu_list, rsp_pdu, tailq); + spdk_put_pdu(rsp_pdu); + pdu->is_rejected = false; +} + +static void +check_login_response(uint8_t status_class, uint8_t status_detail) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_login_rsp *login_rsph; + + rsp_pdu = TAILQ_FIRST(&g_write_pdu_list); + CU_ASSERT(rsp_pdu != NULL); + login_rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + CU_ASSERT(login_rsph->status_class == status_class); + CU_ASSERT(login_rsph->status_detail == status_detail); + + TAILQ_REMOVE(&g_write_pdu_list, rsp_pdu, tailq); + spdk_put_pdu(rsp_pdu); +} + +static void +pdu_hdr_op_login_test(void) +{ + struct spdk_iscsi_sess sess = {}; + struct spdk_iscsi_conn conn = {}; + struct spdk_iscsi_pdu pdu = {}; + struct iscsi_bhs_login_req *login_reqh; + int rc; + + login_reqh = (struct iscsi_bhs_login_req *)&pdu.bhs; + + /* Case 1 - On discovery session, target only accepts text requests with the + * SendTargets key and logout request with reason "close the session". + */ + sess.session_type = SESSION_TYPE_DISCOVERY; + conn.full_feature = true; + conn.sess = &sess; + + rc = iscsi_pdu_hdr_op_login(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 2 - Data segment length is limited to be not more than 8KB, the default + * FirstBurstLength, for login request. + */ + sess.session_type = SESSION_TYPE_INVALID; + conn.full_feature = false; + conn.sess = NULL; + pdu.data_segment_len = SPDK_ISCSI_FIRST_BURST_LENGTH + 1; + + rc = iscsi_pdu_hdr_op_login(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_PROTOCOL_ERROR); + + /* Case 3 - PDU pool is empty */ + pdu.data_segment_len = SPDK_ISCSI_FIRST_BURST_LENGTH; + g_pdu_pool_is_empty = true; + + rc = iscsi_pdu_hdr_op_login(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 4 - A login request with the C bit set to 1 must have the T bit set to 0. */ + g_pdu_pool_is_empty = false; + login_reqh->flags |= ISCSI_LOGIN_TRANSIT; + login_reqh->flags |= ISCSI_LOGIN_CONTINUE; + + rc = iscsi_pdu_hdr_op_login(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_LOGIN_ERROR_RESPONSE); + check_login_response(ISCSI_CLASS_INITIATOR_ERROR, ISCSI_LOGIN_INITIATOR_ERROR); + + /* Case 5 - Both version-min and version-max must be set to 0x00. */ + login_reqh->flags = 0; + login_reqh->version_min = ISCSI_VERSION + 1; + + rc = iscsi_pdu_hdr_op_login(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_LOGIN_ERROR_RESPONSE); + check_login_response(ISCSI_CLASS_INITIATOR_ERROR, ISCSI_LOGIN_UNSUPPORTED_VERSION); + + /* Case 6 - T bit is set to 1 correctly but invalid stage code is set to NSG. */ + login_reqh->version_min = ISCSI_VERSION; + login_reqh->flags |= ISCSI_LOGIN_TRANSIT; + login_reqh->flags |= ISCSI_NSG_RESERVED_CODE; + + rc = iscsi_pdu_hdr_op_login(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_LOGIN_ERROR_RESPONSE); + check_login_response(ISCSI_CLASS_INITIATOR_ERROR, ISCSI_LOGIN_INITIATOR_ERROR); + + /* Case 7 - Login request is correct. Login response is initialized and set to + * the current connection. + */ + login_reqh->flags = 0; + + rc = iscsi_pdu_hdr_op_login(&conn, &pdu); + CU_ASSERT(rc == 0); + CU_ASSERT(conn.login_rsp_pdu != NULL); + + spdk_put_pdu(conn.login_rsp_pdu); +} + +static void +pdu_hdr_op_text_test(void) +{ + struct spdk_iscsi_sess sess = {}; + struct spdk_iscsi_conn conn = {}; + struct spdk_iscsi_pdu pdu = {}; + struct iscsi_bhs_text_req *text_reqh; + int rc; + + text_reqh = (struct iscsi_bhs_text_req *)&pdu.bhs; + + conn.sess = &sess; + + /* Case 1 - Data segment length for text request must not be more than + * FirstBurstLength plus extra space to account for digests. + */ + pdu.data_segment_len = spdk_get_max_immediate_data_size() + 1; + + rc = iscsi_pdu_hdr_op_text(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_PROTOCOL_ERROR); + + /* Case 2 - A text request with the C bit set to 1 must have the F bit set to 0. */ + pdu.data_segment_len = spdk_get_max_immediate_data_size(); + text_reqh->flags |= ISCSI_FLAG_FINAL; + text_reqh->flags |= ISCSI_TEXT_CONTINUE; + + rc = iscsi_pdu_hdr_op_text(&conn, &pdu); + CU_ASSERT(rc == -1); + + /* Case 3 - ExpStatSN of the text request is expected to match StatSN of the current + * connection. But StarPort iSCSI initiator didn't follow the expectation. In this + * case we overwrite StatSN by ExpStatSN and processes the request as correct. + */ + text_reqh->flags = 0; + to_be32(&text_reqh->exp_stat_sn, 1234); + to_be32(&conn.StatSN, 4321); + + rc = iscsi_pdu_hdr_op_text(&conn, &pdu); + CU_ASSERT(rc == 0); + CU_ASSERT(conn.StatSN == 1234); + + /* Case 4 - Text request is the first in the sequence of text requests and responses, + * and so its ITT is hold to the current connection. + */ + sess.current_text_itt = 0xffffffffU; + to_be32(&text_reqh->itt, 5678); + + rc = iscsi_pdu_hdr_op_text(&conn, &pdu); + CU_ASSERT(rc == 0); + CU_ASSERT(sess.current_text_itt == 5678); + + /* Case 5 - If text request is sent as part of a sequence of text requests and responses, + * its ITT must be the same for all the text requests. But it was not. */ + sess.current_text_itt = 5679; + + rc = iscsi_pdu_hdr_op_text(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_PROTOCOL_ERROR); + + /* Case 6 - Different from case 5, its ITT matches the value saved in the connection. */ + text_reqh->flags = 0; + sess.current_text_itt = 5678; + + rc = iscsi_pdu_hdr_op_text(&conn, &pdu); + CU_ASSERT(rc == 0); +} + +static void +check_logout_response(uint8_t response, uint32_t stat_sn, uint32_t exp_cmd_sn, + uint32_t max_cmd_sn) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_logout_resp *logout_rsph; + + rsp_pdu = TAILQ_FIRST(&g_write_pdu_list); + CU_ASSERT(rsp_pdu != NULL); + logout_rsph = (struct iscsi_bhs_logout_resp *)&rsp_pdu->bhs; + CU_ASSERT(logout_rsph->response == response); + CU_ASSERT(from_be32(&logout_rsph->stat_sn) == stat_sn); + CU_ASSERT(from_be32(&logout_rsph->exp_cmd_sn) == exp_cmd_sn); + CU_ASSERT(from_be32(&logout_rsph->max_cmd_sn) == max_cmd_sn); + + TAILQ_REMOVE(&g_write_pdu_list, rsp_pdu, tailq); + spdk_put_pdu(rsp_pdu); +} + +static void +pdu_hdr_op_logout_test(void) +{ + struct spdk_iscsi_sess sess = {}; + struct spdk_iscsi_conn conn = {}; + struct spdk_iscsi_pdu pdu = {}; + struct iscsi_bhs_logout_req *logout_reqh; + int rc; + + logout_reqh = (struct iscsi_bhs_logout_req *)&pdu.bhs; + + /* Case 1 - Target can accept logout request only with the reason "close the session" + * on discovery session. + */ + logout_reqh->reason = 1; + conn.sess = &sess; + sess.session_type = SESSION_TYPE_DISCOVERY; + + rc = iscsi_pdu_hdr_op_logout(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 2 - Session is not established yet but connection was closed successfully. */ + conn.sess = NULL; + conn.StatSN = 1234; + to_be32(&logout_reqh->exp_stat_sn, 1234); + pdu.cmd_sn = 5678; + + rc = iscsi_pdu_hdr_op_logout(&conn, &pdu); + CU_ASSERT(rc == 0); + check_logout_response(0, 1234, 5678, 5678); + CU_ASSERT(conn.StatSN == 1235); + + /* Case 3 - Session type is normal but CID was not found. Hence connection or session + * was not closed. + */ + sess.session_type = SESSION_TYPE_NORMAL; + sess.ExpCmdSN = 5679; + sess.connections = 1; + conn.sess = &sess; + conn.id = 1; + + rc = iscsi_pdu_hdr_op_logout(&conn, &pdu); + CU_ASSERT(rc == 0); + check_logout_response(1, 1235, 5679, 1); + CU_ASSERT(conn.StatSN == 1236); + CU_ASSERT(sess.MaxCmdSN == 1); + + /* Case 4 - Session type is normal and CID was found. Connection or session was closed + * successfully. + */ + to_be16(&logout_reqh->cid, 1); + + rc = iscsi_pdu_hdr_op_logout(&conn, &pdu); + CU_ASSERT(rc == 0); + check_logout_response(0, 1236, 5679, 2); + CU_ASSERT(conn.StatSN == 1237); + CU_ASSERT(sess.MaxCmdSN == 2); + + /* Case 5 - PDU pool is empty. */ + g_pdu_pool_is_empty = true; + + rc = iscsi_pdu_hdr_op_logout(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + g_pdu_pool_is_empty = false; +} + +static void +check_scsi_task(struct spdk_iscsi_pdu *pdu, enum spdk_scsi_data_dir dir) +{ + struct spdk_iscsi_task *task; + + task = pdu->task; + CU_ASSERT(task != NULL); + CU_ASSERT(task->pdu == pdu); + CU_ASSERT(task->scsi.dxfer_dir == dir); + + spdk_iscsi_task_put(task); + pdu->task = NULL; +} + +static void +pdu_hdr_op_scsi_test(void) +{ + struct spdk_iscsi_sess sess = {}; + struct spdk_iscsi_conn conn = {}; + struct spdk_iscsi_pdu pdu = {}; + struct spdk_scsi_dev dev = {}; + struct spdk_scsi_lun lun = {}; + struct iscsi_bhs_scsi_req *scsi_reqh; + int rc; + + scsi_reqh = (struct iscsi_bhs_scsi_req *)&pdu.bhs; + + conn.sess = &sess; + conn.dev = &dev; + + /* Case 1 - SCSI command is acceptable only on normal session. */ + sess.session_type = SESSION_TYPE_DISCOVERY; + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 2 - Task pool is empty. */ + g_task_pool_is_empty = true; + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + g_task_pool_is_empty = false; + + /* Case 3 - bidirectional operations (both R and W flags are set to 1) are not supported. */ + sess.session_type = SESSION_TYPE_NORMAL; + scsi_reqh->read_bit = 1; + scsi_reqh->write_bit = 1; + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 4 - LUN is hot-removed, and return immediately. */ + scsi_reqh->write_bit = 0; + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == 0); + CU_ASSERT(pdu.task == NULL); + + /* Case 5 - SCSI read command PDU is correct, and the configured iSCSI task is set to the PDU. */ + dev.lun[0] = &lun; + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == 0); + check_scsi_task(&pdu, SPDK_SCSI_DIR_FROM_DEV); + + /* Case 6 - For SCSI write command PDU, its data segment length must not be more than + * FirstBurstLength plus extra space to account for digests. + */ + scsi_reqh->read_bit = 0; + scsi_reqh->write_bit = 1; + pdu.data_segment_len = spdk_get_max_immediate_data_size() + 1; + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_PROTOCOL_ERROR); + + /* Case 7 - For SCSI write command PDU, its data segment length must not be more than + * Expected Data Transfer Length (EDTL). + */ + pdu.data_segment_len = spdk_get_max_immediate_data_size(); + to_be32(&scsi_reqh->expected_data_xfer_len, pdu.data_segment_len - 1); + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_PROTOCOL_ERROR); + + /* Case 8 - If ImmediateData is not enabled for the session, SCSI write command PDU + * cannot have data segment. + */ + to_be32(&scsi_reqh->expected_data_xfer_len, pdu.data_segment_len); + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_PROTOCOL_ERROR); + + /* Case 9 - For SCSI write command PDU, its data segment length must not be more + * than FirstBurstLength. + */ + sess.ImmediateData = true; + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_PROTOCOL_ERROR); + + /* Case 10 - SCSI write command PDU is correct, and the configured iSCSI task is set to the PDU. */ + sess.FirstBurstLength = pdu.data_segment_len; + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == 0); + check_scsi_task(&pdu, SPDK_SCSI_DIR_TO_DEV); + + /* Case 11 - R and W must not both be 0 when EDTL is not 0. */ + scsi_reqh->write_bit = 0; + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_INVALID_PDU_FIELD); + + /* Case 11 - R and W are both 0 and EDTL is also 0, and hence SCSI command PDU is accepted. */ + to_be32(&scsi_reqh->expected_data_xfer_len, 0); + + rc = iscsi_pdu_hdr_op_scsi(&conn, &pdu); + CU_ASSERT(rc == 0); + check_scsi_task(&pdu, SPDK_SCSI_DIR_NONE); +} + +static void +check_iscsi_task_mgmt_response(uint8_t response, uint32_t task_tag, uint32_t stat_sn, + uint32_t exp_cmd_sn, uint32_t max_cmd_sn) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_task_resp *rsph; + + rsp_pdu = TAILQ_FIRST(&g_write_pdu_list); + CU_ASSERT(rsp_pdu != NULL); + rsph = (struct iscsi_bhs_task_resp *)&rsp_pdu->bhs; + CU_ASSERT(rsph->response == response); + CU_ASSERT(from_be32(&rsph->itt) == task_tag); + CU_ASSERT(from_be32(&rsph->exp_cmd_sn) == exp_cmd_sn); + CU_ASSERT(from_be32(&rsph->max_cmd_sn) == max_cmd_sn); + + TAILQ_REMOVE(&g_write_pdu_list, rsp_pdu, tailq); + spdk_put_pdu(rsp_pdu); +} + +static void +pdu_hdr_op_task_mgmt_test(void) +{ + struct spdk_iscsi_sess sess = {}; + struct spdk_iscsi_conn conn = {}; + struct spdk_iscsi_pdu pdu = {}; + struct spdk_scsi_dev dev = {}; + struct spdk_scsi_lun lun = {}; + struct iscsi_bhs_task_req *task_reqh; + int rc; + + /* TBD: This test covers only error paths before creating iSCSI task for now. + * Testing iSCSI task creation in iscsi_pdu_hdr_op_task() by UT is not simple + * and do it separately later. + */ + + task_reqh = (struct iscsi_bhs_task_req *)&pdu.bhs; + + conn.sess = &sess; + conn.dev = &dev; + + /* Case 1 - Task Management Function request PDU is acceptable only on normal session. */ + sess.session_type = SESSION_TYPE_DISCOVERY; + + rc = iscsi_pdu_hdr_op_task(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 2 - LUN is hot removed. "LUN does not exist" response is sent. */ + sess.session_type = SESSION_TYPE_NORMAL; + task_reqh->immediate = 0; + to_be32(&task_reqh->itt, 1234); + + rc = iscsi_pdu_hdr_op_task(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_task_mgmt_response(ISCSI_TASK_FUNC_RESP_LUN_NOT_EXIST, 1234, 0, 0, 1); + + /* Case 3 - Unassigned function is specified. "Function rejected" response is sent. */ + dev.lun[0] = &lun; + task_reqh->flags = 0; + + rc = iscsi_pdu_hdr_op_task(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_task_mgmt_response(ISCSI_TASK_FUNC_REJECTED, 1234, 0, 0, 2); + + /* Case 4 - CLEAR TASK SET is not supported. "Task management function not supported" + * response is sent. + */ + task_reqh->flags = ISCSI_TASK_FUNC_CLEAR_TASK_SET; + + rc = iscsi_pdu_hdr_op_task(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_task_mgmt_response(ISCSI_TASK_FUNC_RESP_FUNC_NOT_SUPPORTED, 1234, 0, 0, 3); + + /* Case 5 - CLEAR ACA is not supported. "Task management function not supported" is sent. */ + task_reqh->flags = ISCSI_TASK_FUNC_CLEAR_ACA; + + rc = iscsi_pdu_hdr_op_task(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_task_mgmt_response(ISCSI_TASK_FUNC_RESP_FUNC_NOT_SUPPORTED, 1234, 0, 0, 4); + + /* Case 6 - TARGET WARM RESET is not supported. "Task management function not supported + * is sent. + */ + task_reqh->flags = ISCSI_TASK_FUNC_TARGET_WARM_RESET; + + rc = iscsi_pdu_hdr_op_task(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_task_mgmt_response(ISCSI_TASK_FUNC_RESP_FUNC_NOT_SUPPORTED, 1234, 0, 0, 5); + + /* Case 7 - TARGET COLD RESET is not supported. "Task management function not supported + * is sent. + */ + task_reqh->flags = ISCSI_TASK_FUNC_TARGET_COLD_RESET; + + rc = iscsi_pdu_hdr_op_task(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_task_mgmt_response(ISCSI_TASK_FUNC_RESP_FUNC_NOT_SUPPORTED, 1234, 0, 0, 6); + + /* Case 8 - TASK REASSIGN is not supported. "Task management function not supported" is sent. */ + task_reqh->flags = ISCSI_TASK_FUNC_TASK_REASSIGN; + + rc = iscsi_pdu_hdr_op_task(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_task_mgmt_response(ISCSI_TASK_FUNC_RESP_FUNC_NOT_SUPPORTED, 1234, 0, 0, 7); +} + +static void +pdu_hdr_op_nopout_test(void) +{ + struct spdk_iscsi_sess sess = {}; + struct spdk_iscsi_conn conn = {}; + struct spdk_iscsi_pdu pdu = {}; + struct iscsi_bhs_nop_out *nopout_reqh; + int rc; + + nopout_reqh = (struct iscsi_bhs_nop_out *)&pdu.bhs; + + conn.sess = &sess; + + /* Case 1 - NOP-Out PDU is acceptable only on normal session. */ + sess.session_type = SESSION_TYPE_DISCOVERY; + + rc = iscsi_pdu_hdr_op_nopout(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 2 - The length of the reflected ping data is limited to MaxRecvDataSegmentLength. */ + sess.session_type = SESSION_TYPE_NORMAL; + pdu.data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH + 1; + + rc = iscsi_pdu_hdr_op_nopout(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_PROTOCOL_ERROR); + + /* Case 3 - If Initiator Task Tag contains 0xffffffff, the I bit must be set + * to 1 and Target Transfer Tag should be copied from NOP-In PDU. This case + * satisfies the former but doesn't satisfy the latter, but ignore the error + * for now. + */ + pdu.data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + conn.id = 1234; + to_be32(&nopout_reqh->ttt, 1235); + to_be32(&nopout_reqh->itt, 0xffffffffU); + nopout_reqh->immediate = 1; + + rc = iscsi_pdu_hdr_op_nopout(&conn, &pdu); + CU_ASSERT(rc == 0); + + /* Case 4 - This case doesn't satisfy the above former. This error is not ignored. */ + nopout_reqh->immediate = 0; + + rc = iscsi_pdu_hdr_op_nopout(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); +} + +static void +check_iscsi_r2t(struct spdk_iscsi_task *task, uint32_t len) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_r2t *rsph; + + rsp_pdu = TAILQ_FIRST(&g_write_pdu_list); + CU_ASSERT(rsp_pdu != NULL); + rsph = (struct iscsi_bhs_r2t *)&rsp_pdu->bhs; + CU_ASSERT(rsph->opcode == ISCSI_OP_R2T); + CU_ASSERT(from_be64(&rsph->lun) == spdk_scsi_lun_id_int_to_fmt(task->lun_id)); + CU_ASSERT(from_be32(&rsph->buffer_offset) == task->next_r2t_offset); + CU_ASSERT(from_be32(&rsph->desired_xfer_len) == len); + + TAILQ_REMOVE(&g_write_pdu_list, rsp_pdu, tailq); + spdk_put_pdu(rsp_pdu); +} + +static void +pdu_hdr_op_data_test(void) +{ + struct spdk_iscsi_sess sess = {}; + struct spdk_iscsi_conn conn = {}; + struct spdk_iscsi_pdu pdu = {}; + struct spdk_iscsi_task primary = {}; + struct spdk_scsi_dev dev = {}; + struct spdk_scsi_lun lun = {}; + struct iscsi_bhs_data_out *data_reqh; + int rc; + + data_reqh = (struct iscsi_bhs_data_out *)&pdu.bhs; + + conn.sess = &sess; + conn.dev = &dev; + + /* Case 1 - SCSI Data-Out PDU is acceptable only on normal session. */ + sess.session_type = SESSION_TYPE_DISCOVERY; + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 2 - Data segment length must not be more than MaxRecvDataSegmentLength. */ + sess.session_type = SESSION_TYPE_NORMAL; + pdu.data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH + 1; + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_PROTOCOL_ERROR); + + /* Case 3 - R2T task whose Target Transfer Tag matches is not found. */ + pdu.data_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_INVALID_PDU_FIELD); + + /* Case 4 - R2T task whose Target Transfer Tag matches is found but data segment length + * is more than Desired Data Transfer Length of the R2T. + */ + primary.desired_data_transfer_length = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH - 1; + conn.pending_r2t = 1; + conn.outstanding_r2t_tasks[0] = &primary; + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 5 - Initiator task tag doesn't match tag of R2T task. */ + primary.desired_data_transfer_length = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + to_be32(&data_reqh->itt, 1); + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_INVALID_PDU_FIELD); + + /* Case 6 - DataSN doesn't match the Data-Out PDU number within the current + * output sequence. + */ + to_be32(&data_reqh->itt, 0); + to_be32(&data_reqh->data_sn, 1); + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == 0); + check_iscsi_reject(&pdu, ISCSI_REASON_PROTOCOL_ERROR); + + /* Case 7 - Output sequence must be in increasing buffer offset and must not + * be overlaid but they are not satisfied. + */ + to_be32(&data_reqh->data_sn, 0); + to_be32(&data_reqh->buffer_offset, 4096); + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 8 - Data segment length must not exceed MaxBurstLength. */ + to_be32(&data_reqh->buffer_offset, 0); + sess.MaxBurstLength = pdu.data_segment_len - 1; + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + /* Case 9 - LUN is hot removed. */ + sess.MaxBurstLength = pdu.data_segment_len * 4; + to_be32(&data_reqh->data_sn, primary.r2t_datasn); + to_be32(&data_reqh->buffer_offset, primary.next_expected_r2t_offset); + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == 0); + CU_ASSERT(pdu.task == NULL); + + /* Case 10 - SCSI Data-Out PDU is correct and processed. Created task is held + * to the PDU, but its F bit is 0 and hence R2T is not sent. + */ + dev.lun[0] = &lun; + to_be32(&data_reqh->data_sn, primary.r2t_datasn); + to_be32(&data_reqh->buffer_offset, primary.next_expected_r2t_offset); + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == 0); + CU_ASSERT(pdu.task != NULL); + spdk_iscsi_task_put(pdu.task); + pdu.task = NULL; + + /* Case 11 - SCSI Data-Out PDU is correct and processed. Created task is held + * to the PDU, and Its F bit is 1 and hence R2T is sent. + */ + data_reqh->flags |= ISCSI_FLAG_FINAL; + to_be32(&data_reqh->data_sn, primary.r2t_datasn); + to_be32(&data_reqh->buffer_offset, primary.next_expected_r2t_offset); + primary.scsi.transfer_len = pdu.data_segment_len * 5; + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == 0); + CU_ASSERT(pdu.task != NULL); + check_iscsi_r2t(pdu.task, pdu.data_segment_len * 4); + spdk_iscsi_task_put(pdu.task); + + /* Case 12 - Task pool is empty. */ + to_be32(&data_reqh->data_sn, primary.r2t_datasn); + to_be32(&data_reqh->buffer_offset, primary.next_expected_r2t_offset); + g_task_pool_is_empty = true; + + rc = iscsi_pdu_hdr_op_data(&conn, &pdu); + CU_ASSERT(rc == SPDK_ISCSI_CONNECTION_FATAL); + + g_task_pool_is_empty = false; +} + int main(int argc, char **argv) { @@ -1549,6 +2241,13 @@ main(int argc, char **argv) abort_queued_datain_tasks_test) == NULL || CU_add_test(suite, "build_iovs_test", build_iovs_test) == NULL || CU_add_test(suite, "build_iovs_with_md_test", build_iovs_with_md_test) == NULL + || CU_add_test(suite, "pdu_hdr_op_login_test", pdu_hdr_op_login_test) == NULL + || CU_add_test(suite, "pdu_hdr_op_text_test", pdu_hdr_op_text_test) == NULL + || CU_add_test(suite, "pdu_hdr_op_logout_test", pdu_hdr_op_logout_test) == NULL + || CU_add_test(suite, "pdu_hdr_op_scsi_test", pdu_hdr_op_scsi_test) == NULL + || CU_add_test(suite, "pdu_hdr_op_task_mgmt_test", pdu_hdr_op_task_mgmt_test) == NULL + || CU_add_test(suite, "pdu_hdr_op_nopout_test", pdu_hdr_op_nopout_test) == NULL + || CU_add_test(suite, "pdu_hdr_op_data_test", pdu_hdr_op_data_test) == NULL ) { CU_cleanup_registry(); return CU_get_error();