scodec.bits.ByteVector.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2013, Scodec
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package scodec.bits
import java.io.OutputStream
import java.nio.{ByteBuffer, CharBuffer}
import java.nio.charset.{CharacterCodingException, Charset}
import java.security.{
AlgorithmParameters,
GeneralSecurityException,
Key,
MessageDigest,
SecureRandom
}
import java.util.UUID
import java.util.concurrent.atomic.AtomicLong
import java.util.zip.{DataFormatException, Deflater, Inflater}
import javax.crypto.Cipher
import scala.annotation.tailrec
/** An immutable vector of bytes, backed by a balanced binary tree of
* chunks. Most operations are logarithmic in the depth of this tree,
* including `++`, `:+`, `+:`, `update`, and `insert`. Where possible,
* operations return lazy views rather than copying any underlying bytes.
* Use `copy` to copy all underlying bytes to a fresh, array-backed `ByteVector`.
*
* Unless otherwise noted, operations follow the same naming as the scala
* standard library collections, though this class does not extend any of the
* standard scala collections. Use `toIndexedSeq`, `toSeq`, or `toIterable`
* to obtain a regular `scala.collection` type.
*
* @groupname collection Collection Like Methods
* @groupprio collection 0
*
* @groupname bitwise Bitwise Operations
* @groupprio bitwise 1
*
* @groupname conversions Conversions
* @groupprio conversions 2
*
* @groupname buffer Buffering
* @groupprio buffer 3
*
* @groupname crypto Cryptography
* @groupprio crypto 4
*
* @define bitwiseOperationsReprDescription bit vector
* @define returnsView This method returns a view and hence, is O(1). Call [[compact]] to generate a new strict vector.
*/
sealed abstract class ByteVector
extends BitwiseOperations[ByteVector, Long]
with Ordered[ByteVector]
with Serializable {
import ByteVector._
/** Returns the number of bytes in this vector.
* @group collection
*/
def size: Long
/** Returns the number of bytes in this vector, or `None` if the size does not
* fit into an `Int`.
*
* @group collection
*/
final def intSize: Option[Int] = if (size <= Int.MaxValue) Some(size.toInt) else None
/** Alias for [[size]].
* @group collection
*/
final def length: Long = size
/** Returns true if this vector has no bytes.
* @group collection
*/
final def isEmpty: Boolean = size == 0
/** Returns true if this vector has a non-zero number of bytes.
* @group collection
*/
final def nonEmpty: Boolean = !isEmpty
/** Gets the byte at the specified index.
* @throws IndexOutOfBoundsException if the specified index is not in `[0, size)`
* @group collection
*/
def get(index: Long): Byte = {
checkIndex(index)
getImpl(index)
}
protected def getImpl(index: Long): Byte
/** Alias for [[get]].
* @throws IndexOutOfBoundsException if the specified index is not in `[0, size)`
* @group collection
*/
final def apply(index: Long): Byte = get(index)
/** Returns the byte at the specified index, or `None` if the index is out of range.
* @group collection
*/
final def lift(index: Long): Option[Byte] =
if (index >= 0 && index < size) Some(apply(index))
else None
/** Returns a vector with the byte at the specified index replaced with the specified byte.
* @group collection
*/
final def update(idx: Long, b: Byte): ByteVector = {
checkIndex(idx)
(take(idx) :+ b) ++ drop(idx + 1)
}
/** Returns a vector with the specified byte inserted at the specified index.
* @group collection
*/
final def insert(idx: Long, b: Byte): ByteVector =
(take(idx) :+ b) ++ drop(idx)
/** Returns a vector with the specified byte vector inserted at the specified index.
* @group collection
*/
final def splice(idx: Long, b: ByteVector): ByteVector =
take(idx) ++ b ++ drop(idx)
/** Returns a vector with the specified byte vector replacing bytes `[idx, idx + b.size]`.
* @group collection
*/
final def patch(idx: Long, b: ByteVector): ByteVector =
take(idx) ++ b ++ drop(idx + b.size)
/** Returns a new byte vector representing this vector's contents followed by the specified vector's contents.
* @group collection
*/
def ++(other: ByteVector): ByteVector =
if (this.isEmpty) other
else Chunks(Append(this, other)).bufferBy(64)
/** Returns a new vector with the specified byte prepended.
* @group collection
*/
final def +:(byte: Byte): ByteVector = ByteVector(byte) ++ this
/** Returns a new vector with the specified byte appended.
* @group collection
*/
def :+(byte: Byte): ByteVector =
this ++ ByteVector(byte)
/** Returns a vector of all bytes in this vector except the first `n` bytes.
*
* The resulting vector's size is `0 max (size - n)`.
*
* @group collection
*/
def drop(n: Long): ByteVector = {
val n1 = n.min(size).max(0)
if (n1 == size) ByteVector.empty
else if (n1 == 0) this
else {
@annotation.tailrec
def go(cur: ByteVector, n1: Long, accR: List[ByteVector]): ByteVector =
cur match {
case Chunk(bs) => accR.foldLeft(Chunk(bs.drop(n1)): ByteVector)(_ ++ _).unbuffer
case Append(l, r) =>
if (n1 > l.size) go(r, n1 - l.size, accR)
else go(l, n1, r :: accR)
case b: Buffer =>
if (n1 > b.hd.size) go(b.lastBytes, n1 - b.hd.size, accR)
else go(b.hd, n1, b.lastBytes :: accR)
case c: Chunks => go(c.chunks, n1, accR)
}
go(this, n1, Nil)
}
}
/** Returns a vector of all bytes in this vector except the last `n` bytes.
*
* The resulting vector's size is `0 max (size - n)`.
*
* @group collection
*/
final def dropRight(n: Long): ByteVector =
take(size - n.max(0))
/** Drops the longest prefix of this vector such that every byte of the prefix satisfies the specific predicate.
*
* @group collection
*/
final def dropWhile(f: Byte => Boolean): ByteVector = {
var toDrop = 0L
foreachSPartial(new F1BB {
def apply(b: Byte) = {
val cont = f(b)
if (cont) toDrop += 1
cont
}
})
drop(toDrop)
}
/** Returns a vector of the first `n` bytes of this vector.
*
* The resulting vector's size is `n min size`.
*
* Note: if an `n`-byte vector is required, use the `acquire` method instead.
*
* @see acquire
* @group collection
*/
def take(n: Long): ByteVector = {
val n1 = n.min(size).max(0)
if (n1 == size) this
else if (n1 == 0) ByteVector.empty
else {
@annotation.tailrec
def go(accL: ByteVector, cur: ByteVector, n1: Long): ByteVector =
cur match {
case Chunk(bs) => accL ++ Chunk(bs.take(n1))
case Append(l, r) =>
if (n1 > l.size) go(accL ++ l, r, n1 - l.size)
else go(accL, l, n1)
case c: Chunks => go(accL, c.chunks, n1)
case b: Buffer => go(accL, b.unbuffer, n1)
}
go(ByteVector.empty, this, n1)
}
}
/** Returns a vector of the last `n` bytes of this vector.
*
* The resulting vector's size is `n min size`.
*
* @group collection
*/
final def takeRight(n: Long): ByteVector =
drop(size - n)
/** Returns the longest prefix of this vector such that every byte satisfies the specific predicate.
*
* @group collection
*/
final def takeWhile(f: Byte => Boolean): ByteVector = {
var toTake = 0L
foreachSPartial(new F1BB {
def apply(b: Byte) = {
val cont = f(b)
if (cont) toTake += 1
cont
}
})
take(toTake)
}
/** Returns a pair of vectors that is equal to `(take(n), drop(n))`.
* @group collection
*/
final def splitAt(n: Long): (ByteVector, ByteVector) = (take(n), drop(n))
/** Returns a vector made up of the bytes starting at index `from` up to index `until`.
* @group collection
*/
final def slice(from: Long, until: Long): ByteVector =
drop(from).take(until - (from.max(0)))
/** Returns a vector whose contents are the results of taking the first `n` bytes of this vector.
*
* If this vector does not contain at least `n` bytes, an error message is returned.
*
* @see take
* @group collection
*/
def acquire(n: Long): Either[String, ByteVector] =
if (n <= size) Right(take(n))
else Left(s"cannot acquire $n bytes from a vector that contains $size bytes")
/** Consumes the first `n` bytes of this vector and decodes them with the specified function,
* resulting in a vector of the remaining bytes and the decoded value. If this vector
* does not have `n` bytes or an error occurs while decoding, an error is returned instead.
*
* @group collection
*/
final def consume[A](
n: Long
)(decode: ByteVector => Either[String, A]): Either[String, (ByteVector, A)] =
for {
toDecode <- acquire(n)
decoded <- decode(toDecode)
} yield (drop(n), decoded)
/** Applies a binary operator to a start value and all elements of this vector, going left to right.
* @param z starting value
* @param f operator to apply
* @group collection
*/
final def foldLeft[A](z: A)(f: (A, Byte) => A): A = {
var acc = z
foreachS(new F1BU { def apply(b: Byte) = acc = f(acc, b) })
acc
}
/** Applies a binary operator to a start value and all elements of this vector, going right to left.
* @param z starting value
* @param f operator to apply
* @group collection
*/
final def foldRight[A](z: A)(f: (Byte, A) => A): A =
reverse.foldLeft(z)((tl, h) => f(h, tl))
/** Applies a binary operator to a start value and all segments(views) of this ByteVector expressed as read-only ByteBuffer, going left to right.
* @param z Starting value
* @param f operator to apply
* @group collection
*/
final def foldLeftBB[A](z: A)(f: (A, ByteBuffer) => A): A = {
@annotation.tailrec
def go(rem: List[ByteVector], a: A): A =
rem match {
case Chunk(bs) :: rem => go(rem, f(a, bs.at.asByteBuffer(bs.offset, bs.size.toInt)))
case Append(l, r) :: rem => go(l :: r :: rem, a)
case Chunks(Append(l, r)) :: rem => go(l :: r :: rem, a)
case (b: Buffer) :: rem => go(b.unbuffer :: rem, a)
case Nil => a
}
go(this :: Nil, z)
}
/** Applies a binary operator to a start value and all segments(views) of this ByteVector expressed as read-only ByteBuffer, going right ot left.
* @param z Starting value
* @param f operator to apply
* @group collection
*/
final def foldRightBB[A](z: A)(f: (ByteBuffer, A) => A): A =
reverse.foldLeftBB(z)((tl, h) => f(h, tl))
/** Applies the specified function to each element of this vector.
* @group collection
*/
final def foreach(f: Byte => Unit): Unit = foreachS(new F1BU { def apply(b: Byte) = f(b) })
private[scodec] final def foreachS(f: F1BU): Unit = foreachV(_.foreach(f))
private[scodec] final def foreachSPartial(f: F1BB): Boolean = foreachVPartial(_.foreachPartial(f))
private[scodec] final def foreachV(f: View => Unit): Unit = {
@annotation.tailrec
def go(rem: List[ByteVector]): Unit =
rem match {
case Chunk(bs) :: rem => f(bs); go(rem)
case Append(l, r) :: rem => go(l :: r :: rem)
case Chunks(Append(l, r)) :: rem => go(l :: r :: rem)
case (b: Buffer) :: rem => go(b.unbuffer :: rem)
case Nil => ()
}
go(this :: Nil)
}
private[scodec] final def foreachVPartial(f: View => Boolean): Boolean = {
@annotation.tailrec
def go(rem: List[ByteVector]): Boolean =
rem match {
case Chunk(bs) :: rem => if (f(bs)) go(rem) else false
case Append(l, r) :: rem => go(l :: r :: rem)
case Chunks(Append(l, r)) :: rem => go(l :: r :: rem)
case (b: Buffer) :: rem => go(b.unbuffer :: rem)
case Nil => true
}
go(this :: Nil)
}
/** Returns true if this byte vector starts with the specified vector.
* @group collection
*/
final def startsWith(b: ByteVector): Boolean =
take(b.size) === b
/** Returns true if this byte vector ends with the specified vector.
* @group collection
*/
final def endsWith(b: ByteVector): Boolean =
takeRight(b.size) === b
/** Finds the first index of the specified byte pattern in this vector.
* @return index of slice or -1 if not found
* @group collection
*/
final def indexOfSlice(slice: ByteVector): Long = indexOfSlice(slice, 0)
/** Finds the first index after `from` of the specified byte pattern in this vector.
* @return index of slice or -1 if not found
* @group collection
*/
final def indexOfSlice(slice: ByteVector, from: Long): Long = {
@annotation.tailrec
def go(b: ByteVector, idx: Long): Long =
if (b.startsWith(slice)) idx
else if (b.isEmpty) -1
else go(b.tail, idx + 1)
go(drop(from), from)
}
/** Determines if the specified slice is in this vector.
* @group collection
*/
final def containsSlice(slice: ByteVector): Boolean = indexOfSlice(slice) >= 0
// This was public before version 1.1.8 so it must stay here for bincompat
// The public grouped method is adding via an extension method defined in the companion
private[bits] final def grouped(chunkSize: Long): Stream[ByteVector] =
groupedIterator(chunkSize).toStream
private final def groupedIterator(chunkSize: Long): Iterator[ByteVector] =
if (isEmpty) Iterator.empty
else if (size <= chunkSize) Iterator(this)
else Iterator(take(chunkSize)) ++ drop(chunkSize).groupedIterator(chunkSize)
/** Returns the first byte of this vector or throws if vector is emtpy.
* @group collection
*/
final def head: Byte = apply(0)
/** Returns the first byte of this vector or `None` if vector is emtpy.
* @group collection
*/
final def headOption: Option[Byte] = lift(0)
/** Returns a vector of all bytes in this vector except the first byte.
* @group collection
*/
final def tail: ByteVector = drop(1)
/** Returns a vector of all bytes in this vector except the last byte.
* @group collection
*/
final def init: ByteVector = dropRight(1)
/** Returns the last byte in this vector or throws if vector is empty.
* @group collection
*/
final def last: Byte = apply(size - 1)
/** Returns the last byte in this vector or returns `None` if vector is empty.
* @group collection
*/
final def lastOption: Option[Byte] = lift(size - 1)
/** Alias for `padRight`.
*
* @throws IllegalArgumentException if `n < size`
* @group collection
*/
final def padTo(n: Long): ByteVector = padRight(n)
/** Returns an `n`-byte vector whose contents are this vector's contents followed by 0 or more zero bytes.
*
* @throws IllegalArgumentException if `n < size`
* @group collection
*/
final def padRight(n: Long): ByteVector =
if (n < size) throw new IllegalArgumentException(s"ByteVector.padRight($n)")
else this ++ ByteVector.fill(n - size)(0)
/** Returns an `n`-bytes vector whose contents are 0 or more zero bytes followed by this vector's contents.
*
* @throws IllegalArgumentException if `n < size`
* @group collection
*/
final def padLeft(n: Long): ByteVector =
if (n < size) throw new IllegalArgumentException(s"ByteVector.padLeft($n)")
else ByteVector.fill(n - size)(0) ++ this
/** Returns a vector where each byte is the result of applying the specified function to the corresponding byte in this vector.
* $returnsView
* @group collection
*/
final def map(f: Byte => Byte): ByteVector =
ByteVector.viewAt((i: Long) => f(apply(i)), size)
/** Returns a vector where each byte is the result of applying the specified function to the corresponding byte in this vector.
* Only the least significant byte is used (the three most significant bytes are ignored).
* $returnsView
* @group collection
*/
final def mapI(f: Byte => Int): ByteVector =
map(f.andThen(_.toByte))
private[scodec] final def mapS(f: F1B): ByteVector =
ByteVector.view(new At { def apply(i: Long) = f(ByteVector.this(i)) }, size)
/** Returns a vector with the bytes of this vector in reverse order.
* $returnsView
* @group collection
*/
final def reverse: ByteVector =
ByteVector.viewAt((l: Long) => apply(size - l - 1), size)
final def shiftLeft(n: Long): ByteVector =
BitVector(this).shiftLeft(n).toByteVector
final def shiftRight(n: Long, signExtension: Boolean): ByteVector =
BitVector(this).shiftRight(n, signExtension).toByteVector
final def rotateLeft(n: Long): ByteVector =
BitVector(this).rotateLeft(n).toByteVector
final def rotateRight(n: Long): ByteVector =
BitVector(this).rotateRight(n).toByteVector
/** Returns a vector with the same contents but represented as a single tree node internally.
*
* This may involve copying data, but has the advantage that lookups index directly into a single
* node rather than traversing a logarithmic number of nodes in this tree.
*
* Calling this method on an already compacted vector is a no-op.
*
* @group collection
*/
final def compact: ByteVector =
this match {
case Chunk(_) => this
case _ => this.copy
}
/** Invokes `compact` on any subtrees whose size is `<= chunkSize`.
* @group collection
*/
final def partialCompact(chunkSize: Long): ByteVector =
this match {
case small if small.size <= chunkSize => small.compact
case Append(l, r) => Append(l.partialCompact(chunkSize), r.partialCompact(chunkSize))
case _ => this
}
/** Returns a vector with the same contents as this vector but with a single compacted node made up
* by evaluating all internal nodes and concatenating their values.
* @group collection
*/
final def copy: ByteVector = {
val sz = size
if (sz <= Int.MaxValue) {
val arr = this.toArray
Chunk(View(new AtArray(arr), 0, sz))
} else
take(Int.MaxValue).copy ++ drop(Int.MaxValue).copy
}
/** Converts the contents of this vector to a byte array.
*
* @group conversions
*/
final def toArray: Array[Byte] = {
val buf = new Array[Byte](toIntSize(size))
copyToArray(buf, 0)
buf
}
/** Copies the contents of this vector to array `xs`, beginning at index `start`.
*
* @group conversions
*/
final def copyToArray(xs: Array[Byte], start: Int): Unit = {
var i = start
foreachV { v =>
v.copyToArray(xs, i); i += toIntSize(v.size)
}
}
/** Copies `size` bytes of this vector, starting at index `offset`, to array `xs`, beginning at index `start`.
*
* @group conversions
*/
final def copyToArray(xs: Array[Byte], start: Int, offset: Long, size: Int): Unit = {
var i = 0L
var voffset = 0L
foreachV { v =>
if (i < size) {
val reloff = (offset - voffset).max(0)
if (voffset >= offset || reloff < v.size) {
val sz = (size - i).min(v.size - reloff)
v.copyToArray(xs, toIntSize(start + i), reloff, toIntSize(sz))
i += sz
}
voffset += v.size
}
}
}
/** Copies as many bytes as possible to the given [[ByteBuffer]], starting from its
* current position. This method will not overflow the buffer.
*
* @param buffer a ByteBuffer to copy bytes to
* @return the number of bytes actually copied
* @group conversions
*/
final def copyToBuffer(buffer: ByteBuffer): Int = {
var copied = 0
foreachVPartial { v =>
val copiedFromView = v.copyToBuffer(buffer)
copied += copiedFromView
(copiedFromView == v.size)
}
copied
}
/** Copies the contents of this vector to OutputStream `s`.
*
* @group conversions
*/
final def copyToStream(s: OutputStream): Unit =
foreachV(_.copyToStream(s))
/** Converts the contents of this vector to an `IndexedSeq`.
*
* @group conversions
*/
final def toIndexedSeq: IndexedSeq[Byte] =
new IndexedSeq[Byte] {
val length = toIntSize(ByteVector.this.size)
def apply(i: Int) = ByteVector.this.apply(i.toLong)
override def foldRight[B](z: B)(op: (Byte, B) => B): B = ByteVector.this.foldRight(z)(op)
}
/** Converts the contents of this vector to a `Seq`.
*
* @group conversions
*/
final def toSeq: Seq[Byte] = toIndexedSeq
/** Converts the contents of this vector to an `Iterable`.
*
* @group conversions
*/
final def toIterable: Iterable[Byte] = toIndexedSeq
/** Converts the contents of this vector to a bit vector of `size * 8` bits.
* @group conversions
*/
final def toBitVector: BitVector = BitVector(this)
/** Alias for [[toBitVector]].
* @group conversions
*/
final def bits: BitVector = toBitVector
/** Allocate (unobservable) mutable scratch space at the end of this
* `ByteVector`, which will be used to support fast `:+` and `++`
* of small vectors. A default chunk size is used.
*
* Note that `:+`, `++`, and `drop` on the result of a call to `buffer`
* are guaranteed to return another buffered `ByteVector`.
*
* @group buffer
*/
final def buffer: ByteVector = bufferBy(1024)
/** Allocate (unobservable) mutable scratch space at the end of this
* `ByteVector`, with chunks of the given size, which will be used to
* support fast `:+` and `++` of small vectors.
*
* Note that `:+`, `++`, and `drop` on the result of a call to `buffer`
* are guaranteed to return another buffered `ByteVector`, with the
* same size scratch space.
*
* @group buffer
*/
final def bufferBy(chunkSize: Int): ByteVector =
this match {
case b: Buffer =>
if (b.lastChunk.length >= chunkSize) b
else b.rebuffer(chunkSize)
case _ => Buffer(new AtomicLong(0), 0, this, new Array[Byte](chunkSize), 0)
}
/** Collapse any buffered chunks at the end of this `ByteVector`,
* resulting in an unbuffered `ByteVector`.
*
* @group buffer
*/
def unbuffer: ByteVector = this
/** Represents the contents of this vector as a read-only `java.nio.ByteBuffer`.
*
* The returned buffer is read-only with limit set to the minimum number of bytes needed to
* represent the contents of this vector, position set to zero, and remaining set to the limit.
*
* @group conversions
*/
final def toByteBuffer: ByteBuffer =
this match {
case Chunk(v) => v.asByteBuffer
case _ => ByteBuffer.wrap(toArray).asReadOnlyBuffer()
}
/** Converts the contents of this byte vector to a binary string of `size * 8` digits.
*
* @group conversions
*/
final def toBin: String = toBin(Bases.Alphabets.Binary)
/** Converts the contents of this byte vector to a binary string of `size * 8` digits.
*
* @group conversions
*/
final def toBin(alphabet: Bases.BinaryAlphabet): String = {
val bldr = new StringBuilder
foreachS {
new F1BU {
def apply(b: Byte) = {
var n = 7
while (n >= 0) {
val idx = 1 & (b >> n)
bldr.append(alphabet.toChar(idx))
n -= 1
}
}
}
}
bldr.toString
}
/** Converts the contents of this byte vector to a hexadecimal string of `size * 2` nibbles.
*
* @group conversions
*/
final def toHex: String = toHex(Bases.Alphabets.HexLowercase)
/** Converts the contents of this byte vector to a hexadecimal string of `size * 2` nibbles.
*
* @group conversions
*/
final def toHex(alphabet: Bases.HexAlphabet): String = {
val bldr = new StringBuilder
foreachS {
new F1BU {
def apply(b: Byte) = {
bldr
.append(alphabet.toChar((b >> 4 & 0x0f).toByte.toInt))
.append(alphabet.toChar((b & 0x0f).toByte.toInt))
()
}
}
}
bldr.toString
}
/** Helper alias for [[toHex:String*]]
*
* @group conversions
*/
final def toBase16: String = toHex
/** Helper alias for [[toHex(alphabet:scodec\.bits\.Bases\.HexAlphabet):String*]]
*
* @group conversions
*/
final def toBase16(alphabet: Bases.HexAlphabet): String = toHex(alphabet)
/** Converts the contents of this vector to a base 32 string.
*
* @group conversions
*/
final def toBase32: String = toBase32(Bases.Alphabets.Base32)
/** Selects at most 8 bits from a byte array as a right aligned byte */
private final def bitsAtOffset(bytes: Array[Byte], bitIndex: Long, length: Int): Int = {
val i = (bitIndex / 8).toInt
if (i >= bytes.length) 0
else {
val off = (bitIndex - (i * 8)).toInt
val mask = ((1 << length) - 1) << (8 - length)
val half = (bytes(i) << off) & mask
val full =
if (off + length <= 8 || i + 1 >= bytes.length) half
else half | ((bytes(i + 1) & ((mask << (8 - off)) & 0xff)) >>> (8 - off))
full >>> (8 - length)
}
}
/** Converts the contents of this vector to a base 32 string using the specified alphabet.
*
* @group conversions
*/
final def toBase32(alphabet: Bases.Base32Alphabet): String = {
val bitsPerChar = 5
val bytesPerGroup = 5
val charsPerGroup = bytesPerGroup * 8 / bitsPerChar
val bytes = toArray
val bldr =
CharBuffer.allocate((bytes.length + bytesPerGroup - 1) / bytesPerGroup * charsPerGroup)
{
var bidx: Long = 0
while ((bidx / 8) < bytes.length) {
val char = alphabet.toChar(bitsAtOffset(bytes, bidx, bitsPerChar))
bldr.append(char)
bidx += bitsPerChar
}
}
if (alphabet.pad != 0.toChar) {
val padLen =
(((bytes.length + bitsPerChar - 1) / bitsPerChar * bitsPerChar) - bytes.length) * 8 / bitsPerChar
var i = 0
while (i < padLen) {
bldr.append(alphabet.pad)
i += 1
}
}
bldr.flip.toString
}
/** Converts the contents of this vector to a base 58 string.
*
* @group conversions
*/
final def toBase58: String = toBase58(Bases.Alphabets.Base58)
/** Converts the contents of this vector to a base 58 string using the specified alphabet.
*
* @group conversions
*/
final def toBase58(alphabet: Bases.Alphabet): String =
if (isEmpty)
""
else {
val ZERO = BigInt(0)
val RADIX = BigInt(58L)
val ones = List.fill(takeWhile(_ == 0).length.toInt)('1')
@tailrec
def go(value: BigInt, chars: List[Char]): String =
value match {
case ZERO => (ones ++ chars).mkString
case _ =>
val (div, rem) = value /% RADIX
go(div, alphabet.toChar(rem.toInt) +: chars)
}
go(BigInt(1, toArray), List.empty)
}
/** Converts the contents of this vector to a base 64 string.
*
* @group conversions
*/
final def toBase64: String = toBase64(Bases.Alphabets.Base64)
/** Converts the contents of this vector to a base 64 string using the specified alphabet.
*
* @group conversions
*/
final def toBase64(alphabet: Bases.Base64Alphabet): String = {
val bytes = toArray
val bldr = CharBuffer.allocate(((bytes.length + 2) / 3) * 4)
var idx = 0
val mod = bytes.length % 3
while (idx < bytes.length - mod) {
var buffer =
((bytes(idx) & 0x0ff) << 16) | ((bytes(idx + 1) & 0x0ff) << 8) | (bytes(idx + 2) & 0x0ff)
val fourth = buffer & 0x3f
buffer = buffer >> 6
val third = buffer & 0x3f
buffer = buffer >> 6
val second = buffer & 0x3f
buffer = buffer >> 6
val first = buffer
bldr
.append(alphabet.toChar(first))
.append(alphabet.toChar(second))
.append(alphabet.toChar(third))
.append(alphabet.toChar(fourth))
idx = idx + 3
}
if (mod == 1) {
var buffer = (bytes(idx) & 0x0ff) << 4
val second = buffer & 0x3f
buffer = buffer >> 6
val first = buffer
bldr
.append(alphabet.toChar(first))
.append(alphabet.toChar(second))
if (alphabet.pad != 0.toChar)
bldr
.append(alphabet.pad)
.append(alphabet.pad)
} else if (mod == 2) {
var buffer = ((bytes(idx) & 0x0ff) << 10) | ((bytes(idx + 1) & 0x0ff) << 2)
val third = buffer & 0x3f
buffer = buffer >> 6
val second = buffer & 0x3f
buffer = buffer >> 6
val first = buffer
bldr
.append(alphabet.toChar(first))
.append(alphabet.toChar(second))
.append(alphabet.toChar(third))
if (alphabet.pad != 0.toChar) bldr.append(alphabet.pad)
}
bldr.flip.toString
}
/** Converts the contents of this vector to a base 64 string without padding.
*
* @group conversions
*/
final def toBase64NoPad: String = toBase64(Bases.Alphabets.Base64NoPad)
/** Converts the contents of this vector to a base 64 url string with padding.
*
* @group conversions
*/
final def toBase64Url: String = toBase64(Bases.Alphabets.Base64Url)
/** Converts the contents of this vector to a base 64 url string without padding.
*
* @group conversions
*/
final def toBase64UrlNoPad: String = toBase64(Bases.Alphabets.Base64UrlNoPad)
/** Converts the contents of this vector to a byte.
*
* @param signed whether sign extension should be performed
* @throws IllegalArgumentException if size is greater than 8
* @group conversions
*/
final def toByte(signed: Boolean = true): Byte =
bits.toByte(signed)
/** Converts the contents of this vector to a short.
*
* @param signed whether sign extension should be performed
* @param ordering order bytes should be processed in
* @throws IllegalArgumentException if size is greater than 16
* @group conversions
*/
final def toShort(
signed: Boolean = true,
ordering: ByteOrdering = ByteOrdering.BigEndian
): Short =
bits.toShort(signed, ordering)
/** Converts the contents of this vector to an int.
*
* @param signed whether sign extension should be performed
* @param ordering order bytes should be processed in
* @throws IllegalArgumentException if size is greater than 32
* @group conversions
*/
final def toInt(signed: Boolean = true, ordering: ByteOrdering = ByteOrdering.BigEndian): Int =
bits.toInt(signed, ordering)
/** Converts the contents of this vector to an int.
*
* @param signed whether sign extension should be performed
* @param ordering order bytes should be processed in
* @throws IllegalArgumentException if size is greater than 64
* @group conversions
*/
final def toLong(signed: Boolean = true, ordering: ByteOrdering = ByteOrdering.BigEndian): Long =
bits.toLong(signed, ordering)
/** Converts the contents of this byte vector to a UUID.
*
* @throws IllegalArgumentException if size is not exactly 16.
* @group conversions
*/
final def toUUID: UUID = {
// Sanity check
if (size != 16)
throw new IllegalArgumentException(
s"Cannot convert ByteVector of size $size to UUID; must be 16 bytes"
)
// Convert
val byteBuffer = toByteBuffer
val mostSignificant = byteBuffer.getLong
val leastSignificant = byteBuffer.getLong
new UUID(mostSignificant, leastSignificant)
}
/** Decodes this vector as a string using the implicitly available charset.
* @group conversions
*/
final def decodeString(implicit charset: Charset): Either[CharacterCodingException, String] = {
val decoder = charset.newDecoder
try Right(decoder.decode(toByteBuffer).toString)
catch {
case e: CharacterCodingException => Left(e)
}
}
/** Decodes this vector as a string using the UTF-8 charset.
* @group conversions
*/
final def decodeUtf8: Either[CharacterCodingException, String] =
decodeString(Charset.forName("UTF-8"))
/** Decodes this vector as a string using the US-ASCII charset.
* @group conversions
*/
final def decodeAscii: Either[CharacterCodingException, String] =
decodeString(Charset.forName("US-ASCII"))
final def not: ByteVector = mapS(new F1B { def apply(b: Byte) = (~b).toByte })
final def or(other: ByteVector): ByteVector =
zipWithS(other)(new F2B { def apply(b: Byte, b2: Byte) = (b | b2).toByte })
final def and(other: ByteVector): ByteVector =
zipWithS(other)(new F2B { def apply(b: Byte, b2: Byte) = (b & b2).toByte })
final def xor(other: ByteVector): ByteVector =
zipWithS(other)(new F2B { def apply(b: Byte, b2: Byte) = (b ^ b2).toByte })
/** Returns a new vector where each byte is the result of evaluating the specified function
* against the bytes of this vector and the specified vector at the corresponding index.
* The resulting vector has size `this.size min other.size`.
* $returnsView
* @group collection
*/
final def zipWith(other: ByteVector)(f: (Byte, Byte) => Byte): ByteVector =
zipWithS(other)(new F2B { def apply(b: Byte, b2: Byte) = f(b, b2) })
/** See [[zipWith]]
* $returnsView
* @group collection
*/
final def zipWith2(other: ByteVector, other2: ByteVector)(
f: (Byte, Byte, Byte) => Byte
): ByteVector =
zipWithS(other, other2)(new F3B { def apply(b: Byte, b2: Byte, b3: Byte) = f(b, b2, b3) })
/** See [[zipWith]]
* $returnsView
* @group collection
*/
final def zipWith3(other: ByteVector, other2: ByteVector, other3: ByteVector)(
f: (Byte, Byte, Byte, Byte) => Byte
): ByteVector =
zipWithS(other, other2, other3)(new F4B {
def apply(b: Byte, b2: Byte, b3: Byte, b4: Byte) = f(b, b2, b3, b4)
})
private[scodec] final def zipWithS(other: ByteVector)(f: F2B): ByteVector = {
val at = new At { def apply(i: Long) = f(ByteVector.this(i), other(i)) }
Chunk(View(at, 0, size.min(other.size)))
}
private[scodec] final def zipWithS(other: ByteVector, other2: ByteVector)(f: F3B): ByteVector = {
val at = new At { def apply(i: Long) = f(ByteVector.this(i), other(i), other2(i)) }
Chunk(View(at, 0, (size.min(other.size)).min(other2.size)))
}
private[scodec] final def zipWithS(other: ByteVector, other2: ByteVector, other3: ByteVector)(
f: F4B
): ByteVector = {
val at = new At { def apply(i: Long) = f(ByteVector.this(i), other(i), other2(i), other3(i)) }
Chunk(View(at, 0, ((size.min(other.size)).min(other2.size)).min(other3.size)))
}
/** Returns a new vector where each byte is the result of evaluating the specified function
* against the bytes of this vector and the specified vector at the corresponding index.
* The resulting vector has size `this.size min other.size`.
* Only the least significant byte is used (the three most significant bytes are ignored).
* $returnsView
* @group collection
*/
final def zipWithI(other: ByteVector)(op: (Byte, Byte) => Int): ByteVector =
zipWith(other) { case (l, r) => op(l, r).toByte }
/** See [[zipWithI]]
* $returnsView
* @group collection
*/
final def zipWithI2(other: ByteVector, other2: ByteVector)(
op: (Byte, Byte, Byte) => Int
): ByteVector =
zipWith2(other, other2) { case (l, r1, r2) => op(l, r1, r2).toByte }
/** See [[zipWithI]]
* $returnsView
* @group collection
*/
final def zipWithI3(other: ByteVector, other2: ByteVector, other3: ByteVector)(
op: (Byte, Byte, Byte, Byte) => Int
): ByteVector =
zipWith3(other, other2, other3) { case (l, r1, r2, r3) => op(l, r1, r2, r3).toByte }
/** Compresses this vector using ZLIB.
*
* @param level compression level, 0-9, with 0 disabling compression and 9 being highest level of compression -- see `java.util.zip.Deflater` for details
* @param strategy compression strategy -- see `java.util.zip.Deflater` for details
* @param nowrap if true, ZLIB header and checksum will not be used
* @param chunkSize buffer size, in bytes, to use when compressing
* @group conversions
*/
final def deflate(
level: Int = Deflater.DEFAULT_COMPRESSION,
strategy: Int = Deflater.DEFAULT_STRATEGY,
nowrap: Boolean = false,
chunkSize: Int = 4096
): ByteVector =
if (isEmpty) this
else {
val deflater = new Deflater(level, nowrap)
try {
deflater.setStrategy(strategy)
val buffer = new Array[Byte](chunkSize.toLong.min(size).toInt)
def loop(acc: ByteVector, fin: Boolean): ByteVector =
if ((fin && deflater.finished) || (!fin && deflater.needsInput)) acc
else {
val count = deflater.deflate(buffer)
loop(acc ++ ByteVector(buffer, 0, count), fin)
}
var result = ByteVector.empty
foreachV { v =>
deflater.setInput(v.toArray)
result = result ++ loop(ByteVector.empty, false)
}
deflater.setInput(Array.empty[Byte])
deflater.finish()
result ++ loop(ByteVector.empty, true)
} finally deflater.end()
}
/** Decompresses this vector using ZLIB.
*
* @param chunkSize buffer size, in bytes, to use when compressing
* @param nowrap if true, will assume no ZLIB header and checksum
* @group conversions
*/
final def inflate(
chunkSize: Int = 4096,
nowrap: Boolean = false
): Either[DataFormatException, ByteVector] =
if (isEmpty) Right(this)
else {
val arr = toArray
val inflater = new Inflater(nowrap)
try {
inflater.setInput(arr)
try {
val buffer = new Array[Byte](chunkSize.min(arr.length))
def loop(acc: ByteVector): ByteVector =
if (inflater.finished || inflater.needsInput) acc
else {
val count = inflater.inflate(buffer)
loop(acc ++ ByteVector(buffer, 0, count))
}
val inflated = loop(ByteVector.empty)
if (inflater.finished) Right(inflated)
else
Left(
new DataFormatException(
"Insufficient data -- inflation reached end of input without completing inflation - " + inflated
)
)
} catch {
case e: DataFormatException => Left(e)
}
} finally inflater.end()
}
/** Computes a digest of this byte vector.
* @param algorithm digest algorithm to use
* @group conversions
*/
final def digest(algorithm: String): ByteVector = digest(MessageDigest.getInstance(algorithm))
/** Computes a digest of this byte vector.
* @param digest digest to use
* @group conversions
*/
final def digest(digest: MessageDigest): ByteVector = {
foreachV { v =>
digest.update(v.toArray)
}
ByteVector.view(digest.digest)
}
/** Encrypts this byte vector using the specified cipher and key.
*
* @param ci cipher to use for encryption
* @param key key to encrypt with
* @param aparams optional algorithm paramaters used for encryption (e.g., initialization vector)
* @param sr secure random
* @group crypto
*/
final def encrypt(ci: Cipher, key: Key, aparams: Option[AlgorithmParameters] = None)(implicit
sr: SecureRandom
): Either[GeneralSecurityException, ByteVector] =
cipher(ci, key, Cipher.ENCRYPT_MODE, aparams)
/** Decrypts this byte vector using the specified cipher and key.
*
* @param ci cipher to use for decryption
* @param key key to decrypt with
* @param aparams optional algorithm paramaters used for decryption (e.g., initialization vector)
* @param sr secure random
* @group crypto
*/
final def decrypt(ci: Cipher, key: Key, aparams: Option[AlgorithmParameters] = None)(implicit
sr: SecureRandom
): Either[GeneralSecurityException, ByteVector] =
cipher(ci, key, Cipher.DECRYPT_MODE, aparams)
private[bits] def cipher(
ci: Cipher,
key: Key,
opmode: Int,
aparams: Option[AlgorithmParameters] = None
)(implicit sr: SecureRandom): Either[GeneralSecurityException, ByteVector] =
try {
aparams.fold(ci.init(opmode, key, sr))(aparams => ci.init(opmode, key, aparams, sr))
foreachV { view =>
ci.update(view.toArrayUnsafe); ()
}
Right(ByteVector.view(ci.doFinal()))
} catch {
case e: GeneralSecurityException => Left(e)
}
// implementation details, Object methods
/** Calculates the hash code of this vector. The result is cached.
* @group collection
*/
override lazy val hashCode = {
// todo: this could be recomputed more efficiently using the tree structure
// given an associative hash function
import util.hashing.MurmurHash3._
val chunkSize = 1024 * 64L
@annotation.tailrec
def go(bytes: ByteVector, h: Int, iter: Int): Int =
if (bytes.isEmpty) finalizeHash(h, iter)
else go(bytes.drop(chunkSize), mix(h, bytesHash(bytes.take(chunkSize).toArray)), iter + 1)
go(this, stringHash("ByteVector"), 1)
}
/** Returns true if the specified `ByteVector` has the same contents as this vector.
* @group collection
*/
final def ===(other: ByteVector): Boolean =
if (this.eq(other)) true
else {
val s = this.size
if (s != other.size)
false
else {
@annotation.tailrec
def go(i: Long): Boolean =
if (i < s)
if (this(i) == other(i)) go(i + 1)
else false
else
true
go(0)
}
}
/** Returns true if the specified value is a `ByteVector` with the same contents as this vector.
* @see [[ByteVector.===]]
* @group collection
*/
override def equals(other: Any) =
other match {
case that: ByteVector => this === that
case _ => false
}
/** Display the size and bytes of this `ByteVector`.
* For bit vectors beyond a certain size, only a hash of the
* contents are shown.
* @group collection
*/
override def toString: String =
if (isEmpty) "ByteVector(empty)"
else if (size < 512) s"ByteVector($size bytes, 0x${toHex})"
else s"ByteVector($size bytes, #${hashCode})"
private[bits] def pretty(prefix: String): String =
this match {
case Append(l, r) =>
prefix + "bytes:append\n" +
l.pretty(prefix + " ") + "\n" +
r.pretty(prefix + " ")
case Chunks(c) =>
prefix + "bytes:chunks " + size + "\n" +
c.left.pretty(prefix + " ") + "\n" +
c.right.pretty(prefix + " ")
case b: Buffer =>
prefix + "bytes:buffer " + size + "\n" +
b.hd.pretty(prefix + " ") + "\n" +
b.lastBytes.pretty(prefix + " ")
case Chunk(_) => prefix + (if (size < 16) "0x" + toHex else "#" + hashCode)
}
private def checkIndex(n: Long): Unit =
if (n < 0 || n >= size)
throw new IndexOutOfBoundsException(s"invalid index: $n for size $size")
protected final def writeReplace(): AnyRef = new SerializationProxy(toArray)
override def compare(that: ByteVector): Int =
if (this.eq(that))
0
else {
val thisLength = this.length
val thatLength = that.length
val commonLength = thisLength.min(thatLength)
var i = 0
while (i < commonLength) {
val cmp = (this(i.toLong) & 0xff).compare(that(i.toLong) & 0xff)
if (cmp != 0)
return cmp
i = i + 1
}
if (thisLength < thatLength)
-1
else if (thisLength > thatLength)
1
else
0
}
}
/** Companion for [[ByteVector]].
*
* @groupname constants Constants
* @groupprio constants 0
*
* @groupname constructors Constructors
* @groupprio constructors 1
*
* @groupname numeric Numeric Conversions
* @groupprio numeric 2
*
* @groupname base Base Conversions
* @groupprio base 3
*/
object ByteVector extends ByteVectorPlatform {
// various specialized function types
private[scodec] abstract class F1B { def apply(b: Byte): Byte }
private[scodec] abstract class F1BU { def apply(b: Byte): Unit }
private[scodec] abstract class F1BB { def apply(b: Byte): Boolean }
private[scodec] abstract class F2B { def apply(b: Byte, b2: Byte): Byte }
private[scodec] abstract class F3B { def apply(b: Byte, b2: Byte, b3: Byte): Byte }
private[scodec] abstract class F4B { def apply(b: Byte, b2: Byte, b3: Byte, b4: Byte): Byte }
private[scodec] abstract class F5B {
def apply(b: Byte, b2: Byte, b3: Byte, b4: Byte, b5: Byte): Byte
}
private[scodec] sealed abstract class At {
def apply(i: Long): Byte
def asByteBuffer(offset: Long, size: Int): ByteBuffer = {
val arr = new Array[Byte](size)
copyToArray(arr, 0, offset, size)
ByteBuffer.wrap(arr).asReadOnlyBuffer()
}
def copyToArray(xs: Array[Byte], start: Int, offset: Long, size: Int): Unit = {
var i = 0
while (i < size) {
xs(start + i) = apply(offset + i)
i += 1
}
}
def copyToBuffer(buffer: ByteBuffer, offset: Long, size: Int): Int = {
var i = 0
while (i < size && buffer.remaining > 0) {
buffer.put(apply(offset + i))
i += 1
}
i
}
def copyToStream(s: OutputStream, offset: Long, size: Long): Unit = {
var i = 0
while (i < size) {
s.write(apply(offset + i).toInt)
i += 1
}
}
}
private object AtEmpty extends At {
def apply(i: Long) = throw new IllegalArgumentException("empty view")
override def asByteBuffer(start: Long, size: Int): ByteBuffer =
ByteBuffer.allocate(0).asReadOnlyBuffer()
override def copyToBuffer(buffer: ByteBuffer, offset: Long, size: Int): Int = 0
}
private class AtArray(val arr: Array[Byte]) extends At {
def apply(i: Long) = arr(i.toInt)
override def asByteBuffer(start: Long, size: Int): ByteBuffer = {
val b = ByteBuffer.wrap(arr, start.toInt, size).asReadOnlyBuffer()
if (start == 0 && size == arr.length) b
else b.slice()
}
override def copyToArray(xs: Array[Byte], start: Int, offset: Long, size: Int): Unit =
System.arraycopy(arr, offset.toInt, xs, start, size)
override def copyToStream(s: OutputStream, offset: Long, size: Long): Unit =
s.write(arr, offset.toInt, toIntSize(size))
override def copyToBuffer(buffer: ByteBuffer, offset: Long, size: Int): Int = {
val toCopy = buffer.remaining.min(size)
buffer.put(arr, offset.toInt, toCopy)
toCopy
}
}
private class AtByteBuffer(val buf: ByteBuffer) extends At {
def apply(i: Long) = buf.get(i.toInt)
override def copyToArray(xs: Array[Byte], start: Int, offset: Long, size: Int): Unit = {
val n = buf.duplicate()
n.position(offset.toInt)
n.get(xs, start, size)
()
}
override def asByteBuffer(offset: Long, size: Int): ByteBuffer = {
val b = buf.asReadOnlyBuffer()
if (offset == 0 && b.position() == 0 && size == b.remaining()) b
else {
b.position(offset.toInt)
b.limit(offset.toInt + size)
b.slice()
}
}
override def copyToBuffer(buffer: ByteBuffer, offset: Long, size: Int): Int = {
val toCopy = buffer.remaining.min(size)
buffer.put(asByteBuffer(offset, toCopy))
toCopy
}
}
private[bits] case class View(at: At, offset: Long, size: Long) {
def apply(n: Long) = at(offset + n)
def foreach(f: F1BU): Unit = {
var i = 0L
while (i < size) {
f(at(offset + i))
i += 1
}
}
def foreachPartial(f: F1BB): Boolean = {
var i = 0L
var cont = true
while (i < size && cont) {
cont = f(at(offset + i))
i += 1
}
cont
}
def asByteBuffer: ByteBuffer = at.asByteBuffer(offset, toIntSize(size))
def copyToStream(s: OutputStream): Unit =
at.copyToStream(s, offset, size)
def copyToArray(xs: Array[Byte], start: Int): Unit =
at.copyToArray(xs, start, offset, toIntSize(size))
def copyToArray(xs: Array[Byte], start: Int, offset: Long, size: Int): Unit =
at.copyToArray(xs, start, this.offset + offset, this.size.min(size.toLong).toInt)
def toArray: Array[Byte] = {
val arr = new Array[Byte](toIntSize(size))
copyToArray(arr, 0)
arr
}
def toArrayUnsafe: Array[Byte] =
at match {
case atarr: AtArray if offset == 0 && size == atarr.arr.size => atarr.arr
case _ => toArray
}
def copyToBuffer(buffer: ByteBuffer): Int =
at.copyToBuffer(buffer, offset, toIntSize(size))
def take(n: Long): View =
if (n <= 0) View.empty
else if (n >= size) this
else View(at, offset, n)
def drop(n: Long): View =
if (n <= 0) this
else if (n >= size) View.empty
else View(at, offset + n, size - n)
}
private[bits] object View {
val empty = View(AtEmpty, 0, 0)
}
private[bits] case class Chunk(bytes: View) extends ByteVector {
def size = bytes.size
def getImpl(i: Long) = bytes(i)
}
private[bits] case class Append(left: ByteVector, right: ByteVector) extends ByteVector {
val size = left.size + right.size
def getImpl(n: Long) =
if (n < left.size) left.getImpl(n)
else right.getImpl(n - left.size)
}
/** Empty byte vector.
* @group constants
*/
val empty: ByteVector = Chunk(View(AtEmpty, 0, 0))
/** Constructs a `ByteVector` from a list of literal bytes. Only the least significant
* byte is used of each integral value.
* @group constructors
*/
def apply[A: Integral](bytes: A*): ByteVector = {
val integral = implicitly[Integral[A]]
val buf = new Array[Byte](bytes.size)
var i = 0
bytes.foreach { b =>
buf(i) = integral.toInt(b).toByte
i += 1
}
view(buf)
}
/** Constructs a `ByteVector` from a collection of bytes.
* @group constructors
*/
def apply(bytes: Vector[Byte]): ByteVector = viewAt(idx => bytes(idx.toInt), bytes.size.toLong)
/** Constructs a `ByteVector` from an `Array[Byte]`. The given `Array[Byte]`
* is copied to ensure the resulting `ByteVector` is immutable.
* If this is not desired, use `ByteVector.view`.
* @group constructors
*/
def apply(bytes: Array[Byte]): ByteVector = {
val copy: Array[Byte] = bytes.clone()
view(copy)
}
/** Constructs a `ByteVector` from an `Array[Byte]`, an offset, and a length.
* The given `Array[Byte]` is copied to ensure the resulting `ByteVector` is immutable.
* If this is not desired, use `ByteVector.view`.
* @group constructors
*/
def apply(bytes: Array[Byte], offset: Int, length: Int): ByteVector = {
val fresh: Array[Byte] = new Array[Byte](length)
System.arraycopy(bytes, offset, fresh, 0, length)
view(fresh)
}
/** Constructs a `ByteVector` from a `ByteBuffer`. The given `ByteBuffer` is
* is copied to ensure the resulting `ByteVector` is immutable.
* If this is not desired, use `ByteVector.view`.
*
* The returned vector is a copy of a subsequence of the buffer, with bounds
* determined by the buffer's position and limit at the time this method is called.
*
* @group constructors
*/
def apply(buffer: ByteBuffer): ByteVector = {
val c = buffer.duplicate()
val arr = new Array[Byte](c.remaining)
c.get(arr)
view(arr)
}
/** Constructs a `ByteVector` from a `scala.collection` source of bytes.
* @group constructors
*/
def apply(bs: IterableOnce[Byte]): ByteVector =
view(bs.iterator.toArray[Byte])
/** Constructs a `ByteVector` from an `Array[Byte]`. Unlike `apply`, this
* does not make a copy of the input array, so callers should take care
* not to modify the contents of the array passed to this function.
* @group constructors
*/
def view(bytes: Array[Byte]): ByteVector =
Chunk(View(new AtArray(bytes), 0, bytes.length.toLong))
/** Constructs a `ByteVector` from a slice of an `Array[Byte]`.
* Unlike `apply`, this does not make a copy of the input array, so
* callers should take care not to modify the contents of the array
* passed to this function.
* @group constructors
*/
def view(bytes: Array[Byte], offset: Int, size: Int): ByteVector =
Chunk(View(new AtArray(bytes), offset.toLong, size.toLong))
/** Constructs a `ByteVector` from a `ByteBuffer`. Unlike `apply`, this
* does not make a copy of the input buffer, so callers should take care
* not to modify the contents of the buffer passed to this function.
*
* The returned vector is a view of a subsequence of the buffer, with bounds
* determined by the buffer's position and limit at the time this method is called.
*
* @group constructors
*/
def view(bytes: ByteBuffer): ByteVector = {
val slice = bytes.slice()
Chunk(View(new AtByteBuffer(slice), 0, slice.remaining.toLong))
}
/** Constructs a `ByteVector` from a function from `Long => Byte` and a size.
* @group constructors
*/
def viewAt(at: Long => Byte, size: Long): ByteVector =
Chunk(View(new At { def apply(i: Long) = at(i) }, 0, size))
/** Constructs a `ByteVector` from a function from `At` and a size.
*/
private[scodec] def view(at: At, size: Long): ByteVector =
Chunk(View(at, 0, size))
/** Constructs a `ByteVector` from a function from `Long => Int` and a size,
* where the `Int` returned by `at` must fit in a `Byte`.
* @group constructors
*/
def viewI(at: Long => Int, size: Long): ByteVector =
Chunk(View(new At { def apply(i: Long) = at(i).toByte }, 0, size))
/** Constructs a `ByteVector` of the given size, where all bytes have the value `b`.
* @group constructors
*/
def fill[A: Integral](size: Long)(b: A): ByteVector = {
val integral = implicitly[Integral[A]]
val value = integral.toInt(b).toByte
Chunk(View(new At { def apply(i: Long) = value }, 0, size))
}
/** Constructs a `ByteVector` of the given size, where all bytes have the value `0`.
* @group constructors
*/
def low(size: Long): ByteVector = fill(size)(0)
/** Constructs a `ByteVector` of the given size, where all bytes have the value `0xff`.
* @group constructors
*/
def high(size: Long): ByteVector = fill(size)(0xff)
/** Constructs a `ByteVector` vector with the 2's complement encoding of the specified byte.
* @param b value to encode
* @group numeric
*/
def fromByte(b: Byte): ByteVector =
BitVector.fromByte(b, 8).bytes
/** Constructs a `ByteVector` vector with the 2's complement encoding of the specified value.
* @param s value to encode
* @param size size of vector (<= 2)
* @param ordering byte ordering of vector
* @group numeric
*/
def fromShort(
s: Short,
size: Int = 2,
ordering: ByteOrdering = ByteOrdering.BigEndian
): ByteVector =
BitVector.fromShort(s, size * 8, ordering).bytes
/** Constructs a `ByteVector` with the 2's complement encoding of the specified value.
* @param i value to encode
* @param size size of vector (<= 4)
* @param ordering byte ordering of vector
* @group numeric
*/
def fromInt(i: Int, size: Int = 4, ordering: ByteOrdering = ByteOrdering.BigEndian): ByteVector =
BitVector.fromInt(i, size * 8, ordering).bytes
/** Constructs a `ByteVector` with the 2's complement encoding of the specified value.
* @param l value to encode
* @param size size of vector (<= 8)
* @param ordering byte ordering of vector
* @group numeric
*/
def fromLong(
l: Long,
size: Int = 8,
ordering: ByteOrdering = ByteOrdering.BigEndian
): ByteVector =
BitVector.fromLong(l, size * 8, ordering).bytes
/** Constructs a `ByteVector` containing the binary representation of the specified UUID.
* The bytes are in MSB-to-LSB order.
*
* @param u value to encode
* @group conversions
*/
final def fromUUID(u: UUID): ByteVector = {
val buf = ByteBuffer.allocate(16)
buf.putLong(u.getMostSignificantBits)
buf.putLong(u.getLeastSignificantBits)
// Go via Array[Byte] to avoid hanging on to intermediate ByteBuffer via AtByteBuffer.
view(buf.array())
}
/** Constructs a `ByteVector` from a hexadecimal string or returns an error message if the string is not valid hexadecimal.
*
* The string may start with a `0x` and it may contain whitespace or underscore characters.
* @group base
*/
def fromHexDescriptive(
str: String,
alphabet: Bases.HexAlphabet = Bases.Alphabets.HexLowercase
): Either[String, ByteVector] =
fromHexInternal(str, alphabet).map { case (res, _) => res }
private[bits] def fromHexInternal(
str: String,
alphabet: Bases.HexAlphabet
): Either[String, (ByteVector, Int)] = {
val prefixed = (str.startsWith("0x")) || (str.startsWith("0X"))
val withoutPrefix = if (prefixed) str.substring(2) else str
var idx, hi, count = 0
var midByte = false
var err: String = null
val bldr = ByteBuffer.allocate((str.size + 1) / 2)
while (idx < withoutPrefix.length && (err eq null)) {
val c = withoutPrefix(idx)
if (!alphabet.ignore(c))
try {
val nibble = alphabet.toIndex(c)
if (midByte) {
bldr.put((hi | nibble).toByte)
midByte = false
} else {
hi = (nibble << 4).toByte.toInt
midByte = true
}
count += 1
} catch {
case _: IllegalArgumentException =>
err = s"Invalid hexadecimal character '$c' at index ${idx + (if (prefixed) 2 else 0)}"
}
idx += 1
}
if (err eq null)
Right(
(
if (midByte) {
bldr.put(hi.toByte)
bldr.flip()
ByteVector(bldr).shiftRight(4, false)
} else {
bldr.flip()
ByteVector(bldr)
},
count
)
)
else Left(err)
}
/** Constructs a `ByteVector` from a hexadecimal string or returns `None` if the string is not valid hexadecimal.
*
* The string may start with a `0x` and it may contain whitespace or underscore characters.
* @group base
*/
def fromHex(
str: String,
alphabet: Bases.HexAlphabet = Bases.Alphabets.HexLowercase
): Option[ByteVector] = fromHexDescriptive(str, alphabet).toOption
/** Constructs a `ByteVector` from a hexadecimal string or throws an IllegalArgumentException if the string is not valid hexadecimal.
*
* The string may start with a `0x` and it may contain whitespace or underscore characters.
*
* @throws IllegalArgumentException if the string is not valid hexadecimal
* @group base
*/
def fromValidHex(
str: String,
alphabet: Bases.HexAlphabet = Bases.Alphabets.HexLowercase
): ByteVector =
fromHexDescriptive(str, alphabet).fold(msg => throw new IllegalArgumentException(msg), identity)
/** Constructs a `ByteVector` from a binary string or returns an error message if the string is not valid binary.
*
* The string may start with a `0b` and it may contain whitespace or underscore characters.
* @group base
*/
def fromBinDescriptive(
str: String,
alphabet: Bases.BinaryAlphabet = Bases.Alphabets.Binary
): Either[String, ByteVector] = fromBinInternal(str, alphabet).map { case (res, _) => res }
private[bits] def fromBinInternal(
str: String,
alphabet: Bases.BinaryAlphabet = Bases.Alphabets.Binary
): Either[String, (ByteVector, Int)] = {
val prefixed = (str.startsWith("0b")) || (str.startsWith("0B"))
val withoutPrefix = if (prefixed) str.substring(2) else str
var idx, byte, bits, count = 0
var err: String = null
val bldr = ByteBuffer.allocate((str.size + 7) / 8)
while (idx < withoutPrefix.length && (err eq null)) {
val c = withoutPrefix(idx)
if (!alphabet.ignore(c))
try {
byte = (byte << 1) | (1 & alphabet.toIndex(c))
bits += 1
count += 1
} catch {
case _: IllegalArgumentException =>
err = s"Invalid binary character '$c' at index ${idx + (if (prefixed) 2 else 0)}"
}
if (bits == 8) {
bldr.put(byte.toByte)
byte = 0
bits = 0
}
idx += 1
}
if (err eq null)
Right(
(
if (bits > 0) {
bldr.put((byte << (8 - bits)).toByte)
bldr.flip()
ByteVector(bldr).shiftRight((8 - bits).toLong, false)
} else {
bldr.flip()
ByteVector(bldr)
},
count
)
)
else Left(err)
}
/** Constructs a `ByteVector` from a binary string or returns `None` if the string is not valid binary.
*
* The string may start with a `0b` and it may contain whitespace or underscore characters.
* @group base
*/
def fromBin(
str: String,
alphabet: Bases.BinaryAlphabet = Bases.Alphabets.Binary
): Option[ByteVector] = fromBinDescriptive(str, alphabet).toOption
/** Constructs a `ByteVector` from a binary string or throws an IllegalArgumentException if the string is not valid binary.
*
* The string may start with a `0b` and it may contain whitespace or underscore characters.
*
* @throws IllegalArgumentException if the string is not valid binary
* @group base
*/
def fromValidBin(
str: String,
alphabet: Bases.BinaryAlphabet = Bases.Alphabets.Binary
): ByteVector =
fromBinDescriptive(str, alphabet).fold(msg => throw new IllegalArgumentException(msg), identity)
/** Constructs a `ByteVector` from a base 32 string or returns an error message if the string is not valid base 32.
* An empty input string results in an empty ByteVector.
* The string may contain whitespace characters and hyphens which are ignored.
* @group base
*/
def fromBase32Descriptive(
str: String,
alphabet: Bases.Base32Alphabet = Bases.Alphabets.Base32
): Either[String, ByteVector] = {
val bitsPerChar = 5
val bytesPerGroup = 5
val charsPerGroup = bytesPerGroup * 8 / bitsPerChar
val Pad = alphabet.pad
var idx, bidx, buffer, padding = 0
val acc = ByteBuffer.allocate((str.length + charsPerGroup - 1) / charsPerGroup * bytesPerGroup)
while (idx < str.length) {
val c = str(idx)
if (Pad != 0.toChar && c == Pad)
padding += 1
else if (!alphabet.ignore(c)) {
if (padding > 0)
return Left(
s"Unexpected character '$c' at index $idx after padding character; only '=' and whitespace characters allowed after first padding character"
)
val index =
try alphabet.toIndex(c)
catch {
case _: IllegalArgumentException =>
return Left(s"Invalid base 32 character '$c' at index $idx")
}
buffer |= (index << (8 - bitsPerChar) >>> bidx) & 0xff
bidx += bitsPerChar
if (bidx >= 8) {
bidx -= 8
acc.put(buffer.toByte)
buffer = (index << (8 - bidx)) & 0xff
}
}
idx += 1
}
if (bidx >= bitsPerChar)
acc.put(buffer.toByte)
acc.flip()
val bytes = ByteVector.view(acc)
val expectedPadding =
(((bytes.length + bitsPerChar - 1) / bitsPerChar * bitsPerChar) - bytes.length) * 8 / bitsPerChar
if (padding != 0 && padding != expectedPadding)
return Left(
s"Malformed padding - optionally expected $expectedPadding padding characters such that the quantum is completed"
)
Right(bytes)
}
/** Constructs a `ByteVector` from a base 64 string or returns `None` if the string is not valid base 32.
* Details pertaining to base 32 decoding can be found in the comment for fromBase32Descriptive.
* The string may contain whitespace characters which are ignored.
* @group base
*/
def fromBase32(
str: String,
alphabet: Bases.Base32Alphabet = Bases.Alphabets.Base32
): Option[ByteVector] = fromBase32Descriptive(str, alphabet).toOption
/** Constructs a `ByteVector` from a base 32 string or throws an IllegalArgumentException if the string is not valid base 32.
* Details pertaining to base 32 decoding can be found in the comment for fromBase32Descriptive.
* The string may contain whitespace characters which are ignored.
*
* @throws IllegalArgumentException if the string is not valid base 32
* @group base
*/
def fromValidBase32(
str: String,
alphabet: Bases.Base32Alphabet = Bases.Alphabets.Base32
): ByteVector =
fromBase32Descriptive(str, alphabet)
.fold(msg => throw new IllegalArgumentException(msg), identity)
/** Constructs a `ByteVector` from a base 58 string or returns an error message if the string is not valid base 58.
* It is similar to Base64 but has been modified to avoid both non-alphanumeric characters and letters which might look ambiguous when printed.
* It is therefore designed for human users who manually enter the data, copying from some visual source
* Compared to Base64, the following similar-looking letters are omitted: 0 (zero), O (capital o), I (capital i) and l (lower case L)
* as well as the non-alphanumeric characters + (plus) and / (slash).
* The actual order of letters in the alphabet depends on the application, the default order is the same used in Bitcoin
* An empty input string results in an empty ByteVector.
* The string may contain whitespace characters which are ignored.
* @group base
*/
def fromBase58Descriptive(
str: String,
alphabet: Bases.Alphabet = Bases.Alphabets.Base58
): Either[String, ByteVector] = {
val zeroLength = str.takeWhile(_ == '1').length
val zeroes = ByteVector.fill(zeroLength.toLong)(0)
val trim = str.splitAt(zeroLength)._2.toList
val RADIX = BigInt(58L)
try {
val decoded = trim.foldLeft(BigInt(0)) { (a, c) =>
try a * RADIX + BigInt(alphabet.toIndex(c))
catch {
case _: IllegalArgumentException =>
val idx = trim.takeWhile(_ != c).length
throw new IllegalArgumentException(s"Invalid base 58 character '$c' at index $idx")
}
}
if (trim.isEmpty) Right(zeroes)
else
Right(
zeroes ++ ByteVector(decoded.toByteArray.dropWhile(_ == 0))
) //drop because toByteArray sometimes prepends a zero
} catch {
case e: IllegalArgumentException => Left(e.getMessage)
}
}
/** Constructs a `ByteVector` from a base 58 string or returns `None` if the string is not valid base 58.
* Details pertaining to base 58 decoding can be found in the comment for fromBase58Descriptive.
* The string may contain whitespace characters which are ignored.
* @group base
*/
def fromBase58(
str: String,
alphabet: Bases.Alphabet = Bases.Alphabets.Base58
): Option[ByteVector] = fromBase58Descriptive(str, alphabet).toOption
/** Constructs a `ByteVector` from a base 58 string or throws an IllegalArgumentException if the string is not valid base 58.
* Details pertaining to base 58 decoding can be found in the comment for fromBase58Descriptive.
* The string may contain whitespace characters which are ignored.
*
* @throws IllegalArgumentException if the string is not valid base 58
* @group base
*/
def fromValidBase58(str: String, alphabet: Bases.Alphabet = Bases.Alphabets.Base58): ByteVector =
fromBase58Descriptive(str, alphabet)
.fold(msg => throw new IllegalArgumentException(msg), identity)
private val Base64PaddingError = Left(
"Malformed padding - final quantum may optionally be padded with one or two padding characters such that the quantum is completed"
)
/** Constructs a `ByteVector` from a base 64 string or returns an error message if the string is not valid base 64.
* If the final encoding quantum does not contain 4 characters, i.e. the total number of characters is not evenly divisible
* by 4, padding is inferred if the final quantum contains 2 or 3 characters. This is to say that padding is optional as
* long as the inferred padding would yield a valid base 64 string. The input is considered invalid if the final quantum
* only contains a single character. If padding characters are present, they must be used in accordance with the base 64
* specification and no padding characters will be inferred.
* An empty input string results in an empty ByteVector.
* The string may contain whitespace characters which are ignored.
* @group base
*/
def fromBase64Descriptive(
str: String,
alphabet: Bases.Base64Alphabet = Bases.Alphabets.Base64
): Either[String, ByteVector] = {
val Pad = alphabet.pad
var idx, bidx, buffer, mod, padding = 0
val acc = new Array[Byte]((str.size + 3) / 4 * 3)
while (idx < str.length) {
str(idx) match {
case c if alphabet.ignore(c) => // ignore
case c =>
val cidx = {
if (padding == 0)
if (c == Pad)
if (mod == 2 || mod == 3) {
padding += 1
0
} else
return Base64PaddingError
else
try alphabet.toIndex(c)
catch {
case _: IllegalArgumentException =>
return Left(s"Invalid base 64 character '$c' at index $idx")
}
else if (c == Pad)
if (padding == 1 && mod == 3) {
padding += 1
0
} else
return Base64PaddingError
else
return Left(
s"Unexpected character '$c' at index $idx after padding character; only '=' and whitespace characters allowed after first padding character"
)
}
mod match {
case 0 =>
buffer = (cidx & 0x3f)
mod += 1
case 1 =>
buffer = (buffer << 6) | (cidx & 0x3f)
mod += 1
case 2 =>
buffer = (buffer << 6) | (cidx & 0x3f)
mod += 1
case 3 =>
buffer = (buffer << 6) | (cidx & 0x3f)
mod = 0
val c = buffer & 0x0ff
val b = (buffer >> 8) & 0x0ff
val a = (buffer >> 16) & 0x0ff
acc(bidx) = a.toByte
acc(bidx + 1) = b.toByte
acc(bidx + 2) = c.toByte
bidx += 3
}
}
idx += 1
}
if (padding != 0 && mod != 0) Base64PaddingError
else
mod match {
case 0 => Right(ByteVector(acc).take((bidx - padding).toLong))
case 1 => Left("Final base 64 quantum had only 1 digit - must have at least 2 digits")
case 2 =>
acc(bidx) = ((buffer >> 4) & 0x0ff).toByte
bidx += 1
Right(ByteVector(acc).take(bidx.toLong))
case 3 =>
acc(bidx) = ((buffer >> 10) & 0x0ff).toByte
acc(bidx + 1) = ((buffer >> 2) & 0x0ff).toByte
bidx += 2
Right(ByteVector(acc).take(bidx.toLong))
}
}
/** Constructs a `ByteVector` from a base 64 string or returns `None` if the string is not valid base 64.
* Details pertaining to base 64 decoding can be found in the comment for fromBase64Descriptive.
* The string may contain whitespace characters which are ignored.
* @group base
*/
def fromBase64(
str: String,
alphabet: Bases.Base64Alphabet = Bases.Alphabets.Base64
): Option[ByteVector] = fromBase64Descriptive(str, alphabet).toOption
/** Constructs a `ByteVector` from a base 64 string or throws an IllegalArgumentException if the string is not valid base 64.
* Details pertaining to base 64 decoding can be found in the comment for fromBase64Descriptive.
* The string may contain whitespace characters which are ignored.
*
* @throws IllegalArgumentException if the string is not valid base 64
* @group base
*/
def fromValidBase64(
str: String,
alphabet: Bases.Base64Alphabet = Bases.Alphabets.Base64
): ByteVector =
fromBase64Descriptive(str, alphabet)
.fold(msg => throw new IllegalArgumentException(msg), identity)
/** Encodes the specified string to a `ByteVector` using the implicitly available `Charset`.
*
* @group constructors
*/
def encodeString(
str: String
)(implicit charset: Charset): Either[CharacterCodingException, ByteVector] = {
val encoder = charset.newEncoder
val buffer = CharBuffer.wrap(str)
try Right(ByteVector(encoder.encode(buffer)))
catch {
case e: CharacterCodingException => Left(e)
}
}
/** Encodes the specified string to a `ByteVector` using the UTF-8 charset.
*
* @group constructors
*/
def encodeUtf8(str: String): Either[CharacterCodingException, ByteVector] =
encodeString(str)(Charset.forName("UTF-8"))
/** Encodes the specified string to a `ByteVector` using the US-ASCII charset.
*
* @group constructors
*/
def encodeAscii(str: String): Either[CharacterCodingException, ByteVector] =
encodeString(str)(Charset.forName("US-ASCII"))
/** Concatenates all the given `ByteVector`s into a single instance.
*
* @group constructors
*/
def concat(bvs: IterableOnce[ByteVector]): ByteVector =
bvs.iterator.foldLeft(ByteVector.empty)(_ ++ _).unbuffer
@SerialVersionUID(1L)
private class SerializationProxy(private val bytes: Array[Byte]) extends Serializable {
def readResolve: AnyRef = ByteVector.view(bytes)
}
private[bits] case class Chunks(chunks: Append) extends ByteVector {
def size = chunks.size
def getImpl(n: Long) = chunks.getImpl(n)
override def ++(b: ByteVector): ByteVector =
if (b.isEmpty) this
else if (this.isEmpty) b
else {
@annotation.tailrec
def go(chunks: Append, last: ByteVector): ByteVector = {
val lastN = last.size
if (lastN >= chunks.size || lastN * 2 <= chunks.right.size)
Chunks(Append(chunks, last))
else
chunks.left match {
case left: Append => go(left, Append(chunks.right, last))
case _ => Chunks(Append(chunks, last))
}
}
go(chunks, b.unbuffer) // unbuffering `b` avoids proliferation of unused scratch space
// if `b` happens to have been built up using a buffer
}
}
/*
* `Buffer` is a `ByteVector` supporting fast `:+` and `++` operations, using
* an (unobservable) mutable scratch `Array[Byte]` at the end of the vector.
* The scratch space is written in an append-only fashion, which lets us
* safely share the same `Array` between multiple `ByteVector` instances,
* so long as we mutably append to this scratch space _at most once_ from a
* given `ByteVector`.
*
* That is, given {{{
* val b = ByteVector.empty.buffer
* val b2 = b ++ foo
* val b3 = b ++ bar
* val b22 = b2 ++ baz
* }}}
*
* we obviously cannot have both `b2` and `b3` mutably append to the same scratch
* space, as `b3` would then overwrite what was written by `b2`. The solution
* adopted here (see [1]) is to let `b2` write mutable to the buffer, but `b3`,
* evaluated later, must make a copy.
*
* [1]: http://www.serpentine.com/blog/2014/05/31/attoparsec/
*/
private class Buffer(
val id: AtomicLong,
val stamp: Long,
val hd: ByteVector,
val lastChunk: Array[Byte],
val lastSize: Int
) extends ByteVector {
def size = hd.size + lastSize
override def take(n: Long): ByteVector =
if (n <= hd.size) hd.take(n)
else hd ++ lastBytes.take(n - hd.size)
override def drop(n: Long): ByteVector =
if (n <= hd.size) Buffer(id, stamp, hd.drop(n), lastChunk, lastSize)
else unbuffer.drop(n).bufferBy(lastChunk.length)
def getImpl(n: Long): Byte =
if (n < hd.size) hd.getImpl(n)
else lastChunk((n - hd.size).toInt)
override def :+(b: Byte): ByteVector =
// threads race to increment id, winner gets to update tl mutably
if (id.compareAndSet(stamp, stamp + 1) && lastSize < lastChunk.length) {
lastChunk(lastSize) = b
Buffer(id, stamp + 1, hd, lastChunk, lastSize + 1)
} else // loser has to make a copy of the scratch space
Buffer(new AtomicLong(0), 0, unbuffer, new Array[Byte](lastChunk.length), 0) :+ b
// NB: if `bs` fits in scratch space and is itself a buffer, it will be
// unbuffered before being added. This avoids proliferation of scratch space
// for tiny ByteVectors
override def ++(bs: ByteVector): ByteVector =
if (bs.isEmpty) this
else
// threads race to increment id, winner gets to update tl mutably
if (id.compareAndSet(stamp, stamp + 1) && (lastChunk.length - lastSize > bs.size)) {
bs.copyToArray(lastChunk, lastSize)
Buffer(id, stamp + 1, hd, lastChunk, lastSize + bs.size.toInt)
} else if (lastSize == 0) // just append directly to `hd`
Buffer(id, stamp, (hd ++ bs).unbuffer, lastChunk, lastSize)
else // loser has to make a copy of the scratch space
Buffer(new AtomicLong(0), 0, unbuffer, new Array[Byte](lastChunk.length), 0) ++ bs
def lastBytes = ByteVector.view(lastChunk).take(lastSize.toLong)
override def unbuffer: ByteVector =
// if last chunk more than half unused, copy to a fresh
// bytevector, to avoid proliferation of scratch space
hd ++ (if (lastSize * 2 < lastChunk.length) lastBytes.copy else lastBytes)
def rebuffer(chunkSize: Int): ByteVector = {
require(chunkSize > lastChunk.length)
val lastChunk2 = new Array[Byte](chunkSize)
lastChunk.copyToArray(lastChunk2, 0, lastChunk2.length)
Buffer(new AtomicLong(0), 0, hd, lastChunk, lastSize)
}
}
private object Buffer {
def apply(
id: AtomicLong,
stamp: Long,
hd: ByteVector,
lastChunk: Array[Byte],
lastSize: Int
): Buffer =
new Buffer(id, stamp, hd.unbuffer, lastChunk, lastSize)
}
private def toIntSize(size: Long): Int =
if (size <= Int.MaxValue) size.toInt
else throw new IllegalArgumentException(s"size must be <= Int.MaxValue but is $size")
/** Extractor used in support of pattern matching on the bytes of a vector.
*
* @group constructors
*/
def unapplySeq(b: ByteVector): Some[Seq[Byte]] = Some(b.toIndexedSeq)
implicit class GroupedOp(val self: ByteVector) extends AnyVal {
/** Converts this vector in to a sequence of `chunkSize`-byte vectors.
* @group collection
*/
final def grouped(chunkSize: Long): Iterator[ByteVector] = self.groupedIterator(chunkSize)
}
}