From c62ddcb8a54ce4d27e784a620ea8e126b29edc7c Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 18 Oct 2025 15:00:19 +0100 Subject: [PATCH 1/3] copy over some code from Netty that uses SWAR techniques for checking array segments match --- LICENSE | 1 + .../org/apache/pekko/util/ByteString.scala | 113 ++++++++++++++---- .../org/apache/pekko/util/SWARUtil.scala | 1 + legal/pekko-actor-jar-license.txt | 1 + 4 files changed, 91 insertions(+), 25 deletions(-) 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/scala/org/apache/pekko/util/ByteString.scala b/actor/src/main/scala/org/apache/pekko/util/ByteString.scala index 952e3fe0606..76a8a4de3e3 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,31 @@ 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#L366-L408 + override 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 (apply(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 +615,31 @@ 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#L366-L408 + override 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 (apply(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 +977,20 @@ 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 - 1) { + 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 +1172,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 +1214,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 +1226,13 @@ 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 8f7cc25ffd8..21881b2a1c9 100644 --- a/actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala +++ b/actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala @@ -77,6 +77,7 @@ 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 = { if (longBeArrayViewSupported) { 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 --------------- From d1a996120aad8c397e294593ab0e85d6d4741a2e Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 18 Oct 2025 15:10:18 +0100 Subject: [PATCH 2/3] format --- .../scala/org/apache/pekko/util/ByteString.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) 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 76a8a4de3e3..3532907ff08 100644 --- a/actor/src/main/scala/org/apache/pekko/util/ByteString.scala +++ b/actor/src/main/scala/org/apache/pekko/util/ByteString.scala @@ -617,7 +617,9 @@ object ByteString { // Derived from code in Netty // https://github.com/netty/netty/blob/d28a0fc6598b50fbe8f296831777cf4b653a475f/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java#L366-L408 - override private[util] def bytesMatch(fromIndex: Int, checkBytes: Array[Byte], bytesFromIndex: Int, + override private[util] def bytesMatch(fromIndex: Int, + checkBytes: Array[Byte], + bytesFromIndex: Int, checkLength: Int): Boolean = { var aIndex = fromIndex + startIndex var bIndex = bytesFromIndex @@ -977,8 +979,10 @@ object ByteString { } } - private[util] def bytesMatch(fromIndex: Int, checkBytes: Array[Byte], checkBytesFromIndex: Int, checkLength: Int) - : Boolean = { + private[util] def bytesMatch(fromIndex: Int, + checkBytes: Array[Byte], + checkBytesFromIndex: Int, + checkLength: Int): Boolean = { if (checkLength > 1 && bytestrings.nonEmpty && bytestrings.head.length >= fromIndex + checkLength - 1) { bytestrings.head.bytesMatch(fromIndex, checkBytes, checkBytesFromIndex, checkLength) } else { @@ -1230,8 +1234,10 @@ sealed abstract class ByteString * 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 + private[util] def bytesMatch(fromIndex: Int, + checkBytes: Array[Byte], + checkBytesFromIndex: Int, + checkLength: Int): Boolean override def grouped(size: Int): Iterator[ByteString] = { if (size <= 0) { From f719cfe684220d1812f9aac794f4971f82e30d68 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 18 Oct 2025 16:03:59 +0100 Subject: [PATCH 3/3] fix issue --- actor/src/main/scala/org/apache/pekko/util/ByteString.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3532907ff08..b9e195295ae 100644 --- a/actor/src/main/scala/org/apache/pekko/util/ByteString.scala +++ b/actor/src/main/scala/org/apache/pekko/util/ByteString.scala @@ -346,7 +346,7 @@ object ByteString { } i = 0 while (i < byteCount) { - if (apply(aIndex) != checkBytes(bIndex)) return false + if (bytes(aIndex) != checkBytes(bIndex)) return false aIndex += 1 bIndex += 1 i += 1 @@ -634,7 +634,7 @@ object ByteString { } i = 0 while (i < byteCount) { - if (apply(aIndex) != checkBytes(bIndex)) return false + if (bytes(aIndex) != checkBytes(bIndex)) return false aIndex += 1 bIndex += 1 i += 1