/////////////////////////////////////////////////////////////////////////////// // /// \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 (metadata->header_metadata_size > LZMA_VLI_VALUE_MAX || !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 (eat_index) metadata->index = NULL; 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) return_if_error(lzma_info_size_set( info, LZMA_INFO_HEADER_METADATA, metadata->header_metadata_size)); // Total Size if (metadata->total_size != LZMA_VLI_VALUE_UNKNOWN) return_if_error(lzma_info_size_set(info, LZMA_INFO_TOTAL, metadata->total_size)); // Uncompressed Size if (metadata->uncompressed_size != LZMA_VLI_VALUE_UNKNOWN) return_if_error(lzma_info_size_set(info, LZMA_INFO_UNCOMPRESSED, metadata->uncompressed_size)); 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; } }