diff options
Diffstat (limited to 'lib/iov.c')
-rw-r--r-- | lib/iov.c | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/lib/iov.c b/lib/iov.c new file mode 100644 index 0000000..d148ac9 --- /dev/null +++ b/lib/iov.c @@ -0,0 +1,200 @@ +/* + * 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 <https://www.gnu.org/licenses/> + * + */ + +#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; +} |