• ogg_decoder_aspect.c
  • /* libFLAC - Free Lossless Audio Codec
     * Copyright (C) 2002-2009  Josh Coalson
     * Copyright (C) 2011-2025  Xiph.Org Foundation
     *
     * 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.
     *
     * - Neither the name of the Xiph.org Foundation nor the names of its
     * contributors may be used to endorse or promote products derived from
     * this software without specific prior written permission.
     *
     * 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 FOUNDATION 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.
     */
    
    #ifdef HAVE_CONFIG_H
    #  include <config.h>
    #endif
    
    #include <string.h> /* for memcpy() */
    #include "FLAC/assert.h"
    #include "share/alloc.h" /* for free() */
    #include "private/ogg_decoder_aspect.h"
    #include "private/ogg_mapping.h"
    #include "private/macros.h"
    
    static FLAC__OggDecoderAspectReadStatus read_more_data_(FLAC__OggDecoderAspect *aspect, FLAC__OggDecoderAspectReadCallbackProxy read_callback, size_t bytes_requested, const FLAC__StreamDecoder *decoder, void *client_data);
    
    /***********************************************************************
     *
     * Public class methods
     *
     ***********************************************************************/
    
    static FLAC__OggDecoderAspectReadStatus read_more_data_(FLAC__OggDecoderAspect *aspect, FLAC__OggDecoderAspectReadCallbackProxy read_callback, size_t bytes_requested, const FLAC__StreamDecoder *decoder, void *client_data)
    {
    	static const size_t OGG_BYTES_CHUNK = 8192;
    	const size_t ogg_bytes_to_read = flac_max(bytes_requested, OGG_BYTES_CHUNK);
    	char *oggbuf = ogg_sync_buffer(&aspect->sync_state, ogg_bytes_to_read);
    
    	if(0 == oggbuf) {
    		return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
    	}
    	else {
    		size_t ogg_bytes_read = ogg_bytes_to_read;
    
    		switch(read_callback(decoder, (FLAC__byte*)oggbuf, &ogg_bytes_read, client_data)) {
    			case FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK:
    				break;
    			case FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM:
    				aspect->end_of_stream = true;
    				break;
    			case FLAC__OGG_DECODER_ASPECT_READ_STATUS_ABORT:
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ABORT;
    			default:
    				FLAC__ASSERT(0);
    		}
    
    		if(ogg_sync_wrote(&aspect->sync_state, ogg_bytes_read) < 0) {
    			/* double protection; this will happen if the read callback returns more bytes than the max requested, which would overflow Ogg's internal buffer */
    			FLAC__ASSERT(0);
    			return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
    		}
    	}
    	return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
    }
    
    static FLAC__OggDecoderAspectReadStatus process_page_(FLAC__OggDecoderAspect *aspect, FLAC__StreamDecoderTellCallback tell_callback, const FLAC__StreamDecoder *decoder, void *client_data)
    {
    	/* got a page, grab the serial number if necessary */
    	if(aspect->need_serial_number) {
    		/* Check whether not FLAC. The next if is somewhat confusing: check
    		 * whether the length of the next page body agrees with the length
    		 * of a FLAC 'header' possibly contained in that page */
    		if(aspect->working_page.body_len > (long)(1 + FLAC__OGG_MAPPING_MAGIC_LENGTH) &&
    		   aspect->working_page.body[0] == FLAC__OGG_MAPPING_FIRST_HEADER_PACKET_TYPE &&
    		   memcmp((&aspect->working_page.body) + 1, FLAC__OGG_MAPPING_MAGIC, FLAC__OGG_MAPPING_MAGIC_LENGTH)) {
    			aspect->bos_flag_seen = true;
    			aspect->serial_number = ogg_page_serialno(&aspect->working_page);
    			ogg_stream_reset_serialno(&aspect->stream_state, aspect->serial_number);
    			aspect->need_serial_number = false;
    
    			if(aspect->current_linknumber_advance_read >= aspect->number_of_links_detected) {
    				FLAC__uint64 tell_offset;
    				aspect->number_of_links_detected = aspect->current_linknumber_advance_read + 1;
    				aspect->linkdetails[aspect->current_linknumber_advance_read].serial_number = aspect->serial_number;
    				if(tell_callback != 0) {
    					if(tell_callback(decoder, &tell_offset, client_data) == FLAC__STREAM_DECODER_TELL_STATUS_OK)
    						aspect->linkdetails[aspect->current_linknumber_advance_read].start_byte = tell_offset - aspect->sync_state.fill + aspect->sync_state.returned
    						                                                                          - aspect->working_page.header_len - aspect->working_page.body_len;
    				}
    			}
    		}
    	}
    	if(aspect->beginning_of_link) {
    		if(aspect->bos_flag_seen && !ogg_page_bos(&aspect->working_page)) {
    			/* Page does not have BOS flag, which means we're done scanning for other serial numbers */
    			aspect->beginning_of_link = false;
    		}
    	}
    	if(ogg_stream_pagein(&aspect->stream_state, &aspect->working_page) == 0) {
    		aspect->have_working_page = true;
    		aspect->have_working_packet = false;
    	}
    	else if(aspect->beginning_of_link) {
    		/* At the beginning of a link, store the serial numbers of all other streams, to make
    		 * finding the end of a link through seeking possible */
    		if(ogg_page_bos(&aspect->working_page)) {
    			aspect->bos_flag_seen = true;
    			if(aspect->current_linknumber_advance_read >= aspect->number_of_links_indexed) {
    				FLAC__OggDecoderAspect_LinkDetails * current_link = &aspect->linkdetails[aspect->current_linknumber_advance_read];
    				/* Reallocate in chunks of 4 */
    				if((current_link->number_of_other_streams) % 4 == 0) {
    					long * tmpptr = NULL;
    					if(NULL == (tmpptr = safe_realloc_nofree_mul_2op_(current_link->other_serial_numbers, 4+current_link->number_of_other_streams, sizeof(long)))) {
    						return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
    					}
    					current_link->other_serial_numbers = tmpptr;
    				}
    				current_link->other_serial_numbers[current_link->number_of_other_streams] = ogg_page_serialno(&aspect->working_page);
    				current_link->number_of_other_streams++;
    			}
    		}
    		/* No BOS flag seen yet, these pages might be still from previous link */
    	}
    	/* else do nothing, could be a page from another stream */
    	return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
    }
    
    static FLAC__bool check_size_of_link_allocation_(FLAC__OggDecoderAspect *aspect)
    {
    	/* double on reallocating */
    	if(aspect->current_linknumber >= aspect->number_of_links_allocated || aspect->current_linknumber_advance_read >= aspect->number_of_links_allocated) {
    		FLAC__OggDecoderAspect_LinkDetails * tmpptr = NULL;
    		if(NULL == (tmpptr = safe_realloc_nofree_mul_2op_(aspect->linkdetails,2*aspect->number_of_links_allocated,sizeof(FLAC__OggDecoderAspect_LinkDetails)))) {
    			return false;
    		}
    		aspect->linkdetails = tmpptr;
    		memset(aspect->linkdetails + aspect->number_of_links_allocated, 0, aspect->number_of_links_allocated * sizeof(FLAC__OggDecoderAspect_LinkDetails));
    		aspect->number_of_links_allocated *= 2;
    	}
    	return true;
    }
    
    FLAC__bool FLAC__ogg_decoder_aspect_init(FLAC__OggDecoderAspect *aspect)
    {
    	/* we will determine the serial number later if necessary */
    	if(ogg_stream_init(&aspect->stream_state, aspect->serial_number) != 0)
    		return false;
    
    	if(ogg_sync_init(&aspect->sync_state) != 0)
    		return false;
    
    	aspect->version_major = ~(0u);
    	aspect->version_minor = ~(0u);
    
    	aspect->need_serial_number = aspect->use_first_serial_number || aspect->decode_chained_stream;
    
    	aspect->end_of_stream = false;
    	aspect->have_working_page = false;
    	aspect->end_of_link = false;
    
    	aspect->current_linknumber = 0;
    	aspect->current_linknumber_advance_read = 0;
    	aspect->number_of_links_indexed = 0;
    	aspect->number_of_links_detected = 0;
    	aspect->number_of_links_allocated = 0;
    
    	if(NULL == (aspect->linkdetails = safe_realloc_mul_2op_(NULL,4,sizeof(FLAC__OggDecoderAspect_LinkDetails))))
    		return false;
    	memset(aspect->linkdetails, 0, 4 * sizeof(FLAC__OggDecoderAspect_LinkDetails));
    
    	aspect->number_of_links_allocated = 4;
    
    	return true;
    }
    
    void FLAC__ogg_decoder_aspect_finish(FLAC__OggDecoderAspect *aspect)
    {
    	uint32_t i;
    	(void)ogg_sync_clear(&aspect->sync_state);
    	(void)ogg_stream_clear(&aspect->stream_state);
    	if(NULL != aspect->linkdetails) {
    		for(i = 0; i < aspect->number_of_links_allocated; i++)
    			free(aspect->linkdetails[i].other_serial_numbers);
    		free(aspect->linkdetails);
    	}
    	aspect->linkdetails = NULL;
    }
    
    void FLAC__ogg_decoder_aspect_set_serial_number(FLAC__OggDecoderAspect *aspect, long value)
    {
    	aspect->use_first_serial_number = false;
    	aspect->serial_number = value;
    }
    
    void FLAC__ogg_decoder_aspect_set_defaults(FLAC__OggDecoderAspect *aspect)
    {
    	aspect->use_first_serial_number = true;
    	aspect->decode_chained_stream = false;
    }
    
    void FLAC__ogg_decoder_aspect_flush(FLAC__OggDecoderAspect *aspect)
    {
    	(void)ogg_stream_reset(&aspect->stream_state);
    	(void)ogg_sync_reset(&aspect->sync_state);
    	aspect->end_of_stream = false;
    	aspect->have_working_page = false;
    	aspect->end_of_link = false;
    }
    
    void FLAC__ogg_decoder_aspect_reset(FLAC__OggDecoderAspect *aspect)
    {
    	FLAC__ogg_decoder_aspect_flush(aspect);
    	aspect->current_linknumber = 0;
    	aspect->current_linknumber_advance_read = 0;
    
    	if(aspect->use_first_serial_number || aspect->decode_chained_stream)
    		aspect->need_serial_number = true;
    
    	aspect->beginning_of_link = true;
    	aspect->bos_flag_seen = false;
    }
    
    void FLAC__ogg_decoder_aspect_next_link(FLAC__OggDecoderAspect* aspect)
    {
    	aspect->end_of_link = false;
    	aspect->current_linknumber++;
    	aspect->beginning_of_link = true;
    	aspect->bos_flag_seen = false;
    }
    
    void FLAC__ogg_decoder_aspect_set_decode_chained_stream(FLAC__OggDecoderAspect* aspect, FLAC__bool value)
    {
    	aspect->decode_chained_stream = value;
    }
    
    FLAC__bool FLAC__ogg_decoder_aspect_get_decode_chained_stream(FLAC__OggDecoderAspect* aspect)
    {
    	return aspect->decode_chained_stream;
    }
    
    FLAC__OggDecoderAspect_TargetLink * FLAC__ogg_decoder_aspect_get_target_link(FLAC__OggDecoderAspect* aspect, FLAC__uint64 target_sample)
    {
    	/* This returns the link containing the seek target if known. In
    	 * effect, this function always returns NULL if no links have been
    	 * indexed */
    
    	uint32_t current_link = 0;
    	uint32_t total_samples = 0;
    
    	while(current_link < aspect->number_of_links_indexed) {
    		total_samples += aspect->linkdetails[current_link].samples;
    		if(target_sample < total_samples) {
    			aspect->target_link.serial_number = aspect->linkdetails[current_link].serial_number;
    			aspect->target_link.start_byte = aspect->linkdetails[current_link].start_byte;
    			aspect->target_link.samples_in_preceding_links = total_samples - aspect->linkdetails[current_link].samples;
    			aspect->target_link.end_byte = aspect->linkdetails[current_link].end_byte;
    			aspect->target_link.samples_this_link = aspect->linkdetails[current_link].samples;
    			aspect->target_link.linknumber = current_link;
    			return &aspect->target_link;
    		}
    		current_link++;
    	}
    	return NULL;
    }
    
    void FLAC__ogg_decoder_aspect_set_seek_parameters(FLAC__OggDecoderAspect *aspect, FLAC__OggDecoderAspect_TargetLink *target_link)
    {
    	if(target_link == 0) {
    		aspect->is_seeking = false;
    	}
    	else {
    		aspect->need_serial_number = false;
    		aspect->current_linknumber = target_link->linknumber;
    		aspect->current_linknumber_advance_read = target_link->linknumber;
    		aspect->serial_number = target_link->serial_number;
    		ogg_stream_reset_serialno(&aspect->stream_state, aspect->serial_number);
    		aspect->is_seeking = true;
    	}
    }
    
    
    FLAC__OggDecoderAspectReadStatus FLAC__ogg_decoder_aspect_read_callback_wrapper(FLAC__OggDecoderAspect *aspect, FLAC__byte buffer[], size_t *bytes, FLAC__OggDecoderAspectReadCallbackProxy read_callback, FLAC__StreamDecoderTellCallback tell_callback, const FLAC__StreamDecoder *decoder, void *client_data)
    {
    	const size_t bytes_requested = *bytes;
    
    	const uint32_t header_length =
    		FLAC__OGG_MAPPING_PACKET_TYPE_LENGTH +
    		FLAC__OGG_MAPPING_MAGIC_LENGTH +
    		FLAC__OGG_MAPPING_VERSION_MAJOR_LENGTH +
    		FLAC__OGG_MAPPING_VERSION_MINOR_LENGTH +
    		FLAC__OGG_MAPPING_NUM_HEADERS_LENGTH;
    
    	/*
    	 * The FLAC decoding API uses pull-based reads, whereas Ogg decoding
    	 * is push-based.  In libFLAC, when you ask to decode a frame, the
    	 * decoder will eventually call the read callback to supply some data,
    	 * but how much it asks for depends on how much free space it has in
    	 * its internal buffer.  It does not try to grow its internal buffer
    	 * to accommodate a whole frame because then the internal buffer size
    	 * could not be limited, which is necessary in embedded applications.
    	 *
    	 * Ogg however grows its internal buffer until a whole page is present;
    	 * only then can you get decoded data out.  So we can't just ask for
    	 * the same number of bytes from Ogg, then pass what's decoded down to
    	 * libFLAC.  If what libFLAC is asking for will not contain a whole
    	 * page, then we will get no data from ogg_sync_pageout(), and at the
    	 * same time cannot just read more data from the client for the purpose
    	 * of getting a whole decoded page because the decoded size might be
    	 * larger than libFLAC's internal buffer.
    	 *
    	 * Instead, whenever this read callback wrapper is called, we will
    	 * continually request data from the client until we have at least one
    	 * page, and manage pages internally so that we can send pieces of
    	 * pages down to libFLAC in such a way that we obey its size
    	 * requirement.  To limit the amount of callbacks, we will always try
    	 * to read in enough pages to return the full number of bytes
    	 * requested.
    	 */
    	*bytes = 0;
    	while (*bytes < bytes_requested && !aspect->end_of_stream) {
    		if (aspect->end_of_link && aspect->have_working_page) {
    			/* we've now consumed all packets of this link and have checked that a new page follows it */
    			if(*bytes > 0)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
    			else
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_LINK;
    		}
    		else if (aspect->have_working_page) {
    			if (aspect->have_working_packet) {
    				size_t n = bytes_requested - *bytes;
    				if ((size_t)aspect->working_packet.bytes <= n) {
    					/* the rest of the packet will fit in the buffer */
    					n = aspect->working_packet.bytes;
    					memcpy(buffer, aspect->working_packet.packet, n);
    					*bytes += n;
    					buffer += n;
    					aspect->have_working_packet = false;
    					if(aspect->working_packet.e_o_s) {
    						if(!aspect->decode_chained_stream)
    							aspect->end_of_stream = true;
    						else {
    							aspect->end_of_link = true;
    							aspect->current_linknumber_advance_read = aspect->current_linknumber + 1;
    							if(!check_size_of_link_allocation_(aspect))
    								return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
    							if(aspect->current_linknumber >= aspect->number_of_links_indexed) {
    								FLAC__uint64 tell_offset;
    								FLAC__ASSERT(aspect->current_linknumber == aspect->number_of_links_indexed);
    								aspect->linkdetails[aspect->current_linknumber].samples = aspect->working_packet.granulepos;
    								if(tell_callback != 0) {
    									if(tell_callback(decoder, &tell_offset, client_data) == FLAC__STREAM_DECODER_TELL_STATUS_OK)
    										aspect->linkdetails[aspect->current_linknumber].end_byte = tell_offset - aspect->sync_state.fill + aspect->sync_state.returned;
    								}
    								aspect->number_of_links_indexed++;
    								aspect->need_serial_number = true;
    							}
    							if(!aspect->is_seeking)
    								aspect->need_serial_number = true;
    							aspect->have_working_page = false; /* e-o-s packet ends page */
    						}
    					}
    				}
    				else {
    					/* only n bytes of the packet will fit in the buffer */
    					memcpy(buffer, aspect->working_packet.packet, n);
    					*bytes += n;
    					buffer += n;
    					aspect->working_packet.packet += n;
    					aspect->working_packet.bytes -= n;
    				}
    			}
    			else {
    				/* try and get another packet */
    				const int ret = ogg_stream_packetout(&aspect->stream_state, &aspect->working_packet);
    				if (ret > 0) {
    					aspect->have_working_packet = true;
    					/* if it is the first header packet, check for magic and a supported Ogg FLAC mapping version */
    					if (aspect->working_packet.bytes > 0 && aspect->working_packet.packet[0] == FLAC__OGG_MAPPING_FIRST_HEADER_PACKET_TYPE) {
    						const FLAC__byte *b = aspect->working_packet.packet;
    						if (aspect->working_packet.bytes < (long)header_length)
    							return FLAC__OGG_DECODER_ASPECT_READ_STATUS_NOT_FLAC;
    						b += FLAC__OGG_MAPPING_PACKET_TYPE_LENGTH;
    						if (memcmp(b, FLAC__OGG_MAPPING_MAGIC, FLAC__OGG_MAPPING_MAGIC_LENGTH))
    							return FLAC__OGG_DECODER_ASPECT_READ_STATUS_NOT_FLAC;
    						b += FLAC__OGG_MAPPING_MAGIC_LENGTH;
    						aspect->version_major = (uint32_t)(*b);
    						b += FLAC__OGG_MAPPING_VERSION_MAJOR_LENGTH;
    						aspect->version_minor = (uint32_t)(*b);
    						if (aspect->version_major != 1)
    							return FLAC__OGG_DECODER_ASPECT_READ_STATUS_UNSUPPORTED_MAPPING_VERSION;
    						aspect->working_packet.packet += header_length;
    						aspect->working_packet.bytes -= header_length;
    					}
    				}
    				else if (ret == 0) {
    					aspect->have_working_page = false;
    				}
    				else { /* ret < 0 */
    					/* lost sync, we'll leave the working page for the next call */
    					return FLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
    				}
    			}
    		}
    		else {
    			/* try and get another page */
    			const int ret = ogg_sync_pageout(&aspect->sync_state, &aspect->working_page);
    			if (ret > 0) {
    				FLAC__OggDecoderAspectReadStatus status = process_page_(aspect, tell_callback, decoder, client_data);
    				if(status != FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK)
    					return status;
    			}
    			else if (ret == 0) {
    				/* need more data */
    				FLAC__OggDecoderAspectReadStatus status = read_more_data_(aspect, read_callback, bytes_requested - *bytes, decoder, client_data);
    				if(status != FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK)
    					return status;
    			}
    			else { /* ret < 0 */
    				/* lost sync, bail out */
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
    			}
    		}
    	}
    
    	if (aspect->end_of_stream && *bytes == 0) {
    		aspect->linkdetails[aspect->current_linknumber].is_last = true;
    		return FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM;
    	}
    
    	return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
    }
    
    FLAC__OggDecoderAspectReadStatus FLAC__ogg_decoder_aspect_skip_link(FLAC__OggDecoderAspect *aspect, FLAC__OggDecoderAspectReadCallbackProxy read_callback, FLAC__StreamDecoderSeekCallback seek_callback, FLAC__StreamDecoderTellCallback tell_callback, FLAC__StreamDecoderLengthCallback length_callback, const FLAC__StreamDecoder *decoder, void *client_data)
    {
    	if(seek_callback == NULL || tell_callback == NULL || length_callback == NULL)
    		return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
    
    	/* This extra check is here, because allocation failures while reading cannot always be
    	 * properly passed down the chain with the current API. So, instead, check again */
    	if(!check_size_of_link_allocation_(aspect))
    		return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
    
    	if(aspect->current_linknumber < aspect->number_of_links_indexed) {
    		if(aspect->linkdetails[aspect->current_linknumber].is_last) {
    			/* Seek to end of stream */
    			FLAC__StreamDecoderLengthStatus lstatus;
    			FLAC__StreamDecoderSeekStatus sstatus;
    			uint64_t stream_length = 0;
    
    			lstatus = length_callback(decoder, &stream_length, client_data);
    			if(lstatus == FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
    			if(lstatus == FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
    
    			sstatus = seek_callback(decoder, stream_length, client_data);
    			if(sstatus == FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
    			if(sstatus == FLAC__STREAM_DECODER_SEEK_STATUS_ERROR)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
    
    			return FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM;
    
    		}
    		else {
    			FLAC__StreamDecoderSeekStatus status;
    			status = seek_callback(decoder, aspect->linkdetails[aspect->current_linknumber].end_byte,client_data);
    			if(status == FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
    			if(status == FLAC__STREAM_DECODER_SEEK_STATUS_ERROR)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
    			FLAC__ogg_decoder_aspect_flush(aspect);
    			aspect->beginning_of_link = true;
    			aspect->need_serial_number = true;
    			aspect->bos_flag_seen = false;
    			aspect->current_linknumber++;
    			aspect->current_linknumber_advance_read = aspect->current_linknumber;
    			if(!check_size_of_link_allocation_(aspect))
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
    			aspect->have_working_page = false;
    			return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
    		}
    	}
    	else
    	{
    		/* End of current link is unknown, go search for it */
    		const uint32_t max_page_size = 65307;
    		uint64_t stream_length = 0;
    		uint64_t current_pos = 0;
    		uint64_t page_pos = 0;
    		uint64_t target_pos = 0;
    		uint64_t left_pos = 0;
    		uint64_t right_pos = 0;
    		FLAC__bool did_a_seek;
    		FLAC__bool seek_to_left_pos = false;
    		FLAC__bool keep_reading = false;
    		FLAC__bool find_bos_twice = aspect->need_serial_number;
    		int ret = 0;
    
    		{
    			FLAC__StreamDecoderLengthStatus lstatus;
    			FLAC__StreamDecoderTellStatus tstatus;
    
    			lstatus = length_callback(decoder, &stream_length, client_data);
    			if(lstatus == FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
    			if(lstatus == FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
    
    			tstatus = tell_callback(decoder, &current_pos, client_data);
    			if(tstatus == FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_CALLBACKS_NONFUNCTIONAL;
    			if(tstatus == FLAC__STREAM_DECODER_TELL_STATUS_ERROR)
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
    		}
    
    
    		current_pos = current_pos - aspect->sync_state.fill + aspect->sync_state.returned;
    		left_pos = current_pos;
    		right_pos = stream_length;
    
    		while(1){
    			FLAC__bool seek_was_to_current_link = true;
    
    			if(right_pos <= left_pos || right_pos - left_pos < 9) {
    				/* FLAC frame is at least 9 byte in size */
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
    			}
    			target_pos = left_pos + (right_pos - left_pos)/2;
    
    			if(keep_reading) {
    				/* To not end up in a loop, we need to keep reading until are able to change left_pos */
    				did_a_seek = false;
    			}
    			else if(current_pos < target_pos && current_pos + aspect->sync_state.fill - aspect->sync_state.returned > target_pos) {
    				/* Target location is already in buffer, keep reading */
    				did_a_seek = false;
    			}
    			else if(current_pos < target_pos && current_pos + max_page_size > target_pos) {
    				/* Target is very close to current location, just reading is probably faster than seeking */
    				did_a_seek = false;
    			}
    			else if(aspect->beginning_of_link) {
    				/* Still need to save all serial numbers at start of stream, so don't seek yet */
    				did_a_seek = false;
    			}
    			else {
    				if(seek_to_left_pos || target_pos - left_pos < max_page_size) {
    					/* Seek to start of bisect area */
    					target_pos = left_pos;
    					keep_reading = true;
    					seek_to_left_pos = false;
    				}
    				/* Seek */
    				if(seek_callback(decoder, target_pos, client_data) != FLAC__STREAM_DECODER_SEEK_STATUS_OK)
    					return false;
    				did_a_seek = true;
    				FLAC__ASSERT(tell_callback(decoder, &current_pos, client_data) == FLAC__STREAM_DECODER_TELL_STATUS_OK);
    				FLAC__ASSERT(current_pos == target_pos);
    				current_pos = target_pos;
    				(void)ogg_stream_reset(&aspect->stream_state);
    				(void)ogg_sync_reset(&aspect->sync_state);
    			}
    
    			/* Get a page, resynchronize if necessary */
    			while((ret = ogg_sync_pageseek(&aspect->sync_state, &aspect->working_page)) <= 0 && !aspect->end_of_stream) {
    				if(ret < 0)
    					current_pos -= ret;
    				else {
    					/* need more data */
    					FLAC__OggDecoderAspectReadStatus status = read_more_data_(aspect, read_callback, 0, decoder, client_data);
    					if(status != FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK)
    						return status;
    				}
    			}
    
    			page_pos = current_pos;
    			current_pos += aspect->working_page.header_len + aspect->working_page.body_len;
    
    			/* Check whether the page serial number belongs to this link or another link */
    			if (ret > 0) {
    				if(!aspect->beginning_of_link) {
    					/* got a page, check the serial number */
    					long serial_number = ogg_page_serialno(&aspect->working_page);
    					uint32_t i;
    					seek_was_to_current_link = false;
    					if(serial_number == aspect->linkdetails[aspect->current_linknumber].serial_number) {
    						/* This page belongs to current link */
    						seek_was_to_current_link = true;
    					}
    					for(i = 0; i < aspect->linkdetails[aspect->current_linknumber].number_of_other_streams; i++) {
    						if(serial_number == aspect->linkdetails[aspect->current_linknumber].other_serial_numbers[i]) {
    							/* This page belongs to the current link */
    							seek_was_to_current_link = true;
    						}
    					}
    					if(ogg_page_serialno(&aspect->working_page) == aspect->linkdetails[aspect->current_linknumber].serial_number && ogg_page_eos(&aspect->working_page)) {
    						/* Found EOS */
    						aspect->linkdetails[aspect->current_linknumber].end_byte = current_pos;
    						aspect->linkdetails[aspect->current_linknumber].samples = ogg_page_granulepos(&aspect->working_page);
    
    						aspect->number_of_links_indexed++;
    						aspect->current_linknumber_advance_read = aspect->current_linknumber + 1;
    						if(!check_size_of_link_allocation_(aspect))
    							return FLAC__OGG_DECODER_ASPECT_READ_STATUS_MEMORY_ALLOCATION_ERROR;
    
    						aspect->need_serial_number = true;
    
    						/* Now, continue loop to check whether we are at end of stream or another link follows */
    						FLAC__ogg_decoder_aspect_next_link(aspect);
    						continue;
    					}
    					if(seek_was_to_current_link) {
    						/* If the seek landed on a page with the serial number of the stream we're interested in, we can be sure
    						 * the EOS page will be later in the stream. If the seek landed on a stream with a different serial number
    						 * however, we cannot be sure. It could be the EOS page of that stream has already been seen. In that case
    						 * something else needs to be done, else we could end up in an endless loop */
    						if(ogg_page_serialno(&aspect->working_page) == aspect->linkdetails[aspect->current_linknumber].serial_number) {
    							left_pos = current_pos;
    							keep_reading = false;
    						}
    						else {
    							seek_to_left_pos = true;
    						}
    					}
    					else if(keep_reading) {
    						/* We read from the left_pos but found nothing interesting, so we can move left_pos up */
    						left_pos = current_pos;
    					}
    					else if(did_a_seek) {
    						if(right_pos <= page_pos) {
    							/* Ended up somewhere we've already been */
    							seek_to_left_pos = true;
    						}
    						else
    							right_pos = page_pos;
    					}
    					else {
    						/* Read forward but found an unknown serial number */
    						return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
    					}
    				}
    				else { /* aspect->beginning_of_link == true */
    					if(aspect->end_of_stream) {
    						if(aspect->current_linknumber == 0)
    							return FLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
    						aspect->current_linknumber--;
    						aspect->linkdetails[aspect->current_linknumber].is_last = true;
    						return FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM;
    					}
    					else {
    						/* We can end up here for three reasons:
    						 * 1) We've found the end of the link and are looking whether a new one follows it. If that is the case,
    						 *    we need to finish after we found it
    						 * 2) We've just started skipping this link, and need to look for other BOS pages first. If that is the case,
    						 *    we need to continue after we found them
    						 * 3) We've don't know anything about the link that needs to be skipped yet, and we need to process the BOS
    						 *    pages first, and process the BOS pages of the next link */
    						FLAC__bool need_to_finish = aspect->need_serial_number && !find_bos_twice;
    						FLAC__OggDecoderAspectReadStatus status = process_page_(aspect, tell_callback, decoder, client_data);
    						if(status != FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK)
    							return status;
    						if(!aspect->need_serial_number) {
    							if(need_to_finish) {
    								/* Found start of next link, we're done */
    								return FLAC__OGG_DECODER_ASPECT_READ_STATUS_OK;
    							}
    							find_bos_twice = false;
    						}
    						if(!aspect->beginning_of_link) {
    							/* Done scanning BOS pages, move up left_pos */
    							left_pos = page_pos;
    						}
    					}
    				}
    			}
    			else if(aspect->end_of_stream) {
    				if(aspect->beginning_of_link && !aspect->bos_flag_seen) {
    					/* We were looking for the next link, but found end of stream instead */
    					if(aspect->current_linknumber == 0)
    						return FLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
    					aspect->current_linknumber--;
    					aspect->linkdetails[aspect->current_linknumber].is_last = true;
    					return FLAC__OGG_DECODER_ASPECT_READ_STATUS_END_OF_STREAM;
    				}
    				else if(did_a_seek) {
    					/* Seeking to target_pos did no result in finding a page, set right_pos to that value */
    					right_pos = target_pos;
    				}
    				else {
    					/* We expected to find the EOS page without seeking, but ended up at the end of the stream */
    					return FLAC__OGG_DECODER_ASPECT_READ_STATUS_ERROR;
    				}
    			}
    			else if(ret == 0) {
    				/* ogg error */
    				return FLAC__OGG_DECODER_ASPECT_READ_STATUS_LOST_SYNC;
    			}
    		}
    	}
    }