| /////////////////////////////////////////////////////////////////////////////// |
| // |
| /// \file info.c |
| /// \brief Collects and verifies integrity of Stream size information |
| // |
| // Copyright (C) 2007 Lasse Collin |
| // |
| // This library 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. |
| // |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #include "common.h" |
| |
| |
| struct lzma_info_s { |
| struct { |
| /// Known Size of Header Metadata Block; here's some |
| /// special things: |
| /// - LZMA_VLI_VALUE_UNKNOWN indicates that we don't know |
| /// if Header Metadata Block is present. |
| /// - 0 indicates that Header Metadata Block is not present. |
| lzma_vli header_metadata_size; |
| |
| /// Known Total Size of the Data Blocks in the Stream |
| lzma_vli total_size; |
| |
| /// Known Uncompressed Size of the Data Blocks in the Stream |
| lzma_vli uncompressed_size; |
| |
| /// Known Size of Footer Metadata Block |
| lzma_vli footer_metadata_size; |
| } known; |
| |
| struct { |
| /// Sum of Total Size fields stored to the Index so far |
| lzma_vli total_size; |
| |
| /// Sum of Uncompressed Size fields stored to the Index so far |
| lzma_vli uncompressed_size; |
| |
| /// First Index Record in the list, or NULL if Index is empty. |
| lzma_index *head; |
| |
| /// Number of Index Records |
| size_t record_count; |
| |
| /// Number of Index Records |
| size_t incomplete_count; |
| |
| /// True when we know that no more Records will get added |
| /// to the Index. |
| bool is_final; |
| } index; |
| |
| /// Start offset of the Stream. This is needed to calculate |
| /// lzma_info_iter.stream_offset. |
| lzma_vli stream_start_offset; |
| |
| /// True if Index is present in Header Metadata Block |
| bool has_index_in_header_metadata; |
| }; |
| |
| |
| ////////////////////// |
| // Create/Reset/End // |
| ////////////////////// |
| |
| static void |
| index_init(lzma_info *info) |
| { |
| info->index.total_size = 0; |
| info->index.uncompressed_size = 0; |
| info->index.head = NULL; |
| info->index.record_count = 0; |
| info->index.incomplete_count = 0; |
| info->index.is_final = false; |
| return; |
| } |
| |
| |
| static void |
| info_init(lzma_info *info) |
| { |
| info->known.header_metadata_size = LZMA_VLI_VALUE_UNKNOWN; |
| info->known.total_size = LZMA_VLI_VALUE_UNKNOWN; |
| info->known.uncompressed_size = LZMA_VLI_VALUE_UNKNOWN; |
| info->known.footer_metadata_size = LZMA_VLI_VALUE_UNKNOWN; |
| info->stream_start_offset = 0; |
| info->has_index_in_header_metadata = false; |
| |
| index_init(info); |
| |
| return; |
| } |
| |
| |
| extern LZMA_API lzma_info * |
| lzma_info_init(lzma_info *info, lzma_allocator *allocator) |
| { |
| if (info == NULL) |
| info = lzma_alloc(sizeof(lzma_info), allocator); |
| else |
| lzma_index_free(info->index.head, allocator); |
| |
| if (info != NULL) |
| info_init(info); |
| |
| return info; |
| } |
| |
| |
| extern LZMA_API void |
| lzma_info_free(lzma_info *info, lzma_allocator *allocator) |
| { |
| lzma_index_free(info->index.head, allocator); |
| lzma_free(info, allocator); |
| return; |
| } |
| |
| |
| ///////// |
| // Set // |
| ///////// |
| |
| static lzma_ret |
| set_size(lzma_vli new_size, lzma_vli *known_size, lzma_vli index_size, |
| bool forbid_zero) |
| { |
| assert(new_size <= LZMA_VLI_VALUE_MAX); |
| |
| lzma_ret ret = LZMA_OK; |
| |
| if (forbid_zero && new_size == 0) |
| ret = LZMA_PROG_ERROR; |
| else if (index_size > new_size) |
| ret = LZMA_DATA_ERROR; |
| else if (*known_size == LZMA_VLI_VALUE_UNKNOWN) |
| *known_size = new_size; |
| else if (*known_size != new_size) |
| ret = LZMA_DATA_ERROR; |
| |
| return ret; |
| } |
| |
| |
| extern LZMA_API lzma_ret |
| lzma_info_size_set(lzma_info *info, lzma_info_size type, lzma_vli size) |
| { |
| if (size > LZMA_VLI_VALUE_MAX) |
| return LZMA_PROG_ERROR; |
| |
| switch (type) { |
| case LZMA_INFO_STREAM_START: |
| info->stream_start_offset = size; |
| return LZMA_OK; |
| |
| case LZMA_INFO_HEADER_METADATA: |
| return set_size(size, &info->known.header_metadata_size, |
| 0, false); |
| |
| case LZMA_INFO_TOTAL: |
| return set_size(size, &info->known.total_size, |
| info->index.total_size, true); |
| |
| case LZMA_INFO_UNCOMPRESSED: |
| return set_size(size, &info->known.uncompressed_size, |
| info->index.uncompressed_size, false); |
| |
| case LZMA_INFO_FOOTER_METADATA: |
| return set_size(size, &info->known.footer_metadata_size, |
| 0, true); |
| } |
| |
| return LZMA_PROG_ERROR; |
| } |
| |
| |
| extern LZMA_API lzma_ret |
| lzma_info_index_set(lzma_info *info, lzma_allocator *allocator, |
| lzma_index *i_new, lzma_bool eat_index) |
| { |
| if (i_new == NULL) |
| return LZMA_PROG_ERROR; |
| |
| lzma_index *i_old = info->index.head; |
| |
| if (i_old != NULL) { |
| while (true) { |
| // If the new Index has fewer Records than the old one, |
| // the new Index cannot be valid. |
| if (i_new == NULL) |
| return LZMA_DATA_ERROR; |
| |
| // The new Index must be complete i.e. no unknown |
| // values. |
| if (i_new->total_size > LZMA_VLI_VALUE_MAX |
| || i_new->uncompressed_size |
| > LZMA_VLI_VALUE_MAX) { |
| if (eat_index) |
| lzma_index_free(i_new, allocator); |
| |
| return LZMA_PROG_ERROR; |
| } |
| |
| // Compare the values from the new Index with the old |
| // Index. The old Index may be incomplete; in that |
| // case we |
| // - use the value from the new Index as is; |
| // - update the appropriate info->index.foo_size; and |
| // - decrease the count of incomplete Index Records. |
| bool was_incomplete = false; |
| |
| if (i_old->total_size == LZMA_VLI_VALUE_UNKNOWN) { |
| assert(!info->index.is_final); |
| was_incomplete = true; |
| |
| i_old->total_size = i_new->total_size; |
| |
| if (lzma_vli_add(info->index.total_size, |
| i_new->total_size)) { |
| if (eat_index) |
| lzma_index_free(i_new, |
| allocator); |
| |
| return LZMA_PROG_ERROR; |
| } |
| } else if (i_old->total_size != i_new->total_size) { |
| if (eat_index) |
| lzma_index_free(i_new, allocator); |
| |
| return LZMA_DATA_ERROR; |
| } |
| |
| if (i_old->uncompressed_size |
| == LZMA_VLI_VALUE_UNKNOWN) { |
| assert(!info->index.is_final); |
| was_incomplete = true; |
| |
| i_old->uncompressed_size |
| = i_new->uncompressed_size; |
| |
| if (lzma_vli_add(info->index.uncompressed_size, |
| i_new->uncompressed_size)) { |
| if (eat_index) |
| lzma_index_free(i_new, |
| allocator); |
| |
| return LZMA_PROG_ERROR; |
| } |
| } else if (i_old->uncompressed_size |
| != i_new->uncompressed_size) { |
| if (eat_index) |
| lzma_index_free(i_new, allocator); |
| |
| return LZMA_DATA_ERROR; |
| } |
| |
| if (was_incomplete) { |
| assert(!info->index.is_final); |
| assert(info->index.incomplete_count > 0); |
| --info->index.incomplete_count; |
| } |
| |
| // Get rid of *i_new. It's now identical with *i_old. |
| lzma_index *tmp = i_new->next; |
| if (eat_index) |
| lzma_free(i_new, allocator); |
| |
| i_new = tmp; |
| |
| // We want to leave i_old pointing to the last |
| // Index Record in the old Index. This way we can |
| // concatenate the possible new Records from i_new. |
| if (i_old->next == NULL) |
| break; |
| |
| i_old = i_old->next; |
| } |
| } |
| |
| assert(info->index.incomplete_count == 0); |
| |
| // If Index was already known to be final, i_new must be NULL now. |
| // The new Index cannot contain more Records that we already have. |
| if (info->index.is_final) { |
| assert(info->index.head != NULL); |
| |
| if (i_new != NULL) { |
| if (eat_index) |
| lzma_index_free(i_new, allocator); |
| |
| return LZMA_DATA_ERROR; |
| } |
| |
| return LZMA_OK; |
| } |
| |
| // The rest of the new Index is merged to the old Index. Keep the |
| // current i_new pointer in available. We need it when merging the |
| // new Index with the old one, and if an error occurs so we can |
| // get rid of the broken part of the new Index. |
| lzma_index *i_start = i_new; |
| while (i_new != NULL) { |
| // The new Index must be complete i.e. no unknown values. |
| if (i_new->total_size > LZMA_VLI_VALUE_MAX |
| || i_new->uncompressed_size |
| > LZMA_VLI_VALUE_MAX) { |
| if (eat_index) |
| lzma_index_free(i_start, allocator); |
| |
| return LZMA_PROG_ERROR; |
| } |
| |
| // Update info->index.foo_sizes. |
| if (lzma_vli_add(info->index.total_size, i_new->total_size) |
| || lzma_vli_add(info->index.uncompressed_size, |
| i_new->uncompressed_size)) { |
| if (eat_index) |
| lzma_index_free(i_start, allocator); |
| |
| return LZMA_PROG_ERROR; |
| } |
| |
| ++info->index.record_count; |
| i_new = i_new->next; |
| } |
| |
| // All the Records in the new Index are good, and info->index.foo_sizes |
| // were successfully updated. |
| if (lzma_info_index_finish(info) != LZMA_OK) { |
| if (eat_index) |
| lzma_index_free(i_start, allocator); |
| |
| return LZMA_DATA_ERROR; |
| } |
| |
| // The Index is ready to be merged. If we aren't supposed to eat |
| // the Index, make a copy of it first. |
| if (!eat_index && i_start != NULL) { |
| i_start = lzma_index_dup(i_start, allocator); |
| if (i_start == NULL) |
| return LZMA_MEM_ERROR; |
| } |
| |
| // Concatenate the new Index with the old one. Note that it is |
| // possible that we don't have any old Index. |
| if (info->index.head == NULL) |
| info->index.head = i_start; |
| else |
| i_old->next = i_start; |
| |
| return LZMA_OK; |
| } |
| |
| |
| extern LZMA_API lzma_ret |
| lzma_info_metadata_set(lzma_info *info, lzma_allocator *allocator, |
| lzma_metadata *metadata, lzma_bool is_header_metadata, |
| lzma_bool eat_index) |
| { |
| // Validate *metadata. |
| if (!lzma_vli_is_valid(metadata->header_metadata_size) |
| || !lzma_vli_is_valid(metadata->total_size) |
| || !lzma_vli_is_valid(metadata->uncompressed_size)) { |
| if (eat_index) { |
| lzma_index_free(metadata->index, allocator); |
| metadata->index = NULL; |
| } |
| |
| return LZMA_PROG_ERROR; |
| } |
| |
| // Index |
| if (metadata->index != NULL) { |
| if (is_header_metadata) |
| info->has_index_in_header_metadata = true; |
| |
| const lzma_ret ret = lzma_info_index_set( |
| info, allocator, metadata->index, eat_index); |
| if (ret != LZMA_OK) |
| return ret; |
| |
| } else if (!is_header_metadata |
| && (metadata->total_size == LZMA_VLI_VALUE_UNKNOWN |
| || !info->has_index_in_header_metadata)) { |
| // Either Total Size or Index must be present in Footer |
| // Metadata Block. If Index is not present, it must have |
| // already been in the Header Metadata Block. Since we |
| // got here, these conditions weren't met. |
| return LZMA_DATA_ERROR; |
| } |
| |
| // Size of Header Metadata |
| if (!is_header_metadata) { |
| // If it is marked unknown in Metadata, it means that |
| // it's not present. |
| const lzma_vli size = metadata->header_metadata_size |
| != LZMA_VLI_VALUE_UNKNOWN |
| ? metadata->header_metadata_size : 0; |
| const lzma_ret ret = lzma_info_size_set( |
| info, LZMA_INFO_HEADER_METADATA, size); |
| if (ret != LZMA_OK) |
| return ret; |
| } |
| |
| // Total Size |
| if (metadata->total_size != LZMA_VLI_VALUE_UNKNOWN) { |
| const lzma_ret ret = lzma_info_size_set(info, |
| LZMA_INFO_TOTAL, metadata->total_size); |
| if (ret != LZMA_OK) |
| return ret; |
| } |
| |
| // Uncompressed Size |
| if (metadata->uncompressed_size != LZMA_VLI_VALUE_UNKNOWN) { |
| const lzma_ret ret = lzma_info_size_set(info, |
| LZMA_INFO_UNCOMPRESSED, |
| metadata->uncompressed_size); |
| if (ret != LZMA_OK) |
| return ret; |
| } |
| |
| return LZMA_OK; |
| } |
| |
| |
| ///////// |
| // Get // |
| ///////// |
| |
| extern LZMA_API lzma_vli |
| lzma_info_size_get(const lzma_info *info, lzma_info_size type) |
| { |
| switch (type) { |
| case LZMA_INFO_STREAM_START: |
| return info->stream_start_offset; |
| |
| case LZMA_INFO_HEADER_METADATA: |
| return info->known.header_metadata_size; |
| |
| case LZMA_INFO_TOTAL: |
| return info->known.total_size; |
| |
| case LZMA_INFO_UNCOMPRESSED: |
| return info->known.uncompressed_size; |
| |
| case LZMA_INFO_FOOTER_METADATA: |
| return info->known.footer_metadata_size; |
| } |
| |
| return LZMA_VLI_VALUE_UNKNOWN; |
| } |
| |
| |
| extern LZMA_API lzma_index * |
| lzma_info_index_get(lzma_info *info, lzma_bool detach) |
| { |
| lzma_index *i = info->index.head; |
| |
| if (detach) |
| index_init(info); |
| |
| return i; |
| } |
| |
| |
| extern LZMA_API size_t |
| lzma_info_index_count_get(const lzma_info *info) |
| { |
| return info->index.record_count; |
| } |
| |
| |
| ///////////////// |
| // Incremental // |
| ///////////////// |
| |
| enum { |
| ITER_INFO, |
| ITER_INDEX, |
| ITER_RESERVED_1, |
| ITER_RESERVED_2, |
| }; |
| |
| |
| #define iter_info ((lzma_info *)(iter->internal[ITER_INFO])) |
| |
| #define iter_index ((lzma_index *)(iter->internal[ITER_INDEX])) |
| |
| |
| extern LZMA_API void |
| lzma_info_iter_begin(lzma_info *info, lzma_info_iter *iter) |
| { |
| *iter = (lzma_info_iter){ |
| .total_size = LZMA_VLI_VALUE_UNKNOWN, |
| .uncompressed_size = LZMA_VLI_VALUE_UNKNOWN, |
| .stream_offset = LZMA_VLI_VALUE_UNKNOWN, |
| .uncompressed_offset = LZMA_VLI_VALUE_UNKNOWN, |
| .internal = { info, NULL, NULL, NULL }, |
| }; |
| |
| return; |
| } |
| |
| |
| extern LZMA_API lzma_ret |
| lzma_info_iter_next(lzma_info_iter *iter, lzma_allocator *allocator) |
| { |
| // FIXME debug remove |
| lzma_info *info = iter_info; |
| (void)info; |
| |
| if (iter_index == NULL) { |
| // The first call after lzma_info_iter_begin(). |
| if (iter_info->known.header_metadata_size |
| == LZMA_VLI_VALUE_UNKNOWN) |
| iter->stream_offset = LZMA_VLI_VALUE_UNKNOWN; |
| else if (lzma_vli_sum3(iter->stream_offset, |
| iter_info->stream_start_offset, |
| LZMA_STREAM_HEADER_SIZE, |
| iter_info->known.header_metadata_size)) |
| return LZMA_PROG_ERROR; |
| |
| iter->uncompressed_offset = 0; |
| |
| if (iter_info->index.head != NULL) { |
| // The first Index Record has already been allocated. |
| iter->internal[ITER_INDEX] = iter_info->index.head; |
| iter->total_size = iter_index->total_size; |
| iter->uncompressed_size |
| = iter_index->uncompressed_size; |
| return LZMA_OK; |
| } |
| } else { |
| // Update iter->*_offsets. |
| if (iter->stream_offset != LZMA_VLI_VALUE_UNKNOWN) { |
| if (iter_index->total_size == LZMA_VLI_VALUE_UNKNOWN) |
| iter->stream_offset = LZMA_VLI_VALUE_UNKNOWN; |
| else if (lzma_vli_add(iter->stream_offset, |
| iter_index->total_size)) |
| return LZMA_DATA_ERROR; |
| } |
| |
| if (iter->uncompressed_offset != LZMA_VLI_VALUE_UNKNOWN) { |
| if (iter_index->uncompressed_size |
| == LZMA_VLI_VALUE_UNKNOWN) |
| iter->uncompressed_offset |
| = LZMA_VLI_VALUE_UNKNOWN; |
| else if (lzma_vli_add(iter->uncompressed_offset, |
| iter_index->uncompressed_size)) |
| return LZMA_DATA_ERROR; |
| } |
| |
| if (iter_index->next != NULL) { |
| // The next Record has already been allocated. |
| iter->internal[ITER_INDEX] = iter_index->next; |
| iter->total_size = iter_index->total_size; |
| iter->uncompressed_size |
| = iter_index->uncompressed_size; |
| return LZMA_OK; |
| } |
| } |
| |
| // Don't add new Records to a final Index. |
| if (iter_info->index.is_final) |
| return LZMA_DATA_ERROR; |
| |
| // Allocate and initialize a new Index Record. |
| lzma_index *i = lzma_alloc(sizeof(lzma_index), allocator); |
| if (i == NULL) |
| return LZMA_MEM_ERROR; |
| |
| i->total_size = LZMA_VLI_VALUE_UNKNOWN; |
| i->uncompressed_size = LZMA_VLI_VALUE_UNKNOWN; |
| i->next = NULL; |
| |
| iter->total_size = LZMA_VLI_VALUE_UNKNOWN; |
| iter->uncompressed_size = LZMA_VLI_VALUE_UNKNOWN; |
| |
| // Decide where to put the new Index Record. |
| if (iter_info->index.head == NULL) |
| iter_info->index.head = i; |
| |
| if (iter_index != NULL) |
| iter_index->next = i; |
| |
| iter->internal[ITER_INDEX] = i; |
| |
| ++iter_info->index.record_count; |
| ++iter_info->index.incomplete_count; |
| |
| return LZMA_OK; |
| } |
| |
| |
| extern LZMA_API lzma_ret |
| lzma_info_iter_set(lzma_info_iter *iter, |
| lzma_vli total_size, lzma_vli uncompressed_size) |
| { |
| // FIXME debug remove |
| lzma_info *info = iter_info; |
| (void)info; |
| |
| if (iter_index == NULL || !lzma_vli_is_valid(total_size) |
| || !lzma_vli_is_valid(uncompressed_size)) |
| return LZMA_PROG_ERROR; |
| |
| const bool was_incomplete = iter_index->total_size |
| == LZMA_VLI_VALUE_UNKNOWN |
| || iter_index->uncompressed_size |
| == LZMA_VLI_VALUE_UNKNOWN; |
| |
| if (total_size != LZMA_VLI_VALUE_UNKNOWN) { |
| if (iter_index->total_size == LZMA_VLI_VALUE_UNKNOWN) { |
| iter_index->total_size = total_size; |
| |
| if (lzma_vli_add(iter_info->index.total_size, |
| total_size) |
| || iter_info->index.total_size |
| > iter_info->known.total_size) |
| return LZMA_DATA_ERROR; |
| |
| } else if (iter_index->total_size != total_size) { |
| return LZMA_DATA_ERROR; |
| } |
| } |
| |
| if (uncompressed_size != LZMA_VLI_VALUE_UNKNOWN) { |
| if (iter_index->uncompressed_size == LZMA_VLI_VALUE_UNKNOWN) { |
| iter_index->uncompressed_size = uncompressed_size; |
| |
| if (lzma_vli_add(iter_info->index.uncompressed_size, |
| uncompressed_size) |
| || iter_info->index.uncompressed_size |
| > iter_info->known.uncompressed_size) |
| return LZMA_DATA_ERROR; |
| |
| } else if (iter_index->uncompressed_size |
| != uncompressed_size) { |
| return LZMA_DATA_ERROR; |
| } |
| } |
| |
| // Check if the new information we got managed to finish this |
| // Index Record. If so, update the count of incomplete Index Records. |
| if (was_incomplete && iter_index->total_size |
| != LZMA_VLI_VALUE_UNKNOWN |
| && iter_index->uncompressed_size |
| != LZMA_VLI_VALUE_UNKNOWN) { |
| assert(iter_info->index.incomplete_count > 0); |
| --iter_info->index.incomplete_count; |
| } |
| |
| // Make sure that the known sizes are now available in *iter. |
| iter->total_size = iter_index->total_size; |
| iter->uncompressed_size = iter_index->uncompressed_size; |
| |
| return LZMA_OK; |
| } |
| |
| |
| extern LZMA_API lzma_ret |
| lzma_info_index_finish(lzma_info *info) |
| { |
| if (info->index.record_count == 0 || info->index.incomplete_count > 0 |
| || lzma_info_size_set(info, LZMA_INFO_TOTAL, |
| info->index.total_size) |
| || lzma_info_size_set(info, LZMA_INFO_UNCOMPRESSED, |
| info->index.uncompressed_size)) |
| return LZMA_DATA_ERROR; |
| |
| info->index.is_final = true; |
| |
| return LZMA_OK; |
| } |
| |
| |
| ////////////// |
| // Locating // |
| ////////////// |
| |
| extern LZMA_API lzma_vli |
| lzma_info_metadata_locate(const lzma_info *info, lzma_bool is_header_metadata) |
| { |
| bool error = false; |
| lzma_vli size = 0; |
| |
| if (info->known.header_metadata_size == LZMA_VLI_VALUE_UNKNOWN) { |
| // We don't know if Header Metadata Block is present, thus |
| // we cannot locate it either. |
| // |
| // Well, you could say that just assume that it is present. |
| // I'm not sure if this is useful. But it can be useful to |
| // be able to use this function and get LZMA_VLI_VALUE_UNKNOWN |
| // to detect that Header Metadata Block wasn't present. |
| error = true; |
| } else if (is_header_metadata) { |
| error = lzma_vli_sum(size, info->stream_start_offset, |
| LZMA_STREAM_HEADER_SIZE); |
| } else if (!info->index.is_final) { |
| // Since we don't know if we have all the Index Records yet, |
| // we cannot know where the Footer Metadata Block is. |
| error = true; |
| } else { |
| error = lzma_vli_sum4(size, info->stream_start_offset, |
| LZMA_STREAM_HEADER_SIZE, |
| info->known.header_metadata_size, |
| info->known.total_size); |
| } |
| |
| return error ? LZMA_VLI_VALUE_UNKNOWN : size; |
| } |
| |
| |
| extern LZMA_API uint32_t |
| lzma_info_metadata_alignment_get( |
| const lzma_info *info, lzma_bool is_header_metadata) |
| { |
| uint32_t alignment; |
| |
| if (is_header_metadata) { |
| alignment = info->stream_start_offset |
| + LZMA_STREAM_HEADER_SIZE; |
| } else { |
| alignment = info->stream_start_offset + LZMA_STREAM_HEADER_SIZE |
| + info->known.header_metadata_size |
| + info->known.total_size; |
| } |
| |
| return alignment; |
| } |
| |
| |
| extern LZMA_API lzma_ret |
| lzma_info_iter_locate(lzma_info_iter *iter, lzma_allocator *allocator, |
| lzma_vli uncompressed_offset, lzma_bool allow_alloc) |
| { |
| if (iter == NULL || uncompressed_offset > LZMA_VLI_VALUE_MAX) |
| return LZMA_PROG_ERROR; |
| |
| // Quick check in case Index is final. |
| if (iter_info->index.is_final) { |
| assert(iter_info->known.uncompressed_size |
| == iter_info->index.uncompressed_size); |
| if (uncompressed_offset >= iter_info->index.uncompressed_size) |
| return LZMA_DATA_ERROR; |
| } |
| |
| // TODO: Optimize so that it uses existing info from *iter when |
| // seeking forward. |
| |
| // Initialize *iter |
| if (iter_info->known.header_metadata_size != LZMA_VLI_VALUE_UNKNOWN) { |
| if (lzma_vli_sum3(iter->stream_offset, |
| iter_info->stream_start_offset, |
| LZMA_STREAM_HEADER_SIZE, |
| iter_info->known.header_metadata_size)) |
| return LZMA_PROG_ERROR; |
| } else { |
| // We don't know the Size of Header Metadata Block, thus |
| // we cannot calculate the Stream offset either. |
| iter->stream_offset = LZMA_VLI_VALUE_UNKNOWN; |
| } |
| |
| iter->uncompressed_offset = 0; |
| |
| // If we have no Index Records, it's obvious that we need to |
| // add a new one. |
| if (iter_info->index.head == NULL) { |
| assert(!iter_info->index.is_final); |
| if (!allow_alloc) |
| return LZMA_DATA_ERROR; |
| |
| return lzma_info_iter_next(iter, allocator); |
| } |
| |
| // Locate an appropriate Index Record. |
| lzma_index *i = iter_info->index.head; |
| while (true) { |
| // - If Uncompressed Size in the Record is unknown, |
| // we have no chance to search further. |
| // - If the next Record would go past the requested offset, |
| // we have found our target Data Block. |
| if (i->uncompressed_size == LZMA_VLI_VALUE_UNKNOWN |
| || iter->uncompressed_offset |
| + i->uncompressed_size > uncompressed_offset) { |
| iter->total_size = i->total_size; |
| iter->uncompressed_size = i->uncompressed_size; |
| iter->internal[ITER_INDEX] = i; |
| return LZMA_OK; |
| } |
| |
| // Update the stream offset. It may be unknown if we didn't |
| // know the size of Header Metadata Block. |
| if (iter->stream_offset != LZMA_VLI_VALUE_UNKNOWN) |
| if (lzma_vli_add(iter->stream_offset, i->total_size)) |
| return LZMA_PROG_ERROR; |
| |
| // Update the uncompressed offset. This cannot overflow since |
| // the Index is known to be valid. |
| iter->uncompressed_offset += i->uncompressed_size; |
| |
| // Move to the next Block. |
| if (i->next == NULL) { |
| assert(!iter_info->index.is_final); |
| if (!allow_alloc) |
| return LZMA_DATA_ERROR; |
| |
| iter->internal[ITER_INDEX] = i; |
| return lzma_info_iter_next(iter, allocator); |
| } |
| |
| i = i->next; |
| } |
| } |