/* * Copyright (C) 2019 Red Hat, Inc. * * Author: Daiki Ueno * * This file is part of GnuTLS. * * The GnuTLS is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see * */ #include "gnutls_int.h" #include "iov.h" /** * _gnutls_iov_iter_init: * @iter: the iterator * @iov: the data buffers * @iov_count: the number of data buffers * @block_size: block size to iterate * * Initialize the iterator. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise * an error code is returned */ int _gnutls_iov_iter_init(struct iov_iter_st *iter, const giovec_t *iov, size_t iov_count, size_t block_size) { if (unlikely(block_size > MAX_CIPHER_BLOCK_SIZE)) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); iter->iov = iov; iter->iov_count = iov_count; iter->iov_index = 0; iter->iov_offset = 0; iter->block_size = block_size; iter->block_offset = 0; return 0; } /** * _gnutls_iov_iter_next: * @iter: the iterator * @data: the return location of extracted data * * Retrieve block(s) pointed by @iter and advance it to the next * position. It returns the number of bytes in @data. At the end of * iteration, 0 is returned. * * If the data stored in @iter is not multiple of the block size, the * remaining data is stored in the "block" field of @iter with the * size stored in the "block_offset" field. * * Returns: On success, a value greater than or equal to zero is * returned, otherwise a negative error code is returned */ ssize_t _gnutls_iov_iter_next(struct iov_iter_st *iter, uint8_t **data) { while (iter->iov_index < iter->iov_count) { const giovec_t *iov = &iter->iov[iter->iov_index]; uint8_t *p = iov->iov_base; size_t len = iov->iov_len; size_t block_left; if (!p) { // skip NULL iov entries, else we run into issues below iter->iov_index++; continue; } if (unlikely(len < iter->iov_offset)) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); len -= iter->iov_offset; p += iter->iov_offset; /* We have at least one full block, return a whole set * of full blocks immediately. */ if (iter->block_offset == 0 && len >= iter->block_size) { if ((len % iter->block_size) == 0) { iter->iov_index++; iter->iov_offset = 0; } else { len -= (len % iter->block_size); iter->iov_offset += len; } /* Return the blocks. */ *data = p; return len; } /* We can complete one full block to return. */ block_left = iter->block_size - iter->block_offset; if (len >= block_left) { memcpy(iter->block + iter->block_offset, p, block_left); if (len == block_left) { iter->iov_index++; iter->iov_offset = 0; } else iter->iov_offset += block_left; iter->block_offset = 0; /* Return the filled block. */ *data = iter->block; return iter->block_size; } /* Not enough data for a full block, store in temp * memory and continue. */ memcpy(iter->block + iter->block_offset, p, len); iter->block_offset += len; iter->iov_index++; iter->iov_offset = 0; } if (iter->block_offset > 0) { size_t len = iter->block_offset; /* Return the incomplete block. */ *data = iter->block; iter->block_offset = 0; return len; } return 0; } /** * _gnutls_iov_iter_sync: * @iter: the iterator * @data: data returned by _gnutls_iov_iter_next * @data_size: size of @data * * Flush the content of temp buffer (if any) to the data buffer. */ int _gnutls_iov_iter_sync(struct iov_iter_st *iter, const uint8_t *data, size_t data_size) { size_t iov_index; size_t iov_offset; /* We didn't return the cached block. */ if (data != iter->block) return 0; iov_index = iter->iov_index; iov_offset = iter->iov_offset; /* When syncing a cache block we walk backwards because we only have a * pointer to were the block ends in the iovec, walking backwards is * fine as we are always writing a full block, so the whole content * is written in the right places: * iovec: |--0--|---1---|--2--|-3-| * block: |-----------------------| * 1st write |---| * 2nd write |----- * 3rd write |------- * last write |----- */ while (data_size > 0) { const giovec_t *iov; uint8_t *p; size_t to_write; while (iov_offset == 0) { if (unlikely(iov_index == 0)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); iov_index--; iov_offset = iter->iov[iov_index].iov_len; } iov = &iter->iov[iov_index]; p = iov->iov_base; to_write = MIN(data_size, iov_offset); iov_offset -= to_write; data_size -= to_write; memcpy(p + iov_offset, &iter->block[data_size], to_write); } return 0; }