blob: 4b90143736be0abee95b842df5c2cb57f191a54e [file] [log] [blame]
/*
* Copyright (C) 2019 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO move to RealBufferedSource class: https://youtrack.jetbrains.com/issue/KT-20427
@file:Suppress("NOTHING_TO_INLINE")
package okio.internal
import okio.Buffer
import okio.BufferedSource
import okio.ByteString
import okio.EOFException
import okio.Options
import okio.PeekSource
import okio.RealBufferedSource
import okio.Segment
import okio.Sink
import okio.buffer
import okio.checkOffsetAndCount
internal inline fun RealBufferedSource.commonRead(sink: Buffer, byteCount: Long): Long {
require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
check(!closed) { "closed" }
if (buffer.size == 0L) {
val read = source.read(buffer, Segment.SIZE.toLong())
if (read == -1L) return -1L
}
val toRead = minOf(byteCount, buffer.size)
return buffer.read(sink, toRead)
}
internal inline fun RealBufferedSource.commonExhausted(): Boolean {
check(!closed) { "closed" }
return buffer.exhausted() && source.read(buffer, Segment.SIZE.toLong()) == -1L
}
internal inline fun RealBufferedSource.commonRequire(byteCount: Long) {
if (!request(byteCount)) throw EOFException()
}
internal inline fun RealBufferedSource.commonRequest(byteCount: Long): Boolean {
require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
check(!closed) { "closed" }
while (buffer.size < byteCount) {
if (source.read(buffer, Segment.SIZE.toLong()) == -1L) return false
}
return true
}
internal inline fun RealBufferedSource.commonReadByte(): Byte {
require(1)
return buffer.readByte()
}
internal inline fun RealBufferedSource.commonReadByteString(): ByteString {
buffer.writeAll(source)
return buffer.readByteString()
}
internal inline fun RealBufferedSource.commonReadByteString(byteCount: Long): ByteString {
require(byteCount)
return buffer.readByteString(byteCount)
}
internal inline fun RealBufferedSource.commonSelect(options: Options): Int {
check(!closed) { "closed" }
while (true) {
val index = buffer.selectPrefix(options, selectTruncated = true)
when (index) {
-1 -> {
return -1
}
-2 -> {
// We need to grow the buffer. Do that, then try it all again.
if (source.read(buffer, Segment.SIZE.toLong()) == -1L) return -1
}
else -> {
// We matched a full byte string: consume it and return it.
val selectedSize = options.byteStrings[index].size
buffer.skip(selectedSize.toLong())
return index
}
}
}
}
internal inline fun RealBufferedSource.commonReadByteArray(): ByteArray {
buffer.writeAll(source)
return buffer.readByteArray()
}
internal inline fun RealBufferedSource.commonReadByteArray(byteCount: Long): ByteArray {
require(byteCount)
return buffer.readByteArray(byteCount)
}
internal inline fun RealBufferedSource.commonReadFully(sink: ByteArray) {
try {
require(sink.size.toLong())
} catch (e: EOFException) {
// The underlying source is exhausted. Copy the bytes we got before rethrowing.
var offset = 0
while (buffer.size > 0L) {
val read = buffer.read(sink, offset, buffer.size.toInt())
if (read == -1) throw AssertionError()
offset += read
}
throw e
}
buffer.readFully(sink)
}
internal inline fun RealBufferedSource.commonRead(sink: ByteArray, offset: Int, byteCount: Int): Int {
checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong())
if (buffer.size == 0L) {
val read = source.read(buffer, Segment.SIZE.toLong())
if (read == -1L) return -1
}
val toRead = okio.minOf(byteCount, buffer.size).toInt()
return buffer.read(sink, offset, toRead)
}
internal inline fun RealBufferedSource.commonReadFully(sink: Buffer, byteCount: Long) {
try {
require(byteCount)
} catch (e: EOFException) {
// The underlying source is exhausted. Copy the bytes we got before rethrowing.
sink.writeAll(buffer)
throw e
}
buffer.readFully(sink, byteCount)
}
internal inline fun RealBufferedSource.commonReadAll(sink: Sink): Long {
var totalBytesWritten: Long = 0
while (source.read(buffer, Segment.SIZE.toLong()) != -1L) {
val emitByteCount = buffer.completeSegmentByteCount()
if (emitByteCount > 0L) {
totalBytesWritten += emitByteCount
sink.write(buffer, emitByteCount)
}
}
if (buffer.size > 0L) {
totalBytesWritten += buffer.size
sink.write(buffer, buffer.size)
}
return totalBytesWritten
}
internal inline fun RealBufferedSource.commonReadUtf8(): String {
buffer.writeAll(source)
return buffer.readUtf8()
}
internal inline fun RealBufferedSource.commonReadUtf8(byteCount: Long): String {
require(byteCount)
return buffer.readUtf8(byteCount)
}
internal inline fun RealBufferedSource.commonReadUtf8Line(): String? {
val newline = indexOf('\n'.toByte())
return if (newline == -1L) {
if (buffer.size != 0L) {
readUtf8(buffer.size)
} else {
null
}
} else {
buffer.readUtf8Line(newline)
}
}
internal inline fun RealBufferedSource.commonReadUtf8LineStrict(limit: Long): String {
require(limit >= 0) { "limit < 0: $limit" }
val scanLength = if (limit == Long.MAX_VALUE) Long.MAX_VALUE else limit + 1
val newline = indexOf('\n'.toByte(), 0, scanLength)
if (newline != -1L) return buffer.readUtf8Line(newline)
if (scanLength < Long.MAX_VALUE &&
request(scanLength) && buffer[scanLength - 1] == '\r'.toByte() &&
request(scanLength + 1) && buffer[scanLength] == '\n'.toByte()
) {
return buffer.readUtf8Line(scanLength) // The line was 'limit' UTF-8 bytes followed by \r\n.
}
val data = Buffer()
buffer.copyTo(data, 0, okio.minOf(32, buffer.size))
throw EOFException(
"\\n not found: limit=" + minOf(buffer.size, limit) +
" content=" + data.readByteString().hex() + '…'.toString()
)
}
internal inline fun RealBufferedSource.commonReadUtf8CodePoint(): Int {
require(1)
val b0 = buffer[0].toInt()
when {
b0 and 0xe0 == 0xc0 -> require(2)
b0 and 0xf0 == 0xe0 -> require(3)
b0 and 0xf8 == 0xf0 -> require(4)
}
return buffer.readUtf8CodePoint()
}
internal inline fun RealBufferedSource.commonReadShort(): Short {
require(2)
return buffer.readShort()
}
internal inline fun RealBufferedSource.commonReadShortLe(): Short {
require(2)
return buffer.readShortLe()
}
internal inline fun RealBufferedSource.commonReadInt(): Int {
require(4)
return buffer.readInt()
}
internal inline fun RealBufferedSource.commonReadIntLe(): Int {
require(4)
return buffer.readIntLe()
}
internal inline fun RealBufferedSource.commonReadLong(): Long {
require(8)
return buffer.readLong()
}
internal inline fun RealBufferedSource.commonReadLongLe(): Long {
require(8)
return buffer.readLongLe()
}
internal inline fun RealBufferedSource.commonReadDecimalLong(): Long {
require(1)
var pos = 0L
while (request(pos + 1)) {
val b = buffer[pos]
if ((b < '0'.toByte() || b > '9'.toByte()) && (pos != 0L || b != '-'.toByte())) {
// Non-digit, or non-leading negative sign.
if (pos == 0L) {
throw NumberFormatException("Expected leading [0-9] or '-' character but was 0x${b.toString(16)}")
}
break
}
pos++
}
return buffer.readDecimalLong()
}
internal inline fun RealBufferedSource.commonReadHexadecimalUnsignedLong(): Long {
require(1)
var pos = 0
while (request((pos + 1).toLong())) {
val b = buffer[pos.toLong()]
if ((b < '0'.toByte() || b > '9'.toByte()) &&
(b < 'a'.toByte() || b > 'f'.toByte()) &&
(b < 'A'.toByte() || b > 'F'.toByte())
) {
// Non-digit, or non-leading negative sign.
if (pos == 0) {
throw NumberFormatException("Expected leading [0-9a-fA-F] character but was 0x${b.toString(16)}")
}
break
}
pos++
}
return buffer.readHexadecimalUnsignedLong()
}
internal inline fun RealBufferedSource.commonSkip(byteCount: Long) {
var byteCount = byteCount
check(!closed) { "closed" }
while (byteCount > 0) {
if (buffer.size == 0L && source.read(buffer, Segment.SIZE.toLong()) == -1L) {
throw EOFException()
}
val toSkip = minOf(byteCount, buffer.size)
buffer.skip(toSkip)
byteCount -= toSkip
}
}
internal inline fun RealBufferedSource.commonIndexOf(b: Byte, fromIndex: Long, toIndex: Long): Long {
var fromIndex = fromIndex
check(!closed) { "closed" }
require(fromIndex in 0L..toIndex) { "fromIndex=$fromIndex toIndex=$toIndex" }
while (fromIndex < toIndex) {
val result = buffer.indexOf(b, fromIndex, toIndex)
if (result != -1L) return result
// The byte wasn't in the buffer. Give up if we've already reached our target size or if the
// underlying stream is exhausted.
val lastBufferSize = buffer.size
if (lastBufferSize >= toIndex || source.read(buffer, Segment.SIZE.toLong()) == -1L) return -1L
// Continue the search from where we left off.
fromIndex = maxOf(fromIndex, lastBufferSize)
}
return -1L
}
internal inline fun RealBufferedSource.commonIndexOf(bytes: ByteString, fromIndex: Long): Long {
var fromIndex = fromIndex
check(!closed) { "closed" }
while (true) {
val result = buffer.indexOf(bytes, fromIndex)
if (result != -1L) return result
val lastBufferSize = buffer.size
if (source.read(buffer, Segment.SIZE.toLong()) == -1L) return -1L
// Keep searching, picking up from where we left off.
fromIndex = maxOf(fromIndex, lastBufferSize - bytes.size + 1)
}
}
internal inline fun RealBufferedSource.commonIndexOfElement(targetBytes: ByteString, fromIndex: Long): Long {
var fromIndex = fromIndex
check(!closed) { "closed" }
while (true) {
val result = buffer.indexOfElement(targetBytes, fromIndex)
if (result != -1L) return result
val lastBufferSize = buffer.size
if (source.read(buffer, Segment.SIZE.toLong()) == -1L) return -1L
// Keep searching, picking up from where we left off.
fromIndex = maxOf(fromIndex, lastBufferSize)
}
}
internal inline fun RealBufferedSource.commonRangeEquals(
offset: Long,
bytes: ByteString,
bytesOffset: Int,
byteCount: Int
): Boolean {
check(!closed) { "closed" }
if (offset < 0L ||
bytesOffset < 0 ||
byteCount < 0 ||
bytes.size - bytesOffset < byteCount
) {
return false
}
for (i in 0 until byteCount) {
val bufferOffset = offset + i
if (!request(bufferOffset + 1)) return false
if (buffer[bufferOffset] != bytes[bytesOffset + i]) return false
}
return true
}
internal inline fun RealBufferedSource.commonPeek(): BufferedSource {
return PeekSource(this).buffer()
}
internal inline fun RealBufferedSource.commonClose() {
if (closed) return
closed = true
source.close()
buffer.clear()
}
internal inline fun RealBufferedSource.commonTimeout() = source.timeout()
internal inline fun RealBufferedSource.commonToString() = "buffer($source)"