diff --git a/include/sqsh_file_private.h b/include/sqsh_file_private.h index 030e3ac78..f963f910b 100644 --- a/include/sqsh_file_private.h +++ b/include/sqsh_file_private.h @@ -39,6 +39,7 @@ #include "sqsh_extract_private.h" #include "sqsh_mapper_private.h" #include "sqsh_metablock_private.h" +#include "sqsh_reader_private.h" #ifdef __cplusplus extern "C" { diff --git a/include/sqsh_mapper_private.h b/include/sqsh_mapper_private.h index c44df3acc..04fd99155 100644 --- a/include/sqsh_mapper_private.h +++ b/include/sqsh_mapper_private.h @@ -37,6 +37,7 @@ #include "sqsh_mapper.h" #include "sqsh_primitive_private.h" +#include "sqsh_reader_private.h" #include "sqsh_thread_private.h" #include diff --git a/include/sqsh_primitive_private.h b/include/sqsh_primitive_private.h index ebd0fc763..a592af21c 100644 --- a/include/sqsh_primitive_private.h +++ b/include/sqsh_primitive_private.h @@ -137,6 +137,19 @@ SQSH_NO_EXPORT SQSH_NO_UNUSED int sqsh__buffer_append( SQSH_NO_EXPORT SQSH_NO_UNUSED int sqsh__buffer_move(struct SqshBuffer *buffer, struct SqshBuffer *source); +/** + * @internal + * @memberof SqshBuffer + * @brief resets the buffer size to 0. + * + * This does not free the memory allocated by the buffer so that + * the buffer can be reused. + * + * @param[in,out] buffer The SqshBuffer to drain. + */ + +void sqsh__buffer_drain(struct SqshBuffer *buffer); + /** * @internal * @memberof SqshBuffer @@ -509,110 +522,6 @@ sqsh__lru_touch(struct SqshLru *lru, sqsh_index_t id); */ SQSH_NO_EXPORT int sqsh__lru_cleanup(struct SqshLru *lru); -/*************************************** - * primitive/reader.c - */ - -/** - * @internal - * @brief A buffer that is used to read data from a SqshReader. - */ -struct SqshIteratorImpl { - /** - * @privatesection - */ - int (*next)(void *iterator, size_t desired_size); - int (*skip)(void *iterator, size_t amount, size_t desired_size); - size_t (*block_size)(const void *iterator); - const uint8_t *(*data)(const void *iterator); - size_t (*size)(const void *iterator); -}; - -/** - * @internal - * @brief A buffer that is used to read data from a SqshReader. - */ -struct SqshReader { - /** - * @privatesection - */ - const struct SqshIteratorImpl *impl; - void *iterator; - - sqsh_index_t iterator_offset; - sqsh_index_t buffer_offset; - sqsh_index_t data_offset; - size_t size; - size_t data_size; - struct SqshBuffer buffer; - const uint8_t *data; -}; - -/** - * @internal - * @memberof SqshReader - * @brief Initializes a reader. - * - * @param[out] reader Pointer to the metablock reader to be initialized. - * @param[in] impl Implementation of the iterator. - * @param[in] iterator Iterator to use for the reader. - * - * @return 0 on success, less than zero on error. - */ -SQSH_NO_EXPORT SQSH_NO_UNUSED int sqsh__reader_init( - struct SqshReader *reader, const struct SqshIteratorImpl *impl, - void *iterator); - -/** - * @internal - * @memberof SqshReader - * @brief Advances the reader by the given offset and size. - * - * @param[in,out] reader Pointer to the metablock reader to be advanced. - * @param[in] offset Offset to advance the reader by. - * @param[in] size Size of the block to advance the reader by. - * - * @return 0 on success, less than zero on error. - */ -SQSH_NO_EXPORT SQSH_NO_UNUSED int sqsh__reader_advance( - struct SqshReader *reader, sqsh_index_t offset, size_t size); - -/** - * @internal - * @memberof SqshReader - * @brief Returns a pointer to the data at the current position of the metablock - * reader. - * - * @param[in] reader Pointer to the metablock reader. - * - * @return Pointer to the data at the current position of the metablock reader. - */ -SQSH_NO_EXPORT const uint8_t * -sqsh__reader_data(const struct SqshReader *reader); - -/** - * @internal - * @memberof SqshReader - * @brief Returns the size of the data at the current position of the metablock - * reader. - * - * @param[in] reader Pointer to the metablock reader. - * - * @return Size of the data at the current position of the metablock reader. - */ -SQSH_NO_EXPORT size_t sqsh__reader_size(const struct SqshReader *reader); - -/** - * @internal - * @memberof SqshReader - * @brief Cleans up and frees the resources used by the metablock reader. - * - * @param[in,out] reader Pointer to the metablock reader to be cleaned up. - * - * @return 0 on success, less than zero on error. - */ -SQSH_NO_EXPORT int sqsh__reader_cleanup(struct SqshReader *reader); - #ifdef __cplusplus } #endif diff --git a/include/sqsh_reader_private.h b/include/sqsh_reader_private.h new file mode 100644 index 000000000..fa874446c --- /dev/null +++ b/include/sqsh_reader_private.h @@ -0,0 +1,193 @@ +/****************************************************************************** + * * + * Copyright (c) 2023, Enno Boland * + * * + * Redistribution and use in source and binary forms, with or without * + * modification, are permitted provided that the following conditions are * + * met: * + * * + * * Redistributions of source code must retain the above copyright notice, * + * this list of conditions and the following disclaimer. * + * * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. * + * * + ******************************************************************************/ + +/** + * @author Enno Boland (mail@eboland.de) + * @file sqsh_reader_private.h + */ + +#ifndef SQSH_READER_PRIVATE_H +#define SQSH_READER_PRIVATE_H + +#include "sqsh_common.h" +#include "sqsh_primitive_private.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*************************************** + * reader/reader.c + */ + +struct SqshReaderIteratorImpl { + int (*next)(void *iterator, size_t desired_size); + const uint8_t *(*data)(const void *iterator); + size_t (*size)(const void *iterator); +}; + +/** + * @brief An iterator over extended attributes. + */ +struct SqshReader { + /** + * @privatesection + */ + + /** + * @brief The for this reader. + */ + void *iterator; + + /** + * @brief interface to the iterator. + */ + const struct SqshReaderIteratorImpl *iterator_impl; + + /** + * @brief The offset of the iterator. + * + * A value of "0" indicates, that the reader currently directly maps data + * from the iterator. That means, that `data` points into memory managed by + * the iterator. + * + * A non-zero value indicates, that the reader has copied data from the + * iterator into the buffer. That means, that `data` points into memory + * managed by the buffer. The actual value of iterator_offset is the offset + * between the buffer and the iterator. + * + * example: + * + * ``` + * 0123456789 + * buffer: ########## + * iterator: #### + * ``` + * + * in this case, the iterator_offset is 6. + */ + sqsh_index_t iterator_offset; + + /** + * @brief The offset of mapped data. + * + * This value is set to zero if the reader uses buffered data. + * + * Otherwise is indicates the offset of the data in the iterator. + */ + sqsh_index_t offset; + + /** + * @brief The buffer to store data in if they cannot be directly mapped. + */ + struct SqshBuffer buffer; + + /** + * @brief The data that is presented to the user. + * + * This pointer has the offset already applied if in mapped mode. + * + * Otherwise it points to the beginning of the buffer. + */ + const uint8_t *data; + /** + * @brief The size of the data that is presented to the user. + */ + size_t size; +}; + +/** + * @internal + * @memberof SqshReader + * @brief Initializes a reader. + * + * @param[out] reader Pointer to the metablock reader to be initialized. + * @param[in] iterator_impl Implementation of the iterator. + * @param[in] iterator Iterator to use for the reader. + * + * @return 0 on success, less than zero on error. + */ +SQSH_NO_EXPORT SQSH_NO_UNUSED int sqsh__reader_init( + struct SqshReader *reader, + const struct SqshReaderIteratorImpl *iterator_impl, void *iterator); + +/** + * @internal + * @memberof SqshReader + * @brief Advances the reader by the given offset and size. + * + * @param[in,out] reader Pointer to the metablock reader to be advanced. + * @param[in] offset Offset to advance the reader by. + * @param[in] size Size of the block to advance the reader by. + * + * @return 0 on success, less than zero on error. + */ +SQSH_NO_EXPORT SQSH_NO_UNUSED int sqsh__reader_advance( + struct SqshReader *reader, sqsh_index_t offset, size_t size); + +/** + * @internal + * @memberof SqshReader + * @brief Returns a pointer to the data at the current position of the metablock + * reader. + * + * @param[in] reader Pointer to the metablock reader. + * + * @return Pointer to the data at the current position of the metablock reader. + */ +SQSH_NO_EXPORT const uint8_t * +sqsh__reader_data(const struct SqshReader *reader); + +/** + * @internal + * @memberof SqshReader + * @brief Returns the size of the data at the current position of the metablock + * reader. + * + * @param[in] reader Pointer to the metablock reader. + * + * @return Size of the data at the current position of the metablock reader. + */ +SQSH_NO_EXPORT size_t sqsh__reader_size(const struct SqshReader *reader); + +/** + * @internal + * @memberof SqshReader + * @brief Cleans up and frees the resources used by the metablock reader. + * + * @param[in,out] reader Pointer to the metablock reader to be cleaned up. + * + * @return 0 on success, less than zero on error. + */ +SQSH_NO_EXPORT int sqsh__reader_cleanup(struct SqshReader *reader); + +#ifdef __cplusplus +} +#endif +#endif /* SQSH_READER_PRIVATE_H */ diff --git a/lib/file/file_reader.c b/lib/file/file_reader.c index ff13e9934..8d5ce86b2 100644 --- a/lib/file/file_reader.c +++ b/lib/file/file_reader.c @@ -35,7 +35,7 @@ #include "../../include/sqsh_archive_private.h" #include "../../include/sqsh_error.h" -#include "../../include/sqsh_primitive_private.h" +#include "../../include/sqsh_reader_private.h" #include "../utils/utils.h" @@ -43,14 +43,6 @@ static int file_iterator_next(void *iterator, size_t desired_size) { return sqsh_file_iterator_next(iterator, desired_size); } -static int -file_iterator_skip(void *iterator, size_t amount, size_t desired_size) { - return sqsh_file_iterator_skip(iterator, amount, desired_size); -} -static size_t -file_iterator_block_size(const void *iterator) { - return sqsh_file_iterator_block_size(iterator); -} static const uint8_t * file_iterator_data(const void *iterator) { return sqsh_file_iterator_data(iterator); @@ -60,10 +52,8 @@ file_iterator_size(const void *iterator) { return sqsh_file_iterator_size(iterator); } -static const struct SqshIteratorImpl file_reader_impl = { +static const struct SqshReaderIteratorImpl file_reader_impl = { .next = file_iterator_next, - .skip = file_iterator_skip, - .block_size = file_iterator_block_size, .data = file_iterator_data, .size = file_iterator_size, }; diff --git a/lib/mapper/map_reader.c b/lib/mapper/map_reader.c index 443bc286b..26c52036f 100644 --- a/lib/mapper/map_reader.c +++ b/lib/mapper/map_reader.c @@ -41,15 +41,6 @@ map_iterator_next(void *iterator, size_t desired_size) { (void)desired_size; return sqsh__map_iterator_next(iterator); } -static int -map_iterator_skip(void *iterator, size_t amount, size_t desired_size) { - (void)desired_size; - return sqsh__map_iterator_skip(iterator, amount); -} -static size_t -map_iterator_block_size(const void *iterator) { - return sqsh__map_iterator_block_size(iterator); -} static const uint8_t * map_iterator_data(const void *iterator) { return sqsh__map_iterator_data(iterator); @@ -59,10 +50,8 @@ map_iterator_size(const void *iterator) { return sqsh__map_iterator_size(iterator); } -static const struct SqshIteratorImpl map_reader_impl = { +static const struct SqshReaderIteratorImpl map_reader_impl = { .next = map_iterator_next, - .skip = map_iterator_skip, - .block_size = map_iterator_block_size, .data = map_iterator_data, .size = map_iterator_size, }; diff --git a/lib/meson.build b/lib/meson.build index 7f564518d..c515b64a8 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -1,4 +1,5 @@ libsqsh_sources = files( + 'reader/reader.c', 'archive/archive.c', 'archive/compression_options.c', 'archive/inode_map.c', @@ -40,7 +41,6 @@ libsqsh_sources = files( 'primitive/lru.c', 'primitive/rc_hash_map.c', 'primitive/rc_map.c', - 'primitive/reader.c', 'table/export_table.c', 'table/fragment_table.c', 'table/id_table.c', diff --git a/lib/metablock/metablock_reader.c b/lib/metablock/metablock_reader.c index 7e0e4a964..8f31b5428 100644 --- a/lib/metablock/metablock_reader.c +++ b/lib/metablock/metablock_reader.c @@ -45,16 +45,6 @@ metablock_iterator_next(void *iterator, size_t desired_size) { (void)desired_size; return sqsh__metablock_iterator_next(iterator); } -static int -metablock_iterator_skip(void *iterator, size_t amount, size_t desired_size) { - (void)desired_size; - return sqsh__metablock_iterator_skip(iterator, amount); -} -static size_t -metablock_iterator_block_size(const void *iterator) { - (void)iterator; - return SQSH_METABLOCK_BLOCK_SIZE; -} static const uint8_t * metablock_iterator_data(const void *iterator) { return sqsh__metablock_iterator_data(iterator); @@ -64,10 +54,8 @@ metablock_iterator_size(const void *iterator) { return sqsh__metablock_iterator_size(iterator); } -static const struct SqshIteratorImpl metablock_reader_impl = { +static const struct SqshReaderIteratorImpl metablock_reader_impl = { .next = metablock_iterator_next, - .skip = metablock_iterator_skip, - .block_size = metablock_iterator_block_size, .data = metablock_iterator_data, .size = metablock_iterator_size, }; diff --git a/lib/primitive/buffer.c b/lib/primitive/buffer.c index 3ddeb5407..c7cc11c03 100644 --- a/lib/primitive/buffer.c +++ b/lib/primitive/buffer.c @@ -114,6 +114,11 @@ sqsh__buffer_move(struct SqshBuffer *buffer, struct SqshBuffer *source) { return 0; } +void +sqsh__buffer_drain(struct SqshBuffer *buffer) { + buffer->size = 0; +} + const uint8_t * sqsh__buffer_data(const struct SqshBuffer *buffer) { return buffer->data; diff --git a/lib/primitive/reader.c b/lib/primitive/reader.c deleted file mode 100644 index 499e8f46b..000000000 --- a/lib/primitive/reader.c +++ /dev/null @@ -1,331 +0,0 @@ -/****************************************************************************** - * * - * Copyright (c) 2023, Enno Boland * - * * - * Redistribution and use in source and binary forms, with or without * - * modification, are permitted provided that the following conditions are * - * met: * - * * - * * Redistributions of source code must retain the above copyright notice, * - * this list of conditions and the following disclaimer. * - * * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. * - * * - ******************************************************************************/ - -/** - * @author Enno Boland (mail@eboland.de) - * @file reader.c - */ - -#include "../../include/sqsh_primitive_private.h" - -#include "../../include/sqsh_archive_private.h" -#include "../../include/sqsh_error.h" - -#include "../utils/utils.h" - -/** - * # Algoritm - * - * The reader is a simple wrapper around an arbitrary iterator. In contrast - * to an iterator, that just presents chunks of data, the reader allows to - * read a specific range of data. The reader will try to map the requested - * range to the current data. If the requested range is not fully contained - * in the current data, the reader copy the data into a buffer and presents - * the data from the buffer. - * - * 1. Forward the iterator until the start of the requested range is reached. - * - * 2. Test if the requested range is fully contained in the current iterator - * block. If so, return the data directly. - * - * 3. If the requested range is not fully contained in the current iterator - * block, copy the data into a buffer until the end of the requested range - * is reached. - * - * - * ## Scenarios: - * - * ### Legend - * - * ``` - * ##### = directly mapped data - * - * ===== = data in buffer - * ``` - * - * ### Scenario 1: direct to direct - * - * Before: - * ``` - * Start End - * v v - * ########################## - * ``` - * After: - * ``` - * Start End - * v v - * ########################## - * ``` - * - * ### Scenario 2: direct to buffer - * - * Before: - * ``` - * Start End - * v v - * ########################## - * ``` - * After: - * ``` - * Start End - * v v - * ======= - * ########################## - * ``` - * ### Scenario 2: buffer to direct - * - * Before: - * ``` - * Start End - * v v - * ======= - * ########################## - * ``` - * After: - * ``` - * Start End - * v v - * ########################## - * ``` - * - * ### Scenario 3: buffer over multiple blocks - * Before: - * ``` - * Start End - * v v - * ########################## - * ``` - * After: - * ``` - * Start End - * v v - * ================================ - * ########################## - * ``` - */ - -int -sqsh__reader_init( - struct SqshReader *reader, const struct SqshIteratorImpl *impl, - void *iterator) { - int rv; - memset(reader, 0, sizeof(*reader)); - rv = sqsh__buffer_init(&reader->buffer); - if (rv < 0) { - return rv; - } - reader->impl = impl; - reader->iterator = iterator; - reader->iterator_offset = 0; - reader->data_offset = 0; - reader->data_size = 0; - reader->buffer_offset = 0; - reader->size = 0; - reader->data = NULL; - - return 0; -} - -static int -iterator_forward_to( - struct SqshReader *reader, sqsh_index_t offset, size_t desired_size) { - int rv; - const struct SqshIteratorImpl *impl = reader->impl; - void *iterator = reader->iterator; - const size_t block_size = impl->block_size(iterator); - const size_t iterator_size = impl->size(iterator); - const size_t current_end_offset = reader->iterator_offset + iterator_size; - if (offset < current_end_offset) { - return 0; - } - size_t amount = (offset - current_end_offset) / block_size; - reader->iterator_offset = current_end_offset + amount * block_size; - rv = impl->skip(iterator, amount + 1, desired_size); - if (rv < 0) { - return rv; - } else if (rv == 0) { - return -SQSH_ERROR_OUT_OF_BOUNDS; - } - - return 0; -} - -static int -map_buffered( - struct SqshReader *reader, sqsh_index_t new_offset, - size_t new_end_offset) { - int rv; - struct SqshBuffer new_buffer = {0}; - const sqsh_index_t buffer_offset = reader->buffer_offset; - const sqsh_index_t iterator_offset = reader->iterator_offset; - const size_t target_size = new_end_offset - new_offset; - const struct SqshIteratorImpl *impl = reader->impl; - void *iterator = reader->iterator; - - rv = sqsh__buffer_init(&new_buffer); - if (rv < 0) { - goto out; - } - if (new_offset < buffer_offset) { - /* Should never happen */ - abort(); - } - - /* - * 1. If the requested range is covered by the old buffer, copy - * the data starting from the old buffer to the new buffer. - */ - if (new_offset < iterator_offset && buffer_offset < iterator_offset) { - sqsh_index_t end_offset = SQSH_MIN(new_end_offset, iterator_offset); - - size_t size = end_offset - new_offset; - sqsh_index_t inner_offset = new_offset - buffer_offset; - const uint8_t *data = sqsh__buffer_data(&reader->buffer); - - rv = sqsh__buffer_append(&new_buffer, &data[inner_offset], size); - if (rv < 0) { - goto out; - } - } - - /* - * 2. The remainder of the requested range is read from the iterator - */ - sqsh_index_t inner_offset = 0; - if (new_offset > iterator_offset) { - inner_offset = new_offset - iterator_offset; - } - if (inner_offset >= impl->size(iterator)) { - rv = -SQSH_ERROR_OUT_OF_BOUNDS; - goto out; - } - /* We expect, that the iterator is already at the correct position. */ - while (true) { - const size_t size = SQSH_MIN( - impl->size(iterator) - inner_offset, - target_size - sqsh__buffer_size(&new_buffer)); - const uint8_t *data = impl->data(iterator); - - rv = sqsh__buffer_append(&new_buffer, &data[inner_offset], size); - if (rv < 0) { - goto out; - } - - inner_offset = 0; - if (sqsh__buffer_size(&new_buffer) < target_size) { - const size_t desired_size = - target_size - sqsh__buffer_size(&new_buffer); - reader->iterator_offset += impl->size(iterator); - rv = impl->next(iterator, desired_size); - if (rv < 0) { - goto out; - } else if (rv == 0) { - rv = -SQSH_ERROR_OUT_OF_BOUNDS; - goto out; - } - } else { - break; - } - } - - rv = sqsh__buffer_move(&reader->buffer, &new_buffer); - if (rv < 0) { - goto out; - } - reader->data_offset = new_offset; - reader->buffer_offset = new_offset; - reader->data = sqsh__buffer_data(&reader->buffer); - reader->data_size = sqsh__buffer_size(&reader->buffer); - -out: - sqsh__buffer_cleanup(&new_buffer); - return rv; -} - -int -sqsh__reader_advance( - struct SqshReader *reader, sqsh_index_t offset, size_t size) { - int rv = 0; - sqsh_index_t new_offset; - sqsh_index_t new_end_offset; - if (SQSH_ADD_OVERFLOW(offset, reader->data_offset, &new_offset)) { - return -SQSH_ERROR_INTEGER_OVERFLOW; - } - if (SQSH_ADD_OVERFLOW(new_offset, size, &new_end_offset)) { - return -SQSH_ERROR_INTEGER_OVERFLOW; - } - - /* Finding the start of the new range. */ - if (new_offset >= reader->iterator_offset) { - rv = iterator_forward_to(reader, new_offset, 1); - if (rv < 0) { - return rv; - } - reader->data = reader->impl->data(reader->iterator); - reader->data_size = reader->impl->size(reader->iterator); - reader->data_offset = reader->iterator_offset; - sqsh__buffer_cleanup(&reader->buffer); - } - - /* Forward the data pointer to the requested offset. */ - const sqsh_index_t inner_offset = new_offset - reader->data_offset; - reader->data += inner_offset; - reader->data_size -= inner_offset; - reader->data_offset = new_offset; - - reader->size = size; - - /* If the requested range is not covered by the iterator, */ - /* turn it into a buffer and extend it. */ - if (new_end_offset > reader->data_offset + reader->data_size) { - rv = map_buffered(reader, new_offset, new_end_offset); - if (rv < 0) { - return rv; - } - } - - return 0; -} - -const uint8_t * -sqsh__reader_data(const struct SqshReader *reader) { - return reader->data; -} - -size_t -sqsh__reader_size(const struct SqshReader *reader) { - return reader->size; -} - -int -sqsh__reader_cleanup(struct SqshReader *reader) { - sqsh__buffer_cleanup(&reader->buffer); - - return 0; -} diff --git a/lib/reader/reader.c b/lib/reader/reader.c new file mode 100644 index 000000000..3d719957f --- /dev/null +++ b/lib/reader/reader.c @@ -0,0 +1,242 @@ +/****************************************************************************** + * * + * Copyright (c) 2023, Enno Boland * + * * + * Redistribution and use in source and binary forms, with or without * + * modification, are permitted provided that the following conditions are * + * met: * + * * + * * Redistributions of source code must retain the above copyright notice, * + * this list of conditions and the following disclaimer. * + * * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. * + * * + ******************************************************************************/ + +/** + * @author Enno Boland (mail@eboland.de) + * @file xattr_iterator.c + */ + +#include "../../include/sqsh_reader_private.h" + +#include "../../include/sqsh_error.h" +#include "../utils/utils.h" + +#include + +static int +reader_iterator_next(struct SqshReader *reader, size_t desired_size) { + int rv = reader->iterator_impl->next(reader->iterator, desired_size); + if (rv == 0) { + rv = -SQSH_ERROR_OUT_OF_BOUNDS; + } + return rv; +} + +/** + * @brief Skips the iterator until the offset is reached. + * + * This is a naiv implementation that just calls next until the offset is + * reached. + * + * TODO: Implement a iterator specific skip function. + * + * @param reader The reader + * @param offset The offset + * @param[in] desired_size The desired size + * + * @return 0 on success, negative on error. + */ +static int +reader_iterator_skip( + struct SqshReader *reader, sqsh_index_t *offset, size_t desired_size) { + int rv = 0; + void *iterator = reader->iterator; + const struct SqshReaderIteratorImpl *impl = reader->iterator_impl; + + size_t current_size = impl->size(iterator); + + while (current_size <= *offset) { + *offset -= current_size; + rv = reader_iterator_next(reader, desired_size); + if (rv < 0) { + goto out; + } + current_size = impl->size(iterator); + } + + rv = 0; +out: + return rv; +} + +int +sqsh__reader_init( + struct SqshReader *reader, + const struct SqshReaderIteratorImpl *iterator_impl, void *iterator) { + reader->data = NULL; + reader->size = 0; + reader->offset = 0; + reader->iterator_offset = 0; + reader->iterator_impl = iterator_impl; + reader->iterator = iterator; + return sqsh__buffer_init(&reader->buffer); +} + +/** + * @brief copies data from the iterator to the buffer until the buffer + * reaches the desired size. + * + * @param reader The reader + * @param offset The offset + * @param size The size + * + * @return 0 on success, negative on error. + */ +static int +reader_fill_buffer(struct SqshReader *reader, size_t size) { + int rv = 0; + void *iterator = reader->iterator; + const struct SqshReaderIteratorImpl *impl = reader->iterator_impl; + struct SqshBuffer *buffer = &reader->buffer; + sqsh_index_t offset = reader->offset; + + size_t buffer_size = sqsh__buffer_size(buffer); + for (;;) { + const uint8_t *data = impl->data(iterator); + const size_t data_size = impl->size(iterator); + const size_t copy_size = + SQSH_MIN(data_size - offset, size - buffer_size); + rv = sqsh__buffer_append(buffer, &data[offset], copy_size); + if (rv < 0) { + goto out; + } + + offset = 0; + buffer_size += copy_size; + if (size <= buffer_size) { + assert(size == buffer_size); + break; + } + + rv = reader_iterator_next(reader, size); + if (rv < 0) { + goto out; + } + reader->iterator_offset = buffer_size; + } + + reader->offset = 0; + reader->data = sqsh__buffer_data(buffer); + reader->size = size; + assert(reader->iterator_offset != 0); +out: + return rv; +} + +static int +handle_buffered(struct SqshReader *reader, sqsh_index_t offset, size_t size) { + int rv = 0; + struct SqshBuffer new_buffer = {0}; + sqsh_index_t iterator_offset = reader->iterator_offset; + + struct SqshBuffer *buffer = &reader->buffer; + const uint8_t *buffer_data = sqsh__buffer_data(buffer); + const size_t copy_size = iterator_offset - offset; + + if (offset != 0) { + rv = sqsh__buffer_init(&new_buffer); + if (rv < 0) { + goto out; + } + rv = sqsh__buffer_append(&new_buffer, &buffer_data[offset], copy_size); + if (rv < 0) { + goto out; + } + rv = sqsh__buffer_move(buffer, &new_buffer); + if (rv < 0) { + goto out; + } + } + rv = reader_fill_buffer(reader, size); + +out: + return rv; +} + +static int +handle_mapped(struct SqshReader *reader, sqsh_index_t offset, size_t size) { + int rv = 0; + void *iterator = reader->iterator; + const struct SqshReaderIteratorImpl *impl = reader->iterator_impl; + + if (SQSH_ADD_OVERFLOW(offset, reader->offset, &offset)) { + rv = -SQSH_ERROR_INTEGER_OVERFLOW; + goto out; + } + + rv = reader_iterator_skip(reader, &offset, size); + if (rv < 0) { + goto out; + } + + reader->offset = offset; + sqsh_index_t end_offset; + if (SQSH_ADD_OVERFLOW(offset, size, &end_offset)) { + rv = -SQSH_ERROR_INTEGER_OVERFLOW; + goto out; + } + if (end_offset <= impl->size(iterator)) { + const uint8_t *data = impl->data(iterator); + reader->data = &data[offset]; + reader->size = size; + } else { + struct SqshBuffer *buffer = &reader->buffer; + sqsh__buffer_drain(buffer); + rv = reader_fill_buffer(reader, size); + } + +out: + return rv; +} + +int +sqsh__reader_advance( + struct SqshReader *reader, sqsh_index_t offset, size_t size) { + if (offset >= reader->iterator_offset) { + offset -= reader->iterator_offset; + reader->iterator_offset = 0; + return handle_mapped(reader, offset, size); + } else { + return handle_buffered(reader, offset, size); + } +} + +const uint8_t * +sqsh__reader_data(const struct SqshReader *reader) { + return reader->data; +} + +size_t +sqsh__reader_size(const struct SqshReader *reader) { + return reader->size; +} + +int +sqsh__reader_cleanup(struct SqshReader *reader) { + return sqsh__buffer_cleanup(&reader->buffer); +} diff --git a/test/meson.build b/test/meson.build index a24dfdd71..a7829c4cf 100644 --- a/test/meson.build +++ b/test/meson.build @@ -28,11 +28,11 @@ sqsh_test = [ 'metablock/metablock_iterator.c', 'metablock/metablock_reader.c', 'nasty.c', + 'reader/reader.c', 'primitive/buffer.c', 'primitive/lru.c', 'primitive/rc_hash_map.c', 'primitive/rc_map.c', - 'primitive/reader.c', 'tree/walker.c', 'xattr/xattr_iterator.c', ] diff --git a/test/primitive/reader.c b/test/primitive/reader.c deleted file mode 100644 index 87a8daaa6..000000000 --- a/test/primitive/reader.c +++ /dev/null @@ -1,335 +0,0 @@ -/* - * BSD 2-Clause License - * - * Copyright (c) 2023, Enno Boland - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. - */ - -/** - * @author Enno Boland (mail@eboland.de) - * @file reader.c - */ - -#include "../common.h" -#include - -#include "../../include/sqsh_primitive_private.h" - -struct TestIterator { - char *data; - int remaining; - int block_size; - int size; -}; - -static const uint8_t * -test_iter_data(const void *data) { - struct TestIterator *iter = (struct TestIterator *)data; - return (uint8_t *)iter->data; -} - -static size_t -test_iter_size(const void *data) { - struct TestIterator *iter = (struct TestIterator *)data; - return iter->size; -} - -static size_t -test_iter_block_size(const void *data) { - struct TestIterator *iter = (struct TestIterator *)data; - if (iter->block_size == 0) { - iter->block_size = strlen(iter->data); - } - return iter->block_size; -} - -static int -test_iter_skip(void *data, size_t amount, size_t desired_size) { - struct TestIterator *iter = (struct TestIterator *)data; - (void)desired_size; - if (amount > 0) { - iter->remaining -= amount - 1; - } - if (iter->remaining == 0) { - iter->data = ""; - } - if (amount > (unsigned int)iter->remaining) { - return -1; - } - iter->remaining--; - iter->size = strlen(iter->data); - return iter->size; -} - -static int -test_iter_next(void *data, size_t desired_size) { - return test_iter_skip(data, 1, desired_size); -} - -static const struct SqshIteratorImpl impl = { - .next = test_iter_next, - .skip = test_iter_skip, - .data = test_iter_data, - .size = test_iter_size, - .block_size = test_iter_block_size}; - -static void -reader_init(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = {.data = ""}; - - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - sqsh__reader_cleanup(&reader); -} - -static void -reader_advance_once(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = { - .data = "THIS IS A TEST STRING", .remaining = 1}; - - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - rv = sqsh__reader_advance(&reader, 0, 6); - assert(rv == 0); - - const uint8_t *data = sqsh__reader_data(&reader); - assert(data == (void *)iter.data); - - sqsh__reader_cleanup(&reader); -} - -static void -reader_advance_once_with_offset(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = { - .data = "THIS IS A TEST STRING", .remaining = 1}; - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - rv = sqsh__reader_advance(&reader, 4, 6); - assert(rv == 0); - - const uint8_t *data = sqsh__reader_data(&reader); - assert(data == (uint8_t *)&iter.data[4]); - - sqsh__reader_cleanup(&reader); -} - -static void -reader_advance_twice_with_offset(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = { - .data = "THIS IS A TEST STRING", .remaining = 1}; - - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - rv = sqsh__reader_advance(&reader, 4, 6); - assert(rv == 0); - - const uint8_t *data = sqsh__reader_data(&reader); - assert(data == (uint8_t *)&iter.data[4]); - - rv = sqsh__reader_advance(&reader, 6, 2); - assert(rv == 0); - - const uint8_t *data2 = sqsh__reader_data(&reader); - assert(data2 == (uint8_t *)&iter.data[10]); - - sqsh__reader_cleanup(&reader); -} - -static void -reader_initial_advance(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = { - .data = "THIS IS A TEST STRING", .remaining = 1}; - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - rv = sqsh__reader_advance(&reader, 5, 3); - assert(rv == 0); - const uint8_t *data = sqsh__reader_data(&reader); - assert(3 == sqsh__reader_size(&reader)); - assert(memcmp(data, "IS ", 3) == 0); - assert(data == (uint8_t *)&iter.data[5]); - - sqsh__reader_cleanup(&reader); -} - -static void -reader_advance_to_out_of_bounds(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = { - .data = "THIS IS A TEST STRING", .remaining = 1}; - - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - rv = sqsh__reader_advance(&reader, strlen(iter.data), 1); - assert(rv < 0); - - sqsh__reader_cleanup(&reader); -} - -static void -reader_advance_over_boundary(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = {.data = "0123456789", .remaining = 2}; - - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - rv = sqsh__reader_advance(&reader, 9, 2); - assert(rv == 0); - const uint8_t *data = sqsh__reader_data(&reader); - assert(2 == sqsh__reader_size(&reader)); - assert(memcmp(data, "90", 2) == 0); - - sqsh__reader_cleanup(&reader); -} - -static void -reader_initial_advance_2(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = {.data = "ABCD", .remaining = 10}; - - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - rv = sqsh__reader_advance(&reader, 7, 5); - assert(rv == 0); - - const uint8_t *data = sqsh__reader_data(&reader); - assert(sqsh__reader_size(&reader) == 5); - assert(memcmp(data, "DABCD", 5) == 0); - - sqsh__reader_cleanup(&reader); -} - -static void -reader_error_1(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = {.data = "AB", .remaining = 10}; - - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - rv = sqsh__reader_advance(&reader, 0, 4); - assert(rv == 0); - - const uint8_t *data = sqsh__reader_data(&reader); - assert(sqsh__reader_size(&reader) == 4); - assert(memcmp(data, "ABAB", 4) == 0); - - iter.data = "12"; - rv = sqsh__reader_advance(&reader, 4, 4); - assert(rv == 0); - - data = sqsh__reader_data(&reader); - assert(sqsh__reader_size(&reader) == 4); - assert(memcmp(data, "1212", 4) == 0); - - sqsh__reader_cleanup(&reader); -} - -static void -reader_map_into_buffer(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = {.data = "0123456789", .remaining = 2}; - - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - rv = sqsh__reader_advance(&reader, 8, 4); - assert(rv == 0); - - const uint8_t *data = sqsh__reader_data(&reader); - assert(sqsh__reader_size(&reader) == 4); - assert(memcmp(data, "8901", 4) == 0); - - rv = sqsh__reader_advance(&reader, 1, 4); - assert(rv == 0); - - data = sqsh__reader_data(&reader); - assert(sqsh__reader_size(&reader) == 4); - assert(memcmp(data, "9012", 4) == 0); - - sqsh__reader_cleanup(&reader); -} - -static void -reader_map_into_buffer_twice(void) { - int rv; - struct SqshReader reader = {0}; - struct TestIterator iter = {.data = "0123456789", .remaining = 3}; - - rv = sqsh__reader_init(&reader, &impl, &iter); - assert(rv == 0); - - rv = sqsh__reader_advance(&reader, 8, 4); - assert(rv == 0); - - const uint8_t *data = sqsh__reader_data(&reader); - assert(sqsh__reader_size(&reader) == 4); - assert(memcmp(data, "8901", 4) == 0); - - rv = sqsh__reader_advance(&reader, 1, 14); - assert(rv == 0); - - data = sqsh__reader_data(&reader); - assert(sqsh__reader_size(&reader) == 14); - assert(memcmp(data, "90123456789012", 14) == 0); - - sqsh__reader_cleanup(&reader); -} - -DECLARE_TESTS -TEST(reader_init) -TEST(reader_advance_once) -TEST(reader_advance_once_with_offset) -TEST(reader_advance_twice_with_offset) -TEST(reader_initial_advance) -TEST(reader_advance_to_out_of_bounds) -TEST(reader_advance_over_boundary) -TEST(reader_initial_advance_2) -TEST(reader_error_1) -TEST(reader_map_into_buffer) -TEST(reader_map_into_buffer_twice) -END_TESTS diff --git a/test/reader/reader.c b/test/reader/reader.c new file mode 100644 index 000000000..b78375fb4 --- /dev/null +++ b/test/reader/reader.c @@ -0,0 +1,579 @@ +/****************************************************************************** + * * + * Copyright (c) 2023, Enno Boland * + * * + * Redistribution and use in source and binary forms, with or without * + * modification, are permitted provided that the following conditions are * + * met: * + * * + * * Redistributions of source code must retain the above copyright notice, * + * this list of conditions and the following disclaimer. * + * * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. * + * * + ******************************************************************************/ + +/** + * @author Enno Boland (mail@eboland.de) + * @file integration.c + */ + +#include "../include/sqsh_error.h" +#include "../include/sqsh_reader_private.h" +#include "common.h" +#include +#include + +struct TestIterator { + char *data; + char *current_data; + int remaining; +}; + +static const uint8_t * +test_iter_data(const void *data) { + struct TestIterator *iter = (struct TestIterator *)data; + return (uint8_t *)iter->current_data; +} + +static size_t +test_iter_size(const void *data) { + struct TestIterator *iter = (struct TestIterator *)data; + if (iter->current_data == NULL) { + return 0; + } else { + return strlen(iter->current_data); + } +} + +static int +test_iter_next(void *data, size_t desired_size) { + struct TestIterator *iter = (struct TestIterator *)data; + (void)desired_size; + if (iter->remaining == 0) { + iter->current_data = ""; + return 0; + } + iter->current_data = iter->data; + iter->remaining--; + return strlen(iter->current_data); +} + +static const struct SqshReaderIteratorImpl test_iter = { + .next = test_iter_next, + .data = test_iter_data, + .size = test_iter_size, +}; + +static void +test_reader_init(void) { + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "test", + .remaining = 4, + }; + + int rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_with_offset(void) { + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "test", + .remaining = 1, + }; + + int rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 1, 2); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + size_t size = sqsh__reader_size(&reader); + assert(size == 2); + assert(memcmp(data, "es", 2) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_to_block(void) { + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "test", + .remaining = 1, + }; + + int rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 0, 4); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + size_t size = sqsh__reader_size(&reader); + assert(size == 4); + assert(memcmp(data, "test", 4) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_to_two_blocks() { + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "test", + .remaining = 4, + }; + + int rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 0, 8); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + size_t size = sqsh__reader_size(&reader); + assert(size == 8); + assert(memcmp(data, "testtest", 8) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_to_two_blocks_with_offset() { + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "test", + .remaining = 4, + }; + + int rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 1, 6); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + size_t size = sqsh__reader_size(&reader); + assert(size == 6); + assert(memcmp(data, "esttes", 6) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_from_buffered_to_mapped() { + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "test", + .remaining = 4, + }; + + int rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + // Trigger a buffered result + rv = sqsh__reader_advance(&reader, 1, 6); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + size_t size = sqsh__reader_size(&reader); + assert(size == 6); + assert(memcmp(data, "esttes", 6) == 0); + + // Trigger back to a mapped result + rv = sqsh__reader_advance(&reader, 4, 3); + assert(rv == 0); + + data = sqsh__reader_data(&reader); + size = sqsh__reader_size(&reader); + assert(size == 3); + assert(memcmp(data, "est", 3) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_from_buffered_to_buffered() { + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "test", + .remaining = 4, + }; + + int rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + // Trigger a buffered result + rv = sqsh__reader_advance(&reader, 1, 6); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + size_t size = sqsh__reader_size(&reader); + assert(size == 6); + assert(memcmp(data, "esttes", 6) == 0); + + // Trigger back to a mapped result + rv = sqsh__reader_advance(&reader, 8, 6); + assert(rv == 0); + + data = sqsh__reader_data(&reader); + size = sqsh__reader_size(&reader); + assert(size == 6); + assert(memcmp(data, "esttes", 6) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_inside_buffered() { + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "test", + .remaining = 4, + }; + + int rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + // Trigger a buffered result + rv = sqsh__reader_advance(&reader, 1, 6); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + size_t size = sqsh__reader_size(&reader); + assert(size == 6); + assert(memcmp(data, "esttes", 6) == 0); + + // Trigger back to a mapped result + rv = sqsh__reader_advance(&reader, 1, 6); + assert(rv == 0); + + data = sqsh__reader_data(&reader); + size = sqsh__reader_size(&reader); + assert(size == 6); + assert(memcmp(data, "sttest", 6) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_with_zero_size() { + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "test", + .remaining = 4, + }; + + int rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 3, 0); + assert(rv == 0); + + size_t size = sqsh__reader_size(&reader); + assert(size == 0); + + // Trigger back to a mapped result + rv = sqsh__reader_advance(&reader, 0, 4); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + size = sqsh__reader_size(&reader); + assert(size == 4); + assert(memcmp(data, "ttes", 4) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_once(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "THIS IS A TEST STRING", .remaining = 1}; + + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 0, 6); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + assert(data == (void *)iter.data); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_once_with_offset(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "THIS IS A TEST STRING", .remaining = 1}; + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 4, 6); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + assert(data == (uint8_t *)&iter.data[4]); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_twice_with_offset(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "THIS IS A TEST STRING", .remaining = 1}; + + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 4, 6); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + assert(data == (uint8_t *)&iter.data[4]); + + rv = sqsh__reader_advance(&reader, 6, 2); + assert(rv == 0); + + const uint8_t *data2 = sqsh__reader_data(&reader); + assert(data2 == (uint8_t *)&iter.data[10]); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_initial_advance(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "THIS IS A TEST STRING", .remaining = 1}; + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 5, 3); + assert(rv == 0); + const uint8_t *data = sqsh__reader_data(&reader); + assert(3 == sqsh__reader_size(&reader)); + assert(memcmp(data, "IS ", 3) == 0); + assert(data == (uint8_t *)&iter.data[5]); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_to_out_of_bounds(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = { + .data = "THIS IS A TEST STRING", .remaining = 1}; + + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, strlen(iter.data), 1); + assert(rv < 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_advance_over_boundary(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = {.data = "0123456789", .remaining = 2}; + + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 9, 2); + assert(rv == 0); + const uint8_t *data = sqsh__reader_data(&reader); + assert(2 == sqsh__reader_size(&reader)); + assert(memcmp(data, "90", 2) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_initial_advance_2(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = {.data = "ABCD", .remaining = 10}; + + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 7, 5); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + assert(sqsh__reader_size(&reader) == 5); + assert(memcmp(data, "DABCD", 5) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_error_1(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = {.data = "AB", .remaining = 10}; + + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 0, 4); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + assert(sqsh__reader_size(&reader) == 4); + assert(memcmp(data, "ABAB", 4) == 0); + + iter.data = "12"; + rv = sqsh__reader_advance(&reader, 4, 4); + assert(rv == 0); + + data = sqsh__reader_data(&reader); + assert(sqsh__reader_size(&reader) == 4); + assert(memcmp(data, "1212", 4) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_map_into_buffer(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = {.data = "0123456789", .remaining = 2}; + + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 8, 4); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + assert(sqsh__reader_size(&reader) == 4); + assert(memcmp(data, "8901", 4) == 0); + + rv = sqsh__reader_advance(&reader, 1, 4); + assert(rv == 0); + + data = sqsh__reader_data(&reader); + assert(sqsh__reader_size(&reader) == 4); + assert(memcmp(data, "9012", 4) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_map_into_buffer_twice(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = {.data = "0123456789", .remaining = 3}; + + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 8, 4); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + assert(sqsh__reader_size(&reader) == 4); + assert(memcmp(data, "8901", 4) == 0); + + rv = sqsh__reader_advance(&reader, 1, 14); + assert(rv == 0); + + data = sqsh__reader_data(&reader); + assert(sqsh__reader_size(&reader) == 14); + assert(memcmp(data, "90123456789012", 14) == 0); + + sqsh__reader_cleanup(&reader); +} + +static void +test_reader_extend_size_till_end(void) { + int rv; + struct SqshReader reader = {0}; + struct TestIterator iter = {.data = "0123456789", .remaining = 2}; + + rv = sqsh__reader_init(&reader, &test_iter, &iter); + assert(rv == 0); + + rv = sqsh__reader_advance(&reader, 4, 4); + assert(rv == 0); + + const uint8_t *data = sqsh__reader_data(&reader); + assert(sqsh__reader_size(&reader) == 4); + assert(memcmp(data, "4567", 4) == 0); + + rv = sqsh__reader_advance(&reader, 0, 6); + assert(rv == 0); + + data = sqsh__reader_data(&reader); + assert(sqsh__reader_size(&reader) == 6); + assert(memcmp(data, "456789", 6) == 0); + + iter.data = "ABCDEF"; + + rv = sqsh__reader_advance(&reader, 0, 12); + data = sqsh__reader_data(&reader); + assert(sqsh__reader_size(&reader) == 12); + assert(memcmp(data, "456789ABCDEF", 12) == 0); + + // rv = sqsh__reader_advance(&reader, 0, 13); + // assert(rv == -SQSH_ERROR_OUT_OF_BOUNDS); + + sqsh__reader_cleanup(&reader); +} + +DECLARE_TESTS +TEST(test_reader_init) +TEST(test_reader_advance_to_block) +TEST(test_reader_advance_with_offset) +TEST(test_reader_advance_to_two_blocks) +TEST(test_reader_advance_to_two_blocks_with_offset) +TEST(test_reader_from_buffered_to_mapped) +TEST(test_reader_from_buffered_to_buffered) +TEST(test_reader_from_buffered_to_buffered) +TEST(test_reader_advance_inside_buffered) +TEST(test_reader_advance_with_zero_size) +TEST(test_reader_advance_once) +TEST(test_reader_advance_once_with_offset) +TEST(test_reader_advance_twice_with_offset) +TEST(test_reader_initial_advance) +TEST(test_reader_advance_to_out_of_bounds) +TEST(test_reader_advance_over_boundary) +TEST(test_reader_initial_advance_2) +TEST(test_reader_error_1) +TEST(test_reader_map_into_buffer) +TEST(test_reader_map_into_buffer_twice) +TEST(test_reader_extend_size_till_end) +END_TESTS