Skip to content

Commit 95f605e

Browse files
authored
Refine Source|Buffer.indexOf(ByteBuffer) contracts (#424)
* Refine Source|Buffer.indexOf(ByteBuffer) contracts Closes #422 * Align indexOf(empty bytestring) behavior with CS.indexOf("") Closes #423
1 parent 77b165c commit 95f605e

File tree

3 files changed

+50
-15
lines changed

3 files changed

+50
-15
lines changed

core/common/src/ByteStrings.kt

+23-7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import kotlinx.io.bytestring.isEmpty
1010
import kotlinx.io.bytestring.unsafe.UnsafeByteStringApi
1111
import kotlinx.io.bytestring.unsafe.UnsafeByteStringOperations
1212
import kotlinx.io.unsafe.UnsafeBufferOperations
13+
import kotlin.math.max
1314
import kotlin.math.min
1415

1516
/**
@@ -85,21 +86,26 @@ public fun Source.readByteString(byteCount: Int): ByteString {
8586
* expands the source's buffer as necessary until [byteString] is found. This reads an unbounded number of
8687
* bytes into the buffer. Returns `-1` if the stream is exhausted before the requested bytes are found.
8788
*
89+
* For empty byte strings this function returns [startIndex] if it lays within underlying buffer's bounds,
90+
* `0` if [startIndex] was negative and the size of the underlying buffer if [startIndex] exceeds its size.
91+
* If the [startIndex] value was greater than the underlying buffer's size, the data will be fetched and buffered
92+
* despite the [byteString] is empty.
93+
*
8894
* @param byteString the sequence of bytes to find within the source.
8995
* @param startIndex the index into the source to start searching from.
9096
*
91-
* @throws IllegalArgumentException if [startIndex] is negative.
9297
* @throws IllegalStateException if the source is closed.
9398
* @throws IOException when some I/O error occurs.
9499
*
95100
* @sample kotlinx.io.samples.ByteStringSamples.indexOfByteString
96101
*/
97102
@OptIn(InternalIoApi::class, UnsafeByteStringApi::class)
98103
public fun Source.indexOf(byteString: ByteString, startIndex: Long = 0): Long {
99-
require(startIndex >= 0) { "startIndex: $startIndex" }
104+
val startIndex = max(0, startIndex)
100105

101106
if (byteString.isEmpty()) {
102-
return 0
107+
request(startIndex)
108+
return min(startIndex, buffer.size)
103109
}
104110

105111
var offset = startIndex
@@ -117,12 +123,22 @@ public fun Source.indexOf(byteString: ByteString, startIndex: Long = 0): Long {
117123
return -1
118124
}
119125

126+
/**
127+
* Returns the index of the first match for [byteString] in the buffer at or after [startIndex].
128+
*
129+
* For empty byte strings this function returns [startIndex] if it lays within buffer's bounds,
130+
* `0` if [startIndex] was negative and [Buffer.size] if it was greater or equal to [Buffer.size].
131+
*
132+
* @param byteString the sequence of bytes to find within the buffer.
133+
* @param startIndex the index into the buffer to start searching from.
134+
*
135+
* @sample kotlinx.io.samples.ByteStringSamples.indexOfByteString
136+
*/
120137
@OptIn(UnsafeByteStringApi::class)
121138
public fun Buffer.indexOf(byteString: ByteString, startIndex: Long = 0): Long {
122-
require(startIndex <= size) {
123-
"startIndex ($startIndex) should not exceed size ($size)"
124-
}
125-
if (byteString.isEmpty()) return 0
139+
val startIndex = max(0, min(startIndex, size))
140+
141+
if (byteString.isEmpty()) return startIndex
126142
if (startIndex > size - byteString.size) return -1L
127143

128144
UnsafeByteStringOperations.withByteArrayUnsafe(byteString) { byteStringData ->

core/common/test/AbstractSourceTest.kt

+6-8
Original file line numberDiff line numberDiff line change
@@ -1723,12 +1723,15 @@ abstract class AbstractBufferedSourceTest internal constructor(
17231723
sink.writeString("flop flip flop")
17241724
sink.emit()
17251725
assertEquals(10, source.indexOf("flop".encodeToByteString(), 1))
1726+
assertEquals(0, source.indexOf("flop".encodeToByteString(), -1))
17261727
source.readString() // Clear stream
17271728

1728-
// Make sure we backtrack and resume searching after partial match.
1729+
// Make sure we backtrack and resume searching after the partial match.
17291730
sink.writeString("hi hi hi hi hey")
17301731
sink.emit()
17311732
assertEquals(6, source.indexOf("hi hi hey".encodeToByteString(), 1))
1733+
1734+
assertEquals(-1, source.indexOf("ho ho ho".encodeToByteString(), 9001))
17321735
}
17331736

17341737
@Test
@@ -1738,13 +1741,8 @@ abstract class AbstractBufferedSourceTest internal constructor(
17381741
sink.writeString("blablabla")
17391742
sink.emit()
17401743
assertEquals(0, source.indexOf(ByteString()))
1741-
}
1742-
1743-
@Test
1744-
fun indexOfByteStringInvalidArgumentsThrows() {
1745-
assertFailsWith<IllegalArgumentException> {
1746-
source.indexOf("hi".encodeToByteString(), -1)
1747-
}
1744+
assertEquals(0, source.indexOf(ByteString(), -1))
1745+
assertEquals(9, source.indexOf(ByteString(), 100000))
17481746
}
17491747

17501748
/**

core/common/test/CommonBufferTest.kt

+21
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import kotlinx.io.bytestring.ByteString
2424
import kotlinx.io.bytestring.encodeToByteString
2525
import kotlin.test.Test
2626
import kotlin.test.assertEquals
27+
import kotlin.test.assertFails
2728
import kotlin.test.assertFailsWith
2829
import kotlin.test.assertTrue
2930

@@ -618,4 +619,24 @@ class CommonBufferTest {
618619
assertEquals(null, dst.head?.prev)
619620
assertEquals(null, dst.tail?.next)
620621
}
622+
623+
@Test
624+
fun indexOfByteString() {
625+
val buffer = Buffer()
626+
buffer.writeString("hello")
627+
628+
assertEquals(-1, buffer.indexOf(ByteString(1, 2, 3), -1))
629+
assertEquals(-1, buffer.indexOf(ByteString(1, 2, 3), 10))
630+
631+
assertEquals(2, buffer.indexOf("ll".encodeToByteString()))
632+
assertEquals(2, buffer.indexOf("ll".encodeToByteString(), 2))
633+
assertEquals(2, buffer.indexOf("ll".encodeToByteString(), -2))
634+
assertEquals(-1, buffer.indexOf("ll".encodeToByteString(), 3))
635+
assertEquals(-1, buffer.indexOf("hello world".encodeToByteString()))
636+
637+
assertEquals(0, buffer.indexOf(ByteString()))
638+
assertEquals(buffer.size, buffer.indexOf(ByteString(), 1000))
639+
assertEquals(1, buffer.indexOf(ByteString(), 1))
640+
assertEquals(0, buffer.indexOf(ByteString(), -1))
641+
}
621642
}

0 commit comments

Comments
 (0)