public FindIndexResult find(long streamId, long startOffset, long endOffset, int maxBytes) {
            long nextStartOffset = startOffset;
            int nextMaxBytes = maxBytes;
            boolean matched = false;
            boolean isFulfilled = false;
            List<StreamDataBlock> rst = new LinkedList<>();
            IndexBlockOrderedBytes indexBlockOrderedBytes = new IndexBlockOrderedBytes(this);
            int startIndex = indexBlockOrderedBytes.search(new IndexBlockOrderedBytes.TargetStreamOffset(streamId, startOffset));
            if (startIndex < 0) {
                // mismatched
                return new FindIndexResult(false, nextStartOffset, nextMaxBytes, rst);
            }
            for (int i = startIndex; i < count(); i++) {
                DataBlockIndex index = get(i);
                if (index.streamId() == streamId) {
                    if (nextStartOffset < index.startOffset()) {
                        break;
                    }
                    if (index.endOffset() <= nextStartOffset) {
                        continue;
                    }
                    matched = nextStartOffset == index.startOffset();
                    nextStartOffset = index.endOffset();
                    rst.add(new StreamDataBlock(s3ObjectMetadata == null ? Constants.NOOP_EPOCH : s3ObjectMetadata.objectId(), index));

                    // we consider first block as not matched because we do not know exactly how many bytes are within
                    // the range in first block, as a result we may read one more block than expected.
                    if (matched) {
                        int recordPayloadSize = index.size()
                            - index.recordCount() * StreamRecordBatchCodec.HEADER_SIZE // sum of encoded record header size
                            - ObjectWriter.DataBlock.BLOCK_HEADER_SIZE; // block header size
                        nextMaxBytes -= Math.min(nextMaxBytes, recordPayloadSize);
                    }
                    if ((endOffset != NOOP_OFFSET && nextStartOffset >= endOffset) || nextMaxBytes == 0) {
                        isFulfilled = true;
                        break;
                    }
                } else if (matched) {
                    break;
                }
            }
            return new FindIndexResult(isFulfilled, nextStartOffset, nextMaxBytes, rst);
        }
@param dataBlockSize The total size of the data blocks, which equals to index start position.
@param indexBlock    raw index data.
s3stream/src/main/java/com/automq/stream/s3/ObjectReader.java