/* $NetBSD: isns_pdu.c,v 1.4 2018/02/08 09:05:17 dholland Exp $ */ /*- * Copyright (c) 2004,2009 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Wasabi Systems, Inc. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * isns_pdu.c */ #include __RCSID("$NetBSD: isns_pdu.c,v 1.4 2018/02/08 09:05:17 dholland Exp $"); #include #include #include #include #include #include "isns.h" #include "isns_config.h" /* * Local function prototypes. */ static struct isns_buffer_list_s *isns_lookup_buffer_list(int); static struct isns_pdu_s *isns_init_pdu(struct isns_buffer_s *, struct isns_config_s *, uint16_t, uint16_t, uint16_t); static int isns_add_pdu_payload_data(struct isns_trans_s *, const void *, int); static void isns_get_tlv_info_advance(struct isns_get_tlv_info_s *); static int isns_get_tlv_uint32(struct isns_get_tlv_info_s *, uint32_t *); static int isns_get_tlv_data(struct isns_get_tlv_info_s *, int, void **); static void isns_add_pdu_list(struct isns_pdu_s **, struct isns_pdu_s *); static struct isns_buffer_s *isns_get_pdu_head_buffer(struct isns_pdu_s *); #if 0 static struct isns_buffer_s *isns_get_pdu_tail_buffer(struct isns_pdu_s *); #endif static struct isns_buffer_s *isns_get_pdu_active_buffer(struct isns_pdu_s *); static uint32_t isns_get_next_trans_id(void); /* * Buffer pool structures and global (static) var. */ struct isns_buffer_list_s { int buf_size; int alloc_count; struct isns_buffer_s *head; struct isns_buffer_list_s *next; }; struct isns_buffer_pool_s { int active; struct isns_buffer_list_s *list_p; pthread_mutex_t mutex; }; static struct isns_buffer_pool_s G_buffer_pool; /* * isns_init_buffer_pool - initialize buffer pool for use */ void isns_init_buffer_pool(void) { pthread_mutexattr_t mutexattr; DBG("isns_init_buffer_pool: entered\n"); assert(!G_buffer_pool.active); pthread_mutexattr_init(&mutexattr); pthread_mutexattr_settype(&mutexattr, ISNS_MUTEX_TYPE_NORMAL); pthread_mutex_init(&G_buffer_pool.mutex, &mutexattr); G_buffer_pool.active = 1; } /* * isns_lookup_buffer_list - locates the pool buffer list for the buf_size * specified. * * Returns: pointer to list in pool containing buf_size buffers * NULL if no list for size indicated exists */ static struct isns_buffer_list_s * isns_lookup_buffer_list(int buf_size) { struct isns_buffer_list_s *list_p; /* * WARNING: G_buffer_pool.mutex MUST already be locked. */ list_p = G_buffer_pool.list_p; while (list_p != NULL) { if (list_p->buf_size == buf_size) break; list_p = list_p->next; } return list_p; } /* * isns_add_buffer_pool - allocates buffers of in pool * * If a list containing buf_size buffers already exists in pool, additional * buffers are added (allocated) to the list. */ int isns_add_buffer_pool(int buf_size, int count) { struct isns_buffer_list_s *list_p, *p, *p_next; struct isns_buffer_s *buf_p; int n; DBG("isns_add_buffer_pool: buf_size=%d, count=%d\n", buf_size, count); assert(G_buffer_pool.active); /* Make our buffer lengths always a multiple of 4. */ buf_size = (buf_size + 0x03) & ~0x03; /* * Lookup existing list for size specified. If no exists, allocate * a new list and initialize. */ pthread_mutex_lock(&G_buffer_pool.mutex); list_p = isns_lookup_buffer_list(buf_size); if (list_p == NULL) { pthread_mutex_unlock(&G_buffer_pool.mutex); list_p = (struct isns_buffer_list_s *) isns_malloc(sizeof(struct isns_buffer_list_s)); if (list_p == NULL) { DBG("isns_add_buffer_pool: error on isns_malloc()\n"); return ENOMEM; } list_p->buf_size = buf_size; list_p->alloc_count = 0; list_p->head = NULL; } /* If this is a new list, insert into pool in buf_size order. */ if (list_p->alloc_count == 0) { pthread_mutex_lock(&G_buffer_pool.mutex); if (G_buffer_pool.list_p == NULL) { G_buffer_pool.list_p = list_p; list_p->next = NULL; } else if (G_buffer_pool.list_p->buf_size > buf_size) { list_p->next = G_buffer_pool.list_p; G_buffer_pool.list_p = list_p; } else { p = G_buffer_pool.list_p; while (p->next != NULL) { p_next = p->next; if (p_next->buf_size > buf_size) { p->next = list_p; list_p->next = p_next; break; } p = p->next; } if (p->next == NULL) { p->next = list_p; list_p->next = NULL; } } } /* Allocate (possibly additional) buffers for list. */ for (n = 0; n < count; n++) { buf_p = (struct isns_buffer_s *) isns_malloc(buf_size + sizeof(struct isns_buffer_s)); if (buf_p == NULL) break; buf_p->next = list_p->head; list_p->head = buf_p; } list_p->alloc_count += n; pthread_mutex_unlock(&G_buffer_pool.mutex); DBG("isns_init_buffer_pool: %d %d-byte buffers allocated\n", n, buf_size); return (n > 0 ? 0 : ENOMEM); } /* * isns_destroy_buffer_pool - destroys previously allocated buffer pool */ void isns_destroy_buffer_pool(void) { struct isns_buffer_list_s *list_p; struct isns_buffer_s *buf_p; #ifdef ISNS_DEBUG char dbg_buffer[1024] = { 0 }; #endif DBG("isns_destroy_buffer_pool: entered\n"); assert(G_buffer_pool.active); pthread_mutex_lock(&G_buffer_pool.mutex); while (G_buffer_pool.list_p != NULL) { list_p = G_buffer_pool.list_p; while (list_p->head != NULL) { buf_p = list_p->head; list_p->head = buf_p->next; list_p->alloc_count--; isns_free(buf_p); } #ifdef ISNS_DEBUG if (list_p->alloc_count > 0) { snprintf(&dbg_buffer[(int) strlen(dbg_buffer)], (sizeof(dbg_buffer) - strlen(dbg_buffer)), "isns_destroy_buffer_pool: " "%d %d-byte buffer(s) not freed\n", list_p->alloc_count, list_p->buf_size); } #endif G_buffer_pool.list_p = list_p->next; isns_free(list_p); } G_buffer_pool.active = 0; pthread_mutex_unlock(&G_buffer_pool.mutex); pthread_mutex_destroy(&G_buffer_pool.mutex); DBG(dbg_buffer); } /* * isns_new_buffer - allocates a new ISNS buffer * * Typically, the buffer is returned from the pool, but if no free buffers * are available in the pool, or a buf size larger than the largest pool buffer * size is requested, a normal malloc is used to allocate the buffer. The * buffer type is recorded so that a subsequent isns_free_buffer will correctly * free the buffer or return it to the pool. */ struct isns_buffer_s * isns_new_buffer(int buf_size) { struct isns_buffer_list_s *list_p; struct isns_buffer_s *buf_p; int buf_type; if (buf_size == 0) buf_size = ISNS_BUF_SIZE; buf_p = NULL; pthread_mutex_lock(&G_buffer_pool.mutex); list_p = G_buffer_pool.list_p; while (list_p != NULL) { if ((list_p->head != NULL) && (list_p->buf_size >= buf_size)) { buf_p = list_p->head; list_p->head = buf_p->next; buf_size = list_p->buf_size; buf_type = ISNS_BUF_POOL; break; } list_p = list_p->next; } pthread_mutex_unlock(&G_buffer_pool.mutex); if (buf_p == NULL) { buf_p = (struct isns_buffer_s *)isns_malloc( buf_size + sizeof(struct isns_buffer_s)); buf_type = ISNS_BUF_MALLOC; } if (buf_p != NULL) ISNS_INIT_BUFFER(buf_p, buf_size, buf_type); DBG("isns_new_buffer: %p (buf_size=%d, type=%d)\n", buf_p, buf_size, buf_type); return buf_p; } /* * isns_free_buffer - free a ISNS buffer */ void isns_free_buffer(struct isns_buffer_s *buf_p) { struct isns_buffer_list_s *list_p; DBG("isns_free_buffer: %p (type=%d, alloc_len=%d)\n", buf_p, (buf_p == NULL ? 0 : buf_p->buf_type), (buf_p == NULL ? 0 : buf_p->alloc_len)); if (buf_p != NULL) { switch (buf_p->buf_type) { case ISNS_BUF_POOL: /* Return buffer to proper pool list. */ pthread_mutex_lock(&G_buffer_pool.mutex); list_p = isns_lookup_buffer_list((int)buf_p->alloc_len); if (list_p != NULL) { buf_p->next = list_p->head; list_p->head = buf_p; } pthread_mutex_unlock(&G_buffer_pool.mutex); break; case ISNS_BUF_MALLOC: /* Malloc allocated buf, so free normally. */ isns_free(buf_p); break; case ISNS_BUF_STATIC: /* Static buf with no allocation, so do nothing here. */ break; } } } /* * isns_new_trans - create a new ISNS transaction */ ISNS_TRANS isns_new_trans(ISNS_HANDLE isns_handle, uint16_t func_id, uint16_t pdu_flags) { struct isns_trans_s *trans_p; struct isns_pdu_s *pdu_p; struct isns_buffer_s *buf_p; if (isns_handle == ISNS_INVALID_HANDLE) { DBG("isns_new_trans: error - handle=%p\n", isns_handle); return ISNS_INVALID_TRANS; } buf_p = isns_new_buffer((int)sizeof(struct isns_trans_s)); if (buf_p == NULL) { DBG("isns_new_trans: error on isns_new_buffer()\n"); return ISNS_INVALID_TRANS; } trans_p = (struct isns_trans_s *)isns_buffer_data(buf_p, 0); trans_p->id = isns_get_next_trans_id(); trans_p->func_id = func_id; trans_p->flags = 0; trans_p->cfg_p = (struct isns_config_s *)isns_handle; trans_p->pdu_req_list = NULL; trans_p->pdu_rsp_list = NULL; trans_p->disconnect_cnt = 0; trans_p->get_tlv_info.pdu_p = NULL; trans_p->get_tlv_info.buf_p = NULL; trans_p->get_tlv_info.extra_buf_list = NULL; trans_p->get_tlv_info.buf_ofs = 0; buf_p->cur_len = sizeof(struct isns_trans_s); /* * Mask off all but the AUTH and possibly REPLACE_REG pdu flags. Then, * set the appropriate server/client sender flag. The first/last PDU * flags will be set when the PDU is sent. */ if (func_id == isnsp_DevAttrReg) pdu_flags &= (ISNS_FLAG_AUTH | ISNS_FLAG_REPLACE_REG); else pdu_flags &= ISNS_FLAG_AUTH; if (trans_p->cfg_p->is_server) pdu_flags |= ISNS_FLAG_SND_SERVER; else pdu_flags |= ISNS_FLAG_SND_CLIENT; pdu_p = isns_new_pdu(trans_p->cfg_p, trans_p->id, func_id, pdu_flags); if (pdu_p == NULL) { DBG("isns_new_trans: error on isns_new_pdu()\n"); isns_free_buffer(buf_p); return ISNS_INVALID_TRANS; } isns_add_pdu_request((ISNS_TRANS)trans_p, pdu_p); DBG("isns_new_trans: %p\n", trans_p); return (ISNS_TRANS)trans_p; } /* * isns_free_trans - free ISNS transaction created with isns_new_trans */ void isns_free_trans(ISNS_TRANS trans) { struct isns_trans_s *trans_p; struct isns_pdu_s *pdu_p; struct isns_buffer_s *buf_p, *free_buf_p; uint32_t trans_flags; DBG("isns_free_trans: %p\n", trans); if (trans != ISNS_INVALID_TRANS) { trans_p = (struct isns_trans_s *)trans; trans_flags = isns_set_trans_flags(trans_p, ISNS_TRANSF_FREE_WHEN_COMPLETE); if ((trans_flags & ISNS_TRANSF_COMPLETE) == 0) { DBG("isns_free_trans: deferred - trans not complete\n"); return; } DBG("isns_free_trans: pdu_req_list=%p\n", trans_p->pdu_req_list); while ((pdu_p = trans_p->pdu_req_list) != NULL) { trans_p->pdu_req_list = pdu_p->next; isns_free_pdu(pdu_p); } DBG("isns_free_trans: pdu_rsp_list=%p\n", trans_p->pdu_rsp_list); while ((pdu_p = trans_p->pdu_rsp_list) != NULL) { trans_p->pdu_rsp_list = pdu_p->next; isns_free_pdu(pdu_p); } DBG("isns_free_trans: extra_buf_list=%p\n", trans_p->get_tlv_info.extra_buf_list); buf_p = trans_p->get_tlv_info.extra_buf_list; while (buf_p != NULL) { free_buf_p = buf_p; buf_p = buf_p->next; isns_free_buffer(free_buf_p); } DBG("isns_free_trans: freeing base trans buffer\n"); buf_p = ((struct isns_buffer_s *)(void *)(trans_p))-1; isns_free_buffer(buf_p); } } /* * isns_send_trans - send ISNS transaction PDU(s) and optionally wait * * If a successful wait occurs (i.e., the transaction completes without * a timeout), then the response PDU status is place in *status_p. For * all other cases, the data returned in *status_p is undefined. * */ int isns_send_trans(ISNS_TRANS trans, const struct timespec *timeout_p, uint32_t *status_p) { struct isns_trans_s *trans_p; struct isns_pdu_s *pdu_p; int rval; trans_p = (struct isns_trans_s *)trans; DBG("isns_send_trans: trans_p=%p, timeout_p=%p\n", trans_p, timeout_p); if (status_p != NULL) *status_p = 0; if (!isns_is_socket_init_done(trans_p->cfg_p)) { DBG("isns_send_trans: socket not initialized\n"); isns_complete_trans(trans_p); return EINVAL; } if ((pdu_p = isns_get_pdu_request(trans)) == NULL) { DBG("isns_send_trans: no request PDU\n"); return EINVAL; } /* Set the FIRST_PDU flag in the first PDU. */ pdu_p->hdr.flags |= ISNS_FLAG_FIRST_PDU; /* Set our PDU sequence numbers for the PDU chain. */ while (pdu_p->next != NULL) { pdu_p->next->hdr.seq_id = pdu_p->hdr.seq_id + 1; pdu_p = pdu_p->next; } /* Set the LAST_PDU flag in the last PDU. */ pdu_p->hdr.flags |= ISNS_FLAG_LAST_PDU; rval = isns_send_pdu(trans, isns_get_pdu_request(trans), timeout_p); if ((rval == 0) && (status_p != NULL)) isns_get_pdu_response_status(trans, status_p); return rval; } void isns_complete_trans(struct isns_trans_s *trans_p) { uint32_t flags; DBG("isns_complete_trans: trans_p=%p\n", trans_p); flags = isns_set_trans_flags(trans_p, ISNS_TRANSF_COMPLETE); if ((flags & ISNS_TRANSF_FREE_WHEN_COMPLETE) != 0) isns_free_trans(trans_p); } int isns_abort_trans(struct isns_config_s *cfg_p, uint16_t trans_id) { struct isns_task_s *task_p; /* First, look at current task. */ if (((task_p = cfg_p->curtask_p) != NULL) && (task_p->task_type == ISNS_TASK_SEND_PDU) && (task_p->var.send_pdu.trans_p->id == trans_id)) { isns_complete_trans(task_p->var.send_pdu.trans_p); isns_end_task(task_p); return 0; } /* If not current task, look in task queue. */ task_p = isns_taskq_remove_trans(cfg_p, trans_id); if (task_p) { isns_complete_trans(task_p->var.send_pdu.trans_p); isns_end_task(task_p); return 0; } return EINVAL; } /* * isns_add_string - add a TLV which is a C string * * Wrapper around isns_add_tlv() */ int isns_add_string(ISNS_TRANS trans, uint32_t tag, const char *s) { /* Add string, including required NULL. */ return isns_add_tlv(trans, tag, (uint32_t)strlen(s) + 1, s); } /* * isns_add_tlv - adds a TLV to an existing transaction */ int isns_add_tlv(ISNS_TRANS trans, uint32_t tag, uint32_t data_len, const void *data_p) { struct isns_trans_s *trans_p; uint8_t tlv_buf[ISNS_TLV_HDR_SIZE]; int rval; DBG("isns_add_tlv: trans=%p, tag=%d, data_len=%d, data_p=%p\n", trans, tag, data_len, data_p); if (trans == ISNS_INVALID_TRANS) { DBG("isns_add_tlv: error - trans=%p\n", trans); return EINVAL; } if ((data_len > 0) && (data_p == NULL)) { DBG("isns_add_tlv: error data_len=%d, data_p=%p\n", data_len, data_p); return EINVAL; } /* Set tag, length in header buffer and add to PDU payload data. */ trans_p = (struct isns_trans_s *)trans; ISNS_TLV_SET_TAG(tlv_buf, tag); ISNS_TLV_SET_LEN(tlv_buf, ISNS_PAD4_LEN(data_len)); rval = isns_add_pdu_payload_data(trans_p, tlv_buf, ISNS_TLV_HDR_SIZE); /* If header added okay, add value portion to PDU payload data. */ if ((rval == 0) && (data_len > 0)) rval = isns_add_pdu_payload_data(trans_p, data_p, data_len); return rval; } /* * isns_get_tlv - get TLV value from response PDU for transaction * * returns: * 0 - success * ENOENT - no (more) TLVs in this transaction * EINVAL - invalid arg * EPERM - operation not permitted - transaction not complete * ENOMEM - could not allocate storage for spanning TLV data */ int isns_get_tlv(ISNS_TRANS trans, int which_tlv, uint32_t *tag_p, uint32_t *data_len_p, void **data_pp) { struct isns_trans_s *trans_p; struct isns_get_tlv_info_s *info_p; struct isns_pdu_s *pdu_p; int rval; if (trans == ISNS_INVALID_TRANS) { DBG("isns_get_tlv: error - trans=%p\n", trans); return EINVAL; } trans_p = (struct isns_trans_s *)trans; if ((isns_get_trans_flags(trans_p) & ISNS_TRANSF_COMPLETE) == 0) { DBG("isns_get_tlv: error - trans not complete\n"); return EPERM; } /* Get response PDU for this transaction. */ pdu_p = isns_get_pdu_response(trans_p); if (pdu_p == NULL) { DBG("isns_get_tlv: error - no response PDU in transaction\n"); return EINVAL; } if (pdu_p->payload_p->cur_len == 0) { DBG("isns_get_tlv: error - zero length PDU payload\n"); return EINVAL; } /* If get_tlv_info unset, treat ISNS_TLV_NEXT as ISNS_TLV_FIRST. */ info_p = &trans_p->get_tlv_info; if ((which_tlv == ISNS_TLV_NEXT) && (info_p->pdu_p == NULL)) which_tlv = ISNS_TLV_FIRST; /*!!! make sure PDU uses TLVs here */ switch (which_tlv) { case ISNS_TLV_NEXT: /* For next TLV, nothing to do here - use get_tlv_info as-is. */ break; case ISNS_TLV_FIRST: /* For first TLV, reset get_tlv_info. */ info_p->pdu_p = pdu_p; info_p->buf_p = isns_get_pdu_head_buffer(pdu_p); info_p->buf_ofs = 4; break; default: DBG("isns_get_tlv: invalid arg (which_tlv=%d)\n", which_tlv); return EINVAL; } /* * Get the type, length, and data (value) for the TLV. The get calls * below will advance the pointers in get_tlv_info_s *info_p. Note that * if the get of the TAG type fails, ENOENT is returned indicating that * no more TLVs exist for this request PDU. */ if ((rval = isns_get_tlv_uint32(info_p, tag_p)) != 0) { DBG("isns_get_tlv: error on isns_get_tlv_uint32() tag\n"); return ENOENT; } if ((rval = isns_get_tlv_uint32(info_p, (uint32_t *)data_len_p)) != 0) { DBG("isns_get_tlv: error on isns_get_tlv_uint32() data len\n"); return rval; } rval = isns_get_tlv_data(info_p, *data_len_p, data_pp); if (rval != 0) { DBG("isns_get_tlv: error on isns_get_tlv_data()\n"); return rval; } return 0; } /* * isns_set_trans_flags - sets flag bit(s) in transaction flags member */ uint32_t isns_set_trans_flags(struct isns_trans_s *trans_p, uint32_t flags) { pthread_mutex_lock(&trans_p->cfg_p->trans_mutex); trans_p->flags |= flags; flags = trans_p->flags; pthread_mutex_unlock(&trans_p->cfg_p->trans_mutex); return flags; } /* * isns_add_pdu_request - adds PDU to transaction request PDU list */ void isns_add_pdu_request(struct isns_trans_s *trans_p, struct isns_pdu_s *pdu_p) { DBG("isns_add_pdu_request: trans_p=%p, pdu_p=%p\n", trans_p, pdu_p); isns_add_pdu_list(&trans_p->pdu_req_list, pdu_p); } /* * isns_add_pdu_response - adds PDU to transaction response PDU list */ void isns_add_pdu_response(struct isns_trans_s *trans_p, struct isns_pdu_s *pdu_p) { DBG("isns_add_pdu_response: trans_p=%p, pdu_p=%p\n", trans_p, pdu_p); isns_add_pdu_list(&trans_p->pdu_rsp_list, pdu_p); } /* * isns_get_pdu_request_tail - returns last PDU in request PDU chain */ struct isns_pdu_s * isns_get_pdu_request_tail(struct isns_trans_s *trans_p) { struct isns_pdu_s *pdu_p; if ((pdu_p = isns_get_pdu_request(trans_p)) != NULL) { while (pdu_p->next != NULL) pdu_p = pdu_p->next; } return pdu_p; } /* * isns_new_pdu - allocates a new PDU and assigns funtion ID and flags */ struct isns_pdu_s * isns_new_pdu(struct isns_config_s *cfg_p, uint16_t trans_id, uint16_t func_id, uint16_t flags) { struct isns_buffer_s *buf_p; /* * Allocate a buffer at least large enough for our isns_pdu_s struct * and the embedded isns_buffer_s struct for the PDU payload data and 4 * bytes of actual payload data. */ buf_p = isns_new_buffer(/* CONSTCOND */(int)MAX(ISNS_BUF_SIZE, sizeof(struct isns_pdu_s) + sizeof(struct isns_buffer_s) + 4)); if (buf_p == NULL) { DBG("isns_new_pdu: error on isns_new_buffer()\n"); return NULL; } return isns_init_pdu(buf_p, cfg_p, trans_id, func_id, flags); } /* * isns_free_pdu - frees a PDU and all associated buffers */ void isns_free_pdu(struct isns_pdu_s *pdu_p) { struct isns_buffer_s *buf_p, *free_buf_p; DBG("isns_free_pdu: %p\n", pdu_p); if (pdu_p != NULL) { /* Free all payload buffers. */ buf_p = pdu_p->payload_p; while (buf_p != NULL) { free_buf_p = buf_p; buf_p = buf_p->next; isns_free_buffer(free_buf_p); } /* * Get a pointer to the ISNS buffer in which this PDU is * contained, and then free it. */ buf_p = ((struct isns_buffer_s *)(void *)(pdu_p))-1 ; isns_free_buffer(buf_p); } } /* * isns_send_pdu - initiates the send PDU task */ int isns_send_pdu(ISNS_TRANS trans, struct isns_pdu_s *pdu_p, const struct timespec *timeout_p) { struct isns_trans_s *trans_p; struct isns_task_s* task_p; int rval; if (trans == ISNS_INVALID_TRANS) { DBG("isns_send_pdu: error - trans=%p\n", trans); return EINVAL; } if (pdu_p == NULL) { DBG("isns_send_pdu: error - pdu_p=%p\n", pdu_p); return EINVAL; } trans_p = (struct isns_trans_s *)trans; /* Build SEND_PDU task, insert on queue and issue command. */ task_p = isns_new_task(pdu_p->cfg_p, ISNS_TASK_SEND_PDU, (timeout_p != NULL)); task_p->var.send_pdu.trans_p = trans_p; task_p->var.send_pdu.pdu_p = pdu_p; isns_taskq_insert_tail(pdu_p->cfg_p, task_p); isns_issue_cmd(pdu_p->cfg_p, ISNS_CMD_PROCESS_TASKQ); if (timeout_p == NULL) rval = 0; else { rval = isns_wait_task(task_p, timeout_p); if (rval == ETIMEDOUT) { DBG("isns_send_pdu: " "timeout on isns_wait_task() trans_id=%d\n", trans_p->id); isns_issue_cmd_with_data(task_p->cfg_p, ISNS_CMD_ABORT_TRANS, (void *)&trans_p->id, (int)sizeof(trans_p->id)); } } return rval; } /* * isns_init_pdu - initialize ISNS buffer to be a PDU */ static struct isns_pdu_s * isns_init_pdu(struct isns_buffer_s *buf_p, struct isns_config_s *cfg_p, uint16_t trans_id, uint16_t func_id, uint16_t flags) { struct isns_pdu_s *pdu_p; /* The config and buffer pointers must be valid here. */ assert(cfg_p != NULL); assert(buf_p != NULL); /* The PDU starts at offset 0 for the ISNS buffer data. */ pdu_p = isns_buffer_data(buf_p, 0); buf_p->cur_len = sizeof(struct isns_pdu_s); /* Assign PDU members. */ pdu_p->cfg_p = cfg_p; pdu_p->hdr.isnsp_version = ISNSP_VERSION; pdu_p->hdr.func_id = func_id; pdu_p->hdr.payload_len = 0; pdu_p->hdr.flags = flags; pdu_p->hdr.trans_id = trans_id; pdu_p->hdr.seq_id = 0; pdu_p->byteorder_host = 1; pdu_p->next = NULL; /* * The PDU payload buffer starts after the PDU struct portion in the * ISNS buffer passed in to this function. So, assign the payload_p * pointer accordingly, and then init the buffer with the proper length * and ISNS_BUF_STATIC type. */ pdu_p->payload_p = (struct isns_buffer_s *) isns_buffer_data(buf_p, buf_p->cur_len); ISNS_INIT_BUFFER(pdu_p->payload_p, (unsigned)(buf_p->alloc_len - sizeof(struct isns_pdu_s) - sizeof(struct isns_buffer_s)), ISNS_BUF_STATIC); DBG("isns_init_pdu: %p\n", pdu_p); return pdu_p; } /* * isns_add_pdu_payload_data - add data to PDU payload */ static int isns_add_pdu_payload_data(struct isns_trans_s *trans_p, const void *data_p, int len) { struct isns_pdu_s *pdu_p, *new_pdu_p; struct isns_buffer_s *buf_p, *new_buf_p; const uint8_t *src_p; uint8_t *dst_p; int pad_bytes; /* Get the request PDU for this transaction. */ if ((pdu_p = isns_get_pdu_request_tail(trans_p)) == NULL) { DBG("isns_add_pdu_payload_data: no request PDU\n"); return EINVAL; } /* Get the active buffer for this PDU (where data should be copied). */ buf_p = isns_get_pdu_active_buffer(pdu_p); /* Set up source and destination pointers. Calculate pad bytes. */ src_p = data_p; dst_p = isns_buffer_data(buf_p, buf_p->cur_len); pad_bytes = ISNS_PAD4_BYTES(len); /* * Move data from source to PDU buffer(s), allocated new pdus and * buffers as necessary to accommodate the data. */ while (len--) { /* If at max for one PDU payload, add a new PDU. */ if (pdu_p->hdr.payload_len == ISNS_MAX_PDU_PAYLOAD) { new_pdu_p = isns_new_pdu(trans_p->cfg_p, trans_p->id, trans_p->func_id, pdu_p->hdr.flags); if (new_pdu_p == NULL) return ENOMEM; isns_add_pdu_request(trans_p, new_pdu_p); pdu_p = new_pdu_p; buf_p = pdu_p->payload_p; dst_p = isns_buffer_data(buf_p, 0); } /* If at end of current buffer, add a new buffer. */ if (buf_p->cur_len == buf_p->alloc_len) { if (buf_p->next != NULL) buf_p = buf_p->next; else { new_buf_p = isns_new_buffer(0); if (new_buf_p == NULL) return ENOMEM; buf_p->next = new_buf_p; buf_p = new_buf_p; } dst_p = isns_buffer_data(buf_p, 0); } pdu_p->hdr.payload_len++; buf_p->cur_len++; *dst_p++ = *src_p++; } /* * Since the buffer alloc length is always a multiple of 4, we are * guaranteed to have enough room for the pad bytes. */ if (pad_bytes > 0) { pdu_p->hdr.payload_len += pad_bytes; buf_p->cur_len += pad_bytes; while (pad_bytes--) *dst_p++ = 0; } return 0; } /* * isns_get_tlv_info_advance - advances pdu/buffer pointers if at end of * current buffer. */ static void isns_get_tlv_info_advance(struct isns_get_tlv_info_s *info_p) { if ((info_p->buf_p != NULL) && (info_p->buf_ofs == (int)info_p->buf_p->cur_len)) { info_p->buf_p = info_p->buf_p->next; info_p->buf_ofs = 0; } if ((info_p->buf_p == NULL) && (info_p->pdu_p->next != NULL)) { info_p->pdu_p = info_p->pdu_p->next; info_p->buf_p = isns_get_pdu_head_buffer(info_p->pdu_p); info_p->buf_ofs = 0; } } /* * isns_get_tlv_uint32 - retrieve host-ordered uint32_t from PDU buffer at * starting offset and adjusts isns_get_tlv_info * pointers accordingly. */ static int isns_get_tlv_uint32(struct isns_get_tlv_info_s *info_p, uint32_t *uint32_p) { /* Advance to next buffer/pdu (if necessary). */ isns_get_tlv_info_advance(info_p); if ((info_p->buf_p == NULL) || ((info_p->buf_ofs + 4) > (int)info_p->buf_p->cur_len)) { DBG("isns_get_tlv_uint32: end of buffer reached\n"); return EFAULT; } *uint32_p = ntohl(*(uint32_t *)isns_buffer_data(info_p->buf_p, info_p->buf_ofs)); info_p->buf_ofs += 4; return 0; } /* * isns_get_tlv_data - retrieves data from PDU buffer at starting offset * for TLV data contained in specified isns_get_tlv_info. */ static int isns_get_tlv_data(struct isns_get_tlv_info_s *info_p, int data_len, void **data_pp) { struct isns_buffer_s *extra_buf_p; struct isns_get_tlv_info_s gti; uint8_t *data_p, *extra_data_p; int bytes_remaining, cbytes; /* First, NULL return data pointer. */ *data_pp = NULL; /* Advance to next buffer/pdu (if necessary). */ isns_get_tlv_info_advance(info_p); /* Make sure we have a current get tlv info buffer. */ if (info_p->buf_p == NULL) { DBG("isns_get_tlv_data: no next buffer\n"); return EFAULT; } /* Get pointer into buffer where desired TLV resides. */ data_p = isns_buffer_data(info_p->buf_p, info_p->buf_ofs); /* TLV data completely resides in current buffer. */ if ((info_p->buf_ofs + data_len) <= (int)info_p->buf_p->cur_len) { info_p->buf_ofs += data_len; *data_pp = data_p; return 0; } /* * TLV data extends into next buffer so an "extra" buffer is allocated * that is large enough to hold the entire data value. The extra buffer * is added to the transaction's extra buffer list so we can free it * when the transaction is freed. */ if ((extra_buf_p = isns_new_buffer(data_len)) == NULL) { DBG("isns_get_tlv_data: error on isns_new_buffer()\n"); return ENOMEM; } if (info_p->extra_buf_list == NULL) info_p->extra_buf_list = extra_buf_p; else { extra_buf_p->next = info_p->extra_buf_list; info_p->extra_buf_list = extra_buf_p; } /* Setup to copy data bytes out to extra buffer. */ gti = *info_p; extra_data_p = isns_buffer_data(extra_buf_p, 0); bytes_remaining = data_len; while (bytes_remaining > 0) { /* * Advance to next buffer/pdu (if necessary), using local copy * of the get_tlv_info structure. */ isns_get_tlv_info_advance(>i); if (gti.buf_p == NULL) { DBG("isns_get_tlv_data: no next buffer\n"); return EFAULT; } data_p = isns_buffer_data(gti.buf_p, gti.buf_ofs); cbytes = MIN(bytes_remaining, ((int)gti.buf_p->cur_len - gti.buf_ofs)); bytes_remaining -= cbytes; gti.buf_ofs += cbytes; while (cbytes--) *extra_data_p++ = *data_p++; } /* Update isns_get_tlv_info with our local copy. */ *info_p = gti; /* Assign return data pointer. */ *data_pp = isns_buffer_data(extra_buf_p, 0); return 0; } /* * isns_get_pdu_response_status - returns status in PDU response * * Returns: 0 - success * EPERM - operation not permitted, trans not complete * EINVAL - invalid trans PDU response/payload */ int isns_get_pdu_response_status(struct isns_trans_s *trans_p, uint32_t *status_p) { struct isns_pdu_s *pdu_p; if ((isns_get_trans_flags(trans_p) & ISNS_TRANSF_COMPLETE) == 0) return EPERM; pdu_p = isns_get_pdu_response(trans_p); if ((pdu_p == NULL) || (pdu_p->payload_p == NULL) || (pdu_p->payload_p->cur_len < 4)) return EINVAL; *status_p = htonl(*(uint32_t *)isns_buffer_data(pdu_p->payload_p, 0)); return 0; } /* * isns_add_pdu_list - adds pdu to specified pdu list */ static void isns_add_pdu_list(struct isns_pdu_s **list_pp, struct isns_pdu_s *pdu_p) { struct isns_pdu_s *p, *p_prev; if (*list_pp == NULL) { *list_pp = pdu_p; pdu_p->next = NULL; return; } p = *list_pp; while (p != NULL) { if (pdu_p->hdr.seq_id < p->hdr.seq_id) { if (p == *list_pp) { *list_pp = pdu_p; pdu_p->next = p; } else { p_prev = *list_pp; while (p_prev->next != p) p_prev = p_prev->next; p_prev->next = pdu_p; pdu_p->next = p; } return; } p = p->next; } /* pdu_p->hdr.seq_id > hdr.seq_id of all list elements */ p = *list_pp; while (p->next != NULL) p = p->next; p->next = pdu_p; pdu_p->next = NULL; } /* * isns_get_pdu_head_buffer - returns PDU payload head buffer */ static struct isns_buffer_s * isns_get_pdu_head_buffer(struct isns_pdu_s *pdu_p) { return pdu_p->payload_p; } #if 0 /* * isns_get_pdu_tail_buffer - returns PDU payload tail buffer */ static struct isns_buffer_s * isns_get_pdu_tail_buffer(struct isns_pdu_s *pdu_p) { struct isns_buffer_s *buf_p; buf_p = pdu_p->payload_p; if (buf_p != NULL) while (buf_p->next != NULL) buf_p = buf_p->next; return buf_p; } #endif /* * isns_get_pdu_active_buffer - returns PDU payload "active buffer where the * next TLV/data should be written */ static struct isns_buffer_s * isns_get_pdu_active_buffer(struct isns_pdu_s *pdu_p) { struct isns_buffer_s *buf_p; buf_p = pdu_p->payload_p; while ((buf_p->next != NULL) && (buf_p->cur_len == buf_p->alloc_len)) { buf_p = buf_p->next; } return buf_p; } /* * isns_get_next_trans_id - returns next ISNS transaction ID to use */ static uint32_t isns_get_next_trans_id(void) { static int trans_id = 1; return trans_id++; } #ifdef ISNS_DEBUG /* * isns_dump_pdu - dumps PDU contents */ void isns_dump_pdu(struct isns_pdu_s *pdu_p) { int n, pos; struct isns_buffer_s *buf_p; uint8_t *p; char text[17]; if (pdu_p == NULL) { DBG("isns_dump_pdu: pdu_p is NULL\n"); return; } DBG("pdu header:\n"); if (pdu_p->byteorder_host) { DBG("ver=0x%04X, func=%d(%s), len=%d, flags=0x%04X, trans=%d, " "seq=%d\n", pdu_p->hdr.isnsp_version, pdu_p->hdr.func_id & ~0x8000, (pdu_p->hdr.func_id & 0x8000 ? "rsp" : "req"), pdu_p->hdr.payload_len, pdu_p->hdr.flags, pdu_p->hdr.trans_id, pdu_p->hdr.seq_id); } else { DBG("ver=0x%04X, func=%d(%s), len=%d, flags=0x%04X, trans=%d, " "seq=%d\n", isns_ntohs(pdu_p->hdr.isnsp_version), isns_ntohs(pdu_p->hdr.func_id) & ~0x8000, (pdu_p->hdr.func_id & 0x0080 ? "rsp" : "req"), isns_ntohs(pdu_p->hdr.payload_len), isns_ntohs(pdu_p->hdr.flags), isns_ntohs(pdu_p->hdr.trans_id), isns_ntohs(pdu_p->hdr.seq_id)); } DBG("pdu buffers:\n"); buf_p = pdu_p->payload_p; while (buf_p != NULL) { DBG("[%p]: alloc_len=%d, cur_len=%d\n", buf_p, buf_p->alloc_len, buf_p->cur_len); buf_p = buf_p->next; } DBG("pdu payload:\n"); buf_p = pdu_p->payload_p; if (buf_p == NULL) { DBG("\n"); return; } pos = 0; memset(text, 0, 17); while (buf_p != NULL) { p = isns_buffer_data(buf_p, 0); for (n = 0; n < buf_p->cur_len; n++) { DBG("%02X ", *p); text[pos] = (isprint(*p) ? *p : '.'); pos++; p++; if ((pos % 16) == 0) { DBG(" %s\n", text); memset(text, 0, 17); pos = 0; } } buf_p = buf_p->next; } if ((pos % 16) != 0) DBG("%*c %s\n", (16 - (pos % 16)) * 3, ' ', text); } #endif /* ISNS_DEBUG */