public function substr($start, $length)
    {
        $document = $this->getDocument();

        if ($start < 0) {
            $start = max($document['length'] + $start, 0);
        }

        if ($start > $document['length']) {
            return false;
        }

        if ($length < 0) {
            $length = $document['length'] - $start + $length;
            if ($length < 0) {
                return false;
            }
        }

        $chunkSize = $document['chunkSize'];

        $startChunkNumber = floor($start / $chunkSize);

        $chunkIterator = $this->getChunkIterator();

        if (!$chunkIterator->valid()) {
            // invalid iterator state - recreate iterator
            // unable to use `rewind` due to error "Cursors cannot rewind after starting iteration"
            $chunkIterator = $this->getChunkIterator(true);
        }

        if ($chunkIterator->key() > $startChunkNumber) {
            // unable to go back by iterator
            // unable to use `rewind` due to error "Cursors cannot rewind after starting iteration"
            $chunkIterator = $this->getChunkIterator(true);
        }

        $result = '';

        $chunkDataOffset = $start - $startChunkNumber * $chunkSize;
        while ($chunkIterator->valid()) {
            if ($chunkIterator->key() >= $startChunkNumber) {
                $chunk = $chunkIterator->current();
                $data = $chunk['data']->getData();

                $readLength = min($chunkSize - $chunkDataOffset, $length);

                $result .= StringHelper::byteSubstr($data, $chunkDataOffset, $readLength);

                $length -= $readLength;
                if ($length <= 0) {
                    break;
                }

                $chunkDataOffset = 0;
            }

            $chunkIterator->next();
        }

        return $result;
    }