summaryrefslogtreecommitdiffstats
path: root/lib/iov.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/iov.c')
-rw-r--r--lib/iov.c200
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;
+}