diff --git a/LICENSE b/LICENSE index f4b62dab0e6..26f99d529ba 100644 --- a/LICENSE +++ b/LICENSE @@ -222,6 +222,7 @@ Copyright EPFL and Lightbend, Inc. pekko-actor contains code from Netty which was released under an Apache 2.0 license. Copyright 2014 The Netty Project - actor/src/main/scala/org/apache/pekko/io/dns/DnsSettings.scala +- actor/src/main/scala/org/apache/pekko/util/ByteString.scala - actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala --------------- diff --git a/actor/src/main/mima-filters/2.0.x.backwards.excludes/bytestring-bytesMatch.excludes b/actor/src/main/mima-filters/2.0.x.backwards.excludes/bytestring-bytesMatch.excludes new file mode 100644 index 00000000000..fa0f6d8e5e4 --- /dev/null +++ b/actor/src/main/mima-filters/2.0.x.backwards.excludes/bytestring-bytesMatch.excludes @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Ignore bytesMatch - a package private internal method +ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.util.ByteString.bytesMatch") diff --git a/actor/src/main/scala/org/apache/pekko/util/ByteString.scala b/actor/src/main/scala/org/apache/pekko/util/ByteString.scala index ed647d33b47..01dfe761234 100644 --- a/actor/src/main/scala/org/apache/pekko/util/ByteString.scala +++ b/actor/src/main/scala/org/apache/pekko/util/ByteString.scala @@ -11,6 +11,21 @@ * Copyright (C) 2009-2022 Lightbend Inc. */ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + package org.apache.pekko.util import java.io.{ InputStream, ObjectInputStream, ObjectOutputStream, SequenceInputStream } @@ -314,6 +329,85 @@ object ByteString { else -1 } + // Derived from code in Netty + // https://github.com/netty/netty/blob/d28a0fc6598b50fbe8f296831777cf4b653a475f/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java#L242-L325 + override def indexOfSlice(slice: Array[Byte], from: Int): Int = { + val fromIndex = math.max(0, from) + val n = length - fromIndex + val m = slice.length + if (m == 0) return 0 + // When the needle has only one byte that can be read, + // the indexOf() can be used + if (m == 1) return indexOf(slice.head, fromIndex) + var i = 0 + var j = 0 + val aStartIndex = 0 + val bStartIndex = fromIndex + val suffixes = SWARUtil.maxSuf(slice, m, aStartIndex, true) + val prefixes = SWARUtil.maxSuf(slice, m, aStartIndex, false) + val ell = Math.max((suffixes >> 32).toInt, (prefixes >> 32).toInt) + var per = Math.max(suffixes.toInt, prefixes.toInt) + var memory = 0 + val checkLen = Math.min(m - per, ell + 1) + if (SWARUtil.arrayBytesMatch(slice, aStartIndex, slice, aStartIndex + per, checkLen)) { + memory = -1 + while (j <= n - m) { + i = Math.max(ell, memory) + 1 + while (i < m && slice(i + aStartIndex) == bytes(i + j + bStartIndex)) i += 1 + if (i > n) return -1 + if (i >= m) { + i = ell + while (i > memory && slice(i + aStartIndex) == bytes(i + j + bStartIndex)) i -= 1 + if (i <= memory) return j + bStartIndex + j += per + memory = m - per - 1 + } else { + j += i - ell + memory = -1 + } + } + } else { + per = Math.max(ell + 1, m - ell - 1) + 1 + while (j <= n - m) { + i = ell + 1 + while (i < m && slice(i + aStartIndex) == bytes(i + j + bStartIndex)) i += 1 + if (i > n) return -1 + if (i >= m) { + i = ell + while (i >= 0 && slice(i + aStartIndex) == bytes(i + j + bStartIndex)) i -= 1 + if (i < 0) return j + bStartIndex + j += per + } else j += i - ell + } + } + -1 + } + + // Derived from code in Netty + // https://github.com/netty/netty/blob/d28a0fc6598b50fbe8f296831777cf4b653a475f/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java#L366-L408 + override final private[util] def bytesMatch(fromIndex: Int, checkBytes: Array[Byte], bytesFromIndex: Int, + checkLength: Int): Boolean = { + var aIndex = fromIndex + var bIndex = bytesFromIndex + val longCount = checkLength >>> 3 + val byteCount = checkLength & 7 + var i = 0 + while (i < longCount) { + if (SWARUtil.getLong(bytes, aIndex) != SWARUtil.getLong(checkBytes, bIndex)) return false + aIndex += 8 + bIndex += 8 + i += 1 + } + i = 0 + while (i < byteCount) { + if (bytes(aIndex) != checkBytes(bIndex)) return false + aIndex += 1 + bIndex += 1 + i += 1 + } + true + } + override def slice(from: Int, until: Int): ByteString = if (from <= 0 && until >= length) this else if (from >= length || until <= 0 || from >= until) ByteString.empty @@ -575,6 +669,87 @@ object ByteString { else -1 } + // Derived from code in Netty + // https://github.com/netty/netty/blob/d28a0fc6598b50fbe8f296831777cf4b653a475f/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java#L242-L325 + override def indexOfSlice(slice: Array[Byte], from: Int): Int = { + val fromIndex = math.max(0, from) + val n = length - fromIndex + val m = slice.length + if (m == 0) return 0 + // When the needle has only one byte that can be read, + // the indexOf() can be used + if (m == 1) return indexOf(slice.head, fromIndex) + var i = 0 + var j = 0 + val aStartIndex = 0 + val bStartIndex = fromIndex + startIndex + val suffixes = SWARUtil.maxSuf(slice, m, aStartIndex, true) + val prefixes = SWARUtil.maxSuf(slice, m, aStartIndex, false) + val ell = Math.max((suffixes >> 32).toInt, (prefixes >> 32).toInt) + var per = Math.max(suffixes.toInt, prefixes.toInt) + var memory = 0 + val checkLen = Math.min(m - per, ell + 1) + if (SWARUtil.arrayBytesMatch(slice, aStartIndex, slice, aStartIndex + per, checkLen)) { + memory = -1 + while (j <= n - m) { + i = Math.max(ell, memory) + 1 + while (i < m && (slice(i + aStartIndex) == bytes(i + j + bStartIndex))) i += 1 + if (i > n) return -1 + if (i >= m) { + i = ell + while (i > memory && (slice(i + aStartIndex) == bytes(i + j + bStartIndex))) i -= 1 + if (i <= memory) return j + bStartIndex - startIndex + j += per + memory = m - per - 1 + } else { + j += i - ell + memory = -1 + } + } + } else { + per = Math.max(ell + 1, m - ell - 1) + 1 + while (j <= n - m) { + i = ell + 1 + while (i < m && (slice(i + aStartIndex) == bytes(i + j + bStartIndex))) i += 1 + if (i > n) return -1 + if (i >= m) { + i = ell + while (i >= 0 && (slice(i + aStartIndex) == bytes(i + j + bStartIndex))) i -= 1 + if (i < 0) return j + bStartIndex - startIndex + j += per + } else j += i - ell + } + } + -1 + } + + // Derived from code in Netty + // https://github.com/netty/netty/blob/d28a0fc6598b50fbe8f296831777cf4b653a475f/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java#L366-L408 + override final private[util] def bytesMatch(fromIndex: Int, + checkBytes: Array[Byte], + bytesFromIndex: Int, + checkLength: Int): Boolean = { + var aIndex = fromIndex + startIndex + var bIndex = bytesFromIndex + val longCount = checkLength >>> 3 + val byteCount = checkLength & 7 + var i = 0 + while (i < longCount) { + if (SWARUtil.getLong(bytes, aIndex) != SWARUtil.getLong(checkBytes, bIndex)) return false + aIndex += 8 + bIndex += 8 + i += 1 + } + i = 0 + while (i < byteCount) { + if (bytes(aIndex) != checkBytes(bIndex)) return false + aIndex += 1 + bIndex += 1 + i += 1 + } + true + } + override def copyToArray[B >: Byte](dest: Array[B], start: Int, len: Int): Int = { // min of the bytes available to copy, bytes there is room for in dest and the requested number of bytes val toCopy = math.min(math.min(len, length), dest.length - start) @@ -912,6 +1087,22 @@ object ByteString { } } + private[util] def bytesMatch(fromIndex: Int, + checkBytes: Array[Byte], + checkBytesFromIndex: Int, + checkLength: Int): Boolean = { + if (checkLength > 1 && bytestrings.nonEmpty && bytestrings.head.length >= fromIndex + checkLength) { + bytestrings.head.bytesMatch(fromIndex, checkBytes, checkBytesFromIndex, checkLength) + } else { + var i = 0 + while (i < checkLength) { + if (apply(fromIndex + i) != checkBytes(checkBytesFromIndex + i)) return false + i += 1 + } + true + } + } + protected def writeReplace(): AnyRef = new SerializationProxy(this) } @@ -1093,22 +1284,10 @@ sealed abstract class ByteString * @since 2.0.0 */ def indexOfSlice(slice: Array[Byte], from: Int): Int = { - // this is only called if the first byte matches, so we can skip that check - def check(startPos: Int): Boolean = { - var i = startPos + 1 - var j = 1 - // let's trust the calling code has ensured that we have enough bytes in this ByteString - while (j < slice.length) { - if (apply(i) != slice(j)) return false - i += 1 - j += 1 - } - true - } @tailrec def rec(from: Int): Int = { val startPos = indexOf(slice.head, from, length - slice.length + 1) if (startPos == -1) -1 - else if (check(startPos)) startPos + else if (bytesMatch(startPos, slice, 0, slice.length)) startPos else rec(startPos + 1) } val sliceLength = slice.length @@ -1147,18 +1326,7 @@ sealed abstract class ByteString */ def startsWith(bytes: Array[Byte], offset: Int): Boolean = { if (length - offset < bytes.length) false - else { - var i = offset - var j = 0 - while (j < bytes.length) { - // we know that byteString is at least as long as bytes, - // so no need to check i < length - if (apply(i) != bytes(j)) return false - i += 1 - j += 1 - } - true - } + else bytesMatch(offset, bytes, 0, bytes.length) } /** @@ -1170,6 +1338,15 @@ sealed abstract class ByteString */ def startsWith(bytes: Array[Byte]): Boolean = startsWith(bytes, 0) + /** + * Tests whether the bytes in a segment of this ByteString match the provided bytes. + * Internal use only. ByteString1 and ByteString1C have optimized versions. + */ + private[util] def bytesMatch(fromIndex: Int, + checkBytes: Array[Byte], + checkBytesFromIndex: Int, + checkLength: Int): Boolean + override def grouped(size: Int): Iterator[ByteString] = { if (size <= 0) { throw new IllegalArgumentException(s"size=$size must be positive") diff --git a/actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala b/actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala index cabbb52c1cf..be7d8225133 100644 --- a/actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala +++ b/actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala @@ -41,7 +41,7 @@ private[util] object SWARUtil { /** * Compiles given byte into a long pattern suitable for SWAR operations. */ - def compilePattern(byteToFind: Byte): Long = (byteToFind & 0xFFL) * 0x101010101010101L + final def compilePattern(byteToFind: Byte): Long = (byteToFind & 0xFFL) * 0x101010101010101L /** * Applies a compiled pattern to given word. @@ -51,7 +51,7 @@ private[util] object SWARUtil { * @param pattern the pattern to apply * @return a word where each byte that matches the pattern has the highest bit set */ - def applyPattern(word: Long, pattern: Long): Long = { + final def applyPattern(word: Long, pattern: Long): Long = { val input = word ^ pattern val tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL ~(tmp | input | 0x7F7F7F7F7F7F7F7FL) @@ -65,7 +65,7 @@ private[util] object SWARUtil { * @return the index of the first occurrence of the specified pattern in the specified word. * If no pattern is found, returns 8. */ - def getIndex(word: Long): Int = + final def getIndex(word: Long): Int = java.lang.Long.numberOfLeadingZeros(word) >>> 3 /** @@ -76,8 +76,9 @@ private[util] object SWARUtil { * @param array the byte array to read from * @param index the index to read from * @return the long value at the specified index + * @throws IndexOutOfBoundsException if index is out of bounds */ - def getLong(array: Array[Byte], index: Int): Long = { + final def getLong(array: Array[Byte], index: Int): Long = { if (longBeArrayViewSupported) { longBeArrayView.get(array, index) } else { @@ -91,4 +92,67 @@ private[util] object SWARUtil { (array(index + 7).toLong & 0xFF) } } + + // Derived from code in Netty + // https://github.com/netty/netty/blob/d28a0fc6598b50fbe8f296831777cf4b653a475f/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java#L366-L408 + final def arrayBytesMatch(arrayBytes: Array[Byte], + fromIndex: Int, + checkBytes: Array[Byte], + bytesFromIndex: Int, + checkLength: Int): Boolean = { + var aIndex = fromIndex + var bIndex = bytesFromIndex + val longCount = checkLength >>> 3 + val byteCount = checkLength & 7 + var i = 0 + while (i < longCount) { + if (getLong(arrayBytes, aIndex) != getLong(checkBytes, bIndex)) return false + aIndex += 8 + bIndex += 8 + i += 1 + } + i = 0 + while (i < byteCount) { + if (arrayBytes(aIndex) != checkBytes(bIndex)) return false + aIndex += 1 + bIndex += 1 + i += 1 + } + true + } + + // Derived from code in Netty + // https://github.com/netty/netty/blob/a5343227b10456ec889a3fdc5fa4246f036a216d/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java#L327-L356 + final def maxSuf(arrayBytes: Array[Byte], m: Int, start: Int, isSuffix: Boolean): Long = { + var p = 1 + var ms = -1 + var j = start + var k = 1 + var a = 0 + var b = 0 + while (j + k < m) { + a = arrayBytes(j + k) + b = arrayBytes(ms + k) + val suffix = if (isSuffix) a < b + else a > b + if (suffix) { + j += k + k = 1 + p = j - ms + } else if (a == b) { + if (k != p) { + k += 1 + } else { + j += p + k = 1 + } + } else { + ms = j + j = ms + 1 + k = 1 + p = 1 + } + } + (ms.toLong << 32) + p + } } diff --git a/legal/pekko-actor-jar-license.txt b/legal/pekko-actor-jar-license.txt index fe77d08cc3c..31c60b0a161 100644 --- a/legal/pekko-actor-jar-license.txt +++ b/legal/pekko-actor-jar-license.txt @@ -222,6 +222,7 @@ Copyright EPFL and Lightbend, Inc. pekko-actor contains code from Netty which was released under an Apache 2.0 license. Copyright 2014 The Netty Project - actor/src/main/scala/org/apache/pekko/io/dns/DnsSettings.scala +- actor/src/main/scala/org/apache/pekko/util/ByteString.scala - actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala ---------------