root/lib/libiscsiutil/text.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2012 The FreeBSD Foundation
 *
 * This software was developed by Edward Tomasz Napierala under sponsorship
 * from the FreeBSD Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <netinet/in.h>

#include <stdlib.h>
#include <string.h>

#include <iscsi_proto.h>
#include "libiscsiutil.h"

/* Construct a new TextRequest PDU. */
static struct pdu *
text_new_request(struct connection *conn, uint32_t ttt)
{
        struct pdu *request;
        struct iscsi_bhs_text_request *bhstr;

        request = pdu_new(conn);
        bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
        bhstr->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_REQUEST |
            ISCSI_BHS_OPCODE_IMMEDIATE;
        bhstr->bhstr_flags = BHSTR_FLAGS_FINAL;
        bhstr->bhstr_initiator_task_tag = 0;
        bhstr->bhstr_target_transfer_tag = ttt;

        bhstr->bhstr_cmdsn = conn->conn_cmdsn;
        bhstr->bhstr_expstatsn = htonl(conn->conn_statsn + 1);

        return (request);
}

/* Receive a TextRequest PDU from a connection. */
static struct pdu *
text_receive_request(struct connection *conn)
{
        struct pdu *request;
        struct iscsi_bhs_text_request *bhstr;

        request = pdu_new(conn);
        pdu_receive(request);
        if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
            ISCSI_BHS_OPCODE_TEXT_REQUEST)
                log_errx(1, "protocol error: received invalid opcode 0x%x",
                    request->pdu_bhs->bhs_opcode);
        bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;

        /*
         * XXX: Implement the C flag some day.
         */
        if ((bhstr->bhstr_flags & (BHSTR_FLAGS_FINAL | BHSTR_FLAGS_CONTINUE)) !=
            BHSTR_FLAGS_FINAL)
                log_errx(1, "received TextRequest PDU with invalid "
                    "flags: %u", bhstr->bhstr_flags);
        if (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) {
                log_errx(1, "received TextRequest PDU with decreasing CmdSN: "
                    "was %u, is %u", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn));
        }
        conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn);
        if ((bhstr->bhstr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
                conn->conn_cmdsn++;

        return (request);
}

/* Construct a new TextResponse PDU in reply to a request. */
static struct pdu *
text_new_response(struct pdu *request, uint32_t ttt, bool final)
{
        struct pdu *response;
        struct connection *conn;
        struct iscsi_bhs_text_request *bhstr;
        struct iscsi_bhs_text_response *bhstr2;

        bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
        conn = request->pdu_connection;

        response = pdu_new_response(request);
        bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs;
        bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE;
        if (final)
                bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL;
        else
                bhstr2->bhstr_flags = BHSTR_FLAGS_CONTINUE;
        bhstr2->bhstr_lun = bhstr->bhstr_lun;
        bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag;
        bhstr2->bhstr_target_transfer_tag = ttt;
        bhstr2->bhstr_statsn = htonl(conn->conn_statsn++);
        bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn);
        bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn);

        return (response);
}

/* Receive a TextResponse PDU from a connection. */
static struct pdu *
text_receive_response(struct connection *conn)
{
        struct pdu *response;
        struct iscsi_bhs_text_response *bhstr;
        uint8_t flags;

        response = pdu_new(conn);
        pdu_receive(response);
        if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_TEXT_RESPONSE)
                log_errx(1, "protocol error: received invalid opcode 0x%x",
                    response->pdu_bhs->bhs_opcode);
        bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs;
        flags = bhstr->bhstr_flags & (BHSTR_FLAGS_FINAL | BHSTR_FLAGS_CONTINUE);
        switch (flags) {
        case BHSTR_FLAGS_CONTINUE:
                if (bhstr->bhstr_target_transfer_tag == 0xffffffff)
                        log_errx(1, "received continue TextResponse PDU with "
                            "invalid TTT 0x%x",
                            bhstr->bhstr_target_transfer_tag);
                break;
        case BHSTR_FLAGS_FINAL:
                if (bhstr->bhstr_target_transfer_tag != 0xffffffff)
                        log_errx(1, "received final TextResponse PDU with "
                            "invalid TTT 0x%x",
                            bhstr->bhstr_target_transfer_tag);
                break;
        default:
                log_errx(1, "received TextResponse PDU with invalid "
                    "flags: %u", bhstr->bhstr_flags);
        }
        if (ntohl(bhstr->bhstr_statsn) != conn->conn_statsn + 1) {
                log_errx(1, "received TextResponse PDU with wrong StatSN: "
                    "is %u, should be %u", ntohl(bhstr->bhstr_statsn),
                    conn->conn_statsn + 1);
        }
        conn->conn_statsn = ntohl(bhstr->bhstr_statsn);

        return (response);
}

/*
 * Send a list of keys from the initiator to the target in a
 * TextRequest PDU.
 */
void
text_send_request(struct connection *conn, struct keys *request_keys)
{
        struct pdu *request;

        request = text_new_request(conn, 0xffffffff);
        keys_save_pdu(request_keys, request);
        if (request->pdu_data_len == 0)
                log_errx(1, "No keys to send in a TextRequest");
        if (request->pdu_data_len >
            (size_t)conn->conn_max_send_data_segment_length)
                log_errx(1, "Keys to send in TextRequest are too long");

        pdu_send(request);
        pdu_delete(request);
}

/*
 * Read a list of keys from the target in a series of TextResponse
 * PDUs.
 */
struct keys *
text_read_response(struct connection *conn)
{
        struct keys *response_keys;
        char *keys_data;
        size_t keys_len;
        uint32_t ttt;

        keys_data = NULL;
        keys_len = 0;
        ttt = 0xffffffff;
        for (;;) {
                struct pdu *request, *response;
                struct iscsi_bhs_text_response *bhstr;

                response = text_receive_response(conn);
                bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs;
                if (keys_data == NULL) {
                        ttt = bhstr->bhstr_target_transfer_tag;
                        keys_data = response->pdu_data;
                        keys_len = response->pdu_data_len;
                        response->pdu_data = NULL;
                } else {
                        keys_data = realloc(keys_data,
                            keys_len + response->pdu_data_len);
                        if (keys_data == NULL)
                                log_err(1, "failed to grow keys block");
                        memcpy(keys_data + keys_len, response->pdu_data,
                            response->pdu_data_len);
                        keys_len += response->pdu_data_len;
                }
                if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) != 0) {
                        pdu_delete(response);
                        break;
                }
                if (bhstr->bhstr_target_transfer_tag != ttt)
                        log_errx(1, "received non-final TextRequest PDU with "
                            "invalid TTT 0x%x",
                            bhstr->bhstr_target_transfer_tag);
                pdu_delete(response);

                /* Send an empty request. */
                request = text_new_request(conn, ttt);
                pdu_send(request);
                pdu_delete(request);
        }

        response_keys = keys_new();
        keys_load(response_keys, keys_data, keys_len);
        free(keys_data);
        return (response_keys);
}

/*
 * Read a list of keys from the initiator in a TextRequest PDU.
 */
struct keys *
text_read_request(struct connection *conn, struct pdu **requestp)
{
        struct iscsi_bhs_text_request *bhstr;
        struct pdu *request;
        struct keys *request_keys;

        request = text_receive_request(conn);
        bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
        if (bhstr->bhstr_target_transfer_tag != 0xffffffff)
                log_errx(1, "received TextRequest PDU with invalid TTT 0x%x",
                    bhstr->bhstr_target_transfer_tag);
        if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) {
                log_errx(1, "received TextRequest PDU with wrong ExpStatSN: "
                    "is %u, should be %u", ntohl(bhstr->bhstr_expstatsn),
                    conn->conn_statsn);
        }

        request_keys = keys_new();
        keys_load_pdu(request_keys, request);
        *requestp = request;
        return (request_keys);
}

/*
 * Send a response back to the initiator as a series of TextResponse
 * PDUs.
 */
void
text_send_response(struct pdu *request, struct keys *response_keys)
{
        struct connection *conn = request->pdu_connection;
        char *keys_data;
        size_t keys_len;
        size_t keys_offset;
        uint32_t ttt;

        keys_save(response_keys, &keys_data, &keys_len);
        keys_offset = 0;
        ttt = keys_len;
        for (;;) {
                struct pdu *request2, *response;
                struct iscsi_bhs_text_request *bhstr;
                size_t todo;
                bool final;

                todo = keys_len - keys_offset;
                if (todo > (size_t)conn->conn_max_send_data_segment_length) {
                        final = false;
                        todo = conn->conn_max_send_data_segment_length;
                } else {
                        final = true;
                        ttt = 0xffffffff;
                }

                response = text_new_response(request, ttt, final);
                response->pdu_data = keys_data + keys_offset;
                response->pdu_data_len = todo;
                keys_offset += todo;

                pdu_send(response);
                response->pdu_data = NULL;
                pdu_delete(response);

                if (final)
                        break;

                /*
                 * Wait for an empty request.
                 *
                 * XXX: Linux's Open-iSCSI initiator doesn't update
                 * ExpStatSN when receiving a TextResponse PDU.
                 */
                request2 = text_receive_request(conn);
                bhstr = (struct iscsi_bhs_text_request *)request2->pdu_bhs;
                if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) == 0)
                        log_errx(1, "received continuation TextRequest PDU "
                            "without F set");
                if (pdu_data_segment_length(request2) != 0)
                        log_errx(1, "received non-empty continuation "
                            "TextRequest PDU");
                if (bhstr->bhstr_target_transfer_tag != ttt)
                        log_errx(1, "received TextRequest PDU with invalid "
                            "TTT 0x%x", bhstr->bhstr_target_transfer_tag);
                pdu_delete(request2);
        }
        free(keys_data);
}