From 0182c3d8f6666a877197382146ad46bb3928408b Mon Sep 17 00:00:00 2001 From: Leonid Stashevsky Date: Thu, 7 Jul 2022 16:21:43 +0300 Subject: [PATCH] Update Buffer implementation (#9) --- src/commonMain/kotlin/io/ktor/io/Buffer.kt | 441 ++++++++++++++++-- .../kotlin/io/ktor/io/BufferOperations.kt | 56 +++ .../io/ktor/io/BufferedBytesDestination.kt | 20 +- .../kotlin/io/ktor/io/BufferedBytesSource.kt | 17 +- .../kotlin/io/ktor/io/ByteArrayBuffer.kt | 221 ++++----- .../kotlin/io/ktor/io/ByteArrayBufferPool.kt | 31 +- .../kotlin/io/ktor/io/ByteArrayOperations.kt | 88 ++++ .../kotlin/io/ktor/io/ByteOperations.kt | 5 + .../kotlin/io/ktor/io/BytesDestination.kt | 2 +- .../io/ktor/io/internal/PlatformUtils.kt | 7 - .../kotlin/io/ktor/io/BufferTest.kt | 200 ++++++++ .../ktor/io/BufferedBytesDestinationTest.kt | 12 +- .../io/ktor/io/BufferedBytesSourceTest.kt | 4 +- .../kotlin/io/ktor/io/ByteArrayBufferTest.kt | 168 +------ .../io/ktor/io/utils/TestBytesDestination.kt | 13 +- .../io/ktor/io/internal/PlatformUtilsJs.kt | 13 - .../kotlin/io/ktor/io/DefaultBuffer.kt | 143 ------ .../kotlin/io/ktor/io/DirectByteBufferPool.kt | 23 +- .../kotlin/io/ktor/io/FileBytesDestination.kt | 17 +- .../kotlin/io/ktor/io/FileBytesSource.kt | 20 +- src/jvmMain/kotlin/io/ktor/io/JvmBuffer.kt | 166 +++++++ .../kotlin/io/ktor/io/JvmBufferPool.kt | 20 + .../io/ktor/io/internal/PlatformUtilsJvm.kt | 23 - .../kotlin/io.ktor.io/DefaultBuffetTest.kt | 42 -- .../{io.ktor.io => io/ktor/io}/FilesTest.kt | 17 +- .../kotlin/io/ktor/io/JvmBufferTest.kt | 34 ++ .../ktor/io/internal/PlatformUtilsNative.kt | 13 - 27 files changed, 1180 insertions(+), 636 deletions(-) create mode 100644 src/commonMain/kotlin/io/ktor/io/BufferOperations.kt create mode 100644 src/commonMain/kotlin/io/ktor/io/ByteArrayOperations.kt create mode 100644 src/commonMain/kotlin/io/ktor/io/ByteOperations.kt delete mode 100644 src/commonMain/kotlin/io/ktor/io/internal/PlatformUtils.kt create mode 100644 src/commonTest/kotlin/io/ktor/io/BufferTest.kt delete mode 100644 src/jsMain/kotlin/io/ktor/io/internal/PlatformUtilsJs.kt delete mode 100644 src/jvmMain/kotlin/io/ktor/io/DefaultBuffer.kt create mode 100644 src/jvmMain/kotlin/io/ktor/io/JvmBuffer.kt create mode 100644 src/jvmMain/kotlin/io/ktor/io/JvmBufferPool.kt delete mode 100644 src/jvmMain/kotlin/io/ktor/io/internal/PlatformUtilsJvm.kt delete mode 100644 src/jvmTest/kotlin/io.ktor.io/DefaultBuffetTest.kt rename src/jvmTest/kotlin/{io.ktor.io => io/ktor/io}/FilesTest.kt (79%) create mode 100644 src/jvmTest/kotlin/io/ktor/io/JvmBufferTest.kt delete mode 100644 src/nativeMain/kotlin/io/ktor/io/internal/PlatformUtilsNative.kt diff --git a/src/commonMain/kotlin/io/ktor/io/Buffer.kt b/src/commonMain/kotlin/io/ktor/io/Buffer.kt index cf65c6c..4c0648a 100644 --- a/src/commonMain/kotlin/io/ktor/io/Buffer.kt +++ b/src/commonMain/kotlin/io/ktor/io/Buffer.kt @@ -1,43 +1,424 @@ package io.ktor.io -public abstract class Buffer { - public abstract var readIndex: Int - public abstract var writeIndex: Int - public abstract val capacity: Int +import kotlin.math.max +import kotlin.math.min - public abstract operator fun get(index: Int): Byte - public abstract operator fun set(index: Int, value: Byte) +/** + * The [Buffer] class represents a mutable sequence of bytes in memory. + * + * The buffer is not thread-safe by default. + */ +public interface Buffer : Closeable { + /** + * The number of bytes can be stored in the buffer. Upper bound for write operations. + */ + public val capacity: Int - public abstract fun readByte(): Byte - public abstract fun readShort(): Short - public abstract fun readInt(): Int - public abstract fun readLong(): Long - public abstract fun writeByte(value: Byte) - public abstract fun writeShort(value: Short) - public abstract fun writeInt(value: Int) - public abstract fun writeLong(value: Long) + /** + * The index in buffer for the read operation. + * + * Should be between 0 and [writeIndex] + */ + public var readIndex: Int - public abstract fun read(destination: ByteArray, startIndex: Int = 0, endIndex: Int = destination.size): Int - public abstract fun write(source: ByteArray, startIndex: Int = 0, endIndex: Int = source.size): Int + /** + * The index in buffer for the write operation. + * + * Should be between the [readIndex] and [capacity]. + */ + public var writeIndex: Int - public abstract fun read(destination: Buffer) - public abstract fun write(source: Buffer) + /** + * Reads [Byte] at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @throws IndexOutOfBoundsException if [index + 1] is greater [capacity]. + */ + public fun getByteAt(index: Int): Byte - public abstract fun release() - public abstract fun compact() + /** + * Writes [Byte] at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @throws IndexOutOfBoundsException if [index + 1] is greater than [capacity]. + */ + public fun setByteAt(index: Int, value: Byte) - public companion object { - public val Empty: Buffer = ByteArrayBuffer(0) + /** + * Reads [Byte] from the buffer at [readIndex]. + * + * @throws IndexOutOfBoundsException if [availableForRead] < 1. + */ + public fun readByte(): Byte { + ensureCanRead(1) + return getByteAt(readIndex++) } -} -public fun Buffer.canRead(): Boolean = readIndex < writeIndex -public fun Buffer.canWrite(): Boolean = writeIndex < capacity + /** + * Writes [Byte] to the buffer at [writeIndex]. + * + * @throws IndexOutOfBoundsException if [availableForWrite] < 1. + */ + public fun writeByte(value: Byte) { + ensureCanWrite(1) + setByteAt(writeIndex++, value) + } + + /** + * Reads [Boolean] at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @throws IndexOutOfBoundsException if [index + 1] is greater [capacity]. + */ + public fun getBooleanAt(index: Int): Boolean = getByteAt(index) != 0.toByte() + + /** + * Writes [Boolean] at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @throws IndexOutOfBoundsException if [index + 1] is greater than [capacity]. + */ + public fun setBooleanAt(index: Int, value: Boolean) { + setByteAt(index, if (value) 1.toByte() else 0.toByte()) + } + + /** + * Read boolean from the buffer at [readIndex]. + * + * @throws IndexOutOfBoundsException if [availableForRead] < 1. + */ + public fun readBoolean(): Boolean = getBooleanAt(readIndex++) -public fun Buffer.readCapacity(): Int = writeIndex - readIndex -public fun Buffer.writeCapacity(): Int = capacity - writeIndex + /** + * Write boolean to the buffer at [writeIndex]. + * + * @throws IndexOutOfBoundsException if [availableForWrite] < 1. + */ + public fun writeBoolean(value: Boolean) { + ensureCanWrite(1) + setBooleanAt(writeIndex++, value) + } -public fun Buffer.reset() { - readIndex = 0 - writeIndex = 0 + /** + * Reads [Short] at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @throws IndexOutOfBoundsException if [index + 2] is greater [capacity]. + */ + public fun getShortAt(index: Int): Short { + ensureCanRead(index, 2) + + val byte1 = getByteAt(index) + val byte2 = getByteAt(index + 1) + return Short(byte1, byte2) + } + + /** + * Writes [Short] at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @throws IndexOutOfBoundsException if [index + 2] is greater than [capacity]. + */ + public fun setShortAt(index: Int, value: Short) { + ensureCanWrite(index, 2) + + setByteAt(index, value.highByte) + setByteAt(index + 1, value.lowByte) + } + + /** + * Writes [Short] to the buffer at [writeIndex]. + * + * @throws IndexOutOfBoundsException if [availableForWrite] < 2. + */ + public fun writeShort(value: Short) { + ensureCanWrite(2) + + setShortAt(writeIndex, value) + writeIndex += 2 + } + + /** + * Reads [Short] from the buffer at [readIndex]. + * + * @throws IndexOutOfBoundsException if [availableForRead] < 2. + */ + public fun readShort(): Short { + ensureCanRead(2) + + val result = getShortAt(readIndex) + readIndex += 2 + return result + } + + /** + * Reads [Int] at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @throws IndexOutOfBoundsException if [index + 4] is greater than [capacity]. + */ + public fun getIntAt(index: Int): Int { + ensureCanRead(index, 4) + + val highShort = getShortAt(index) + val lowShort = getShortAt(index + 2) + return Int(highShort, lowShort) + } + + /** + * Writes [Int] at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @throws IndexOutOfBoundsException if [index + 4] is greater than [capacity]. + */ + public fun setIntAt(index: Int, value: Int) { + ensureCanWrite(index, 4) + + setShortAt(index, value.highShort) + setShortAt(index + 2, value.lowShort) + } + + /** + * Reads [Int] from the buffer at [readIndex]. + * + * @throws IndexOutOfBoundsException if [availableForRead] < 4. + */ + public fun readInt(): Int { + ensureCanRead(4) + + val result = getIntAt(readIndex) + readIndex += 4 + return result + } + + /** + * Writes [Int] to the buffer at [writeIndex]. + * + * @throws IndexOutOfBoundsException if [availableForWrite] < 4. + */ + public fun writeInt(value: Int) { + ensureCanWrite(4) + + setIntAt(writeIndex, value) + writeIndex += 4 + } + + /** + * Reads [Long] at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @throws IndexOutOfBoundsException if [index + 8] is greater than [capacity]. + */ + public fun getLongAt(index: Int): Long { + ensureCanRead(index, 8) + + val highInt = getIntAt(index) + val lowInt = getIntAt(index + 4) + return Long(highInt, lowInt) + } + + /** + * Writes [Long] at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @throws IndexOutOfBoundsException if [index + 8] is greater than [capacity] or not enough space available. + */ + public fun setLongAt(index: Int, value: Long) { + ensureCanWrite(index, 8) + + setIntAt(index, value.highInt) + setIntAt(index + 4, value.lowInt) + } + + /** + * Reads [Long] from the buffer at [readIndex]. + * + * @throws IndexOutOfBoundsException if [availableForRead] < 8. + */ + public fun readLong(): Long { + ensureCanRead(8) + + val result = getLongAt(readIndex) + readIndex += 8 + return result + } + + /** + * Writes [Long] to the buffer at [writeIndex]. + * + * @throws IndexOutOfBoundsException if [availableForWrite] < 8. + */ + public fun writeLong(value: Long) { + ensureCanWrite(8) + + setLongAt(writeIndex, value) + writeIndex += 8 + } + + /** + * Writes as many bytes as possible from the [value] at specific [index]. + * + * The [value.readIndex] increased by amount of copied bytes. + * + * @return Number of written bytes: `min(availableForWrite, buffer.availableForRead)` + */ + public fun copyFromBufferAt(index: Int, value: Buffer): Int { + val count = min(capacity - index, value.availableForRead) + for (currentIndex in 0 until count) { + setByteAt(index + currentIndex, value.getByteAt(value.readIndex++)) + } + + return max(count, 0) + } + + /** + * Write [value] to the current buffer. The implementation depends on the actual buffer implementation. + */ + public fun copyFromBuffer(value: Buffer): Int { + val count = copyFromBufferAt(writeIndex, value) + writeIndex += count + return count + } + + /** + * Copy as much as possible bytes from the current buffer to the [destination] between [startIndex] and [endIndex]. + * + * This operation increase [readIndex] by the number of copied bytes. + * + * @return Number of copied bytes: `min(availableForRead, endPosition - startPosition)` + */ + public fun copyToByteArrayAt( + index: Int, + destination: ByteArray, + startIndex: Int = 0, + endIndex: Int = destination.size + ): Int { + val count = min(endIndex - startIndex, capacity - index) + for (offset in 0 until count) { + destination[startIndex + offset] = getByteAt(index + offset) + } + + return max(count, 0) + } + + /** + * Copy as much as possible bytes from the current buffer to the [destination] between [startIndex] and [endIndex]. + * + * This operation increase [readIndex] by the number of copied bytes. + * + * @return Number of copied bytes: `min(availableForRead, endPosition - startPosition)` + */ + public fun copyToByteArray( + destination: ByteArray, + startIndex: Int = 0, + endIndex: Int = destination.size + ): Int { + val count = min(endIndex - startIndex, availableForRead) + if (count < 0) return 0 + + val result = copyToByteArrayAt(readIndex, destination, startIndex, startIndex + count) + readIndex += result + return result + } + + /** + * Copy all bytes from [value] between [startIndex] and [endIndex] to the buffer at specific [index]. + * + * The operation doesn't modify [readIndex] or [writeIndex]. + * + * @return Number of written bytes: `min(availableForWrite, endPosition - startPosition)` + * @throws IndexOutOfBoundsException if [index] is greater or equal [capacity]. + */ + public fun copyFromByteArrayAt( + index: Int, + value: ByteArray, + startIndex: Int = 0, + endIndex: Int = value.size + ): Int { + val count = min(endIndex - startIndex, capacity - index) + for (offset in 0 until count) { + setByteAt(index + offset, value[startIndex + offset]) + } + + return max(count, 0) + } + + /** + * Copy values from byte array to the buffer at [writeIndex] between [startIndex] and [endIndex]. + * + * @ return number of copied bytes = `min(availableForWrite, endIndex - startIndex)` + */ + public fun copyFromByteArray(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size): Int { + val result = copyFromByteArrayAt(writeIndex, value, startIndex, endIndex) + writeIndex += result + return result + } + + /** + * Move all bytes in range [readIndex], [writeIndex] to range [0] and [writeIndex - readIndex] and modifies the + * [readIndex] and [writeIndex] accordingly. + */ + public fun compact() { + if (readIndex == 0) return + + if (readIndex == writeIndex) { + readIndex = 0 + writeIndex = 0 + return + } + + val count = writeIndex - readIndex + for (index in 0 until count) { + setByteAt(index, getByteAt(readIndex + index)) + } + + readIndex = 0 + writeIndex = count + } + + /** + * Release [Buffer] back to pool if necessary. + */ + override fun close() { + } + + public companion object { + /** + * The buffer with zero capacity. + */ + public val Empty: Buffer = object : Buffer { + override val capacity: Int + get() = 0 + + override var readIndex: Int + get() = 0 + set(value) { + require(value == 0) { "Can't modify default empty buffer" } + } + + override var writeIndex: Int + get() = 0 + set(value) { + require(value == 0) { "Can't modify default empty buffer" } + } + + override fun getByteAt(index: Int): Byte { + throw IndexOutOfBoundsException("Can't read from empty buffer") + } + + override fun setByteAt(index: Int, value: Byte) { + throw IndexOutOfBoundsException("Can't write to empty buffer") + } + } + } } diff --git a/src/commonMain/kotlin/io/ktor/io/BufferOperations.kt b/src/commonMain/kotlin/io/ktor/io/BufferOperations.kt new file mode 100644 index 0000000..3425127 --- /dev/null +++ b/src/commonMain/kotlin/io/ktor/io/BufferOperations.kt @@ -0,0 +1,56 @@ +package io.ktor.io + +public val Buffer.isEmpty: Boolean get() = availableForRead == 0 +public val Buffer.isNotEmpty: Boolean get() = !isEmpty + +public val Buffer.isFull: Boolean get() = availableForWrite == 0 +public val Buffer.isNotFull: Boolean get() = !isFull + +public val Buffer.availableForRead: Int get() = writeIndex - readIndex +public val Buffer.availableForWrite: Int get() = capacity - writeIndex + +internal fun Buffer.reset() { + readIndex = 0 + writeIndex = 0 +} + +public operator fun Buffer.get(index: Int): Byte = getByteAt(index) + +public operator fun Buffer.set(index: Int, value: Byte) { + setByteAt(index, value) +} + +/** + * Check if the Buffer has [count] bytes to read. + * + * @throws IndexOutOfBoundsException if the [count] is greater [availableForRead]. + */ +public fun Buffer.ensureCanRead(count: Int) { + if (availableForRead < count) { + throw IndexOutOfBoundsException("Can't read $count bytes. Available: $availableForRead.") + } +} + +internal fun Buffer.ensureCanRead(index: Int, count: Int) { + if (index + count > capacity) { + throw IndexOutOfBoundsException("Can't read $count bytes at index $index. Capacity: $capacity.") + } +} + + +/** + * Check if the Buffer has space to write [count] bytes. + * + * @throws IndexOutOfBoundsException if the [count] is greater [availableForWrite]. + */ +public fun Buffer.ensureCanWrite(count: Int) { + if (availableForWrite < count) { + throw IndexOutOfBoundsException("Can't write $count bytes. Available space: $availableForWrite.") + } +} + +internal fun Buffer.ensureCanWrite(index: Int, count: Int) { + if (index + count > capacity) { + throw IndexOutOfBoundsException("Can't write $count bytes at index $index. Capacity: $capacity.") + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/io/ktor/io/BufferedBytesDestination.kt b/src/commonMain/kotlin/io/ktor/io/BufferedBytesDestination.kt index 9db677d..bb9cf91 100644 --- a/src/commonMain/kotlin/io/ktor/io/BufferedBytesDestination.kt +++ b/src/commonMain/kotlin/io/ktor/io/BufferedBytesDestination.kt @@ -20,16 +20,16 @@ public class BufferedBytesDestination( override fun canWrite(): Boolean = delegate.canWrite() - override fun write(buffer: Buffer) { + override fun write(buffer: Buffer): Int { closedCause?.let { throw it } - this.buffer.write(buffer) + return this.buffer.copyFromBuffer(buffer) } override suspend fun flush() { closedCause?.let { throw it } - while (buffer.canRead()) { + while (buffer.isNotEmpty) { delegate.write(buffer) delegate.awaitFreeSpace() } @@ -41,7 +41,7 @@ public class BufferedBytesDestination( override suspend fun awaitFreeSpace() { closedCause?.let { throw it } - while (!buffer.canWrite()) { + while (buffer.isFull) { delegate.awaitFreeSpace() delegate.write(buffer) buffer.compact() @@ -49,7 +49,7 @@ public class BufferedBytesDestination( } override fun close(cause: Throwable?) { - buffer.release() + buffer.close() delegate.close(cause) } @@ -60,7 +60,7 @@ public class BufferedBytesDestination( public suspend fun writeByte(value: Byte) { closedCause?.let { throw it } - if (buffer.canWrite()) { + if (buffer.isNotFull) { buffer.writeByte(value) } else { awaitFreeSpace() @@ -73,7 +73,7 @@ public class BufferedBytesDestination( public suspend fun writeShort(value: Short) { closedCause?.let { throw it } - if (buffer.writeCapacity() >= 2) { + if (buffer.availableForWrite >= 2) { buffer.writeShort(value) } else { writeByte(value.highByte) @@ -86,7 +86,7 @@ public class BufferedBytesDestination( public suspend fun writeInt(value: Int) { closedCause?.let { throw it } - if (buffer.writeCapacity() >= 4) { + if (buffer.availableForWrite >= 4) { buffer.writeInt(value) } else { writeShort(value.highShort) @@ -99,7 +99,7 @@ public class BufferedBytesDestination( public suspend fun writeLong(value: Long) { closedCause?.let { throw it } - if (buffer.writeCapacity() >= 8) { + if (buffer.availableForWrite >= 8) { buffer.writeLong(value) } else { writeInt(value.highInt) @@ -110,7 +110,7 @@ public class BufferedBytesDestination( } private suspend fun flushIfFull() { - if (!buffer.canWrite()) { + if (buffer.isFull) { flush() } } diff --git a/src/commonMain/kotlin/io/ktor/io/BufferedBytesSource.kt b/src/commonMain/kotlin/io/ktor/io/BufferedBytesSource.kt index 18334b7..84f6cb9 100644 --- a/src/commonMain/kotlin/io/ktor/io/BufferedBytesSource.kt +++ b/src/commonMain/kotlin/io/ktor/io/BufferedBytesSource.kt @@ -14,17 +14,17 @@ public class BufferedBytesSource( get() = delegate.closedCause override fun canRead(): Boolean { - return delegate.canRead() || buffer.canRead() + return delegate.canRead() || buffer.isNotEmpty } override fun read(): Buffer { closedCause?.let { throw it } - if (buffer.canRead()) { + if (buffer.isNotEmpty) { return buffer } - buffer.release() + buffer.close() buffer = delegate.read() return buffer } @@ -32,8 +32,7 @@ public class BufferedBytesSource( override suspend fun awaitContent() { closedCause?.let { throw it } - if (buffer.canRead()) return - + if (buffer.isNotEmpty) return delegate.awaitContent() } @@ -44,7 +43,7 @@ public class BufferedBytesSource( public suspend fun readByte(): Byte { closedCause?.let { throw it } - while (!buffer.canRead()) { + while (buffer.isEmpty) { awaitContent() read() } @@ -54,7 +53,7 @@ public class BufferedBytesSource( public suspend fun readShort(): Short { closedCause?.let { throw it } - if (buffer.readCapacity() >= 2) { + if (buffer.availableForRead >= 2) { return buffer.readShort() } return Short(readByte(), readByte()) @@ -63,7 +62,7 @@ public class BufferedBytesSource( public suspend fun readInt(): Int { closedCause?.let { throw it } - if (buffer.readCapacity() >= 4) { + if (buffer.availableForRead >= 4) { return buffer.readInt() } return Int(readShort(), readShort()) @@ -72,7 +71,7 @@ public class BufferedBytesSource( public suspend fun readLong(): Long { closedCause?.let { throw it } - if (buffer.readCapacity() >= 8) { + if (buffer.availableForRead >= 8) { return buffer.readLong() } return Long(readInt(), readInt()) diff --git a/src/commonMain/kotlin/io/ktor/io/ByteArrayBuffer.kt b/src/commonMain/kotlin/io/ktor/io/ByteArrayBuffer.kt index 63dff3a..fee6031 100644 --- a/src/commonMain/kotlin/io/ktor/io/ByteArrayBuffer.kt +++ b/src/commonMain/kotlin/io/ktor/io/ByteArrayBuffer.kt @@ -1,184 +1,121 @@ package io.ktor.io -import io.ktor.io.internal.* import kotlin.math.min public const val DEFAULT_POOL_CAPACITY: Int = 2000 public const val DEFAULT_BUFFER_SIZE: Int = 1024 * 16 public class ByteArrayBuffer( - public override val capacity: Int, - private val pool: ObjectPool -) : Buffer() { - - public constructor(capacity: Int) : this(capacity, ByteArrayBufferPool.NoPool) + array: ByteArray, + readIndex: Int = 0, + writeIndex: Int = array.size, + /** + * The pool used for allocation of the [array]. + */ + public val pool: ObjectPool = ByteArrayPool.Empty +) : Buffer { + + /** + * Creates buffer of fixed [capacity]. + */ + public constructor(capacity: Int) : this(ByteArray(capacity), readIndex = 0, writeIndex = 0) + + public constructor(pool: ObjectPool = ByteArrayPool.Empty) : this( + pool.borrow(), + readIndex = 0, + writeIndex = 0, + pool = pool + ) + + /** + * Provides access to underlying byte array. + * + * Please note, all changes of the array will be reflected in the buffer. + */ + @Suppress("CanBePrimaryConstructorProperty") + public var array: ByteArray = array + private set + + override val capacity: Int + get() = array.size + + override var readIndex: Int = readIndex + set(value) { + if (value < 0 || value > writeIndex) { + throw IndexOutOfBoundsException("readIndex($value) must be >= 0 and < writeIndex: $writeIndex") + } + field = value + } - internal val array = ByteArray(capacity) + override var writeIndex: Int = writeIndex + set(value) { + if (value < 0 || value > capacity) { + throw IndexOutOfBoundsException("Write index $value is out of bounds: $capacity") + } - override var readIndex: Int = 0 - override var writeIndex: Int = 0 + field = value + } - override fun get(index: Int): Byte { + override fun getByteAt(index: Int): Byte { + ensureCanRead(index, 1, writeIndex) return array[index] } - override fun set(index: Int, value: Byte) { + override fun setByteAt(index: Int, value: Byte) { + ensureCanWrite(index, 1, capacity) array[index] = value } - override fun readByte(): Byte { - checkHasBytesToRead(1) - return array[readIndex++] + override fun copyFromBufferAt(index: Int, value: Buffer): Int { + return value.copyToByteArray(array, index, capacity) } - override fun readShort(): Short { - checkHasBytesToRead(2) - return doReadShort() - } + override fun copyToByteArrayAt(index: Int, destination: ByteArray, startIndex: Int, endIndex: Int): Int { + require(startIndex >= 0) { "startIndex($startIndex) must be >= 0" } + require(endIndex <= destination.size) { "endIndex($endIndex) must be <= destination.size(${destination.size})" } + require(startIndex <= endIndex) { "startIndex($startIndex) must be <= endIndex($endIndex)" } + require(index < capacity) { "index($index) must be < capacity($capacity)" } - override fun readInt(): Int { - checkHasBytesToRead(4) - return doReadInt() - } - - override fun readLong(): Long { - checkHasBytesToRead(8) - return doReadLong() - } + val count = min(capacity - index, endIndex - startIndex) - override fun writeByte(value: Byte) { - checkHasSpaceToWrite(1) - doWriteByte(value) - } - - override fun writeShort(value: Short) { - checkHasSpaceToWrite(2) - doWriteShort(value) + array.copyInto(destination, startIndex, index, index + count) + readIndex += count + return count } - override fun writeInt(value: Int) { - checkHasSpaceToWrite(4) - doWriteInt(value) - } + override fun copyToByteArray(destination: ByteArray, startIndex: Int, endIndex: Int): Int { + require(startIndex >= 0) { "startIndex($startIndex) must be >= 0" } + require(endIndex <= destination.size) { "endIndex($endIndex) must be <= destination.size(${destination.size})" } + require(startIndex <= endIndex) { "startIndex($startIndex) must be <= endIndex($endIndex)" } - override fun writeLong(value: Long) { - checkHasSpaceToWrite(8) - doWriteLong(value) - } + val count = min(availableForRead, endIndex - startIndex) - override fun read(destination: ByteArray, startIndex: Int, endIndex: Int): Int { - checkArrayBounds(destination, startIndex, endIndex) - - val count = min(endIndex - startIndex, writeIndex - readIndex) array.copyInto(destination, startIndex, readIndex, readIndex + count) readIndex += count - return count } - override fun write(source: ByteArray, startIndex: Int, endIndex: Int): Int { - checkArrayBounds(source, startIndex, endIndex) - - val count = min(endIndex - startIndex, capacity - writeIndex) - source.copyInto(array, writeIndex, startIndex, startIndex + count) - writeIndex += endIndex + override fun copyFromByteArrayAt(index: Int, value: ByteArray, startIndex: Int, endIndex: Int): Int { + require(startIndex >= 0) { "startPosition($startIndex) must be >= 0" } + require(endIndex <= value.size) { "endPosition($endIndex) must be <= value.size(${value.size})" } + require(startIndex <= endIndex) { "startPosition($startIndex) must be <= endPosition($endIndex)" } + val count = min(capacity - index, endIndex - startIndex) + value.copyInto(array, index, startIndex, startIndex + count) return count } - override fun read(destination: Buffer) { - if (platformRead(this, destination)) { - return - } - - if (destination is ByteArrayBuffer) { - val count = read(destination.array, destination.writeIndex, destination.capacity) - destination.writeIndex += count - return - } - - while (destination.canWrite() && canRead()) { - destination.writeByte(readByte()) - } - } - - override fun write(source: Buffer) { - if (platformWrite(this, source)) { - return - } - - if (source is ByteArrayBuffer) { - val count = write(source.array, source.readIndex, source.writeIndex) - source.readIndex += count - return - } - - while (source.canRead() && canWrite()) { - writeByte(source.readByte()) - } - } - - override fun release() { - pool.recycle(this) + /** + * Returns this buffer back to the pool. + */ + override fun close() { + pool.recycle(array) } override fun compact() { if (readIndex == 0) return array.copyInto(array, 0, readIndex, writeIndex) - writeIndex = readCapacity() + writeIndex = availableForRead readIndex = 0 } - - private fun doWriteByte(value: Byte) { - array[writeIndex++] = value - } - - private fun doWriteShort(value: Short) { - doWriteByte(value.highByte) - doWriteByte(value.lowByte) - } - - private fun doWriteInt(value: Int) { - doWriteShort(value.highShort) - doWriteShort(value.lowShort) - } - - private fun doWriteLong(value: Long) { - doWriteInt(value.highInt) - doWriteInt(value.lowInt) - } - - private fun doReadShort() = Short(readByte(), readByte()) - - private fun doReadInt() = Int(doReadShort(), doReadShort()) - - private fun doReadLong() = Long(doReadInt(), doReadInt()) - - private fun checkHasBytesToRead(count: Int) { - if (readIndex + count > writeIndex) { - throw IndexOutOfBoundsException( - "Read overflow, " + - "trying to read $count bytes " + - "from buffer with ${writeIndex - 1} bytes " + - "and readIndex at $readIndex" - ) - } - } - - private fun checkHasSpaceToWrite(count: Int) { - if (writeIndex + count > capacity) { - throw IndexOutOfBoundsException( - "Write overflow, " + - "trying to write $count bytes " + - "to buffer of $capacity capacity " + - "and writeIndex at $writeIndex" - ) - } - } -} - -private fun checkArrayBounds(array: ByteArray, startIndex: Int, endIndex: Int) { - require(startIndex >= 0) { "The startIndex must be non-negative, was $startIndex" } - require(endIndex <= array.size) { "The endIndex must be less than or equal to size, was $endIndex" } - require(startIndex <= endIndex) { "Range of negative size is given: [$startIndex, $endIndex)" } } diff --git a/src/commonMain/kotlin/io/ktor/io/ByteArrayBufferPool.kt b/src/commonMain/kotlin/io/ktor/io/ByteArrayBufferPool.kt index 285bc25..00a2c56 100644 --- a/src/commonMain/kotlin/io/ktor/io/ByteArrayBufferPool.kt +++ b/src/commonMain/kotlin/io/ktor/io/ByteArrayBufferPool.kt @@ -2,9 +2,10 @@ package io.ktor.io public class ByteArrayBufferPool( capacity: Int = DEFAULT_POOL_CAPACITY, + public val arrayPool: ObjectPool = ByteArrayPool.Default, ) : DefaultPool(capacity) { - override fun produceInstance(): ByteArrayBuffer = ByteArrayBuffer(DEFAULT_BUFFER_SIZE, this) + override fun produceInstance(): ByteArrayBuffer = ByteArrayBuffer(arrayPool) override fun clearInstance(instance: ByteArrayBuffer): ByteArrayBuffer { instance.reset() @@ -14,10 +15,28 @@ public class ByteArrayBufferPool( public companion object { public val Default: ObjectPool = ByteArrayBufferPool() - public val NoPool: NoPoolImpl = object : NoPoolImpl() { - override fun borrow(): ByteArrayBuffer { - throw NotImplementedError() - } - } + public val Empty: ObjectPool = ByteArrayBufferPool( + capacity = 0, + arrayPool = ByteArrayPool.Empty + ) } } + +public class ByteArrayPool( + private val size: Int = DEFAULT_BUFFER_SIZE, + capacity: Int = DEFAULT_POOL_CAPACITY, +) : DefaultPool(capacity) { + + override fun produceInstance(): ByteArray = ByteArray(size) + + override fun clearInstance(instance: ByteArray): ByteArray { + instance.fill(0) + return instance + } + + public companion object { + public val Default: ObjectPool = ByteArrayPool() + + public val Empty: ObjectPool = ByteArrayPool(capacity = 0) + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/io/ktor/io/ByteArrayOperations.kt b/src/commonMain/kotlin/io/ktor/io/ByteArrayOperations.kt new file mode 100644 index 0000000..1046e51 --- /dev/null +++ b/src/commonMain/kotlin/io/ktor/io/ByteArrayOperations.kt @@ -0,0 +1,88 @@ +package io.ktor.io + +/** + * Loads [Short] from the byte array at the specified [index]. + * + * @throws IndexOutOfBoundsException if there are not enough bytes in the array. + */ +public fun ByteArray.getShortAt(index: Int): Short { + ensureCanRead(index, 2, size) + return Short(this[index], this[index + 1]) +} + +/** + * Loads [Int] from the byte array at the specified [index]. + * + * @throws IndexOutOfBoundsException if there are not enough bytes in the array. + */ +public fun ByteArray.getIntAt(index: Int): Int { + ensureCanRead(index, 4, size) + + return Int(getShortAt(index), getShortAt(index + 2)) +} + +/** + * Loads [Long] from the byte array at the specified [index]. + * + * @throws IndexOutOfBoundsException if there are not enough bytes in the array. + */ +public fun ByteArray.getLongAt(index: Int): Long { + ensureCanRead(index, 8, size) + return Long(getIntAt(index), getIntAt(index + 4)) +} + +/** + * Stores [value] to the byte array at the specified [index]. + * + * @throws IndexOutOfBoundsException if there are not enough bytes in the array. + */ +public fun ByteArray.setShortAt(index: Int, value: Short): ByteArray { + ensureCanWrite(index, 2, size) + + this[index] = value.highByte + this[index + 1] = value.lowByte + + return this +} + +/** + * Stores [value] to the byte array at the specified [index]. + * + * @throws IndexOutOfBoundsException if there are not enough bytes in the array. + */ +public fun ByteArray.setIntAt(index: Int, value: Int): ByteArray { + ensureCanWrite(index, 4, size) + + setShortAt(index, value.highShort) + setShortAt(index + 2, value.lowShort) + + return this +} + +/** + * Stores [value] to the byte array at the specified [index]. + * + * @throws IndexOutOfBoundsException if there are not enough bytes in the array. + */ +public fun ByteArray.setLongAt(index: Int, value: Long): ByteArray { + ensureCanWrite(index, 8, size) + + setIntAt(index, value.highInt) + setIntAt(index + 4, value.lowInt) + + return this +} + +internal fun ensureCanRead(index: Int, count: Int, capacity: Int) { + if (index + count > capacity) { + throw IndexOutOfBoundsException("Can't read $count bytes at index $index from array of size $capacity") + } +} + +internal fun ensureCanWrite(index: Int, count: Int, capacity: Int) { + if (index + count > capacity) { + throw IndexOutOfBoundsException("Can't write $count bytes at index $index to array of size $capacity") + } +} + +private fun Byte.asInt(): Int = toInt() and 0xFF \ No newline at end of file diff --git a/src/commonMain/kotlin/io/ktor/io/ByteOperations.kt b/src/commonMain/kotlin/io/ktor/io/ByteOperations.kt new file mode 100644 index 0000000..d2a97dc --- /dev/null +++ b/src/commonMain/kotlin/io/ktor/io/ByteOperations.kt @@ -0,0 +1,5 @@ +package io.ktor.io + +public infix fun Byte.and(other: Byte): Byte = ((toInt() and 0xFF) and (other.toInt() and 0xFF)).toByte() + +public infix fun Byte.or(other: Byte): Byte = ((toInt() and 0xFF) and (other.toInt() and 0xFF)).toByte() diff --git a/src/commonMain/kotlin/io/ktor/io/BytesDestination.kt b/src/commonMain/kotlin/io/ktor/io/BytesDestination.kt index ab251cf..22d255c 100644 --- a/src/commonMain/kotlin/io/ktor/io/BytesDestination.kt +++ b/src/commonMain/kotlin/io/ktor/io/BytesDestination.kt @@ -4,7 +4,7 @@ public abstract class BytesDestination : Closeable { public abstract val closedCause: Throwable? public abstract fun canWrite(): Boolean - public abstract fun write(buffer: Buffer) + public abstract fun write(buffer: Buffer): Int public abstract suspend fun flush() public abstract suspend fun awaitFreeSpace() diff --git a/src/commonMain/kotlin/io/ktor/io/internal/PlatformUtils.kt b/src/commonMain/kotlin/io/ktor/io/internal/PlatformUtils.kt deleted file mode 100644 index 73c0acd..0000000 --- a/src/commonMain/kotlin/io/ktor/io/internal/PlatformUtils.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.ktor.io.internal - -import io.ktor.io.* - -internal expect fun platformRead(byteArrayBuffer: ByteArrayBuffer, buffer: Buffer): Boolean - -internal expect fun platformWrite(byteArrayBuffer: ByteArrayBuffer, buffer: Buffer): Boolean diff --git a/src/commonTest/kotlin/io/ktor/io/BufferTest.kt b/src/commonTest/kotlin/io/ktor/io/BufferTest.kt new file mode 100644 index 0000000..c725a94 --- /dev/null +++ b/src/commonTest/kotlin/io/ktor/io/BufferTest.kt @@ -0,0 +1,200 @@ +package io.ktor.io + +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +abstract class BufferTest { + + abstract fun createBuffer(): Buffer + + @Test + fun testWriteCanReadByte() { + val buffer = createBuffer() + buffer.writeIndex = 0 + + buffer.writeByte(99) + buffer.writeByte(-99) + + assertEquals(0, buffer.readIndex) + assertEquals(2, buffer.writeIndex) + + assertEquals(99, buffer.readByte()) + assertEquals(-99, buffer.readByte()) + + assertEquals(2, buffer.readIndex) + assertEquals(2, buffer.writeIndex) + } + + @Test + fun testWriteCanReadShort() { + val buffer = createBuffer() + buffer.writeIndex = 0 + + buffer.writeShort(999) + buffer.writeShort(-999) + + assertEquals(0, buffer.readIndex) + assertEquals(4, buffer.writeIndex) + + assertEquals(999, buffer.readShort()) + assertEquals(-999, buffer.readShort()) + + assertEquals(4, buffer.readIndex) + assertEquals(4, buffer.writeIndex) + } + + @Test + fun testWriteCanReadInt() { + val buffer = createBuffer() + buffer.writeIndex = 0 + + buffer.writeInt(999_999) + buffer.writeInt(-999_999) + + assertEquals(0, buffer.readIndex) + assertEquals(8, buffer.writeIndex) + + assertEquals(999_999, buffer.readInt()) + assertEquals(-999_999, buffer.readInt()) + + assertEquals(8, buffer.readIndex) + assertEquals(8, buffer.writeIndex) + } + + @Test + fun testWriteCanReadLong() { + val buffer = createBuffer() + buffer.writeIndex = 0 + + buffer.writeLong(9_999_999_999_999) + buffer.writeLong(-9_999_999_999_999) + + assertEquals(0, buffer.readIndex) + assertEquals(16, buffer.writeIndex) + + assertEquals(9_999_999_999_999, buffer.readLong()) + assertEquals(-9_999_999_999_999, buffer.readLong()) + + assertEquals(16, buffer.readIndex) + assertEquals(16, buffer.writeIndex) + } + + @Test + fun testWriteCanReadArray() { + val buffer = createBuffer() + buffer.writeIndex = 0 + + val array = ByteArray(123) { it.toByte() } + buffer.copyFromByteArray(array) + + assertEquals(0, buffer.readIndex) + assertEquals(123, buffer.writeIndex) + + val newArray = ByteArray(123).also { buffer.copyToByteArray(it) } + assertContentEquals(array, newArray) + + assertEquals(123, buffer.readIndex) + assertEquals(123, buffer.writeIndex) + } + + @Test + fun testBoundsByte() { + val buffer = createBuffer() + + assertFailsWith { buffer.readByte() } + buffer.writeIndex = buffer.capacity - 1 + buffer.readIndex = buffer.writeIndex + buffer.writeByte(1) + assertEquals(1, buffer.readByte()) + assertFailsWith { buffer.writeByte(2) } + } + + @Test + fun testBoundsShort() { + val buffer = createBuffer() + + assertFailsWith { buffer.readShort() } + buffer.writeIndex = buffer.capacity - 2 + buffer.readIndex = buffer.writeIndex + buffer.writeShort(1) + assertEquals(1, buffer.readShort()) + + buffer.writeIndex = buffer.capacity - 1 + assertFailsWith { buffer.writeShort(2) } + } + + @Test + fun testBoundsInt() { + val buffer = createBuffer() + + assertFailsWith { buffer.readInt() } + buffer.writeIndex = buffer.capacity - 4 + buffer.readIndex = buffer.writeIndex + buffer.writeInt(1) + assertEquals(1, buffer.readInt()) + buffer.writeIndex = buffer.capacity - 3 + assertFailsWith { buffer.writeInt(2) } + } + + @Test + fun testBoundsLong() { + val buffer = createBuffer() + + assertFailsWith { buffer.readLong() } + buffer.writeIndex = buffer.capacity - 8 + buffer.readIndex = buffer.writeIndex + buffer.writeLong(1) + assertEquals(1, buffer.readLong()) + buffer.writeIndex = buffer.capacity - 7 + assertFailsWith { buffer.writeLong(2) } + } + + @Test + fun testBoundsArray() { + val buffer = createBuffer() + + val array = ByteArray(123) + var count = buffer.copyToByteArray(array) + assertEquals(0, count) + + buffer.writeIndex = buffer.capacity - 123 + buffer.readIndex = buffer.writeIndex + buffer.copyFromByteArray(array) + + val newArray = ByteArray(123) + assertContentEquals(array, newArray) + buffer.writeIndex = buffer.capacity - 122 + count = buffer.copyFromByteArray(array) + assertEquals(122, count) + } + + + @Test + fun testWriteCanRead() { + val buffer = createBuffer() + buffer.writeIndex = 0 + + buffer.writeByte(99) + buffer.writeShort(999) + buffer.writeInt(999_999) + buffer.writeLong(9_999_999_999_999) + + val array = ByteArray(123) { it.toByte() } + buffer.copyFromByteArray(array) + + assertEquals(0, buffer.readIndex) + assertEquals(138, buffer.writeIndex) + + assertEquals(99, buffer.readByte()) + assertEquals(999, buffer.readShort()) + assertEquals(999_999, buffer.readInt()) + assertEquals(9_999_999_999_999, buffer.readLong()) + val newArray = ByteArray(123).also { buffer.copyToByteArray(it) } + assertContentEquals(array, newArray) + + assertEquals(138, buffer.readIndex) + assertEquals(138, buffer.writeIndex) + } +} \ No newline at end of file diff --git a/src/commonTest/kotlin/io/ktor/io/BufferedBytesDestinationTest.kt b/src/commonTest/kotlin/io/ktor/io/BufferedBytesDestinationTest.kt index 3d906b0..fe54ab4 100644 --- a/src/commonTest/kotlin/io/ktor/io/BufferedBytesDestinationTest.kt +++ b/src/commonTest/kotlin/io/ktor/io/BufferedBytesDestinationTest.kt @@ -12,10 +12,10 @@ class BufferedBytesDestinationTest { val buffered = BufferedBytesDestination(destination, 1024) val buffer1 = ByteArrayBuffer(512) - buffer1.write(ByteArray(512) { it.toByte() }) + buffer1.copyFromByteArray(ByteArray(512) { it.toByte() }) val buffer2 = ByteArrayBuffer(1024) - buffer2.write(ByteArray(1024) { it.toByte() }) + buffer2.copyFromByteArray(ByteArray(1024) { it.toByte() }) buffered.write(buffer1) buffered.write(buffer2) @@ -31,7 +31,7 @@ class BufferedBytesDestinationTest { val buffered = BufferedBytesDestination(destination, 1024) val buffer1 = ByteArrayBuffer(1023) - buffer1.write(ByteArray(1023) { it.toByte() }) + buffer1.copyFromByteArray(ByteArray(1023) { it.toByte() }) buffered.write(buffer1) buffered.awaitFreeSpace() @@ -45,7 +45,7 @@ class BufferedBytesDestinationTest { val buffered = BufferedBytesDestination(destination, 1024) val buffer1 = ByteArrayBuffer(1024) - buffer1.write(ByteArray(1024) { it.toByte() }) + buffer1.copyFromByteArray(ByteArray(1024) { it.toByte() }) buffered.write(buffer1) buffered.awaitFreeSpace() @@ -59,7 +59,7 @@ class BufferedBytesDestinationTest { val buffered = BufferedBytesDestination(destination, 1024) val buffer1 = ByteArrayBuffer(1023) - buffer1.write(ByteArray(1023) { it.toByte() }) + buffer1.copyFromByteArray(ByteArray(1023) { it.toByte() }) buffered.write(buffer1) assertEquals(0, destination.writeCount) @@ -74,7 +74,7 @@ class BufferedBytesDestinationTest { val buffered = BufferedBytesDestination(destination, 1024) val buffer1 = ByteArrayBuffer(1022) - buffer1.write(ByteArray(1022) { it.toByte() }) + buffer1.copyFromByteArray(ByteArray(1022) { it.toByte() }) buffered.write(buffer1) buffered.writeByte(1) diff --git a/src/commonTest/kotlin/io/ktor/io/BufferedBytesSourceTest.kt b/src/commonTest/kotlin/io/ktor/io/BufferedBytesSourceTest.kt index 194ef12..e53f03e 100644 --- a/src/commonTest/kotlin/io/ktor/io/BufferedBytesSourceTest.kt +++ b/src/commonTest/kotlin/io/ktor/io/BufferedBytesSourceTest.kt @@ -23,10 +23,10 @@ class BufferedBytesSourceTest { @Test fun testReadReturnsSameBufferIfHasContent() { val buffer1 = ByteArrayBuffer(1024) - buffer1.write(ByteArray(123) { it.toByte() }) + buffer1.copyFromByteArray(ByteArray(123) { it.toByte() }) val buffer2 = ByteArrayBuffer(1024) - buffer2.write(ByteArray(123) { it.toByte() }) + buffer2.copyFromByteArray(ByteArray(123) { it.toByte() }) val source = TestBytesSource(buffer1, buffer2) val buffered = BufferedBytesSource(source) diff --git a/src/commonTest/kotlin/io/ktor/io/ByteArrayBufferTest.kt b/src/commonTest/kotlin/io/ktor/io/ByteArrayBufferTest.kt index fa6d559..6b6594c 100644 --- a/src/commonTest/kotlin/io/ktor/io/ByteArrayBufferTest.kt +++ b/src/commonTest/kotlin/io/ktor/io/ByteArrayBufferTest.kt @@ -1,170 +1,30 @@ package io.ktor.io import kotlin.test.Test -import kotlin.test.assertContentEquals import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -class ByteArrayBufferTest { - - @Test - fun testWriteCanReadByte() { - val buffer = ByteArrayBuffer(1024) - buffer.writeIndex = 0 - - buffer.writeByte(99) - buffer.writeByte(-99) - - assertEquals(0, buffer.readIndex) - assertEquals(2, buffer.writeIndex) - - assertEquals(99, buffer.readByte()) - assertEquals(-99, buffer.readByte()) - - assertEquals(2, buffer.readIndex) - assertEquals(2, buffer.writeIndex) - } - - @Test - fun testWriteCanReadShort() { - val buffer = ByteArrayBuffer(1024) - buffer.writeIndex = 0 - - buffer.writeShort(999) - buffer.writeShort(-999) - - assertEquals(0, buffer.readIndex) - assertEquals(4, buffer.writeIndex) - - assertEquals(999, buffer.readShort()) - assertEquals(-999, buffer.readShort()) +class ByteArrayWithDefaultPoolTest : BufferTest() { + override fun createBuffer(): Buffer = ByteArrayBuffer(ByteArrayPool.Default) +} - assertEquals(4, buffer.readIndex) - assertEquals(4, buffer.writeIndex) - } +class ByteArrayWithEmptyPoolTest : BufferTest() { + override fun createBuffer(): Buffer = ByteArrayBuffer(ByteArrayPool.Empty) +} +class ByteArrayTest { @Test - fun testWriteCanReadInt() { - val buffer = ByteArrayBuffer(1024) - buffer.writeIndex = 0 - - buffer.writeInt(999_999) - buffer.writeInt(-999_999) + fun testConstructorFromArray() { + val array = ByteArray(10) + val buffer = ByteArrayBuffer(array) assertEquals(0, buffer.readIndex) - assertEquals(8, buffer.writeIndex) - - assertEquals(999_999, buffer.readInt()) - assertEquals(-999_999, buffer.readInt()) - - assertEquals(8, buffer.readIndex) - assertEquals(8, buffer.writeIndex) + assertEquals(array.size, buffer.writeIndex) } @Test - fun testWriteCanReadLong() { - val buffer = ByteArrayBuffer(1024) - buffer.writeIndex = 0 - - buffer.writeLong(9_999_999_999_999) - buffer.writeLong(-9_999_999_999_999) - + fun testConstructorFromPool() { + val buffer = ByteArrayBuffer() assertEquals(0, buffer.readIndex) - assertEquals(16, buffer.writeIndex) - - assertEquals(9_999_999_999_999, buffer.readLong()) - assertEquals(-9_999_999_999_999, buffer.readLong()) - - assertEquals(16, buffer.readIndex) - assertEquals(16, buffer.writeIndex) - } - - @Test - fun testWriteCanReadArray() { - val buffer = ByteArrayBuffer(1024) - buffer.writeIndex = 0 - - val array = ByteArray(123) { it.toByte() } - buffer.write(array) - - assertEquals(0, buffer.readIndex) - assertEquals(123, buffer.writeIndex) - - val newArray = ByteArray(123).also { buffer.read(it) } - assertContentEquals(array, newArray) - - assertEquals(123, buffer.readIndex) - assertEquals(123, buffer.writeIndex) - } - - @Test - fun testBoundsByte() { - val buffer = ByteArrayBuffer(1024) - - assertFailsWith { buffer.readByte() } - buffer.writeIndex = buffer.capacity - 1 - buffer.readIndex = buffer.writeIndex - buffer.writeByte(1) - assertEquals(1, buffer.readByte()) - assertFailsWith { buffer.writeByte(2) } - } - - @Test - fun testBoundsShort() { - val buffer = ByteArrayBuffer(1024) - - assertFailsWith { buffer.readShort() } - buffer.writeIndex = buffer.capacity - 2 - buffer.readIndex = buffer.writeIndex - buffer.writeShort(1) - assertEquals(1, buffer.readShort()) - - buffer.writeIndex = buffer.capacity - 1 - assertFailsWith { buffer.writeShort(2) } - } - - @Test - fun testBoundsInt() { - val buffer = ByteArrayBuffer(1024) - - assertFailsWith { buffer.readInt() } - buffer.writeIndex = buffer.capacity - 4 - buffer.readIndex = buffer.writeIndex - buffer.writeInt(1) - assertEquals(1, buffer.readInt()) - buffer.writeIndex = buffer.capacity - 3 - assertFailsWith { buffer.writeInt(2) } - } - - @Test - fun testBoundsLong() { - val buffer = ByteArrayBuffer(1024) - - assertFailsWith { buffer.readLong() } - buffer.writeIndex = buffer.capacity - 8 - buffer.readIndex = buffer.writeIndex - buffer.writeLong(1) - assertEquals(1, buffer.readLong()) - buffer.writeIndex = buffer.capacity - 7 - assertFailsWith { buffer.writeLong(2) } - } - - @Test - fun testBoundsArray() { - val buffer = ByteArrayBuffer(1024) - - val array = ByteArray(123) - var count = buffer.read(array) - assertEquals(0, count) - - buffer.writeIndex = buffer.capacity - 123 - buffer.readIndex = buffer.writeIndex - buffer.write(array) - - val newArray = ByteArray(123) - assertContentEquals(array, newArray) - buffer.writeIndex = buffer.capacity - 122 - count = buffer.write(array) - assertEquals(122, count) + assertEquals(0, buffer.writeIndex) } } diff --git a/src/commonTest/kotlin/io/ktor/io/utils/TestBytesDestination.kt b/src/commonTest/kotlin/io/ktor/io/utils/TestBytesDestination.kt index 29f7d6e..3090562 100644 --- a/src/commonTest/kotlin/io/ktor/io/utils/TestBytesDestination.kt +++ b/src/commonTest/kotlin/io/ktor/io/utils/TestBytesDestination.kt @@ -10,13 +10,14 @@ class TestBytesDestination : BytesDestination() { override fun canWrite(): Boolean = closedCause != null - override fun write(buffer: Buffer) { - val copy = ByteArrayBuffer(buffer.readCapacity()) - val array = ByteArray(buffer.readCapacity()) - buffer.read(array) - copy.write(array) + override fun write(buffer: Buffer): Int { + val copy = ByteArrayBuffer(buffer.availableForRead) + val array = ByteArray(buffer.availableForRead) + buffer.copyToByteArray(array) + copy.copyFromByteArray(array) buffers.add(copy) buffer.readIndex = buffer.writeIndex + return array.size } override suspend fun flush() = Unit @@ -30,5 +31,5 @@ class TestBytesDestination : BytesDestination() { close(null) } - class ClosedDestinationException() : IOException("closed") + class ClosedDestinationException : IOException("closed") } diff --git a/src/jsMain/kotlin/io/ktor/io/internal/PlatformUtilsJs.kt b/src/jsMain/kotlin/io/ktor/io/internal/PlatformUtilsJs.kt deleted file mode 100644 index 1e19116..0000000 --- a/src/jsMain/kotlin/io/ktor/io/internal/PlatformUtilsJs.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.ktor.io.internal - -import io.ktor.io.* - -internal actual fun platformRead( - byteArrayBuffer: ByteArrayBuffer, - buffer: Buffer -): Boolean = false - -internal actual fun platformWrite( - byteArrayBuffer: ByteArrayBuffer, - buffer: Buffer -): Boolean = false diff --git a/src/jvmMain/kotlin/io/ktor/io/DefaultBuffer.kt b/src/jvmMain/kotlin/io/ktor/io/DefaultBuffer.kt deleted file mode 100644 index 24580ad..0000000 --- a/src/jvmMain/kotlin/io/ktor/io/DefaultBuffer.kt +++ /dev/null @@ -1,143 +0,0 @@ -package io.ktor.io - -import java.lang.Integer.min -import java.nio.ByteBuffer - -public val BufferPool: ObjectPool = DefaultBufferPool() - -public class DefaultBufferPool( - capacity: Int = DEFAULT_POOL_CAPACITY, -) : DefaultPool(capacity) { - - override fun produceInstance(): DefaultBuffer = - DefaultBuffer(DefaultDirectByteBufferPool.borrow(), DefaultDirectByteBufferPool) -} - -public class DefaultBuffer(internal val buffer: ByteBuffer, private val pool: ObjectPool) : Buffer() { - - override var readIndex: Int - get() = buffer.position() - set(value) { - buffer.position(value) - } - - override var writeIndex: Int - get() = buffer.limit() - set(value) { - buffer.limit(value) - } - - override val capacity: Int - get() = buffer.capacity() - - override fun get(index: Int): Byte { - return buffer.get(index) - } - - override fun set(index: Int, value: Byte) { - buffer.put(index, value) - } - - override fun readByte(): Byte { - return buffer.get() - } - - override fun readShort(): Short { - return buffer.short - } - - override fun readInt(): Int { - return buffer.int - } - - override fun readLong(): Long { - return buffer.long - } - - override fun writeByte(value: Byte) { - val oldLimit = buffer.limit() - buffer.limit(capacity) - buffer.put(oldLimit, value) - writeIndex = oldLimit + 1 - } - - override fun writeShort(value: Short) { - val oldLimit = buffer.limit() - buffer.limit(capacity) - buffer.putShort(oldLimit, value) - writeIndex = oldLimit + 2 - } - - override fun writeInt(value: Int) { - val oldLimit = buffer.limit() - buffer.limit(capacity) - buffer.putInt(oldLimit, value) - writeIndex = oldLimit + 4 - } - - override fun writeLong(value: Long) { - val oldLimit = buffer.limit() - buffer.limit(capacity) - buffer.putLong(oldLimit, value) - writeIndex = oldLimit + 8 - } - - override fun read(destination: ByteArray, startIndex: Int, endIndex: Int): Int { - require(startIndex >= 0) { "startIndex should be non-negative: $startIndex" } - require(startIndex <= endIndex) { "startIndex should be less than or equal to endIndex: $startIndex, $endIndex" } - require(endIndex <= destination.size) { "endIndex should be less than or equal to destination.size: $endIndex, ${destination.size}" } - - val count = min(endIndex - startIndex, buffer.remaining()) - buffer.get(destination, startIndex, count) - return count - } - - override fun write(source: ByteArray, startIndex: Int, endIndex: Int): Int { - require(startIndex >= 0) { "startIndex should be non-negative: $startIndex" } - require(startIndex <= endIndex) { "startIndex should be less than or equal to endIndex: $startIndex, $endIndex" } - require(endIndex <= source.size) { "endIndex should be less than or equal to source.size: $endIndex, ${source.size}" } - - val count = min(endIndex - startIndex, capacity - writeIndex) - - val storedReadIndex = readIndex - val storedWriteIndex = writeIndex - - buffer.position(buffer.limit()) - buffer.limit(buffer.capacity()) - buffer.put(source, startIndex, count) - - readIndex = storedReadIndex - writeIndex = storedWriteIndex + count - return count - } - - override fun read(destination: Buffer) { - if (destination is DefaultBuffer) { - destination.buffer.put(this.buffer) - return - } - - while (canRead() && destination.canWrite()) { - destination.writeByte(readByte()) - } - } - - override fun write(source: Buffer) { - if (source is DefaultBuffer) { - this.buffer.put(source.buffer) - return - } - - while (canWrite() && source.canRead()) { - writeByte(source.readByte()) - } - } - - override fun release() { - pool.recycle(buffer) - } - - override fun compact() { - buffer.compact() - } -} diff --git a/src/jvmMain/kotlin/io/ktor/io/DirectByteBufferPool.kt b/src/jvmMain/kotlin/io/ktor/io/DirectByteBufferPool.kt index b431e87..3c6195a 100644 --- a/src/jvmMain/kotlin/io/ktor/io/DirectByteBufferPool.kt +++ b/src/jvmMain/kotlin/io/ktor/io/DirectByteBufferPool.kt @@ -3,14 +3,17 @@ package io.ktor.io import java.nio.ByteBuffer import java.nio.ByteOrder -public val DefaultDirectByteBufferPool: ObjectPool = DirectByteBufferPool() - -public class DirectByteBufferPool( +public class ByteBufferPool( capacity: Int = DEFAULT_POOL_CAPACITY, - private val bufferSize: Int = DEFAULT_BUFFER_SIZE + public val direct: Boolean = true, + public val bufferSize: Int = DEFAULT_BUFFER_SIZE ) : DefaultPool(capacity) { - override fun produceInstance(): ByteBuffer = ByteBuffer.allocateDirect(bufferSize)!! + override fun produceInstance(): ByteBuffer = if (direct) { + ByteBuffer.allocateDirect(bufferSize)!! + } else { + ByteBuffer.allocate(bufferSize)!! + } override fun clearInstance(instance: ByteBuffer): ByteBuffer = instance.apply { clear() @@ -21,4 +24,14 @@ public class DirectByteBufferPool( check(instance.capacity() == bufferSize) check(instance.isDirect) } + + public companion object { + public val Default: ObjectPool get() = Direct + + public val Direct: ObjectPool = ByteBufferPool(direct = true) + + public val Heap: ObjectPool = ByteBufferPool(direct = false) + + public val Empty: ObjectPool = ByteBufferPool(capacity = 0) + } } diff --git a/src/jvmMain/kotlin/io/ktor/io/FileBytesDestination.kt b/src/jvmMain/kotlin/io/ktor/io/FileBytesDestination.kt index d5b27ae..4de5e38 100644 --- a/src/jvmMain/kotlin/io/ktor/io/FileBytesDestination.kt +++ b/src/jvmMain/kotlin/io/ktor/io/FileBytesDestination.kt @@ -26,24 +26,23 @@ public class FileBytesDestination(private val channel: FileChannel) : BytesDesti return true } - override fun write(buffer: Buffer) { + override fun write(buffer: Buffer): Int { closedCause?.let { throw it } try { - if (buffer is DefaultBuffer) { - channel.write(buffer.buffer) - return + if (buffer is JvmBuffer) { + return channel.write(buffer.buffer) } - slowWrite(buffer) + return slowWrite(buffer) } catch (cause: Throwable) { close(cause) throw cause } } - private fun slowWrite(buffer: Buffer) { - DefaultDirectByteBufferPool.useInstance { byteBuffer -> - val toWrite = min(buffer.readCapacity(), byteBuffer.remaining()) + private fun slowWrite(buffer: Buffer): Int { + ByteBufferPool.Default.useInstance { byteBuffer -> + val toWrite = min(buffer.availableForRead, byteBuffer.remaining()) if (buffer is ByteArrayBuffer) { byteBuffer.put(buffer.array, buffer.readIndex, toWrite) buffer.readIndex += toWrite @@ -53,7 +52,7 @@ public class FileBytesDestination(private val channel: FileChannel) : BytesDesti } } byteBuffer.flip() - channel.write(byteBuffer) + return channel.write(byteBuffer) } } diff --git a/src/jvmMain/kotlin/io/ktor/io/FileBytesSource.kt b/src/jvmMain/kotlin/io/ktor/io/FileBytesSource.kt index c076c91..ae048b1 100644 --- a/src/jvmMain/kotlin/io/ktor/io/FileBytesSource.kt +++ b/src/jvmMain/kotlin/io/ktor/io/FileBytesSource.kt @@ -31,7 +31,7 @@ public class FileBytesSource(private val channel: AsynchronousFileChannel) : Byt override var closedCause: Throwable? = null private set - private var buffer: Buffer? = null + private var state: JvmBuffer? = null override fun canRead(): Boolean { closedCause?.let { throw it } @@ -42,20 +42,22 @@ public class FileBytesSource(private val channel: AsynchronousFileChannel) : Byt override fun read(): Buffer { closedCause?.let { throw it } - return buffer.also { buffer = null } ?: Buffer.Empty + return state.also { state = null } ?: Buffer.Empty } override suspend fun awaitContent() { closedCause?.let { throw it } - val buffer = BufferPool.borrow() - val byteBuffer = buffer.buffer - val read = channel.read(byteBuffer) + val buffer = ByteBufferPool.Direct.borrow() + val count = channel.read(buffer) + if (count == -1) { + isClosedForRead = true + ByteBufferPool.Direct.recycle(buffer) + return + } - bytesRead += read - isClosedForRead = read == -1 - byteBuffer.flip() - this.buffer = buffer + bytesRead += count + state = JvmBuffer(buffer.flip(), ByteBufferPool.Direct) } override fun cancel(cause: Throwable) { diff --git a/src/jvmMain/kotlin/io/ktor/io/JvmBuffer.kt b/src/jvmMain/kotlin/io/ktor/io/JvmBuffer.kt new file mode 100644 index 0000000..f017d28 --- /dev/null +++ b/src/jvmMain/kotlin/io/ktor/io/JvmBuffer.kt @@ -0,0 +1,166 @@ +package io.ktor.io + +import java.lang.Integer.min +import java.nio.ByteBuffer + +/** + * The [Buffer] implementation using [ByteBuffer] class on the JVM. + * + * @constructor creates buffer prepared for reading. + */ +public class JvmBuffer( + buffer: ByteBuffer, + private val pool: ObjectPool = ByteBufferPool.Default +) : Buffer { + + /** + * Creates a new [JvmBuffer] instance with the [ByteBuffer] instance from the [pool]. + * + * The buffer is empty and prepared for write operations. + */ + public constructor(capacity: Int) : this( + ByteBuffer.allocateDirect(capacity).limit(0), + ByteBufferPool.Empty + ) + + /** + * Creates a new [JvmBuffer] instance with the [ByteBuffer] instance from the [pool]. + * + * The buffer is empty and prepared for write operations. + */ + public constructor(pool: ObjectPool = ByteBufferPool.Default) : this(pool.borrow().limit(0), pool) + + /** + * Provides access to the underlying [ByteBuffer]. + * + * The [buffer.position()] reflects [readIndex] and [buffer.limit()] reflects [writeIndex]. + * + * All modifications of the [ByteBuffer] is reflected by the [JvmBuffer] itself. + */ + @Suppress("CanBePrimaryConstructorProperty") + public var buffer: ByteBuffer = buffer + private set + + override var readIndex: Int + get() = buffer.position() + set(value) { + buffer.position(value) + } + + override var writeIndex: Int + get() = buffer.limit() + set(value) { + buffer.limit(value) + } + + override val capacity: Int + get() = buffer.capacity() + + override fun copyToByteArrayAt(index: Int, destination: ByteArray, startIndex: Int, endIndex: Int): Int { + require(startIndex >= 0) { "startIndex should be non-negative: $startIndex" } + require(startIndex <= endIndex) { "startIndex should be less than or equal to endIndex: $startIndex, $endIndex" } + require(endIndex <= destination.size) { "endIndex should be less than or equal to destination.size: $endIndex, ${destination.size}" } + require(index < capacity) { "index should be less than capacity: $index, $capacity" } + + val count = min(endIndex - startIndex, capacity - index) + randomAccess { + it.position(index) + buffer.get(destination, startIndex, count) + } + + return count + } + + override fun copyToByteArray(destination: ByteArray, startIndex: Int, endIndex: Int): Int { + require(startIndex >= 0) { "startIndex should be non-negative: $startIndex" } + require(startIndex <= endIndex) { "startIndex should be less than or equal to endIndex: $startIndex, $endIndex" } + require(endIndex <= destination.size) { "endIndex should be less than or equal to destination.size: $endIndex, ${destination.size}" } + + val count = min(endIndex - startIndex, buffer.remaining()) + buffer.get(destination, startIndex, count) + return count + } + + /** + * Return the underlying buffer to the pool. + */ + override fun close() { + pool.recycle(buffer) + } + + override fun compact() { + buffer.compact() + } + + override fun getByteAt(index: Int): Byte = buffer.get(index) + + override fun getShortAt(index: Int): Short = buffer.getShort(index) + + override fun getIntAt(index: Int): Int = buffer.getInt(index) + + override fun getLongAt(index: Int): Long = buffer.getLong(index) + + override fun setByteAt(index: Int, value: Byte) { + randomAccess { + it.put(index, value) + } + } + + override fun setShortAt(index: Int, value: Short) { + randomAccess { + it.putShort(index, value) + } + } + + override fun setIntAt(index: Int, value: Int) { + randomAccess { + it.putInt(index, value) + } + } + + override fun setLongAt(index: Int, value: Long) { + randomAccess { + it.putLong(index, value) + } + } + + override fun copyFromBufferAt(index: Int, value: Buffer): Int { + var current = index + while (value.isNotEmpty) { + setByteAt(current++, value.readByte()) + } + + return current - index + } + + override fun copyFromByteArrayAt(index: Int, value: ByteArray, startIndex: Int, endIndex: Int): Int { + check(index < capacity) { "Index should be less than capacity: $index, $capacity" } + check(startIndex >= 0) { "startPosition should be non-negative: $startIndex" } + check(startIndex <= endIndex) { "startPosition should be less than or equal to endPosition: $startIndex, $endIndex" } + check(endIndex <= value.size) { "endPosition should be less than or equal to value.size: $endIndex, ${value.size}" } + + + val count = min(endIndex - startIndex, capacity - index) + + randomAccess { + it.position(index) + buffer.put(value, startIndex, count) + } + + return count + } + + private fun randomAccess(block: (ByteBuffer) -> Unit) { + val oldPosition = buffer.position() + val oldLimit = buffer.limit() + try { + buffer.position(0) + buffer.limit(capacity) + block(buffer) + } finally { + buffer.position(oldPosition) + buffer.limit(oldLimit) + } + } +} + diff --git a/src/jvmMain/kotlin/io/ktor/io/JvmBufferPool.kt b/src/jvmMain/kotlin/io/ktor/io/JvmBufferPool.kt new file mode 100644 index 0000000..8757bff --- /dev/null +++ b/src/jvmMain/kotlin/io/ktor/io/JvmBufferPool.kt @@ -0,0 +1,20 @@ +package io.ktor.io + +import java.nio.ByteBuffer + +public class JvmBufferPool( + capacity: Int = DEFAULT_POOL_CAPACITY, + public val byteBufferPool: ObjectPool = ByteBufferPool.Default +) : DefaultPool(capacity) { + + override fun produceInstance(): JvmBuffer = JvmBuffer(byteBufferPool) + + public companion object { + public val Default: ObjectPool = JvmBufferPool() + + public val Empty: ObjectPool = JvmBufferPool( + capacity = 0, + byteBufferPool = ByteBufferPool.Empty + ) + } +} diff --git a/src/jvmMain/kotlin/io/ktor/io/internal/PlatformUtilsJvm.kt b/src/jvmMain/kotlin/io/ktor/io/internal/PlatformUtilsJvm.kt deleted file mode 100644 index f64cd7b..0000000 --- a/src/jvmMain/kotlin/io/ktor/io/internal/PlatformUtilsJvm.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.ktor.io.internal - -import io.ktor.io.* - -internal actual fun platformRead( - byteArrayBuffer: ByteArrayBuffer, - buffer: Buffer -): Boolean { - if (buffer !is DefaultBuffer) return false - - buffer.write(byteArrayBuffer) - return true -} - -internal actual fun platformWrite( - byteArrayBuffer: ByteArrayBuffer, - buffer: Buffer -): Boolean { - if (buffer !is DefaultBuffer) return false - - buffer.read(byteArrayBuffer) - return true -} diff --git a/src/jvmTest/kotlin/io.ktor.io/DefaultBuffetTest.kt b/src/jvmTest/kotlin/io.ktor.io/DefaultBuffetTest.kt deleted file mode 100644 index d92ce29..0000000 --- a/src/jvmTest/kotlin/io.ktor.io/DefaultBuffetTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.ktor.io - -import java.nio.ByteBuffer -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals - -class DefaultBuffetTest { - - private val noPool = object : NoPoolImpl() { - override fun borrow(): ByteBuffer { - throw NotImplementedError() - } - } - - @Test - fun testWriteCanRead() { - val buffer = DefaultBuffer(ByteBuffer.allocate(1024).clear(), noPool) - buffer.writeIndex = 0 - - buffer.writeByte(99) - buffer.writeShort(999) - buffer.writeInt(999_999) - buffer.writeLong(9_999_999_999_999) - - val array = ByteArray(123) { it.toByte() } - buffer.write(array) - - assertEquals(0, buffer.readIndex) - assertEquals(138, buffer.writeIndex) - - assertEquals(99, buffer.readByte()) - assertEquals(999, buffer.readShort()) - assertEquals(999_999, buffer.readInt()) - assertEquals(9_999_999_999_999, buffer.readLong()) - val newArray = ByteArray(123).also { buffer.read(it) } - assertContentEquals(array, newArray) - - assertEquals(138, buffer.readIndex) - assertEquals(138, buffer.writeIndex) - } -} \ No newline at end of file diff --git a/src/jvmTest/kotlin/io.ktor.io/FilesTest.kt b/src/jvmTest/kotlin/io/ktor/io/FilesTest.kt similarity index 79% rename from src/jvmTest/kotlin/io.ktor.io/FilesTest.kt rename to src/jvmTest/kotlin/io/ktor/io/FilesTest.kt index a10b598..8ff915f 100644 --- a/src/jvmTest/kotlin/io.ktor.io/FilesTest.kt +++ b/src/jvmTest/kotlin/io/ktor/io/FilesTest.kt @@ -2,7 +2,6 @@ package io.ktor.io import kotlinx.coroutines.runBlocking import java.nio.file.Files -import java.nio.file.Path import kotlin.io.path.deleteIfExists import kotlin.io.path.readText import kotlin.io.path.writeText @@ -15,9 +14,9 @@ class FilesTest { fun testCopyingFile(): Unit = runBlocking { val content = buildString { repeat(100000) { append("testString$it") } } - val originalFile = Files.createFile(Path.of("original")) + val originalFile = Files.createTempFile("test", "origin") originalFile.writeText(content) - val fileCopy = Path.of("copy") + val fileCopy = Files.createTempFile("test", "copy") val source = FileBytesSource(originalFile) val destination = FileBytesDestination(fileCopy) @@ -44,19 +43,25 @@ class FilesTest { fun testCopyingFileBuffered(): Unit = runBlocking { val content = buildString { repeat(100000) { append("testString$it") } } - val originalFile = Files.createFile(Path.of("original")) + val originalFile = Files.createTempFile("tmp", "original") originalFile.writeText(content) - val fileCopy = Path.of("copy") + val fileCopy = Files.createTempFile("tmp", "copy") val source = BufferedBytesSource(FileBytesSource(originalFile)) val destination = BufferedBytesDestination(FileBytesDestination(fileCopy), 12 * 1024) + var writeCount = 0 while (source.canRead()) { val buffer = source.read() - destination.write(buffer) + val count = destination.write(buffer) + writeCount += count + + println("Written $buffer $count/$writeCount/${content.length} bytes") + destination.awaitFreeSpace() source.awaitContent() } + destination.flush() source.cancel() destination.close() diff --git a/src/jvmTest/kotlin/io/ktor/io/JvmBufferTest.kt b/src/jvmTest/kotlin/io/ktor/io/JvmBufferTest.kt new file mode 100644 index 0000000..135f3bd --- /dev/null +++ b/src/jvmTest/kotlin/io/ktor/io/JvmBufferTest.kt @@ -0,0 +1,34 @@ +package io.ktor.io + +import org.junit.jupiter.api.Test +import java.nio.ByteBuffer +import kotlin.test.assertEquals + +class JvmBufferWithDefaultPoolTest : BufferTest() { + override fun createBuffer(): Buffer = JvmBuffer(ByteBufferPool.Default) +} + +class JvmBufferWithEmptyPoolTest : BufferTest() { + override fun createBuffer(): Buffer = JvmBuffer(ByteBufferPool.Empty) +} + +class JvmBufferTest { + + @Test + fun testConstructorFromByteBuffer() { + val data = ByteBuffer.allocateDirect(1024) + data.position(21) + data.limit(42) + + val buffer = JvmBuffer(data) + assertEquals(21, buffer.readIndex) + assertEquals(42, buffer.writeIndex) + } + + @Test + fun testConstructorFromPool() { + val buffer = JvmBuffer(ByteBufferPool.Default) + assertEquals(0, buffer.readIndex) + assertEquals(0, buffer.writeIndex) + } +} \ No newline at end of file diff --git a/src/nativeMain/kotlin/io/ktor/io/internal/PlatformUtilsNative.kt b/src/nativeMain/kotlin/io/ktor/io/internal/PlatformUtilsNative.kt deleted file mode 100644 index 5d94f8b..0000000 --- a/src/nativeMain/kotlin/io/ktor/io/internal/PlatformUtilsNative.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.ktor.io.internal - -import io.ktor.io.* - -internal actual fun platformRead( - byteArrayBuffer: ByteArrayBuffer, - buffer: Buffer -): Boolean = false - -internal actual fun platformWrite( - byteArrayBuffer: ByteArrayBuffer, - buffer: Buffer -): Boolean = false \ No newline at end of file